1 /*
   2  * Copyright (c) 2005, 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.stream;
  27 
  28 import com.sun.xml.internal.stream.events.XMLEventAllocatorImpl;
  29 import java.util.NoSuchElementException;
  30 import javax.xml.stream.XMLInputFactory;
  31 import javax.xml.stream.XMLStreamConstants;
  32 import javax.xml.stream.XMLStreamException;
  33 import javax.xml.stream.XMLStreamReader;
  34 import javax.xml.stream.events.EntityReference;
  35 import javax.xml.stream.events.XMLEvent;
  36 import javax.xml.stream.util.XMLEventAllocator;
  37 
  38 /**
  39  * @author  @author  Neeraj Bajaj Sun Microsystems
  40  *
  41  */
  42 
  43 public class XMLEventReaderImpl implements javax.xml.stream.XMLEventReader{
  44 
  45     protected XMLStreamReader fXMLReader ;
  46     protected XMLEventAllocator fXMLEventAllocator;
  47 
  48     //only constructor will do because we delegate everything to underlying XMLStreamReader
  49     public XMLEventReaderImpl(XMLStreamReader reader) throws  XMLStreamException {
  50         fXMLReader = reader ;
  51         fXMLEventAllocator = (XMLEventAllocator)reader.getProperty(XMLInputFactory.ALLOCATOR);
  52         if(fXMLEventAllocator == null){
  53             fXMLEventAllocator = new XMLEventAllocatorImpl();
  54         }
  55         fPeekedEvent = fXMLEventAllocator.allocate(fXMLReader);
  56     }
  57 
  58 
  59     public boolean hasNext() {
  60         //if we have the peeked event return 'true'
  61         if(fPeekedEvent != null)return true;
  62         //this is strange XMLStreamReader throws XMLStreamException
  63         //XMLEventReader doesn't throw XMLStreamException
  64         boolean next = false ;
  65         try{
  66             next = fXMLReader.hasNext();
  67         }catch(XMLStreamException ex){
  68             return false;
  69         }
  70         return next ;
  71     }
  72 
  73 
  74     public XMLEvent nextEvent() throws XMLStreamException {
  75         //if application peeked return the peeked event
  76         if(fPeekedEvent != null){
  77             fLastEvent = fPeekedEvent ;
  78             fPeekedEvent = null;
  79             return fLastEvent ;
  80         }
  81         else if(fXMLReader.hasNext()){
  82             //advance the reader to next state.
  83             fXMLReader.next();
  84             return fLastEvent = fXMLEventAllocator.allocate(fXMLReader);
  85         }
  86         else{
  87             fLastEvent = null;
  88             throw new NoSuchElementException();
  89         }
  90     }
  91 
  92     public void remove(){
  93         //remove of the event is not supported.
  94         throw new java.lang.UnsupportedOperationException();
  95     }
  96 
  97 
  98     public void close() throws XMLStreamException {
  99         fXMLReader.close();
 100     }
 101 
 102     /** Reads the content of a text-only element. Precondition:
 103      * the current event is START_ELEMENT. Postcondition:
 104      * The current event is the corresponding END_ELEMENT.
 105      * @throws XMLStreamException if the current event is not a START_ELEMENT
 106      * or if a non text element is encountered
 107      */
 108     public String getElementText() throws XMLStreamException {
 109         //we have to keep reference to the 'last event' of the stream to be able
 110         //to make this check - is there another way ? - nb.
 111         if(fLastEvent.getEventType() != XMLEvent.START_ELEMENT){
 112             throw new XMLStreamException(
 113             "parser must be on START_ELEMENT to read next text", fLastEvent.getLocation());
 114         }
 115 
 116         // STag content ETag
 117         //[43]   content   ::=   CharData? ((element | Reference | CDSect | PI | Comment) CharData?)*
 118 
 119         //<foo>....some long text say in KB and underlying parser reports multiple character
 120         // but getElementText() events....</foo>
 121 
 122         String data = null;
 123         //having a peeked event makes things really worse -- we have to test the first event
 124         if(fPeekedEvent != null){
 125             XMLEvent event = fPeekedEvent ;
 126             fPeekedEvent = null;
 127             int type = event.getEventType();
 128 
 129             if(  type == XMLEvent.CHARACTERS || type == XMLEvent.SPACE ||
 130             type == XMLEvent.CDATA){
 131                 data = event.asCharacters().getData();
 132             }
 133             else if(type == XMLEvent.ENTITY_REFERENCE){
 134                 data = ((EntityReference)event).getDeclaration().getReplacementText();
 135             }
 136             else if(type == XMLEvent.COMMENT || type == XMLEvent.PROCESSING_INSTRUCTION){
 137                 //ignore
 138             } else if(type == XMLEvent.START_ELEMENT) {
 139                 throw new XMLStreamException(
 140                 "elementGetText() function expects text only elment but START_ELEMENT was encountered.", event.getLocation());
 141             }else if(type == XMLEvent.END_ELEMENT){
 142                 return "";
 143             }
 144 
 145             //create the string buffer and add initial data
 146             StringBuffer buffer = new StringBuffer();
 147             if(data != null && data.length() > 0 ) {
 148                 buffer.append(data);
 149             }
 150             //get the next event -- we should stop at END_ELEMENT but it can be any thing
 151             //things are worse when implementing this function in XMLEventReader because
 152             //there isn't any function called getText() which can get values for
 153             //space, cdata, characters and entity reference
 154             //nextEvent() would also set the last event.
 155             event = nextEvent();
 156             while(event.getEventType() != XMLEvent.END_ELEMENT){
 157                 if(  type == XMLEvent.CHARACTERS || type == XMLEvent.SPACE ||
 158                 type == XMLEvent.CDATA){
 159                     data = event.asCharacters().getData();
 160                 }
 161                 else if(type == XMLEvent.ENTITY_REFERENCE){
 162                     data = ((EntityReference)event).getDeclaration().getReplacementText();
 163                 }
 164                 else if(type == XMLEvent.COMMENT || type == XMLEvent.PROCESSING_INSTRUCTION){
 165                     //ignore
 166                 } else if(type == XMLEvent.END_DOCUMENT) {
 167                     throw new XMLStreamException("unexpected end of document when reading element text content");
 168                 } else if(type == XMLEvent.START_ELEMENT) {
 169                     throw new XMLStreamException(
 170                     "elementGetText() function expects text only elment but START_ELEMENT was encountered.", event.getLocation());
 171                 } else {
 172                     throw new XMLStreamException(
 173                     "Unexpected event type "+ type, event.getLocation());
 174                 }
 175                 //add the data to the buffer
 176                 if(data != null && data.length() > 0 ) {
 177                     buffer.append(data);
 178                 }
 179                 event = nextEvent();
 180             }
 181             return buffer.toString();
 182         }//if (fPeekedEvent != null)
 183 
 184         //if there was no peeked, delegate everything to fXMLReader
 185         //update the last event before returning the text
 186         data = fXMLReader.getElementText();
 187         fLastEvent = fXMLEventAllocator.allocate(fXMLReader);
 188         return data;
 189     }
 190 
 191     /** Get the value of a feature/property from the underlying implementation
 192      * @param name The name of the property
 193      * @return The value of the property
 194      * @throws IllegalArgumentException if the property is not supported
 195      */
 196     public Object getProperty(java.lang.String name) throws java.lang.IllegalArgumentException {
 197         return fXMLReader.getProperty(name) ;
 198     }
 199 
 200     /** Skips any insignificant space events until a START_ELEMENT or
 201      * END_ELEMENT is reached. If anything other than space characters are
 202      * encountered, an exception is thrown. This method should
 203      * be used when processing element-only content because
 204      * the parser is not able to recognize ignorable whitespace if
 205      * the DTD is missing or not interpreted.
 206      * @throws XMLStreamException if anything other than space characters are encountered
 207      */
 208     public XMLEvent nextTag() throws XMLStreamException {
 209         //its really a pain if there is peeked event before calling nextTag()
 210         if(fPeekedEvent != null){
 211             //check the peeked event first.
 212             XMLEvent event = fPeekedEvent;
 213             fPeekedEvent = null ;
 214             int eventType = event.getEventType();
 215             //if peeked event is whitespace move to the next event
 216             //if peeked event is PI or COMMENT move to the next event
 217             if( (event.isCharacters() && event.asCharacters().isWhiteSpace())
 218             || eventType == XMLStreamConstants.PROCESSING_INSTRUCTION
 219             || eventType == XMLStreamConstants.COMMENT
 220             || eventType == XMLStreamConstants.START_DOCUMENT){
 221                 event = nextEvent();
 222                 eventType = event.getEventType();
 223             }
 224 
 225             //we have to have the while loop because there can be many PI or comment event in sucession
 226             while((event.isCharacters() && event.asCharacters().isWhiteSpace())
 227             || eventType == XMLStreamConstants.PROCESSING_INSTRUCTION
 228             || eventType == XMLStreamConstants.COMMENT){
 229 
 230                 event = nextEvent();
 231                 eventType = event.getEventType();
 232             }
 233 
 234             if (eventType != XMLStreamConstants.START_ELEMENT && eventType != XMLStreamConstants.END_ELEMENT) {
 235                 throw new XMLStreamException("expected start or end tag", event.getLocation());
 236             }
 237             return event;
 238         }
 239 
 240         //if there is no peeked event -- delegate the work of getting next event to fXMLReader
 241         fXMLReader.nextTag();
 242         return (fLastEvent = fXMLEventAllocator.allocate(fXMLReader));
 243     }
 244 
 245     public Object next() {
 246         Object object = null;
 247         try{
 248             object = nextEvent();
 249         }catch(XMLStreamException streamException){
 250             fLastEvent = null ;
 251             //don't swallow the cause
 252             NoSuchElementException e = new NoSuchElementException(streamException.getMessage());
 253             e.initCause(streamException.getCause());
 254             throw e;
 255         }
 256         return object;
 257     }
 258 
 259     public XMLEvent peek() throws XMLStreamException{
 260         //if someone call peek() two times we should just return the peeked event
 261         //this is reset if we call next() or nextEvent()
 262         if(fPeekedEvent != null) return fPeekedEvent;
 263 
 264         if(hasNext()){
 265             //revisit: we can implement peek() by calling underlying reader to advance
 266             // the stream and returning the event without the knowledge of the user
 267             // that the stream was advanced but the point is we are advancing the stream
 268             //here. -- nb.
 269 
 270             // Is there any application that relies on this behavior ?
 271             //Can it be an application knows that there is particularly very large 'comment' section
 272             //or character data which it doesn't want to read or to be returned as event
 273             //But as of now we are creating every event but it can be optimized not to create
 274             // the event.
 275             fXMLReader.next();
 276             fPeekedEvent = fXMLEventAllocator.allocate(fXMLReader);
 277             return fPeekedEvent;
 278         }else{
 279             return null;
 280         }
 281     }//peek()
 282 
 283     private XMLEvent fPeekedEvent;
 284     private XMLEvent fLastEvent;
 285 
 286 }//XMLEventReaderImpl