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: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.core.filesystems;
043:
044: import java.lang.ref.WeakReference;
045: import java.util.logging.Level;
046: import java.util.logging.Logger;
047: import org.openide.filesystems.FileObject;
048: import org.openide.util.NbBundle;
049: import org.openide.xml.XMLUtil;
050: import org.xml.sax.Attributes;
051: import org.xml.sax.SAXException;
052: import org.xml.sax.SAXParseException;
053: import org.xml.sax.XMLReader;
054: import org.xml.sax.ext.LexicalHandler;
055:
056: /**
057: * This source represents a <b>XML rules</b> core plugin to <tt>MIMEReolverImpl</tt>.
058: *
059: * @author Petr Kuzel
060: * @version
061: */
062: final class XMLMIMEComponent extends DefaultParser implements
063: MIMEComponent {
064:
065: private short parseState = INIT;
066:
067: // template obtained form parsed description
068: private final Smell template = new Smell();
069:
070: // cached and reused parser used for sniffing
071: private static final LocalSniffingParser local = new LocalSniffingParser();
072:
073: // FileObjectFilter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
074:
075: public boolean acceptFileObject(FileObject fo) {
076:
077: // it may come from arbitrary thread
078: // retrive per thread instance
079:
080: SniffingParser sniffer = local.getParser();
081: Smell print = sniffer.sniff(fo);
082: // System.err.println("Print of " + fo);
083: // System.err.println("print " + print);
084: // System.err.println("template " + template);
085: return template.match(print);
086: }
087:
088: public String toString() {
089: return template.toString();
090: }
091:
092: // XML description -> memory representation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
093:
094: // pseudo validation states
095: private static final short INIT = 0;
096: private static final short IN_ROOT = 1;
097: private static final short IN_DOCTYPE = 2;
098: private static final short IN_ELEMENT = 3;
099:
100: // grammar elements
101: private static final String ROOT = "xml-rule"; // NOI18N
102: private static final String PI = "pi"; // NOI18N
103: private static final String ELEMENT = "element"; // NOI18N
104: private static final String DOCTYPE = "doctype"; // NOI18N
105: private static final String PUBLIC_ID = "public-id"; // NOI18N
106: private static final String ID = "id"; // NOI18N
107: private static final String ATTR = "attr"; // NOI18N
108: private static final String NAME = "name"; // NOI18N
109: private static final String VALUE = "text"; // NOI18N
110: private static final String NS = "ns"; // NOI18N
111: private static final String TARGET = "target"; // NOI18N
112:
113: public void startElement(String namespaceURI, String localName,
114: String qName, Attributes atts) throws SAXException {
115:
116: String s;
117:
118: switch (parseState) {
119:
120: case INIT:
121: if (ROOT.equals(qName) == false)
122: error();
123: parseState = IN_ROOT;
124: break;
125:
126: case IN_ROOT:
127: if (PI.equals(qName)) {
128: s = atts.getValue(TARGET);
129: if (s == null)
130: error();
131: template.addPI(s);
132:
133: //!!! TODO presudo atts
134:
135: } else if (DOCTYPE.equals(qName)) {
136: s = atts.getValue(PUBLIC_ID);
137: if (s == null) {
138: parseState = IN_DOCTYPE;
139: break;
140: } else {
141: template.addDoctype(s);
142: }
143:
144: } else if (ELEMENT.equals(qName)) {
145:
146: s = atts.getValue(NAME);
147: if (s == null) {
148: s = atts.getValue(NS);
149: if (s != null)
150: template.addElementNS(s);
151: } else {
152: template.addElementName(s);
153: s = atts.getValue(NS);
154: if (s != null)
155: template.addElementNS(s);
156: }
157:
158: parseState = IN_ELEMENT;
159:
160: } else {
161: error();
162: }
163: break;
164:
165: case IN_DOCTYPE:
166: if (PUBLIC_ID.equals(qName) == false)
167: error();
168: s = atts.getValue(ID);
169: if (s == null)
170: error();
171: template.addDoctype(s);
172: break;
173:
174: case IN_ELEMENT:
175: if (ATTR.equals(qName)) {
176: s = atts.getValue(NAME);
177: if (s == null)
178: error();
179: template.addElementAtt(s, atts.getValue(VALUE));
180:
181: } else if (NS.equals(qName)) {
182: s = atts.getValue(NAME);
183: if (s == null)
184: error();
185: template.addElementNS(s);
186:
187: } else {
188: error();
189: }
190:
191: }
192: }
193:
194: public void endElement(String namespaceURI, String localName,
195: String qName) {
196:
197: switch (parseState) {
198: case IN_ELEMENT:
199: if (ELEMENT.equals(qName))
200: parseState = IN_ROOT;
201: break;
202:
203: case IN_DOCTYPE:
204: if (DOCTYPE.equals(qName))
205: parseState = IN_ROOT;
206: break;
207: }
208: }
209:
210: // Sniffing parser ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
211:
212: /**
213: * Create just one shared parser instance per thread.
214: * Consequently one instance cannot be run in paralel eliminating need for sync.
215: */
216: private static class LocalSniffingParser extends
217: ThreadLocal<WeakReference<SniffingParser>> {
218: LocalSniffingParser() {
219: }
220:
221: private WeakReference<SniffingParser> wref = null;
222:
223: protected WeakReference<SniffingParser> initialValue() {
224: SniffingParser parser = new SniffingParser();
225: wref = new WeakReference<SniffingParser>(parser);
226: return wref;
227: }
228:
229: public SniffingParser getParser() {
230: WeakReference<SniffingParser> cache = get();
231: SniffingParser cached = cache.get();
232: if (cached == null) {
233: cached = new SniffingParser();
234: wref = new WeakReference<SniffingParser>(cached);
235: super .set(wref);
236: }
237: return cached;
238: }
239:
240: public void set(WeakReference<SniffingParser> data) {
241: // we are read only!
242: }
243: }
244:
245: /**
246: * Parser that test XML Document header.
247: */
248: private static class SniffingParser extends DefaultParser implements
249: LexicalHandler {
250:
251: SniffingParser() {
252: super (null);
253: }
254:
255: // last succesfully sniffed fileobject
256: private FileObject lastFileObject = null;
257:
258: private Smell print = null;
259:
260: // the only way how to stop parser is throwing an exception
261: private static final SAXException STOP = new SAXException(
262: "STOP"); //NOI18N
263:
264: /**
265: * Go ahead and retrieve a print or null
266: */
267: protected Smell sniff(FileObject fo) {
268:
269: if (fo == null)
270: return null;
271:
272: if (fo.equals(lastFileObject))
273: return print;
274:
275: if (fo.isValid() == false)
276: return null;
277:
278: if (fo.getSize() == 0)
279: return null;
280:
281: print = new Smell();
282: parse(fo);
283: if (this .state == ERROR) {
284: return null;
285: }
286:
287: lastFileObject = fo;
288: return print;
289: }
290:
291: protected XMLReader createXMLReader() {
292: XMLReader parser = null;
293:
294: try {
295: parser = XMLUtil.createXMLReader(false, true);
296: try {
297: parser
298: .setProperty(
299: "http://xml.org/sax/properties/lexical-handler",
300: this ); //NOI18N
301: } catch (SAXException sex) {
302: Logger.getLogger(XMLMIMEComponent.class.getName())
303: .fine(
304: NbBundle.getMessage(
305: XMLMIMEComponent.class,
306: "W-003")); //NOI18N
307: }
308: } catch (SAXException ex) {
309: Logger.getLogger(XMLMIMEComponent.class.getName()).log(
310: Level.WARNING, null, ex);
311: }
312: return parser;
313: }
314:
315: protected boolean isStopException(Exception e) {
316: return STOP.getMessage().equals(e.getMessage());
317: }
318:
319: public void startElement(String namespaceURI, String localName,
320: String qName, Attributes atts) throws SAXException {
321: if (namespaceURI != null) {
322: print.addElementNS(namespaceURI);
323: }
324: if ("".equals(localName))
325: localName = null; //#16484 //NOI18N
326: print.addElementName(localName != null ? localName : qName);
327: for (int i = 0; i < atts.getLength(); i++) {
328: print.addElementAtt(atts.getQName(i), atts.getValue(i));
329: }
330: throw STOP;
331: }
332:
333: public void processingInstruction(String target, String data)
334: throws SAXException {
335: print.addPI(target);
336: }
337:
338: // LexicalHandler
339:
340: public void startDTD(String root, String pID, String sID)
341: throws SAXException {
342: print.addDoctype(pID);
343: }
344:
345: public void endDTD() {
346: }
347:
348: public void startEntity(String name) {
349: }
350:
351: public void endEntity(String name) {
352: }
353:
354: public void startCDATA() {
355: }
356:
357: public void endCDATA() {
358: }
359:
360: public void comment(char[] ch, int start, int length) {
361: }
362:
363: public void error(SAXParseException exception)
364: throws SAXException {
365: // we are not validating should not occure
366: Logger.getLogger(XMLMIMEComponent.class.getName()).warning(
367: exception.getMessage());
368: this .state = ERROR;
369: throw STOP;
370: }
371:
372: public void fatalError(SAXParseException exception)
373: throws SAXException {
374:
375: // it may be caused by wrong user XML documents, notify only in debug mode
376: // also see #16484 if the error message makes no sense
377: Logger emgr = Logger
378: .getLogger("org.netbeans.core.filesystems.XMLMIMEComponent"); // NOI18N
379: if (emgr.isLoggable(Level.FINE)) {
380: emgr.fine("[while parsing " + fo + "] "
381: + exception.getSystemId() + ":"
382: + exception.getLineNumber() + ":"
383: + exception.getColumnNumber() + ": "
384: + exception.getMessage()); // NOI18N
385: }
386:
387: this .state = ERROR;
388: throw STOP;
389: }
390:
391: }
392:
393: // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
394:
395: /**
396: * Template smell per resolver and print data per FileObject.
397: */
398: private static class Smell {
399: Smell() {
400: }
401:
402: private String[] doctypes = null;
403: private String[] pis = null;
404:
405: private String root = null;
406: private String[] nss = null;
407:
408: private String[] attns = null;
409: private String[] attvs = null;
410:
411: public String toString() {
412: StringBuffer buf = new StringBuffer();
413: int i = 0;
414: buf.append("xml-check(");
415:
416: if (doctypes != null) {
417: buf.append("doctypes:");
418: for (i = 0; i < doctypes.length; i++)
419: buf.append(doctypes[i]).append(", ");
420: }
421:
422: if (pis != null) {
423: buf.append("PIs:");
424: for (i = 0; i < pis.length; i++)
425: buf.append(pis[i]).append(", ");
426: }
427:
428: if (root != null) {
429: buf.append("root:").append(root);
430: }
431:
432: if (nss != null) {
433: buf.append("root-namespaces:");
434: for (i = 0; i < nss.length; i++)
435: buf.append(nss[i]).append(", ");
436: }
437:
438: if (attns != null) {
439: buf.append("attributes:");
440: for (i = 0; i < attns.length; i++)
441: buf.append(attns[i]).append("='").append(attvs[i])
442: .append("'");
443: }
444:
445: buf.append(')');
446: return buf.toString();
447:
448: }
449:
450: private void addDoctype(String s) {
451: if (doctypes == null) {
452: doctypes = new String[] { s };
453: } else {
454: doctypes = Util.addString(doctypes, s);
455: }
456: }
457:
458: private void addPI(String s) {
459: if (pis == null) {
460: pis = new String[] { s };
461: } else {
462: pis = Util.addString(pis, s);
463: }
464: }
465:
466: private void addElementNS(String s) {
467: if (nss == null) {
468: nss = new String[] { s };
469: } else {
470: nss = Util.addString(nss, s);
471: }
472: }
473:
474: private void addElementName(String name) {
475: root = name;
476: }
477:
478: private void addElementAtt(String name, String value) {
479: if (attns == null) {
480: attns = new String[] { name };
481: attvs = new String[] { value };
482: } else {
483: attns = Util.addString(attns, name);
484: attvs = Util.addString(attvs, value);
485: }
486:
487: }
488:
489: /**
490: * Matches passed data this template?
491: * Any of constructs must match.
492: */
493: public boolean match(Smell t) {
494:
495: if (t == null)
496: return false;
497:
498: // try if a doctype public-id matches
499:
500: if (doctypes != null && t.doctypes != null) {
501: if (Util.contains(doctypes, t.doctypes[0]))
502: return true;
503: }
504:
505: // try root element match
506:
507: if (root != null && root.equals(t.root)) {
508: if (nss == null) {
509: if (attMatch(t))
510: return true;
511: } else {
512: if (t.nss != null && Util.contains(nss, t.nss[0])) {
513: if (attMatch(t))
514: return true;
515: }
516: }
517: } else {
518: if (root == null && nss != null && t.nss != null
519: && Util.contains(nss, t.nss[0])) {
520: if (attMatch(t))
521: return true;
522: }
523: }
524:
525: // try if a PI matches
526:
527: if (pis != null && t.pis != null) {
528: for (int i = 0; i < pis.length; i++) {
529: for (int j = 0; j < t.pis.length; j++) {
530: if (pis[i].equals(t.pis[j]))
531: return true;
532: }
533: }
534: }
535:
536: return false;
537: }
538:
539: private boolean attMatch(Smell t) {
540:
541: if (attns == null)
542: return true;
543: if (t.attns == null)
544: return false;
545:
546: // all attributes must match by name ...
547: for (int i = 0; i < attns.length; i++) {
548: int match = Util.indexOf(t.attns, attns[i]);
549: if (match == -1) {
550: return false;
551: }
552:
553: // ... and value if specified in template
554:
555: if (attvs[i] != null
556: && (!attvs[i].equals(t.attvs[match]))) {
557: return false;
558: }
559: }
560:
561: return true;
562:
563: }
564:
565: }
566: }
|