View Javadoc
1   /*
2    * Copyright 2011 Jakub Narloch
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5    * in compliance with the License. You may obtain a copy of the License at
6    *
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software distributed under the License
10   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11   * or implied. See the License for the specific language governing permissions and limitations under
12   * the License.
13   */
14  package org.exolab.castor.xml.util;
15  
16  import org.castor.core.util.Assert;
17  import org.exolab.castor.xml.Namespaces;
18  import org.exolab.castor.xml.NamespacesStack;
19  import org.xml.sax.Attributes;
20  import org.xml.sax.SAXException;
21  import org.xml.sax.helpers.DefaultHandler;
22  
23  import javax.xml.namespace.QName;
24  import javax.xml.stream.XMLEventFactory;
25  import javax.xml.stream.XMLEventWriter;
26  import javax.xml.stream.XMLStreamException;
27  import javax.xml.stream.events.Attribute;
28  import javax.xml.stream.events.Namespace;
29  import java.util.Enumeration;
30  import java.util.Iterator;
31  
32  /**
33   * A document handler that uses internally a instance of {@link javax.xml.stream.XMLEventWriter} to
34   * output the result xml.
35   *
36   * @author <a herf="mailto:jmnarloch AT gmail DOT com">Jakub Narloch</a>
37   * @version 1.3.3
38   * @since 1.3.3
39   */
40  public class StaxEventHandler extends DefaultHandler {
41  
42    /**
43     * Instance of {@link XMLEventFactory} that will be used to create instances of xml events.
44     */
45    private final XMLEventFactory eventFactory = XMLEventFactory.newInstance();
46  
47    /**
48     * Instance of {@link XMLEventWriter} to be used to write the marshalled object.
49     */
50    private final XMLEventWriter xmlEventWriter;
51  
52    /**
53     * Instance of {@link Namespaces} used for handling the namespace.
54     */
55    private NamespacesStack namespacesStack = new NamespacesStack();
56  
57    /**
58     * Flag indicating whether the new namespace scope is required to create.
59     */
60    private boolean createNamespaceScope = true;
61  
62    /**
63     * Creates new instance of {@link StaxEventHandler} with given {@link XMLEventWriter}.
64     *
65     * @param xmlEventWriter the {@link XMLEventWriter} to be used
66     *
67     * @throws IllegalArgumentException if xmlEventWriter is null
68     */
69    public StaxEventHandler(XMLEventWriter xmlEventWriter) {
70      Assert.paramNotNull(xmlEventWriter, "xmlEventWriter");
71  
72      this.xmlEventWriter = xmlEventWriter;
73    }
74  
75    @Override
76    public void startDocument() throws SAXException {
77      try {
78        // writes the start of document
79        xmlEventWriter.add(eventFactory.createStartDocument());
80      } catch (XMLStreamException e) {
81        convertToSAXException("Error occurred when writing document start.", e);
82      }
83    }
84  
85    @Override
86    public void endDocument() throws SAXException {
87      try {
88        // writes the end of document
89        xmlEventWriter.add(eventFactory.createEndDocument());
90      } catch (XMLStreamException e) {
91        convertToSAXException("Error occurred when writing document end.", e);
92      }
93    }
94  
95    @Override
96    public void startPrefixMapping(String prefix, String uri) throws SAXException {
97      if (createNamespaceScope) {
98        namespacesStack.addNewNamespaceScope();
99        createNamespaceScope = false;
100     }
101 
102     namespacesStack.addNamespace(prefix, uri);
103   }
104 
105   @Override
106   public void startElement(String uri, String localName, String qName, Attributes attributes)
107       throws SAXException {
108     try {
109       // writes the start of element
110       xmlEventWriter.add(eventFactory.createStartElement(new QName(qName),
111           new AttributeIterator(attributes), new NamespaceIterator(namespacesStack)));
112     } catch (XMLStreamException e) {
113       convertToSAXException("Error occurred when writing element start.", e);
114     }
115   }
116 
117   @Override
118   public void endElement(String uri, String localName, String qName) throws SAXException {
119     try {
120       // writes the end of element
121       xmlEventWriter.add(eventFactory.createEndElement(new QName(qName), null));
122     } catch (XMLStreamException e) {
123       convertToSAXException("Error occurred when writing element end.", e);
124     }
125   }
126 
127   @Override
128   public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
129     try {
130       // writes the characters
131       xmlEventWriter.add(eventFactory.createCharacters(new String(ch, start, length)));
132     } catch (XMLStreamException e) {
133       convertToSAXException("Error occurred when writing white spaces.", e);
134     }
135   }
136 
137   @Override
138   public void characters(char[] ch, int start, int length) throws SAXException {
139     try {
140       // writes the characters
141       xmlEventWriter.add(eventFactory.createCharacters(new String(ch, start, length)));
142     } catch (XMLStreamException e) {
143       convertToSAXException("Error occurred when writing characters.", e);
144     }
145   }
146 
147   @Override
148   public void processingInstruction(String target, String data) throws SAXException {
149     try {
150       // writes the processing instruction
151       xmlEventWriter.add(eventFactory.createProcessingInstruction(target, data));
152     } catch (XMLStreamException e) {
153       convertToSAXException("Error occurred when writing processing instruction.", e);
154     }
155   }
156 
157   /**
158    * Converts the passed exception into a {@link SAXException} instance with using the provided
159    * error message and exception cause.
160    *
161    * @param msg the error message
162    * @param e the inner cause of newly created exception
163    *
164    * @throws SAXException the newly created exception instance
165    */
166   private void convertToSAXException(String msg, XMLStreamException e) throws SAXException {
167     throw new SAXException(msg, e);
168   }
169 
170   /**
171    * An attribute iterator that converts the representation of attributes between sax and stax.
172    *
173    * @author <a herf="mailto:jmnarloch AT gmail DOT com">Jakub Narloch</a>
174    * @version 1.3.3
175    * @since 1.3.3
176    */
177   private class AttributeIterator implements Iterator {
178 
179     /**
180      * Represents the list of attributes.
181      */
182     private final Attributes attributes;
183 
184     /**
185      * Represents the index that points to current attributes on the list.
186      */
187     private int index;
188 
189     /**
190      * Creates new instance of {@link AttributeIterator} class.
191      *
192      * @param attributes the list of attributes to use
193      */
194     private AttributeIterator(Attributes attributes) {
195       this.attributes = attributes;
196     }
197 
198     public boolean hasNext() {
199       return index < attributes.getLength();
200     }
201 
202     public Object next() {
203       // creates stax attribute instance
204       Attribute attribute =
205           eventFactory.createAttribute(attributes.getQName(index), attributes.getValue(index));
206       // increments the current index
207       index++;
208       // returns the instance of created attribute
209       return attribute;
210     }
211 
212     public void remove() {
213       throw new UnsupportedOperationException("Method 'remove' is not supported.");
214     }
215   }
216 
217   /**
218    * An namespace iterator that converts the representation of namespace between internal Castor
219    * representation and stax.
220    *
221    * @author <a herf="mailto:jmnarloch AT gmail DOT com">Jakub Narloch</a>
222    * @version 1.3.3
223    * @since 1.3.3
224    */
225   private class NamespaceIterator implements Iterator<Namespace> {
226 
227     /**
228      * Represents the current namespace context.
229      */
230     private final NamespacesStack namespaces;
231 
232     /**
233      * Represents the current namespace context.
234      */
235     private final Enumeration namespaceEnumerator;
236 
237     /**
238      * Indicates whether the default namespace exists.
239      */
240     private boolean hasDefaultNamespace;
241 
242     /**
243      * Indicates whether the default namespace has been written.
244      */
245     private boolean defaultNamespaceWritten;
246 
247     /**
248      * Creates new instance of {@link AttributeIterator} class.
249      *
250      * @param namespaces the list of attributes to use
251      */
252     private NamespaceIterator(NamespacesStack namespaces) {
253       this.namespaces = namespaces;
254       this.namespaceEnumerator = namespaces.getLocalNamespacePrefixes();
255 
256       // retrieves the default namespace
257       String defaultNamespace = namespaces.getDefaultNamespaceURI();
258       if (defaultNamespace != null && defaultNamespace.length() > 0) {
259         hasDefaultNamespace = true;
260       }
261     }
262 
263     public boolean hasNext() {
264       return hasDefaultNamespace && !defaultNamespaceWritten
265           || namespaceEnumerator.hasMoreElements();
266     }
267 
268     public Namespace next() {
269       Namespace namespace;
270 
271       // creates namespace instance
272       if (hasDefaultNamespace && !defaultNamespaceWritten) {
273 
274         // creates a default namespace instance
275         namespace = eventFactory.createNamespace(namespaces.getDefaultNamespaceURI());
276         defaultNamespaceWritten = true;
277       } else {
278 
279         // creates a namespace instance
280         String prefix = (String) namespaceEnumerator.nextElement();
281         namespace = eventFactory.createNamespace(prefix, namespaces.getNamespaceURI(prefix));
282       }
283 
284       // returns the instance of created namespace
285       return namespace;
286     }
287 
288     public void remove() {
289       throw new UnsupportedOperationException("Method 'remove' is not supported.");
290     }
291   }
292 }