1 /*
2  * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.xml.internal.bind.v2.runtime.unmarshaller;
27 
28 import java.lang.reflect.Constructor;
29 
30 import javax.xml.stream.Location;
31 import javax.xml.stream.XMLStreamConstants;
32 import javax.xml.stream.XMLStreamException;
33 import javax.xml.stream.XMLStreamReader;
34 
35 import com.sun.xml.internal.bind.WhiteSpaceProcessor;
36 
37 import org.xml.sax.Attributes;
38 import org.xml.sax.SAXException;
39 
40 /**
41  * Reads XML from StAX {@link XMLStreamReader} and
42  * feeds events to {@link XmlVisitor}.
43  * <p>
44  * TODO:
45  * Finding the optimized FI implementations is a bit hacky and not very
46  * extensible. Can we use the service provider mechanism in general for
47  * concrete implementations of StAXConnector.
48  *
49  * @author Ryan.Shoemaker@Sun.COM
50  * @author Kohsuke Kawaguchi
51  * @version JAXB 2.0
52  */
53 class StAXStreamConnector extends StAXConnector {
54 
55     /**
56      * Creates a {@link StAXConnector} from {@link XMLStreamReader}.
57      *
58      * This method checks if the parser is FI parser and acts accordingly.
59      */
create(XMLStreamReader reader, XmlVisitor visitor)60     public static StAXConnector create(XMLStreamReader reader, XmlVisitor visitor) {
61         // try optimized codepath
62         final Class readerClass = reader.getClass();
63         if (FI_STAX_READER_CLASS != null && FI_STAX_READER_CLASS.isAssignableFrom(readerClass) && FI_CONNECTOR_CTOR!=null) {
64             try {
65                 return FI_CONNECTOR_CTOR.newInstance(reader,visitor);
66             } catch (Exception t) {
67             }
68         }
69 
70         // Quick hack until SJSXP fixes 6270116
71         boolean isZephyr = readerClass.getName().equals("com.sun.xml.internal.stream.XMLReaderImpl");
72         if (getBoolProp(reader,"org.codehaus.stax2.internNames") &&
73                 getBoolProp(reader,"org.codehaus.stax2.internNsUris"))
74             ; // no need for interning
75         else
76         if (isZephyr)
77             ; // no need for interning
78         else
79         if (checkImplementaionNameOfSjsxp(reader))
80             ; // no need for interning.
81         else
82             visitor = new InterningXmlVisitor(visitor);
83 
84         if (STAX_EX_READER_CLASS!=null && STAX_EX_READER_CLASS.isAssignableFrom(readerClass)) {
85             try {
86                 return STAX_EX_CONNECTOR_CTOR.newInstance(reader,visitor);
87             } catch (Exception t) {
88             }
89         }
90 
91         return new StAXStreamConnector(reader,visitor);
92     }
93 
checkImplementaionNameOfSjsxp(XMLStreamReader reader)94     private static boolean checkImplementaionNameOfSjsxp(XMLStreamReader reader) {
95         try {
96             Object name = reader.getProperty("http://java.sun.com/xml/stream/properties/implementation-name");
97             return name!=null && name.equals("sjsxp");
98         } catch (Exception e) {
99             // be defensive against broken StAX parsers since javadoc is not clear
100             // about when an error happens
101             return false;
102         }
103     }
104 
getBoolProp(XMLStreamReader r, String n)105     private static boolean getBoolProp(XMLStreamReader r, String n) {
106         try {
107             Object o = r.getProperty(n);
108             if(o instanceof Boolean)    return (Boolean)o;
109             return false;
110         } catch (Exception e) {
111             // be defensive against broken StAX parsers since javadoc is not clear
112             // about when an error happens
113             return false;
114         }
115     }
116 
117 
118     // StAX event source
119     private final XMLStreamReader staxStreamReader;
120 
121     /**
122      * SAX may fire consecutive characters event, but we don't allow it.
123      * so use this buffer to perform buffering.
124      */
125     protected final StringBuilder buffer = new StringBuilder();
126 
127     /**
128      * Set to true if the text() event is reported, and therefore
129      * the following text() event should be suppressed.
130      */
131     protected boolean textReported = false;
132 
StAXStreamConnector(XMLStreamReader staxStreamReader, XmlVisitor visitor)133     protected StAXStreamConnector(XMLStreamReader staxStreamReader, XmlVisitor visitor) {
134         super(visitor);
135         this.staxStreamReader = staxStreamReader;
136     }
137 
bridge()138     public void bridge() throws XMLStreamException {
139 
140         try {
141             // remembers the nest level of elements to know when we are done.
142             int depth=0;
143 
144             // if the parser is at the start tag, proceed to the first element
145             int event = staxStreamReader.getEventType();
146             if(event == XMLStreamConstants.START_DOCUMENT) {
147                 // nextTag doesn't correctly handle DTDs
148                 while( !staxStreamReader.isStartElement() )
149                     event = staxStreamReader.next();
150             }
151 
152 
153             if( event!=XMLStreamConstants.START_ELEMENT)
154                 throw new IllegalStateException("The current event is not START_ELEMENT\n but " + event);
155 
156             handleStartDocument(staxStreamReader.getNamespaceContext());
157 
158             OUTER:
159             while(true) {
160                 // These are all of the events listed in the javadoc for
161                 // XMLEvent.
162                 // The spec only really describes 11 of them.
163                 switch (event) {
164                     case XMLStreamConstants.START_ELEMENT :
165                         handleStartElement();
166                         depth++;
167                         break;
168                     case XMLStreamConstants.END_ELEMENT :
169                         depth--;
170                         handleEndElement();
171                         if(depth==0)    break OUTER;
172                         break;
173                     case XMLStreamConstants.CHARACTERS :
174                     case XMLStreamConstants.CDATA :
175                     case XMLStreamConstants.SPACE :
176                         handleCharacters();
177                         break;
178                     // otherwise simply ignore
179                 }
180 
181                 event=staxStreamReader.next();
182             }
183 
184             staxStreamReader.next();    // move beyond the end tag.
185 
186             handleEndDocument();
187         } catch (SAXException e) {
188             throw new XMLStreamException(e);
189         }
190     }
191 
getCurrentLocation()192     protected Location getCurrentLocation() {
193         return staxStreamReader.getLocation();
194     }
195 
getCurrentQName()196     protected String getCurrentQName() {
197         return getQName(staxStreamReader.getPrefix(),staxStreamReader.getLocalName());
198     }
199 
handleEndElement()200     private void handleEndElement() throws SAXException {
201         processText(false);
202 
203         // fire endElement
204         tagName.uri = fixNull(staxStreamReader.getNamespaceURI());
205         tagName.local = staxStreamReader.getLocalName();
206         visitor.endElement(tagName);
207 
208         // end namespace bindings
209         int nsCount = staxStreamReader.getNamespaceCount();
210         for (int i = nsCount - 1; i >= 0; i--) {
211             visitor.endPrefixMapping(fixNull(staxStreamReader.getNamespacePrefix(i)));
212         }
213     }
214 
handleStartElement()215     private void handleStartElement() throws SAXException {
216         processText(true);
217 
218         // start namespace bindings
219         int nsCount = staxStreamReader.getNamespaceCount();
220         for (int i = 0; i < nsCount; i++) {
221             visitor.startPrefixMapping(
222                     fixNull(staxStreamReader.getNamespacePrefix(i)),
223                     fixNull(staxStreamReader.getNamespaceURI(i)));
224         }
225 
226         // fire startElement
227         tagName.uri = fixNull(staxStreamReader.getNamespaceURI());
228         tagName.local = staxStreamReader.getLocalName();
229         tagName.atts = attributes;
230 
231         visitor.startElement(tagName);
232     }
233 
234     /**
235      * Proxy of {@link Attributes} that read from {@link XMLStreamReader}.
236      */
237     private final Attributes attributes = new Attributes() {
238         public int getLength() {
239             return staxStreamReader.getAttributeCount();
240         }
241 
242         public String getURI(int index) {
243             String uri = staxStreamReader.getAttributeNamespace(index);
244             if(uri==null)   return "";
245             return uri;
246         }
247 
248         public String getLocalName(int index) {
249             return staxStreamReader.getAttributeLocalName(index);
250         }
251 
252         public String getQName(int index) {
253             String prefix = staxStreamReader.getAttributePrefix(index);
254             if(prefix==null || prefix.length()==0)
255                 return getLocalName(index);
256             else
257                 return prefix + ':' + getLocalName(index);
258         }
259 
260         public String getType(int index) {
261             return staxStreamReader.getAttributeType(index);
262         }
263 
264         public String getValue(int index) {
265             return staxStreamReader.getAttributeValue(index);
266         }
267 
268         public int getIndex(String uri, String localName) {
269             for( int i=getLength()-1; i>=0; i-- )
270                 if( localName.equals(getLocalName(i)) && uri.equals(getURI(i)))
271                     return i;
272             return -1;
273         }
274 
275         // this method sholdn't be used that often (if at all)
276         // so it's OK to be slow.
277         public int getIndex(String qName) {
278             for( int i=getLength()-1; i>=0; i-- ) {
279                 if(qName.equals(getQName(i)))
280                     return i;
281             }
282             return -1;
283         }
284 
285         public String getType(String uri, String localName) {
286             int index = getIndex(uri,localName);
287             if(index<0)     return null;
288             return getType(index);
289         }
290 
291         public String getType(String qName) {
292             int index = getIndex(qName);
293             if(index<0)     return null;
294             return getType(index);
295         }
296 
297         public String getValue(String uri, String localName) {
298             int index = getIndex(uri,localName);
299             if(index<0)     return null;
300             return getValue(index);
301         }
302 
303         public String getValue(String qName) {
304             int index = getIndex(qName);
305             if(index<0)     return null;
306             return getValue(index);
307         }
308     };
309 
handleCharacters()310     protected void handleCharacters() throws XMLStreamException, SAXException {
311         if( predictor.expectText() )
312             buffer.append(
313                     staxStreamReader.getTextCharacters(),
314                     staxStreamReader.getTextStart(),
315                     staxStreamReader.getTextLength() );
316     }
317 
processText( boolean ignorable )318     private void processText( boolean ignorable ) throws SAXException {
319         if( predictor.expectText() && (!ignorable || !WhiteSpaceProcessor.isWhiteSpace(buffer) || context.getCurrentState().isMixed())) {
320             if(textReported) {
321                 textReported = false;
322             } else {
323                 visitor.text(buffer);
324             }
325         }
326         buffer.setLength(0);
327     }
328 
329 
330 
331     /**
332      * Reference to FI's StAXReader class, if FI can be loaded.
333      */
334     private static final Class FI_STAX_READER_CLASS = initFIStAXReaderClass();
335     private static final Constructor<? extends StAXConnector> FI_CONNECTOR_CTOR = initFastInfosetConnectorClass();
336 
initFIStAXReaderClass()337     private static Class initFIStAXReaderClass() {
338         try {
339             Class<?> fisr = Class.forName("com.sun.xml.internal.org.jvnet.fastinfoset.stax.FastInfosetStreamReader");
340             Class<?> sdp = Class.forName("com.sun.xml.internal.fastinfoset.stax.StAXDocumentParser");
341             // Check if StAXDocumentParser implements FastInfosetStreamReader
342             if (fisr.isAssignableFrom(sdp))
343                 return sdp;
344             else
345                 return null;
346         } catch (Throwable e) {
347             return null;
348         }
349     }
350 
initFastInfosetConnectorClass()351     private static Constructor<? extends StAXConnector> initFastInfosetConnectorClass() {
352         try {
353             if (FI_STAX_READER_CLASS == null)
354                 return null;
355 
356             Class c = Class.forName(
357                     "com.sun.xml.internal.bind.v2.runtime.unmarshaller.FastInfosetConnector");
358             return c.getConstructor(FI_STAX_READER_CLASS,XmlVisitor.class);
359         } catch (Throwable e) {
360             return null;
361         }
362     }
363 
364     //
365     // reference to StAXEx classes
366     //
367     private static final Class STAX_EX_READER_CLASS = initStAXExReader();
368     private static final Constructor<? extends StAXConnector> STAX_EX_CONNECTOR_CTOR = initStAXExConnector();
369 
initStAXExReader()370     private static Class initStAXExReader() {
371         try {
372             return Class.forName("com.sun.xml.internal.org.jvnet.staxex.XMLStreamReaderEx");
373         } catch (Throwable e) {
374             return null;
375         }
376     }
377 
initStAXExConnector()378     private static Constructor<? extends StAXConnector> initStAXExConnector() {
379         try {
380             Class c = Class.forName("com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXExConnector");
381             return c.getConstructor(STAX_EX_READER_CLASS,XmlVisitor.class);
382         } catch (Throwable e) {
383             return null;
384         }
385     }
386 
387 }
388