1 /* Woodstox XML processor
2  *
3  * Copyright (c) 2004 Tatu Saloranta, tatu.saloranta@iki.fi
4  *
5  * Licensed under the License specified in file LICENSE, included with
6  * the source code.
7  * You may not use this file except in compliance with the License.
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */

15
16 package com.ctc.wstx.evt;
17
18 import java.util.*;
19
20 import javax.xml.namespace.QName;
21 import javax.xml.namespace.NamespaceContext;
22 import javax.xml.stream.*;
23 import javax.xml.stream.events.Attribute;
24 import javax.xml.stream.events.Namespace;
25 import javax.xml.stream.events.XMLEvent;
26 import javax.xml.stream.util.XMLEventAllocator;
27 import javax.xml.stream.util.XMLEventConsumer;
28
29 import org.codehaus.stax2.*;
30 import org.codehaus.stax2.ri.evt.*;
31
32 import com.ctc.wstx.cfg.ErrorConsts;
33 import com.ctc.wstx.dtd.DTDSubset;
34 import com.ctc.wstx.ent.EntityDecl;
35 import com.ctc.wstx.exc.WstxException;
36 import com.ctc.wstx.sr.ElemAttrs;
37 import com.ctc.wstx.sr.ElemCallback;
38 import com.ctc.wstx.sr.StreamReaderImpl;
39 import com.ctc.wstx.util.BaseNsContext;
40
41 /**
42  * Straight-forward implementation of {@link XMLEventAllocator}, to be
43  * used with Woodstox' event reader.
44  *<p>
45  * One of few complications here is the way start elements are constructed.
46  * The pattern used is double-indirection, needed to get a callback from
47  * the stream reader, with data we need for constructing even Object...
48  * but without stream reader having any understanding of event Objects
49  * per se.
50  *<p>
51  * 03-Dec-2004, TSa: One additional twist is that it's now possible to
52  *   create slightly faster event handling, by indicating that the
53  *   fully accurate Location information is not necessary. If so,
54  *   allocator will just use one shared Location object passed to
55  *   all event objects constructed.
56  */

