View Javadoc
1   /* -*-             c-basic-offset: 4; indent-tabs-mode: nil; -*-  //------100-columns-wide------>|*/
2   // for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/)
3   
4   package org.codehaus.plexus.metadata.merge;
5   
6   import java.io.FileInputStream;
7   import java.io.FileNotFoundException;
8   import java.io.IOException;
9   import java.io.InputStream;
10  import java.io.Reader;
11  import java.net.MalformedURLException;
12  import java.net.URL;
13  
14  import org.codehaus.plexus.util.xml.pull.XmlPullParser;
15  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
16  import org.xml.sax.Attributes;
17  import org.xml.sax.ContentHandler;
18  import org.xml.sax.DTDHandler;
19  import org.xml.sax.EntityResolver;
20  import org.xml.sax.ErrorHandler;
21  import org.xml.sax.InputSource;
22  import org.xml.sax.Locator;
23  import org.xml.sax.SAXException;
24  import org.xml.sax.SAXNotRecognizedException;
25  import org.xml.sax.SAXNotSupportedException;
26  import org.xml.sax.SAXParseException;
27  import org.xml.sax.XMLReader;
28  import org.xml.sax.helpers.DefaultHandler;
29  
30  /**
31   * SAX2 Driver that pulls events from XmlPullParser
32   * and comverts them into SAX2 callbacks.
33   *
34   * @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander Slominski</a>
35   */
36  public class Driver implements Locator, XMLReader, Attributes {
37  
38      protected static final String EXTERNAL_GENERAL_ENTITIES_PROPERTY =
39              "http://xml.org/sax/features/external-general-entities";
40  
41      protected static final String DECLARATION_HANDLER_PROPERTY = "http://xml.org/sax/properties/declaration-handler";
42  
43      protected static final String LEXICAL_HANDLER_PROPERTY = "http://xml.org/sax/properties/lexical-handler";
44  
45      protected static final String NAMESPACES_FEATURE = "http://xml.org/sax/features/namespaces";
46  
47      protected static final String NAMESPACE_PREFIXES_FEATURE = "http://xml.org/sax/features/namespace-prefixes";
48  
49      protected static final String VALIDATION_FEATURE = "http://xml.org/sax/features/validation";
50  
51      protected static final String APACHE_SCHEMA_VALIDATION_FEATURE = "http://apache.org/xml/features/validation/schema";
52  
53      protected static final String APACHE_DYNAMIC_VALIDATION_FEATURE =
54              "http://apache.org/xml/features/validation/dynamic";
55  
56      protected ContentHandler contentHandler = new DefaultHandler();
57      protected ErrorHandler errorHandler = new DefaultHandler();
58      ;
59  
60      protected String systemId;
61  
62      protected XmlPullParser pp;
63  
64      public Driver() throws XmlPullParserException {
65          pp = new MXParser();
66  
67          try {
68              setFeature(NAMESPACES_FEATURE, true);
69              setFeature(NAMESPACE_PREFIXES_FEATURE, true);
70  
71          } catch (Exception e) {
72              e.printStackTrace();
73          }
74      }
75  
76      // -- Attributes interface
77  
78      public int getLength() {
79          return pp.getAttributeCount();
80      }
81  
82      public String getURI(int index) {
83          return pp.getAttributeNamespace(index);
84      }
85  
86      public String getLocalName(int index) {
87          return pp.getAttributeName(index);
88      }
89  
90      public String getQName(int index) {
91          final String prefix = pp.getAttributePrefix(index);
92          if (prefix != null) {
93              return prefix + ':' + pp.getAttributeName(index);
94          } else {
95              return pp.getAttributeName(index);
96          }
97      }
98  
99      public String getType(int index) {
100         return pp.getAttributeType(index);
101     }
102 
103     public String getValue(int index) {
104         return pp.getAttributeValue(index);
105     }
106 
107     public int getIndex(String uri, String localName) {
108         for (int i = 0; i < pp.getAttributeCount(); i++) {
109             if (pp.getAttributeNamespace(i).equals(uri)
110                     && pp.getAttributeName(i).equals(localName)) {
111                 return i;
112             }
113         }
114         return -1;
115     }
116 
117     public int getIndex(String qName) {
118         for (int i = 0; i < pp.getAttributeCount(); i++) {
119             if (pp.getAttributeName(i).equals(qName)) {
120                 return i;
121             }
122         }
123         return -1;
124     }
125 
126     public String getType(String uri, String localName) {
127         for (int i = 0; i < pp.getAttributeCount(); i++) {
128             if (pp.getAttributeNamespace(i).equals(uri)
129                     && pp.getAttributeName(i).equals(localName)) {
130                 return pp.getAttributeType(i);
131             }
132         }
133         return null;
134     }
135 
136     public String getType(String qName) {
137         for (int i = 0; i < pp.getAttributeCount(); i++) {
138             if (pp.getAttributeName(i).equals(qName)) {
139                 return pp.getAttributeType(i);
140             }
141         }
142         return null;
143     }
144 
145     public String getValue(String uri, String localName) {
146         return pp.getAttributeValue(uri, localName);
147     }
148 
149     public String getValue(String qName) {
150         return pp.getAttributeValue(null, qName);
151     }
152 
153     // -- Locator interface
154 
155     public String getPublicId() {
156         return null;
157     }
158 
159     public String getSystemId() {
160         return systemId;
161     }
162 
163     public int getLineNumber() {
164         return pp.getLineNumber();
165     }
166 
167     public int getColumnNumber() {
168         return pp.getColumnNumber();
169     }
170 
171     // --- XMLReader interface
172 
173     // "http://xml.org/sax/features/namespaces",
174     // true
175 
176     public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
177         if (NAMESPACES_FEATURE.equals(name)) {
178             return pp.getFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES);
179         } else if (NAMESPACE_PREFIXES_FEATURE.equals(name)) {
180             return pp.getFeature(XmlPullParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES);
181         } else if (VALIDATION_FEATURE.equals(name)) {
182             return pp.getFeature(XmlPullParser.FEATURE_VALIDATION);
183             //        } else if(APACHE_SCHEMA_VALIDATION_FEATURE.equals(name)) {
184             //            return false;  //TODO
185             //        } else if(APACHE_DYNAMIC_VALIDATION_FEATURE.equals(name)) {
186             //            return false; //TODO
187         } else {
188             return pp.getFeature(name);
189             // throw new SAXNotRecognizedException("unrecognized feature "+name);
190         }
191     }
192 
193     public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
194         try {
195             if (NAMESPACES_FEATURE.equals(name)) {
196                 pp.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, value);
197             } else if (NAMESPACE_PREFIXES_FEATURE.equals(name)) {
198                 if (pp.getFeature(XmlPullParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES) != value) {
199                     pp.setFeature(XmlPullParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES, value);
200                 }
201             } else if (VALIDATION_FEATURE.equals(name)) {
202                 pp.setFeature(XmlPullParser.FEATURE_VALIDATION, value);
203             } else if (EXTERNAL_GENERAL_ENTITIES_PROPERTY.equals(name)) {
204                 // ignore
205             } else {
206                 pp.setFeature(name, value);
207                 // throw new SAXNotRecognizedException("unrecognized feature "+name);
208             }
209         } catch (XmlPullParserException ex) {
210             throw new SAXNotSupportedException("problem with setting feature " + name + ": " + ex);
211         }
212     }
213 
214     public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
215         if (DECLARATION_HANDLER_PROPERTY.equals(name)) {
216             return null;
217         } else if (LEXICAL_HANDLER_PROPERTY.equals(name)) {
218             return null;
219         } else {
220             return pp.getProperty(name);
221             // throw new SAXNotRecognizedException("not recognized get property "+name);
222         }
223     }
224 
225     public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
226         //
227         if (DECLARATION_HANDLER_PROPERTY.equals(name)) {
228             throw new SAXNotSupportedException("not supported setting property " + name); // +" to "+value);
229         } else if (LEXICAL_HANDLER_PROPERTY.equals(name)) {
230             throw new SAXNotSupportedException("not supported setting property " + name); // +" to "+value);
231         } else {
232             try {
233                 pp.setProperty(name, value);
234             } catch (XmlPullParserException ex) {
235                 throw new SAXNotSupportedException("not supported set property " + name + ": " + ex);
236             }
237             // throw new SAXNotRecognizedException("not recognized set property "+name);
238         }
239     }
240 
241     public void setEntityResolver(EntityResolver resolver) {}
242 
243     public EntityResolver getEntityResolver() {
244         return null;
245     }
246 
247     public void setDTDHandler(DTDHandler handler) {}
248 
249     public DTDHandler getDTDHandler() {
250         return null;
251     }
252 
253     public void setContentHandler(ContentHandler handler) {
254         this.contentHandler = handler;
255     }
256 
257     public ContentHandler getContentHandler() {
258         return contentHandler;
259     }
260 
261     public void setErrorHandler(ErrorHandler handler) {
262         this.errorHandler = handler;
263     }
264 
265     public ErrorHandler getErrorHandler() {
266         return errorHandler;
267     }
268 
269     public void parse(InputSource source) throws SAXException, IOException {
270 
271         systemId = source.getSystemId();
272         contentHandler.setDocumentLocator(this);
273 
274         final Reader reader = source.getCharacterStream();
275         try {
276             if (reader == null) {
277                 InputStream stream = source.getByteStream();
278                 final String encoding = source.getEncoding();
279 
280                 if (stream == null) {
281                     systemId = source.getSystemId();
282                     if (systemId == null) {
283                         SAXParseException saxException = new SAXParseException("null source systemId", this);
284                         errorHandler.fatalError(saxException);
285                         return;
286                     }
287                     // NOTE: replace with Connection to run in J2ME environment
288                     try {
289                         final URL url = new URL(systemId);
290                         stream = url.openStream();
291                     } catch (MalformedURLException nue) {
292                         try {
293                             stream = new FileInputStream(systemId);
294                         } catch (FileNotFoundException fnfe) {
295                             final SAXParseException saxException =
296                                     new SAXParseException("could not open file with systemId " + systemId, this, fnfe);
297                             errorHandler.fatalError(saxException);
298                             return;
299                         }
300                     }
301                 }
302                 pp.setInput(stream, encoding);
303             } else {
304                 pp.setInput(reader);
305             }
306         } catch (XmlPullParserException ex) {
307             final SAXParseException saxException =
308                     new SAXParseException("parsing initialization error: " + ex, this, ex);
309             // if(DEBUG) ex.printStackTrace();
310             errorHandler.fatalError(saxException);
311             return;
312         }
313 
314         // start parsing - move to first start tag
315         try {
316             contentHandler.startDocument();
317             // get first event
318             pp.next();
319             // it should be start tag...
320             if (pp.getEventType() != XmlPullParser.START_TAG) {
321                 final SAXParseException saxException =
322                         new SAXParseException("expected start tag not" + pp.getPositionDescription(), this);
323                 // throw saxException;
324                 errorHandler.fatalError(saxException);
325                 return;
326             }
327         } catch (XmlPullParserException ex) {
328             final SAXParseException saxException =
329                     new SAXParseException("parsing initialization error: " + ex, this, ex);
330             // ex.printStackTrace();
331             errorHandler.fatalError(saxException);
332             return;
333         }
334 
335         // now real parsing can start!
336 
337         parseSubTree(pp);
338 
339         // and finished ...
340 
341         contentHandler.endDocument();
342     }
343 
344     public void parse(String systemId) throws SAXException, IOException {
345         parse(new InputSource(systemId));
346     }
347 
348     public void parseSubTree(XmlPullParser pp) throws SAXException, IOException {
349         this.pp = pp;
350         final boolean namespaceAware = pp.getFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES);
351         try {
352             if (pp.getEventType() != XmlPullParser.START_TAG) {
353                 throw new SAXException("start tag must be read before skiping subtree" + pp.getPositionDescription());
354             }
355             final int[] holderForStartAndLength = new int[2];
356             final StringBuilder rawName = new StringBuilder(16);
357             String prefix = null;
358             String name = null;
359             int level = pp.getDepth() - 1;
360             int type = XmlPullParser.START_TAG;
361 
362             LOOP:
363             do {
364                 switch (type) {
365                     case XmlPullParser.START_TAG:
366                         if (namespaceAware) {
367                             final int depth = pp.getDepth() - 1;
368                             final int countPrev = (level > depth) ? pp.getNamespaceCount(depth) : 0;
369                             // int countPrev = pp.getNamespaceCount(pp.getDepth() - 1);
370                             final int count = pp.getNamespaceCount(depth + 1);
371                             for (int i = countPrev; i < count; i++) {
372                                 contentHandler.startPrefixMapping(pp.getNamespacePrefix(i), pp.getNamespaceUri(i));
373                             }
374                             name = pp.getName();
375                             prefix = pp.getPrefix();
376                             if (prefix != null) {
377                                 rawName.setLength(0);
378                                 rawName.append(prefix);
379                                 rawName.append(':');
380                                 rawName.append(name);
381                             }
382                             startElement(pp.getNamespace(), name, prefix != null ? name : rawName.toString());
383                         } else {
384                             startElement(pp.getNamespace(), pp.getName(), pp.getName());
385                         }
386                         // ++level;
387 
388                         break;
389                     case XmlPullParser.TEXT:
390                         final char[] chars = pp.getTextCharacters(holderForStartAndLength);
391                         contentHandler.characters(
392                                 chars,
393                                 holderForStartAndLength[0], // start
394                                 holderForStartAndLength[1] // len
395                                 );
396                         break;
397                     case XmlPullParser.END_TAG:
398                         // --level;
399                         if (namespaceAware) {
400                             name = pp.getName();
401                             prefix = pp.getPrefix();
402                             if (prefix != null) {
403                                 rawName.setLength(0);
404                                 rawName.append(prefix);
405                                 rawName.append(':');
406                                 rawName.append(name);
407                             }
408                             contentHandler.endElement(
409                                     pp.getNamespace(), name, prefix != null ? name : rawName.toString());
410                             // when entering show prefixes for all levels!!!!
411                             final int depth = pp.getDepth();
412                             final int countPrev = (level > depth) ? pp.getNamespaceCount(pp.getDepth()) : 0;
413                             int count = pp.getNamespaceCount(pp.getDepth() - 1);
414                             // undeclare them in reverse order
415                             for (int i = count - 1; i >= countPrev; i--) {
416                                 contentHandler.endPrefixMapping(pp.getNamespacePrefix(i));
417                             }
418                         } else {
419                             contentHandler.endElement(pp.getNamespace(), pp.getName(), pp.getName());
420                         }
421                         break;
422                     case XmlPullParser.END_DOCUMENT:
423                         break LOOP;
424                 }
425                 type = pp.next();
426             } while (pp.getDepth() > level);
427         } catch (XmlPullParserException ex) {
428             final SAXParseException saxException = new SAXParseException("parsing error: " + ex, this, ex);
429             ex.printStackTrace();
430             errorHandler.fatalError(saxException);
431         }
432     }
433 
434     /**
435      * Calls {@link ContentHandler#startElement(String, String, String, Attributes) ContentHandler.startElement}
436      * on the <code>ContentHandler</code> with <code>this</code> driver object as the
437      * {@link Attributes} implementation. In default implementation
438      * {@link Attributes} object is valid only during this method call and may not
439      * be stored. Sub-classes can overwrite this method to cache attributes.
440      * @param namespace The namespace.
441      * @param localName The localname.
442      * @param qName The qName.
443      * @throws SAXException in case of an error.
444      */
445     protected void startElement(String namespace, String localName, String qName) throws SAXException {
446         contentHandler.startElement(namespace, localName, qName, this);
447     }
448 }