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
26package com.sun.xml.internal.bind.v2.runtime.unmarshaller;
27
28import java.lang.reflect.Constructor;
29
30import javax.xml.stream.Location;
31import javax.xml.stream.XMLStreamConstants;
32import javax.xml.stream.XMLStreamException;
33import javax.xml.stream.XMLStreamReader;
34
35import com.sun.xml.internal.bind.WhiteSpaceProcessor;
36
37import org.xml.sax.Attributes;
38import 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 */
53class 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     */
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
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
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
133    protected StAXStreamConnector(XMLStreamReader staxStreamReader, XmlVisitor visitor) {
134        super(visitor);
135        this.staxStreamReader = staxStreamReader;
136    }
137
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
192    protected Location getCurrentLocation() {
193        return staxStreamReader.getLocation();
194    }
195
196    protected String getCurrentQName() {
197        return getQName(staxStreamReader.getPrefix(),staxStreamReader.getLocalName());
198    }
199
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
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
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
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
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
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
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
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