57 public class DefaultEventAllocator
58     extends ElemCallback
59     implements XMLEventAllocator, XMLStreamConstants
60 {
61     final static DefaultEventAllocator sStdInstance = new DefaultEventAllocator(true);
62
63     /*
64     ///////////////////////////////////////////////////////////
65     // Configuration
66     ///////////////////////////////////////////////////////////
67     */

68
69     protected final boolean mAccurateLocation;
70
71     /*
72     ///////////////////////////////////////////////////////////
73     // Recycled objects
74     ///////////////////////////////////////////////////////////
75     */

76
77     /**
78      * Last used location info; only relevant to non-accurate-location
79      * allocators.
80      */

81     protected Location mLastLocation = null;
82
83     /**
84      * @param accurateLocation If true, allocator will construct instances
85      *   that have accurate location information; if false, instances
86      *   will only have some generic shared Location info. Latter option
87      *   will reduce memory usage/thrashing a bit, and may improve speed.
88      */

89     protected DefaultEventAllocator(boolean accurateLocation) {
90         mAccurateLocation = accurateLocation;
91     }
92
93     public static DefaultEventAllocator getDefaultInstance() {
94         /* Default (accurate location) instance can be shared as it
95          * has no state
96          */

97         return sStdInstance;
98     }
99
100     public static DefaultEventAllocator getFastInstance() {
101         /* Can not share instances, due to QName caching, as well as because
102          * of Location object related state
103          */

104         return new DefaultEventAllocator(false);
105     }
106
107     /*
108     ///////////////////////////////////////////////////////////
109     // XMLEventAllocator implementation
110     ///////////////////////////////////////////////////////////
111      */

112
113     @Override
114     public XMLEvent allocate(XMLStreamReader r) throws XMLStreamException
115     {
116         Location loc;
117
118         // Need to keep track of accurate location info?
119         if (mAccurateLocation) {
120             loc = r.getLocation();
121         } else {
122             loc = mLastLocation;
123             /* And even if we can just share one instance, we need that
124              * first instance...
125              */

126             if (loc == null) {
127                 loc = mLastLocation = r.getLocation();
128             }
129         }
130
131         switch (r.getEventType()) {
132         case CDATA:
133             return new CharactersEventImpl(loc, r.getText(), true);
134         case CHARACTERS:
135             return new CharactersEventImpl(loc, r.getText(), false);
136         case COMMENT:
137             return new CommentEventImpl(loc, r.getText());
138         case DTD:
139             // Not sure if we really need this defensive coding but...
140             if (r instanceof XMLStreamReader2) {
141                 XMLStreamReader2 sr2 = (XMLStreamReader2) r;
142                 DTDInfo dtd = sr2.getDTDInfo();
143                 return new WDTD(loc,
144                                 dtd.getDTDRootName(),
145                                 dtd.getDTDSystemId(), dtd.getDTDPublicId(),
146                                 dtd.getDTDInternalSubset(),
147                                 (DTDSubset) dtd.getProcessedDTD());
148             }
149             /* No way to get all information... the real big problem is
150              * that of how to access root name: it's obligatory for
151              * DOCTYPE construct. :-/
152              */

153             return new WDTD(loc, null, r.getText());
154
155         case END_DOCUMENT:
156             return new EndDocumentEventImpl(loc);
157
158         case END_ELEMENT:
159             return new EndElementEventImpl(loc, r);
160
161         case PROCESSING_INSTRUCTION:
162             return new ProcInstrEventImpl(loc, r.getPITarget(), r.getPIData());
163         case SPACE:
164             {
165                 CharactersEventImpl ch = new CharactersEventImpl(loc, r.getText(), false);
166                 ch.setWhitespaceStatus(true);
167                 return ch;
168             }
169         case START_DOCUMENT:
170             return new StartDocumentEventImpl(loc, r);
171
172         case START_ELEMENT:
173             {
174                 /* Creating the event is bit complicated, as the stream
175                  * reader is not to know anything about event objects.
176                  * To do this, we do double-indirection, which means that
177                  * this object actually gets a callback:
178                  */

179                 /* 19-Jul-2006, TSa: WSTX-61 points out that the code was
180                  *   assuming it's always Woodstox reader we had... not
181                  *   necessarily so.
182                  */

183                 if (r instanceof StreamReaderImpl) {
184                     StreamReaderImpl sr = (StreamReaderImpl) r;
185                     BaseStartElement be = (BaseStartElement) sr.withStartElement(this, loc);
186                     if (be == null) { // incorrect state
187                         throw new WstxException("Trying to create START_ELEMENT when current event is "
188                                                 +ErrorConsts.tokenTypeDesc(sr.getEventType()),
189                                                 loc);
190                     }
191                     return be;
192                 }
193                 /* Ok, not woodstox impl, will be bit more work (plus less
194                  * efficient, and may miss some info)... but can be done.
195                  */

196                 NamespaceContext nsCtxt = null;
197                 if (r instanceof XMLStreamReader2) {
198                     nsCtxt = ((XMLStreamReader2) r).getNonTransientNamespaceContext();
199                 }
200                 Map<QName,Attribute> attrs;
201                 {
202                     int attrCount = r.getAttributeCount();
203                     if (attrCount < 1) {
204                         attrs = null;
205                     } else {
206                         attrs = new LinkedHashMap<QName, Attribute>();
207                         for (int i = 0; i < attrCount; ++i) {
208                             QName aname = r.getAttributeName(i);
209                             attrs.put(aname, new AttributeEventImpl(loc, aname, r.getAttributeValue(i), r.isAttributeSpecified(i)));
210                         }
211                     }
212                 }
213                 List<Namespace> ns;
214                 {
215                     int nsCount = r.getNamespaceCount();
216                     if (nsCount < 1) {
217                         ns = null;
218                     } else {
219                         ns = new ArrayList<Namespace>(nsCount);
220                         for (int i = 0; i < nsCount; ++i) {
221                             ns.add(NamespaceEventImpl.constructNamespace(loc, r.getNamespacePrefix(i), r.getNamespaceURI(i)));
222                         }
223                     }
224                 }
225                 
226                 return SimpleStartElement.construct(loc, r.getName(), attrs, ns, nsCtxt);
227             }
228
229         case ENTITY_REFERENCE:
230             {
231                 /* 19-Jul-2006, TSa: Let's also allow other impls, although
232                  *   we can't get actual declaration if so...
233                  */

234                 if (r instanceof StreamReaderImpl) {
235                     EntityDecl ed = ((StreamReaderImpl) r).getCurrentEntityDecl();
236                     if (ed == null) { // undefined?
237                     // We'll still know the name though...
238                         return new WEntityReference(loc, r.getLocalName());
239                     }
240                     return new WEntityReference(loc, ed);
241                 }
242                 return new WEntityReference(loc, r.getLocalName());
243             }
244
245
246             /* Following 2 types should never get in here; they are directly
247              * handled by DTDReader, and can only be accessed via DTD event
248              * element.
249              */

250         case ENTITY_DECLARATION:
251         case NOTATION_DECLARATION:
252             /* Following 2 types should never get in here; they are directly
253              * handled by the reader, and can only be accessed via start
254              * element.
255              */

256         case NAMESPACE:
257         case ATTRIBUTE:
258             throw new WstxException("Internal error: should not get "
259                                     +ErrorConsts.tokenTypeDesc(r.getEventType()));
260         default:
261             throw new IllegalStateException("Unrecognized event type "+r.getEventType()+".");
262         }
263     }
264     
265     @Override
266     public void allocate(XMLStreamReader r, XMLEventConsumer consumer)
267         throws XMLStreamException
268     {
269         consumer.add(allocate(r));
270     }
271
272     @Override
273     public XMLEventAllocator newInstance() {
274         return new DefaultEventAllocator(mAccurateLocation);
275     }
276     
277     /*
278     ///////////////////////////////////////////////////////////
279     // ElemCallback implementation
280     ///////////////////////////////////////////////////////////
281      */

282
283     @Override
284     public Object withStartElement(Location loc, QName name,
285             BaseNsContext nsCtxt, ElemAttrs attrs, boolean wasEmpty)
286     {
287         return new CompactStartElement(loc, name, nsCtxt, attrs);
288     }
289 }
290