View Javadoc
1   package org.exolab.castor.xml;
2   
3   import java.util.List;
4   import java.util.Stack;
5   
6   import javax.xml.namespace.QName;
7   import javax.xml.stream.Location;
8   import javax.xml.stream.XMLEventReader;
9   import javax.xml.stream.XMLStreamConstants;
10  import javax.xml.stream.XMLStreamReader;
11  
12  import org.apache.commons.logging.Log;
13  import org.apache.commons.logging.LogFactory;
14  import org.xml.sax.Attributes;
15  import org.xml.sax.ContentHandler;
16  import org.xml.sax.ErrorHandler;
17  import org.xml.sax.Locator;
18  import org.xml.sax.SAXException;
19  
20  /**
21   * This provides shared code for {@link Sax2EventFromStaxEventProducer} and
22   * {@link Sax2EventFromStaxStreamProducer}. It consumes StAX events and 
23   * produces SAX2 events.
24   * 
25   * @author <a href="mailto:philipp DOT erlacher AT gmail DOT com">Philipp
26   *         Erlacher</a>
27   * 
28   */
29  public abstract class BaseSax2EventFromStaxProducer implements
30          SAX2EventAndErrorProducer {
31  
32      /**
33       * Logger from commons-logging.
34       */
35      private static final Log LOG = LogFactory
36              .getLog(BaseSax2EventFromStaxProducer.class);
37  
38      /**
39       * A stack to keep track when it's time to invoke endPrefixMapping
40       */
41      private Stack<List<String>> prefixes = new Stack<List<String>>();
42  
43  
44      /**
45       * On this interface the SAX methods get invoked
46       */
47      private ContentHandler contentHandler;
48  
49      /**
50       * Callback Interface to handle errors
51       */
52      private ErrorHandler errorHandler;
53  
54      public static SAX2EventAndErrorProducer createSax2EventFromStax(
55              XMLStreamReader streamReader) {
56          return new Sax2EventFromStaxStreamProducer(streamReader);
57      }
58  
59      public static SAX2EventAndErrorProducer createSax2EventFromStax(
60              XMLEventReader eventReader) {
61          return new Sax2EventFromStaxEventProducer(eventReader);
62      }
63  
64      public void setContentHandler(ContentHandler contentHandler) {
65          this.contentHandler = contentHandler;
66      }
67  
68      public void setErrorHandler(ErrorHandler errorHandler) {
69          this.errorHandler = errorHandler;
70      }
71  
72      public Stack<List<String>> getPrefixes() {
73          return prefixes;
74      }
75  
76      public ContentHandler getContentHandler() {
77          return contentHandler;
78      }
79  
80      public ErrorHandler getErrorHandler() {
81          return errorHandler;
82      }
83  
84      /**
85       * This method takes an eventType and invokes a method to handle that event.
86       * 
87       * <p>
88       * It also takes information about the depth of the read element. Maybe
89       * depth changes due to handling that event.
90       * </p>
91       * 
92       * @param eventType
93       *            The event type
94       * @param depth
95       *            The current depth of the element
96       * @return depth The updated depth
97       * @throws SAXException
98       */
99      int handleEventType(int eventType, int depth) throws SAXException {
100         switch (eventType) {
101         case XMLStreamConstants.START_ELEMENT:
102             handleStartElement();
103             return ++depth;
104         case XMLStreamConstants.END_ELEMENT:
105             handleEndElement();
106             return --depth;
107         case XMLStreamConstants.START_DOCUMENT:
108             handleStartDocument();
109             return ++depth;
110         case XMLStreamConstants.END_DOCUMENT:
111             handleEndDocument();
112             return --depth;
113         case XMLStreamConstants.CHARACTERS:
114             handleCharacters();
115             return depth;
116         case XMLStreamConstants.SPACE:
117             handleSpace();
118             return depth;
119         default:
120             return depth;
121         }
122     }
123 
124    /**
125      * Invoke {@link #handleDocumentLocator()} and {@link
126      * getContentHandler().startDocument()};
127      * 
128      * @throws SAXException
129      */
130     void handleStartDocument() throws SAXException {
131         LOG.info("< handleStartDocument >");
132 
133         handleDocumentLocator();
134 
135         contentHandler.startDocument();
136     }
137 
138     /**
139      * Handles a end document event.
140      * <p>
141      * Invoke {@link getContentHandler().endDocument()};
142      * </p>
143      * 
144      * @throws SAXException
145      */
146     void handleEndDocument() throws SAXException {
147         LOG.info("< handleEndDocument >");
148         contentHandler.endDocument();
149 
150     }
151 
152     /**
153      * Handles a start element event.
154      * <p>
155      * Invoke {@link #doStartPrefixMapping()} and {@link
156      * getContentHandler().startElement()};
157      * </p>
158      * 
159      * @throws SAXException
160      */
161     void handleStartElement() throws SAXException {
162         LOG.info("< handleStartElement >");
163 
164         QName qName = getQName();
165         String localName = qName.getLocalPart();
166         String uri = qName.getNamespaceURI();
167         String prefix = qName.getPrefix();
168         String qNameString = getQName(prefix, localName);
169 
170         Attributes atts = getAttributes();
171 
172         doStartPrefixMapping();
173 
174         contentHandler.startElement(uri, localName, qNameString, atts);
175 
176     }
177 
178     /**
179      * Handles an end element event.
180      * <p>
181      * Invoke {@link getContentHandler().endElement()} and
182      * {@link #doEndPrefixMapping()};
183      * </p>
184      * 
185      * @throws SAXException
186      */
187     void handleEndElement() throws SAXException {
188         LOG.info("< handleEndElement >");
189 
190         QName qName = getQName();
191         String localName = qName.getLocalPart();
192         String uri = qName.getNamespaceURI();
193         String prefix = qName.getPrefix();
194         String qNameString = getQName(prefix, localName);
195 
196         contentHandler.endElement(uri, localName, qNameString);
197 
198         doEndPrefixMapping();
199     }
200 
201     /**
202      * Handles a space event.
203      * 
204      * @throws SAXException
205      */
206     void handleSpace() throws SAXException {
207 
208     }
209 
210     /**
211      * Handles a character event.
212      * <p>
213      * If chars is ignorable whitespace {@link
214      * getContentHandler().ignorableWhitespace will be called. Otherwise {@link
215      * getContentHandler().characters()} will be called with characters(char[],
216      * 0, length)
217      * 
218      * </p>
219      * 
220      * @throws SAXException
221      */
222     void handleCharacters() throws SAXException {
223         LOG.info("< handleCharacters >");
224         char[] chars;
225         chars = getCharacters();
226 
227         if (isIgnorableWhitespace(chars, 0, chars.length))
228             contentHandler.ignorableWhitespace(chars, 0, chars.length);
229         else
230             contentHandler.characters(chars, 0, chars.length);
231 
232     }
233 
234     /**
235      * @param prefix
236      * @param localPart
237      * @return qName. If prefix length >=1 then it's like prefix:localPart,
238      *         otherwise it's just the localPart
239      */
240     String getQName(String prefix, String localPart) {
241         return getNonEmpty(prefix).length() >= 1 ? prefix + ":" + localPart
242                 : localPart;
243     }
244 
245     /**
246      * If a chars without leading and trailing whitespaces would be empty, this
247      * method returns true, otherwise false,
248      * 
249      * @param chars
250      * @param start
251      *            the offset
252      * @param length
253      * @return
254      */
255     boolean isIgnorableWhitespace(char[] chars, int start, int length) {
256         StringBuffer buffer = new StringBuffer();
257         buffer.append(chars, start, length);
258         String string = buffer.toString();
259         if (string.trim().length() == 0)
260             return true;
261         return false;
262     }
263 
264     /**
265      * If string equals null this returns an empty string, otherwise it returns
266      * the string
267      * 
268      * @param string
269      *            the string to check
270      * @return a string. If string equals null this returns an empty string,
271      *         otherwise it returns the string
272      */
273     String getNonEmpty(String string) {
274         return string == null ? "" : string;
275     }
276 
277     /**
278      * @return a Location
279      */
280     abstract Location getLocation();
281 
282     /**
283      * @return characters of the current event.
284      */
285     abstract char[] getCharacters();
286 
287     /**
288      * For every declared namespace in the current event {@link
289      * getContentHandler().startPrefixMapping()} gets invoked.
290      * 
291      * @throws SAXException
292      */
293     abstract void doStartPrefixMapping() throws SAXException;
294 
295     /**
296      * 
297      * @throws SAXException
298      */
299     abstract void doEndPrefixMapping() throws SAXException;
300 
301     /**
302      * @return attributes of the current event
303      */
304     abstract Attributes getAttributes();
305 
306     /**
307      * @return QName of the current event
308      */
309     abstract QName getQName();
310 
311     /**
312      * If {@link #getLocation()} gets a location {@link
313      * getContentHandler().setDocumentLocator()} will be called, otherwise not.
314      */
315     private void handleDocumentLocator() {
316         Locator locator = getSAXLocator(getLocation());
317         contentHandler.setDocumentLocator(locator);
318     }
319 
320     /**
321      * Gets a {@link org.xml.sax.Locator} to a given {@link Location}.
322      * 
323      * @param location
324      *            A {@link Location}
325      * @return A {@link Locator}
326      */
327     protected Locator getSAXLocator(Location location) {
328         return new Locator() {
329             public String getSystemId() {
330                 return getLocation().getSystemId();
331             }
332 
333             public String getPublicId() {
334                 return getLocation().getPublicId();
335             }
336 
337             public int getLineNumber() {
338                 return getLocation().getLineNumber();
339             }
340 
341             public int getColumnNumber() {
342                 return getLocation().getColumnNumber();
343             }
344         };
345     }
346 }