2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.core.filesystems;
44
import java.lang.ref.WeakReference;
45
import java.util.logging.Level;
46
import java.util.logging.Logger;
47
import org.openide.filesystems.FileObject;
48
import org.openide.util.NbBundle;
49
import org.openide.xml.XMLUtil;
50
import org.xml.sax.Attributes;
51
import org.xml.sax.SAXException;
52
import org.xml.sax.SAXParseException;
53
import org.xml.sax.XMLReader;
54
import org.xml.sax.ext.LexicalHandler;
57
* This source represents a <b>XML rules</b> core plugin to <tt>MIMEReolverImpl</tt>.
62
final class XMLMIMEComponent extends DefaultParser implements MIMEComponent {
64
private short parseState = INIT;
66
// template obtained form parsed description
67
private final Smell template = new Smell();
69
// cached and reused parser used for sniffing
70
private static final LocalSniffingParser local = new LocalSniffingParser();
72
// FileObjectFilter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
74
public boolean acceptFileObject(FileObject fo) {
76
// it may come from arbitrary thread
77
// retrive per thread instance
79
SniffingParser sniffer = local.getParser();
80
Smell print = sniffer.sniff(fo);
81
// System.err.println("Print of " + fo);
82
// System.err.println("print " + print);
83
// System.err.println("template " + template);
84
return template.match(print);
87
public String toString() {
88
return template.toString();
91
// XML description -> memory representation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
94
// pseudo validation states
95
private static final short INIT = 0;
96
private static final short IN_ROOT = 1;
97
private static final short IN_DOCTYPE = 2;
98
private static final short IN_ELEMENT = 3;
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
114
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
118
switch (parseState) {
121
if (ROOT.equals(qName) == false) error();
122
parseState = IN_ROOT;
126
if (PI.equals(qName)) {
127
s = atts.getValue(TARGET); if (s == null) error();
130
//!!! TODO presudo atts
132
} else if (DOCTYPE.equals(qName)) {
133
s = atts.getValue(PUBLIC_ID);
135
parseState = IN_DOCTYPE;
138
template.addDoctype(s);
141
} else if (ELEMENT.equals(qName)) {
143
s = atts.getValue(NAME);
145
s = atts.getValue(NS);
146
if (s != null) template.addElementNS(s);
148
template.addElementName(s);
149
s = atts.getValue(NS);
150
if (s != null) template.addElementNS(s);
153
parseState = IN_ELEMENT;
161
if (PUBLIC_ID.equals(qName) == false) error();
162
s = atts.getValue(ID); if (s == null) error();
163
template.addDoctype(s);
167
if (ATTR.equals(qName)) {
168
s = atts.getValue(NAME); if (s == null) error();
169
template.addElementAtt(s, atts.getValue(VALUE));
171
} else if (NS.equals(qName)) {
172
s = atts.getValue(NAME); if (s == null) error();
173
template.addElementNS(s);
182
public void endElement(String namespaceURI, String localName, String qName) {
184
switch (parseState) {
186
if (ELEMENT.equals(qName)) parseState = IN_ROOT;
190
if (DOCTYPE.equals(qName)) parseState = IN_ROOT;
195
// Sniffing parser ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
199
* Create just one shared parser instance per thread.
200
* Consequently one instance cannot be run in paralel eliminating need for sync.
202
private static class LocalSniffingParser extends ThreadLocal<WeakReference<SniffingParser>> {
203
LocalSniffingParser() {}
205
private WeakReference<SniffingParser> wref = null;
207
protected WeakReference<SniffingParser> initialValue() {
208
SniffingParser parser = new SniffingParser();
209
wref = new WeakReference<SniffingParser>(parser);
213
public SniffingParser getParser() {
214
WeakReference<SniffingParser> cache = get();
215
SniffingParser cached = cache.get();
216
if (cached == null) {
217
cached = new SniffingParser();
218
wref = new WeakReference<SniffingParser>(cached);
224
public void set(WeakReference<SniffingParser> data) {
231
* Parser that test XML Document header.
233
private static class SniffingParser extends DefaultParser implements LexicalHandler {
239
// last succesfully sniffed fileobject
240
private FileObject lastFileObject = null;
242
private Smell print = null;
244
// the only way how to stop parser is throwing an exception
245
private static final SAXException STOP = new SAXException("STOP"); //NOI18N
248
* Go ahead and retrieve a print or null
250
protected Smell sniff(FileObject fo) {
252
if (fo == null) return null;
254
if (fo.equals(lastFileObject)) return print;
256
if (fo.isValid() == false) return null;
258
if (fo.getSize() == 0) return null;
262
if (this.state == ERROR) {
270
protected XMLReader createXMLReader() {
271
XMLReader parser = null;
274
parser = XMLUtil.createXMLReader(false, true);
276
parser.setProperty("http://xml.org/sax/properties/lexical-handler", this); //NOI18N
277
} catch (SAXException sex) {
278
Logger.getLogger(XMLMIMEComponent.class.getName()).fine(NbBundle.getMessage(XMLMIMEComponent.class, "W-003")); //NOI18N
280
} catch (SAXException ex) {
281
Logger.getLogger(XMLMIMEComponent.class.getName()).log(Level.WARNING, null, ex);
286
protected boolean isStopException(Exception e) {
287
return STOP.getMessage().equals(e.getMessage());
291
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
292
if (namespaceURI != null) {
293
print.addElementNS(namespaceURI);
295
if ("".equals(localName)) localName = null; //#16484 //NOI18N
296
print.addElementName(localName != null ? localName : qName);
297
for (int i = 0; i<atts.getLength(); i++) {
298
print.addElementAtt(atts.getQName(i), atts.getValue(i));
303
public void processingInstruction(String target, String data) throws SAXException {
309
public void startDTD(String root, String pID, String sID) throws SAXException {
310
print.addDoctype(pID);
313
public void endDTD() {}
315
public void startEntity(String name) {}
317
public void endEntity(String name) {}
319
public void startCDATA() {}
321
public void endCDATA() {}
323
public void comment(char[] ch, int start, int length) {}
325
public void error(SAXParseException exception) throws SAXException {
326
// we are not validating should not occure
327
Logger.getLogger(XMLMIMEComponent.class.getName()).warning(exception.getMessage());
332
public void fatalError(SAXParseException exception) throws SAXException {
334
// it may be caused by wrong user XML documents, notify only in debug mode
335
// also see #16484 if the error message makes no sense
336
Logger emgr = Logger.getLogger("org.netbeans.core.filesystems.XMLMIMEComponent"); // NOI18N
337
if (emgr.isLoggable(Level.FINE)) {
338
emgr.fine("[while parsing " + fo + "] " + exception.getSystemId() + ":" + exception.getLineNumber() + ":" + exception.getColumnNumber() + ": " + exception.getMessage()); // NOI18N
348
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
351
* Template smell per resolver and print data per FileObject.
353
private static class Smell {
356
private String[] doctypes = null;
357
private String[] pis = null;
359
private String root = null;
360
private String[] nss = null;
362
private String[] attns = null;
363
private String[] attvs = null;
365
public String toString() {
366
StringBuffer buf = new StringBuffer();
368
buf.append("xml-check(");
370
if (doctypes != null) {
371
buf.append("doctypes:");
372
for (i = 0; i<doctypes.length; i++)
373
buf.append(doctypes[i]).append(", ");
378
for (i = 0; i<pis.length; i++)
379
buf.append(pis[i]).append(", ");
383
buf.append("root:").append(root);
387
buf.append("root-namespaces:");
388
for (i = 0; i<nss.length; i++)
389
buf.append(nss[i]).append(", ");
393
buf.append("attributes:");
394
for (i = 0; i<attns.length; i++)
395
buf.append(attns[i]).append("='").append(attvs[i]).append("'");
399
return buf.toString();
403
private void addDoctype(String s) {
404
if (doctypes == null) {
405
doctypes = new String[] { s };
407
doctypes = Util.addString(doctypes, s);
411
private void addPI(String s) {
413
pis = new String[] { s };
415
pis = Util.addString(pis, s);
419
private void addElementNS(String s) {
421
nss = new String[] { s };
423
nss = Util.addString(nss, s);
427
private void addElementName(String name) {
431
private void addElementAtt(String name, String value) {
433
attns = new String[] {name};
434
attvs = new String[] {value};
436
attns = Util.addString(attns, name);
437
attvs = Util.addString(attvs, value);
443
* Matches passed data this template?
444
* Any of constructs must match.
446
public boolean match(Smell t) {
448
if (t == null) return false;
450
// try if a doctype public-id matches
452
if (doctypes != null && t.doctypes != null) {
453
if (Util.contains(doctypes, t.doctypes[0])) return true;
456
// try root element match
458
if (root != null && root.equals(t.root)) {
460
if (attMatch(t)) return true;
462
if (t.nss != null && Util.contains(nss, t.nss[0])) {
463
if (attMatch(t)) return true;
467
if (root == null && nss != null && t.nss != null && Util.contains(nss, t.nss[0])) {
468
if (attMatch(t)) return true;
472
// try if a PI matches
474
if (pis != null && t.pis!=null) {
475
for (int i = 0; i<pis.length; i++) {
476
for (int j = 0; j<t.pis.length; j++) {
477
if (pis[i].equals(t.pis[j])) return true;
486
private boolean attMatch(Smell t) {
488
if (attns == null) return true;
489
if (t.attns == null) return false;
491
// all attributes must match by name ...
492
for (int i = 0 ; i<attns.length; i++) {
493
int match = Util.indexOf(t.attns, attns[i]);
498
// ... and value if specified in template
500
if (attvs[i] != null && (!attvs[i].equals(t.attvs[match]))) {