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      */
  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 }