001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * Portions Copyrighted 2007 Sun Microsystems, Inc.
027: */
028: package org.openide.loaders;
029:
030: import java.io.FileNotFoundException;
031: import java.io.IOException;
032: import java.io.InputStream;
033: import java.io.StringReader;
034: import java.lang.ref.Reference;
035: import java.lang.ref.WeakReference;
036: import java.net.URL;
037: import java.util.Properties;
038: import java.util.jar.Attributes;
039: import java.util.logging.Level;
040: import java.util.logging.Logger;
041: import org.openide.cookies.InstanceCookie;
042: import org.openide.filesystems.FileAttributeEvent;
043: import org.openide.filesystems.FileChangeListener;
044: import org.openide.filesystems.FileEvent;
045: import org.openide.filesystems.FileObject;
046: import org.openide.filesystems.FileRenameEvent;
047: import org.openide.nodes.Node;
048: import org.openide.util.Exceptions;
049: import org.openide.util.Lookup;
050: import org.openide.util.LookupEvent;
051: import org.openide.util.LookupListener;
052: import org.openide.xml.XMLUtil;
053: import org.xml.sax.EntityResolver;
054: import org.xml.sax.InputSource;
055: import org.xml.sax.SAXException;
056: import org.xml.sax.SAXParseException;
057: import org.xml.sax.XMLReader;
058: import org.xml.sax.ext.LexicalHandler;
059: import org.xml.sax.helpers.DefaultHandler;
060:
061: final class XMLDataObjectInfoParser extends DefaultHandler implements
062: FileChangeListener, LexicalHandler, LookupListener {
063:
064: //~~~~~~~~~~~~~~~~~~~~~~~~ PARSER ----------------------------------
065:
066: // internally stops documet parsing when looking for public id
067: private static class StopSaxException extends SAXException {
068: public StopSaxException() {
069: super ("STOP");
070: } //NOI18N
071: }
072:
073: // static fields that that are logically a part of InfoParser
074:
075: private static final StopSaxException STOP = new StopSaxException();
076:
077: /** We are guaranteed to be executed in one thread let reuse parser, etc. */
078: private static XMLReader sharedParserImpl = null;
079:
080: static {
081: try {
082: sharedParserImpl = XMLUtil.createXMLReader();
083: sharedParserImpl
084: .setEntityResolver(new EmptyEntityResolver());
085: } catch (SAXException ex) {
086: Exceptions
087: .attachLocalizedMessage(ex,
088: "System does not contain JAXP 1.1 compliant parser!"); // NOI18N
089: Logger.getLogger(XMLDataObject.class.getName()).log(
090: Level.WARNING, null, ex);
091: }
092:
093: //initialize stuff possibly needed by libs that do not use
094: //JAXP but SAX 2 directly
095: try {
096: final Properties props = System.getProperties();
097: final String SAX2_KEY = "org.xml.sax.driver"; //NOI18N
098: if (props.getProperty(SAX2_KEY) == null) {
099: props.put(SAX2_KEY, sharedParserImpl.getClass()
100: .getName());
101: }
102: } catch (RuntimeException ex) {
103: //ignore it (we did the best efford)
104: }
105: }
106: /** a string to signal null value for parsedId */
107: private static final String NULL = ""; // NOI18N
108:
109: private Reference<XMLDataObject> xml;
110: private String parsedId;
111: private Lookup lookup;
112: private Lookup.Result result;
113: private ThreadLocal<Class<?>> QUERY = new ThreadLocal<Class<?>>();
114:
115: XMLDataObjectInfoParser(XMLDataObject xml) {
116: this .xml = new WeakReference<XMLDataObject>(xml);
117: }
118:
119: public String getPublicId() {
120: String id = waitFinished();
121: Object nu = NULL;
122: return id == nu ? null : id;
123: }
124:
125: public Object lookupCookie(final Class<?> clazz) {
126: if (QUERY.get() == clazz) {
127: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
128: XMLDataObject.ERR.fine("Cyclic deps on queried class: "
129: + clazz + " for " + getXml());
130: }
131: return new InstanceCookie() {
132:
133: public Class<?> instanceClass() {
134: return clazz;
135: }
136:
137: public Object instanceCreate() throws IOException {
138: throw new IOException("Cyclic reference, sorry: "
139: + clazz);
140: }
141:
142: public String instanceName() {
143: return clazz.getName();
144: }
145: };
146: }
147: Class<?> previous = QUERY.get();
148: try {
149: QUERY.set(clazz);
150: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
151: XMLDataObject.ERR.fine("Will do query for class: "
152: + clazz + " for " + getXml());
153: }
154: Lookup l;
155: for (;;) {
156: String id = waitFinished();
157: synchronized (this ) {
158: if (lookup != null) {
159: l = lookup;
160: } else {
161: l = null;
162: }
163: }
164: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
165: XMLDataObject.ERR.fine("Lookup is " + l
166: + " for id: " + id);
167: }
168: if (l == null) {
169: l = updateLookup(getXml(), null, id);
170: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
171: XMLDataObject.ERR.fine("Updating lookup: " + l);
172: }
173: }
174: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
175: XMLDataObject.ERR.fine("Wait lookup is over: " + l
176: + getXml());
177: }
178: if (l != null) {
179: break;
180: }
181: if (parsedId == null) {
182: l = Lookup.EMPTY;
183: break;
184: }
185: }
186: Lookup.Result r = result;
187: if (r != null) {
188: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
189: XMLDataObject.ERR.fine("Querying the result: " + r);
190: }
191: r.allItems();
192: } else {
193: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
194: XMLDataObject.ERR.fine("No result for lookup: "
195: + lookup);
196: }
197: }
198: Object ret = l.lookup(clazz);
199: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
200: XMLDataObject.ERR.fine("Returning value: " + ret
201: + " for " + getXml());
202: }
203: return ret;
204: } finally {
205: QUERY.set(previous);
206: }
207: }
208:
209: public String waitFinished() {
210: return waitFinished(null);
211: }
212:
213: private String waitFinished(String ignorePreviousId) {
214: if (sharedParserImpl == null) {
215: XMLDataObject.ERR.fine("No sharedParserImpl, exiting");
216: return NULL;
217: }
218: XMLReader parser = sharedParserImpl;
219: XMLDataObject realXML = getXml();
220: if (realXML == null) {
221: return NULL;
222: }
223:
224: FileObject myFileObject = realXML.getPrimaryFile();
225: String newID = null;
226: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
227: XMLDataObject.ERR.fine("Going to read parsedId for "
228: + realXML);
229: }
230: String previousID;
231: synchronized (this ) {
232: previousID = parsedId;
233: }
234: if (previousID != null) {
235: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
236: XMLDataObject.ERR.fine("Has already been parsed: "
237: + parsedId + " for " + realXML);
238: }
239: return previousID;
240: }
241: URL url = null;
242: InputStream in = null;
243: try {
244: url = myFileObject.getURL();
245: } catch (IOException ex) {
246: warning(ex,
247: "I/O exception while retrieving xml FileObject URL.");
248: return NULL;
249: }
250: synchronized (this ) {
251: try {
252: if (!myFileObject.isValid()) {
253: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
254: XMLDataObject.ERR.fine("Invalid file object: "
255: + myFileObject);
256: }
257: return NULL;
258: }
259: parsedId = NULL;
260: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
261: XMLDataObject.ERR.fine("parsedId set to NULL for "
262: + realXML);
263: }
264: try {
265: in = myFileObject.getInputStream();
266: } catch (IOException ex) {
267: warning(ex, "I/O exception while openning xml.");
268: return NULL;
269: }
270: try {
271: synchronized (sharedParserImpl) {
272: configureParser(parser, false, this );
273: parser.setContentHandler(this );
274: parser.setErrorHandler(this );
275: InputSource input = new InputSource(url
276: .toExternalForm());
277: input.setByteStream(in);
278: parser.parse(input);
279: }
280: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
281: XMLDataObject.ERR.fine("Parse finished for "
282: + realXML);
283: }
284: } catch (StopSaxException stopped) {
285: newID = parsedId;
286: XMLDataObject.ERR
287: .fine("Parsing successfully stopped: "
288: + parsedId + " for " + realXML);
289: } catch (SAXException checkStop) {
290: if (STOP.getMessage()
291: .equals(checkStop.getMessage())) {
292: newID = parsedId;
293: XMLDataObject.ERR
294: .fine("Parsing stopped with STOP message: "
295: + parsedId + " for " + realXML);
296: } else {
297: String msg = "Thread:"
298: + Thread.currentThread().getName();
299: XMLDataObject.ERR
300: .warning("DocListener should not throw SAXException but STOP one.\n"
301: + msg);
302: XMLDataObject.ERR.log(Level.WARNING, null,
303: checkStop);
304: Exception ex = checkStop.getException();
305: if (ex != null) {
306: XMLDataObject.ERR.log(Level.WARNING, null,
307: ex);
308: }
309: }
310: } catch (FileNotFoundException ex) {
311: XMLDataObject.ERR.log(Level.WARNING, null, ex);
312: } catch (IOException ex) {
313: XMLDataObject.ERR.log(Level.WARNING, null, ex);
314: } finally {
315: if (Boolean.getBoolean("netbeans.profile.memory")) {
316: parser
317: .setContentHandler(XMLDataObject.NullHandler.INSTANCE);
318: parser
319: .setErrorHandler(XMLDataObject.NullHandler.INSTANCE);
320: try {
321: parser
322: .setProperty(
323: "http://xml.org/sax/properties/lexical-handler",
324: XMLDataObject.NullHandler.INSTANCE);
325: } catch (SAXException ignoreIt) {
326: }
327: try {
328: parser.parse((InputSource) null);
329: } catch (Exception ignoreIt) {
330: }
331: }
332: parser = null;
333: }
334: } finally {
335: try {
336: if (in != null) {
337: in.close();
338: }
339: } catch (IOException ex) {
340: XMLDataObject.ERR.log(Level.WARNING, null, ex);
341: }
342: }
343: }
344: if (ignorePreviousId != null && newID.equals(ignorePreviousId)) {
345: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
346: XMLDataObject.ERR.fine("No update to ID: "
347: + ignorePreviousId + " for " + realXML);
348: }
349: return newID;
350: }
351: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
352: XMLDataObject.ERR.fine("New id: " + newID + " for "
353: + realXML);
354: }
355: if (newID != null) {
356: updateLookup(realXML, previousID, newID);
357: }
358: return newID;
359: }
360:
361: private Lookup updateLookup(XMLDataObject realXML,
362: String previousID, String id) {
363: if (realXML == null) {
364: return lookup;
365: }
366:
367: synchronized (this ) {
368: if (previousID != null && previousID.equals(id)
369: && lookup != null) {
370: XMLDataObject.ERR.fine("No need to update lookup: "
371: + id + " for " + realXML);
372: return lookup;
373: }
374: }
375: Lookup newLookup;
376: @SuppressWarnings("deprecation")
377: XMLDataObject.Info info = XMLDataObject.getRegisteredInfo(id);
378: if (info != null) {
379: newLookup = XMLDataObject.createInfoLookup(realXML, info);
380: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
381: XMLDataObject.ERR.fine("Lookup from info: " + newLookup
382: + " for " + realXML);
383: }
384: } else {
385: newLookup = Environment.findForOne(realXML);
386: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
387: XMLDataObject.ERR.fine("Lookup from env: " + newLookup
388: + " for " + realXML);
389: }
390: if (newLookup == null) {
391: newLookup = Lookup.EMPTY;
392: }
393: }
394: synchronized (this ) {
395: Lookup.Result prevRes = result;
396: lookup = newLookup;
397: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
398: XMLDataObject.ERR.fine("Shared lookup updated: "
399: + lookup + " for " + realXML);
400: }
401: result = lookup.lookupResult(Node.Cookie.class);
402: result.addLookupListener(this );
403: if (prevRes != null) {
404: prevRes.removeLookupListener(this );
405: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
406: XMLDataObject.ERR
407: .fine("Firing property change for "
408: + realXML);
409: }
410: realXML.firePropertyChange(DataObject.PROP_COOKIE,
411: null, null);
412: if (XMLDataObject.ERR.isLoggable(Level.FINE)) {
413: XMLDataObject.ERR
414: .fine("Firing done for " + realXML);
415: }
416: }
417: return newLookup;
418: }
419: }
420:
421: private void configureParser(XMLReader parser, boolean validation,
422: LexicalHandler lex) {
423: try {
424: parser.setFeature("http://xml.org/sax/features/validation",
425: validation);
426: } catch (SAXException sex) {
427: XMLDataObject.ERR
428: .fine("Warning: XML parser does not support validation feature.");
429: }
430: try {
431: parser.setProperty(
432: "http://xml.org/sax/properties/lexical-handler",
433: lex);
434: } catch (SAXException sex) {
435: XMLDataObject.ERR
436: .fine("Warning: XML parser does not support lexical-handler feature.");
437: }
438: }
439:
440: public void warning(Throwable ex) {
441: warning(ex, null);
442: }
443:
444: public void warning(Throwable ex, String annotation) {
445: XMLDataObject.ERR.log(Level.WARNING, annotation, ex);
446: }
447:
448: public void startDTD(String root, String pID, String sID)
449: throws SAXException {
450: parsedId = pID == null ? NULL : pID;
451: XMLDataObject.ERR.fine("Parsed to " + parsedId);
452: stop();
453: }
454:
455: public void endDTD() throws SAXException {
456: stop();
457: }
458:
459: public void startEntity(String name) throws SAXException {
460: XMLDataObject.ERR.log(Level.FINEST, "startEntity {0}", name);
461: }
462:
463: public void endEntity(String name) throws SAXException {
464: XMLDataObject.ERR.log(Level.FINEST, "endEntity {0}", name);
465: }
466:
467: public void startCDATA() throws SAXException {
468: XMLDataObject.ERR.log(Level.FINEST, "startCDATA");
469: }
470:
471: public void endCDATA() throws SAXException {
472: XMLDataObject.ERR.log(Level.FINEST, "endCDATA");
473: }
474:
475: public void comment(char[] ch, int start, int length)
476: throws SAXException {
477: XMLDataObject.ERR.log(Level.FINEST, "comment len: {0}", length);
478: }
479:
480: @Override
481: public void error(final SAXParseException p1)
482: throws org.xml.sax.SAXException {
483: stop();
484: }
485:
486: @Override
487: public void fatalError(final SAXParseException p1)
488: throws org.xml.sax.SAXException {
489: stop();
490: }
491:
492: @Override
493: public void endDocument() throws SAXException {
494: stop();
495: }
496:
497: public void startElement(String uri, String lName, String qName,
498: Attributes atts) throws SAXException {
499: stop();
500: }
501:
502: @Override
503: public void characters(char[] ch, int start, int length)
504: throws SAXException {
505: XMLDataObject.ERR.log(Level.FINEST, "characters len: {0}",
506: length);
507: }
508:
509: @Override
510: public void endElement(String uri, String localName, String qName)
511: throws SAXException {
512: XMLDataObject.ERR.log(Level.FINEST, "endElement: {0}", qName);
513: }
514:
515: @Override
516: public void endPrefixMapping(String prefix) throws SAXException {
517: XMLDataObject.ERR.log(Level.FINEST, "endPrefix: {0}", prefix);
518: }
519:
520: @Override
521: public void ignorableWhitespace(char[] ch, int start, int length)
522: throws SAXException {
523: XMLDataObject.ERR.log(Level.FINEST, "ignorableWhitespace: {0}",
524: length);
525: }
526:
527: @Override
528: public void notationDecl(String name, String publicId,
529: String systemId) throws SAXException {
530: XMLDataObject.ERR.log(Level.FINEST, "notationDecl: {0}", name);
531: }
532:
533: @Override
534: public void processingInstruction(String target, String data)
535: throws SAXException {
536: XMLDataObject.ERR.log(Level.FINEST,
537: "processingInstruction: {0}", target);
538: }
539:
540: @Override
541: public InputSource resolveEntity(String publicId, String systemId)
542: throws IOException, SAXException {
543: XMLDataObject.ERR.log(Level.FINEST, "resolveEntity: {0}",
544: publicId);
545: return super .resolveEntity(publicId, systemId);
546: }
547:
548: @Override
549: public void skippedEntity(String name) throws SAXException {
550: XMLDataObject.ERR.log(Level.FINEST, "skippedEntity: {0}", name);
551: }
552:
553: @Override
554: public void startDocument() throws SAXException {
555: XMLDataObject.ERR.log(Level.FINEST, "startDocument");
556: }
557:
558: @Override
559: public void startElement(String uri, String localName,
560: String qName, org.xml.sax.Attributes attributes)
561: throws SAXException {
562: XMLDataObject.ERR.log(Level.FINEST, "startElement: {0}", qName);
563: stop();
564: }
565:
566: @Override
567: public void startPrefixMapping(String prefix, String uri)
568: throws SAXException {
569: XMLDataObject.ERR.log(Level.FINEST, "startPrefixMapping: {0}",
570: prefix);
571: }
572:
573: @Override
574: public void unparsedEntityDecl(String name, String publicId,
575: String systemId, String notationName) throws SAXException {
576: XMLDataObject.ERR.log(Level.FINEST, "unparsedEntityDecl: {0}",
577: name);
578: }
579:
580: private void stop() throws SAXException {
581: XMLDataObject.ERR.log(Level.FINEST, "stop");
582: throw STOP;
583: }
584:
585: public void fileFolderCreated(FileEvent fe) {
586: }
587:
588: public void fileDataCreated(FileEvent fe) {
589: }
590:
591: private void fileCreated(FileObject fo) {
592: }
593:
594: public void fileChanged(FileEvent fe) {
595: XMLDataObject realXML = getXml();
596: if (realXML == null) {
597: return;
598: }
599:
600: if (realXML.getPrimaryFile().equals(fe.getFile())) {
601: realXML.clearDocument();
602: String prevId = parsedId;
603: parsedId = null;
604: XMLDataObject.ERR.fine("cleared parsedId");
605: waitFinished(prevId);
606: }
607: }
608:
609: public void fileDeleted(FileEvent fe) {
610: }
611:
612: public void fileRenamed(FileRenameEvent fe) {
613: }
614:
615: public void fileAttributeChanged(FileAttributeEvent fe) {
616: }
617:
618: public void resultChanged(LookupEvent lookupEvent) {
619: XMLDataObject realXML = getXml();
620: if (realXML == null) {
621: return;
622: }
623: realXML.firePropertyChange(DataObject.PROP_COOKIE, null, null);
624: Node n = realXML.getNodeDelegateOrNull();
625: if (n instanceof XMLDataObject.XMLNode) {
626: ((XMLDataObject.XMLNode) n).update();
627: }
628: }
629:
630: /** Avoid Internet connections */
631: private static class EmptyEntityResolver implements EntityResolver {
632: EmptyEntityResolver() {
633: }
634:
635: public InputSource resolveEntity(String publicId,
636: String systemID) {
637: InputSource ret = new InputSource(new StringReader("")); //??? we should tolerate file: and nbfs: // NOI18N
638: ret.setSystemId("StringReader"); //NOI18N
639: return ret;
640: }
641: }
642:
643: private XMLDataObject getXml() {
644: return xml.get();
645: }
646: }
|