View Javadoc

1   /**
2    * Copyright (c) 2011, University of Konstanz, Distributed Systems Group
3    * All rights reserved.
4    * 
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions are met:
7    * * Redistributions of source code must retain the above copyright
8    * notice, this list of conditions and the following disclaimer.
9    * * Redistributions in binary form must reproduce the above copyright
10   * notice, this list of conditions and the following disclaimer in the
11   * documentation and/or other materials provided with the distribution.
12   * * Neither the name of the University of Konstanz nor the
13   * names of its contributors may be used to endorse or promote products
14   * derived from this software without specific prior written permission.
15   * 
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
20   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   */
27  
28  package org.treetank.service.xml.serialize;
29  
30  import static org.treetank.node.IConstants.ATTRIBUTE;
31  import static org.treetank.node.IConstants.ELEMENT;
32  import static org.treetank.node.IConstants.NAMESPACE;
33  import static org.treetank.node.IConstants.ROOT;
34  import static org.treetank.node.IConstants.TEXT;
35  
36  import java.io.IOException;
37  import java.util.Iterator;
38  import java.util.Stack;
39  
40  import javax.xml.namespace.QName;
41  import javax.xml.stream.XMLEventFactory;
42  import javax.xml.stream.XMLEventReader;
43  import javax.xml.stream.XMLStreamConstants;
44  import javax.xml.stream.XMLStreamException;
45  import javax.xml.stream.events.Attribute;
46  import javax.xml.stream.events.Namespace;
47  import javax.xml.stream.events.XMLEvent;
48  
49  import org.treetank.api.INodeReadTrx;
50  import org.treetank.axis.AbsAxis;
51  import org.treetank.axis.DescendantAxis;
52  import org.treetank.axis.FilterAxis;
53  import org.treetank.axis.filter.TextFilter;
54  import org.treetank.exception.TTException;
55  import org.treetank.exception.TTIOException;
56  import org.treetank.node.ElementNode;
57  import org.treetank.node.interfaces.IStructNode;
58  
59  /**
60   * <h1>StAXSerializer</h1>
61   * 
62   * <p>
63   * Provides a StAX implementation (event API) for retrieving a Treetank database.
64   * </p>
65   * 
66   * @author Johannes Lichtenberger, University of Konstanz
67   * 
68   */
69  public final class StAXSerializer implements XMLEventReader {
70  
71      /**
72       * Determines if start tags have to be closed, thus if end tags have to be
73       * emitted.
74       */
75      private transient boolean mCloseElements;
76  
77      /** {@link XMLEvent}. */
78      private transient XMLEvent mEvent;
79  
80      /** {@link XMLEventFactory} to create events. */
81      private transient XMLEventFactory mFac = XMLEventFactory.newFactory();
82  
83      /** Current node key. */
84      private transient long mKey;
85  
86      /** Determines if all end tags have been emitted. */
87      private transient boolean mCloseElementsEmitted;
88  
89      /** Determines if nextTag() method has been called. */
90      private transient boolean mNextTag;
91  
92      /** {@link IAxis} for iteration. */
93      private final transient AbsAxis mAxis;
94  
95      /** Stack for reading end element. */
96      private final transient Stack<Long> mStack;
97  
98      /**
99       * Determines if the cursor has to move back after empty elements (used in
100      * getElementText().
101      */
102     private transient boolean mGoBack;
103 
104     /**
105      * Determines if the cursor has moved up and therefore has to move back
106      * after to the right node (used in getElementText()).
107      */
108     private transient boolean mGoUp;
109 
110     /**
111      * Last emitted key (start tags, text... except end tags; used in
112      * getElementText()).
113      */
114     private transient long mLastKey;
115 
116     /**
117      * Determines if {@link IReadTransaction} should be closed afterwards.
118      * */
119     private transient boolean mCloseRtx;
120 
121     /**
122      * Rtx for access to the data.
123      */
124     private final INodeReadTrx mRtx;
125 
126     /**
127      * Initialize XMLStreamReader implementation with transaction. The cursor
128      * points to the node the XMLStreamReader starts to read. Do not serialize
129      * the tank ids.
130      * 
131      * @param paramAxis
132      *            {@link AbsAxis} which is used to iterate over and generate
133      *            StAX events.
134      */
135     public StAXSerializer(final AbsAxis paramAxis, final INodeReadTrx pRtx) {
136         this(paramAxis, pRtx, true);
137     }
138 
139     /**
140      * Initialize XMLStreamReader implementation with transaction. The cursor
141      * points to the node the XMLStreamReader starts to read. Do not serialize
142      * the tank ids.
143      * 
144      * @param paramAxis
145      *            {@link AbsAxis} which is used to iterate over and generate
146      *            StAX events.
147      * @param paramCloseRtx
148      *            Determines if rtx should be closed afterwards.
149      */
150     public StAXSerializer(final AbsAxis paramAxis, final INodeReadTrx pRtx, final boolean paramCloseRtx) {
151         mNextTag = false;
152         mAxis = paramAxis;
153         mCloseRtx = paramCloseRtx;
154         mStack = new Stack<Long>();
155         mRtx = pRtx;
156     }
157 
158     /**
159      * Emit end tag.
160      * 
161      * @param paramRTX
162      *            Treetank reading transaction {@link IReadTransaction}.
163      * @throws TTIOException
164      */
165     private void emitEndTag() throws TTIOException {
166         final long nodeKey = mRtx.getNode().getDataKey();
167         mEvent = mFac.createEndElement(mRtx.getQNameOfCurrentNode(), new NamespaceIterator(mRtx));
168         mRtx.moveTo(nodeKey);
169     }
170 
171     /**
172      * Emit a node.
173      * 
174      * @param paramRTX
175      *            Treetank reading transaction {@link IReadTransaction}.
176      * @throws TTIOException
177      */
178     private void emitNode() throws TTIOException {
179         switch (mRtx.getNode().getKind()) {
180         case ROOT:
181             mEvent = mFac.createStartDocument();
182             break;
183         case ELEMENT:
184             final long key = mRtx.getNode().getDataKey();
185             final QName qName = mRtx.getQNameOfCurrentNode();
186             mEvent = mFac.createStartElement(qName, new AttributeIterator(mRtx), new NamespaceIterator(mRtx));
187             mRtx.moveTo(key);
188             break;
189         case TEXT:
190             mEvent = mFac.createCharacters(mRtx.getValueOfCurrentNode());
191             break;
192         default:
193             throw new IllegalStateException("Kind not known!");
194         }
195     }
196 
197     /** {@inheritDoc} */
198     @Override
199     public void close() throws XMLStreamException {
200         if (mCloseRtx) {
201             try {
202                 mAxis.close();
203             } catch (final TTException exc) {
204                 exc.printStackTrace();
205             }
206         }
207     }
208 
209     /** {@inheritDoc} */
210     @Override
211     public String getElementText() throws XMLStreamException {
212 
213         final long nodeKey = mAxis.getNode().getDataKey();
214 
215         /*
216          * The cursor has to move back (once) after determining, that a closing
217          * tag would be the next event (precond: closeElement and either goBack
218          * or goUp is true).
219          */
220         if (mCloseElements && (mGoBack || mGoUp)) {
221             if (mGoUp) {
222                 mAxis.moveTo(mLastKey);
223                 mGoUp = false;
224             } else if (mGoBack) {
225                 mAxis.moveTo(mStack.peek());
226                 mGoBack = false;
227             }
228         }
229 
230         if (mEvent.getEventType() != XMLStreamConstants.START_ELEMENT) {
231             mAxis.moveTo(nodeKey);
232             throw new XMLStreamException("getElementText() only can be called on a start element");
233         }
234         final FilterAxis textFilterAxis =
235             new FilterAxis(new DescendantAxis(mRtx), mRtx, new TextFilter(mRtx));
236         final StringBuilder strBuilder = new StringBuilder();
237 
238         while (textFilterAxis.hasNext()) {
239             textFilterAxis.next();
240             strBuilder.append(mRtx.getValueOfCurrentNode());
241         }
242 
243         try {
244             mRtx.moveTo(nodeKey);
245         } catch (TTIOException exc) {
246             throw new XMLStreamException(exc);
247         }
248         return strBuilder.toString();
249     }
250 
251     /** {@inheritDoc} */
252     @Override
253     public Object getProperty(final String mName) {
254         throw new UnsupportedOperationException("Not supported by Treetank!");
255     }
256 
257     /** {@inheritDoc} */
258     @Override
259     public boolean hasNext() {
260         boolean retVal = false;
261 
262         if (!mStack.empty() && (mCloseElements || mCloseElementsEmitted)) {
263             /*
264              * mAxis.hasNext() can't be used in this case, because it would
265              * iterate to the next node but at first all end-tags have to be
266              * emitted.
267              */
268             retVal = true;
269         } else {
270             retVal = mAxis.hasNext();
271         }
272 
273         return retVal;
274     }
275 
276     /** {@inheritDoc} */
277     @Override
278     public XMLEvent nextEvent() throws XMLStreamException {
279         try {
280             if (!mCloseElements && !mCloseElementsEmitted) {
281                 mKey = mAxis.next();
282 
283                 if (mNextTag) {
284                     if (mAxis.getNode().getKind() != ELEMENT) {
285                         throw new XMLStreamException("The next tag isn't a start- or end-tag!");
286                     }
287                     mNextTag = false;
288                 }
289             }
290             emit();
291         } catch (final TTIOException exc) {
292             throw new XMLStreamException(exc);
293         }
294 
295         return mEvent;
296     }
297 
298     /** {@inheritDoc} */
299     @Override
300     public XMLEvent nextTag() throws XMLStreamException {
301         mNextTag = true;
302         return nextEvent();
303     }
304 
305     /** {@inheritDoc} */
306     @Override
307     public XMLEvent peek() throws XMLStreamException {
308         final long currNodeKey = mAxis.getNode().getDataKey();
309         try {
310             if (mCloseElements) {
311                 mRtx.moveTo(mStack.peek());
312                 emitEndTag();
313             } else {
314                 final int nodeKind = mRtx.getNode().getKind();
315                 if (((IStructNode)mRtx.getNode()).hasFirstChild()) {
316                     mRtx.moveTo(((IStructNode)mRtx.getNode()).getFirstChildKey());
317                     emitNode();
318                 } else if (((IStructNode)mRtx.getNode()).hasRightSibling()) {
319                     mRtx.moveTo(((IStructNode)mRtx.getNode()).getRightSiblingKey());
320                     processNode(nodeKind);
321                 } else if (((IStructNode)mRtx.getNode()).hasParent()) {
322                     mRtx.moveTo(mRtx.getNode().getParentKey());
323                     emitEndTag();
324                 }
325             }
326 
327             mRtx.moveTo(currNodeKey);
328         } catch (final TTIOException exc) {
329             throw new XMLStreamException(exc);
330         }
331         return mEvent;
332     }
333 
334     /**
335      * Just calls nextEvent().
336      * 
337      * @return next event.
338      */
339     @Override
340     public Object next() {
341         try {
342             mEvent = nextEvent();
343         } catch (final XMLStreamException exc) {
344             exc.printStackTrace();
345         }
346 
347         return mEvent;
348     }
349 
350     /** {@inheritDoc} */
351     @Override
352     public void remove() {
353         throw new UnsupportedOperationException("Not supported!");
354     }
355 
356     /**
357      * Determines if a node or an end element has to be emitted.
358      * 
359      * @param paramNodeKind
360      *            the node kind
361      * @throws IOException
362      *             In case of any I/O error.
363      */
364     private void processNode(final int paramNodeKind) throws TTIOException {
365         switch (paramNodeKind) {
366         case ELEMENT:
367             emitEndTag();
368             break;
369         case TEXT:
370             emitNode();
371             break;
372         default:
373             // Do nothing.
374         }
375     }
376 
377     /**
378      * Move to node and emit it.
379      * 
380      * @throws IOException
381      *             In case of any I/O error.
382      */
383     private void emit() throws TTIOException {
384         // Emit pending end elements.
385         if (mCloseElements) {
386             if (!mStack.empty() && mStack.peek() != ((IStructNode)mRtx.getNode()).getLeftSiblingKey()) {
387                 mRtx.moveTo(mStack.pop());
388                 emitEndTag();
389                 mRtx.moveTo(mKey);
390             } else if (!mStack.empty()) {
391                 mRtx.moveTo(mStack.pop());
392                 emitEndTag();
393                 mRtx.moveTo(mKey);
394                 mCloseElements = false;
395                 mCloseElementsEmitted = true;
396             }
397         } else {
398             mCloseElementsEmitted = false;
399 
400             // Emit node.
401             emitNode();
402 
403             final long nodeKey = mRtx.getNode().getDataKey();
404             mLastKey = nodeKey;
405 
406             // Push end element to stack if we are a start element.
407             if (mRtx.getNode().getKind() == ELEMENT) {
408                 mStack.push(nodeKey);
409             }
410 
411             // Remember to emit all pending end elements from stack if
412             // required.
413             if (!((IStructNode)mRtx.getNode()).hasFirstChild()
414                 && !((IStructNode)mRtx.getNode()).hasRightSibling()) {
415                 mGoUp = true;
416                 moveToNextNode();
417             } else if (mRtx.getNode().getKind() == ELEMENT && !((ElementNode)mRtx.getNode()).hasFirstChild()) {
418                 // Case: Empty elements with right siblings.
419                 mGoBack = true;
420                 moveToNextNode();
421             }
422         }
423     }
424 
425     /**
426      * Move to next node in tree either in case of a right sibling of an empty
427      * element or if no further child and no right sibling can be found, so that
428      * the next node is in the following axis.
429      */
430     private void moveToNextNode() {
431         mCloseElements = true;
432         if (mAxis.hasNext()) {
433             mKey = mAxis.next();
434         }
435     }
436 
437     /**
438      * Implements an iterator for attributes.
439      */
440     final class AttributeIterator implements Iterator<Attribute> {
441 
442         /**
443          * Treetank {@link IReadTransaction}.
444          */
445         private final INodeReadTrx mRTX;
446 
447         /** Number of attribute nodes. */
448         private final int mAttCount;
449 
450         /** Index of attribute node. */
451         private int mIndex;
452 
453         /** Node key. */
454         private final long mNodeKey;
455 
456         /** Factory to create nodes {@link XMLEventFactory}. */
457         private final transient XMLEventFactory mFac = XMLEventFactory.newFactory();
458 
459         /**
460          * Constructor.
461          * 
462          * @param rtx
463          *            Treetank reading transaction.
464          */
465         public AttributeIterator(final INodeReadTrx rtx) {
466             mRTX = rtx;
467             mNodeKey = mRTX.getNode().getDataKey();
468             mIndex = 0;
469 
470             if (mRTX.getNode().getKind() == ELEMENT) {
471                 mAttCount = ((ElementNode)mRTX.getNode()).getAttributeCount();
472             } else {
473                 mAttCount = 0;
474             }
475         }
476 
477         @Override
478         public boolean hasNext() {
479             boolean retVal = false;
480 
481             if (mIndex < mAttCount) {
482                 retVal = true;
483             }
484 
485             return retVal;
486         }
487 
488         @Override
489         public Attribute next() {
490             try {
491                 mRTX.moveTo(mNodeKey);
492                 mRTX.moveToAttribute(mIndex++);
493                 assert mRTX.getNode().getKind() == ATTRIBUTE;
494                 final QName qName = mRTX.getQNameOfCurrentNode();
495                 final String value = mRTX.getValueOfCurrentNode();
496                 mRTX.moveTo(mNodeKey);
497                 return mFac.createAttribute(qName, value);
498             } catch (TTIOException exc) {
499                 throw new RuntimeException(exc);
500             }
501         }
502 
503         @Override
504         public void remove() {
505             throw new UnsupportedOperationException("Not supported!");
506         }
507     }
508 
509     /**
510      * Implements a namespace iterator, which is needed for the StAX
511      * implementation.
512      * 
513      * @author Johannes Lichtenberger, University of Konstanz
514      * 
515      */
516     final class NamespaceIterator implements Iterator<Namespace> {
517 
518         /**
519          * Treetank {@link IReadTransaction}.
520          */
521         private final INodeReadTrx mRTX;
522 
523         /** Number of namespace nodes. */
524         private final int mNamespCount;
525 
526         /** Index of namespace node. */
527         private int mIndex;
528 
529         /** Node key. */
530         private final long mNodeKey;
531 
532         /** Factory to create nodes {@link XMLEventFactory}. */
533         private final transient XMLEventFactory mFac = XMLEventFactory.newInstance();
534 
535         /**
536          * Constructor.
537          * 
538          * @param rtx
539          *            Treetank reading transaction.
540          */
541         public NamespaceIterator(final INodeReadTrx rtx) {
542             mRTX = rtx;
543             mNodeKey = mRTX.getNode().getDataKey();
544             mIndex = 0;
545 
546             if (mRTX.getNode().getKind() == ELEMENT) {
547                 mNamespCount = ((ElementNode)mRTX.getNode()).getNamespaceCount();
548             } else {
549                 mNamespCount = 0;
550             }
551         }
552 
553         @Override
554         public boolean hasNext() {
555             boolean retVal = false;
556 
557             if (mIndex < mNamespCount) {
558                 retVal = true;
559             }
560 
561             return retVal;
562         }
563 
564         @Override
565         public Namespace next() {
566             try {
567                 mRTX.moveTo(mNodeKey);
568                 mRTX.moveToNamespace(mIndex++);
569                 assert mRTX.getNode().getKind() == NAMESPACE;
570                 final QName qName = mRTX.getQNameOfCurrentNode();
571                 mRTX.moveTo(mNodeKey);
572                 return mFac.createNamespace(qName.getLocalPart(), qName.getNamespaceURI());
573             } catch (TTIOException exc) {
574                 throw new RuntimeException(exc);
575             }
576         }
577 
578         @Override
579         public void remove() {
580             throw new UnsupportedOperationException("Not supported!");
581         }
582     }
583 }