View Javadoc

1   /*-------------------------------------------------------------------------
2    Copyright 2006 Olivier Berlanger
3   
4    Licensed under the Apache License, Version 2.0 (the "License");
5    you may not use this file except in compliance with the License.
6    You may obtain a copy of the License at
7   
8    http://www.apache.org/licenses/LICENSE-2.0
9   
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15   -------------------------------------------------------------------------*/
16  package net.sf.xolite.impl;
17  
18  
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.HashSet;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import javax.xml.namespace.NamespaceContext;
28  
29  import net.sf.xolite.NamespacedName;
30  import net.sf.xolite.XMLEventParser;
31  import net.sf.xolite.XMLObjectFactory;
32  import net.sf.xolite.XMLParseException;
33  import net.sf.xolite.XMLSerializable;
34  
35  
36  /**
37   * A base <code>XMLEventParser</code> implementation common to many parsers.
38   * 
39   * @author Olivier Berlanger
40   */
41  public abstract class BaseXMLEventParser implements XMLEventParser {
42  
43  
44      /** Handler to where the simplified SAX parse events are forwarded. */
45      private XMLSerializable currentHandler;
46      /** the last object that called <code>endParsing(..)</code> method on the parser. */
47      private XMLSerializable lastParsed;
48      /** Stack of all the <code>XMLSerializable</code> handlers parent of the current one. */
49      private List<LevelInfo> levels;
50      /** current level in the 'levels' list. */
51      private int currentLevel = -1;
52      /** Flag telling if we are currently in a startElement or a endElement notification. */
53      private boolean currentlyInTagEnd;
54      /** Flag telling if we are notifying the first or last event to a handler (to be combined with currentlyInTagEnd). */
55      private boolean currentlyInFirstOrLast;
56      /** A factory which can be used to instantiate objects from XML elements. */
57      private XMLObjectFactory factory;
58      /** Map to hold custom objects. */
59      private Map<Object, Object> customObjects;
60  
61  
62      public BaseXMLEventParser() {
63          levels = new ArrayList<LevelInfo>();
64      }
65  
66  
67      /**
68       * Set the factory to be used by this parser.
69       * 
70       * @param newFactory
71       *            a factory able to instantiate objects corresponding to XML elements.
72       */
73      public void setFactory(XMLObjectFactory newFactory) {
74          factory = newFactory;
75      }
76  
77  
78      /**
79       * Get namespace URI corresponding to the given prefix. <br>
80       * Note: the mapped namespace set depends on the currently parsed node.
81       * 
82       * @see javax.xml.namespace.NamespaceContext#getPrefix(java.lang.String)
83       */
84      public String getNamespaceURI(String prefix) {
85          for (int i = currentLevel; i >= 0; i--) {
86              String uri = levels.get(i).getPrefixMappings().getNamespaceURI(prefix);
87              if (uri != null) return uri;
88          }
89          return null;
90      }
91  
92  
93      /**
94       * Get the first prefix mapped to the given namespace.
95       * 
96       * @see javax.xml.namespace.NamespaceContext#getPrefix(java.lang.String)
97       */
98      public String getPrefix(String namespaceURI) {
99          for (int i = currentLevel; i >= 0; i--) {
100             String prefix = levels.get(i).getPrefixMappings().getPrefix(namespaceURI);
101             if (prefix != null) return prefix;
102         }
103         return null;
104     }
105 
106 
107     /**
108      * Get all the prefixes mapped to the given namespace.
109      * 
110      * @see javax.xml.namespace.NamespaceContext#getPrefixes(java.lang.String)
111      */
112     public Iterator<String> getPrefixes(String namespaceURI) {
113         Set<String> prefixes = new HashSet<String>();
114         for (int i = currentLevel; i >= 0; i--) {
115             MapPrefixResolver prefixMappings = levels.get(i).getPrefixMappings();
116             for (Iterator<String> levelPrefixes = prefixMappings.getPrefixes(namespaceURI); levelPrefixes.hasNext();) {
117                 prefixes.add(levelPrefixes.next());
118             }
119         }
120         return prefixes.iterator();
121     }
122 
123 
124     // ------------------------ XMLEventParser interface implementation -----------------------------------------------
125 
126 
127     public NamespacedName getCurrentElementName() {
128         LevelInfo level = getCurrentLevel();
129         return new NamespacedName(level.getCurrentURI(), level.getCurrentElementName());
130     }
131 
132 
133     public NamespaceContext getCurrentDefinedNamespaces() {
134         MapPrefixResolver context = new MapPrefixResolver();
135         for (int i = 0; i <= currentLevel; i++) {
136             MapPrefixResolver prefixMappings = levels.get(i).getPrefixMappings();
137             context.addAll(prefixMappings);
138         }
139         return context;
140     }
141 
142 
143     public void delegateParsingTo(XMLSerializable handlerOfSubElements) throws XMLParseException {
144         if (currentHandler == null) throw new IllegalStateException("The currentHandler is null");
145         LevelInfo level = getCurrentLevel();
146         if (level == null)
147             throw new IllegalStateException("The delegateParsingTo(..) method can only be called from startElement(..)");
148         if (currentlyInTagEnd)
149             throw new IllegalStateException("The delegateParsingTo(..) method can only be called from startElement(..)");
150         level.addHandler(handlerOfSubElements);
151         currentHandler = handlerOfSubElements;
152         currentlyInFirstOrLast = true;
153         handlerOfSubElements.startElement(level.getCurrentURI(), level.getCurrentElementName(), this);
154         currentlyInFirstOrLast = false;
155     }
156 
157 
158     public XMLSerializable parseElement(String uri, String localName) throws XMLParseException {
159         XMLSerializable child = getFactory().createObject(uri, localName, this);
160         delegateParsingTo(child);
161         return child;
162     }
163 
164 
165     public boolean isFirstEvent() {
166         return currentlyInFirstOrLast && !currentlyInTagEnd;
167     }
168 
169 
170     public boolean isLastEvent() {
171         return currentlyInFirstOrLast && currentlyInTagEnd;
172     }
173 
174 
175     public XMLSerializable getLastParsedObject() throws XMLParseException {
176         return lastParsed;
177     }
178 
179 
180     public NamespacedName getQualifiedName(String qName) throws XMLParseException {
181         int colonIndex = qName.indexOf(':');
182         String prefix = (colonIndex < 0) ? "" : qName.substring(0, colonIndex);
183         String uri = getNamespaceURI(prefix);
184         if ((uri == null) && !prefix.equals("")) throwParseException("Unbound prefix for '" + qName + "'", null);
185         String name = (colonIndex < 0) ? qName : qName.substring(colonIndex + 1);
186         NamespacedName qualified = new NamespacedName(uri, name);
187         return qualified;
188     }
189 
190 
191     public XMLObjectFactory getFactory() throws XMLParseException {
192         if (factory == null) throw new XMLParseException("XmlObjectFactory is not defined");
193         return factory;
194     }
195 
196 
197     public Object getCustomObject(Object key) {
198         return (customObjects == null) ? null : customObjects.get(key);
199     }
200 
201 
202     public void putCustomObject(Object key, Object value) {
203         if (customObjects == null) customObjects = new HashMap<Object, Object>();
204         customObjects.put(key, value);
205     }
206 
207 
208     // --------------------------------- Exception management -----------------------------------
209 
210 
211     public void throwUnexpectedNamespaceException(String expected) throws XMLParseException {
212         StringBuffer sb = new StringBuffer("Unexpected namespace: ");
213         LevelInfo level = getCurrentLevel();
214         if (level != null) {
215             sb.append("'");
216             sb.append(level.getCurrentURI());
217             if (expected != null) {
218                 sb.append("', expected: '");
219                 sb.append(expected);
220                 sb.append("'");
221             }
222         }
223         throwParseException(sb.toString(), null, true);
224     }
225 
226 
227     public void throwUnexpectedElementException(String expectedTags) throws XMLParseException {
228         StringBuilder sb = new StringBuilder("Unexpected element ");
229         appendCurrentElement(sb);
230         if (expectedTags != null) {
231             sb.append(" (expected= ");
232             sb.append(expectedTags);
233             sb.append(")");
234         }
235         throwParseException(sb.toString(), null, false);
236     }
237 
238 
239     public void throwParseException(String message, Throwable cause) throws XMLParseException {
240         throwParseException(message, cause, true);
241     }
242 
243 
244     private void throwParseException(String message, Throwable cause, boolean addLocation) throws XMLParseException {
245         StringBuilder sb = new StringBuilder(message);
246         if (addLocation) {
247             sb.append(" in element ");
248             appendCurrentElement(sb);
249         }
250         XMLParseException pe = new XMLParseException(sb.toString());
251         if (cause != null) pe.initCause(cause);
252         addLocationInfo(pe);
253         throw pe;
254     }
255 
256 
257     private void appendCurrentElement(StringBuilder sb) {
258         sb.append("<");
259         if (currentlyInTagEnd) sb.append("/");
260         LevelInfo level = getCurrentLevel();
261         if (level != null) {
262             if ((level.getCurrentURI() != null) && !level.getCurrentURI().equals("")) {
263                 sb.append(level.getCurrentURI());
264                 sb.append(":");
265             }
266             sb.append(level.getCurrentElementName());
267         } else {
268             sb.append("???");
269         }
270         sb.append(">");
271     }
272 
273 
274     protected void transformAndThrowException(Exception source) throws XMLParseException {
275         if (source instanceof XMLParseException) {
276             throw (XMLParseException) source;
277         } else {
278             XMLParseException xpe = new XMLParseException(source.getMessage(), source);
279             addLocationInfo(xpe);
280             throw xpe;
281         }
282     }
283 
284 
285     protected abstract void addLocationInfo(XMLParseException xpe);
286 
287 
288     // ------------------------ Implementation -----------------------------------------------
289 
290 
291     protected void setup(XMLSerializable rootHandler) {
292         clearState();
293         setupRootHandler(rootHandler);
294         currentlyInFirstOrLast = true;
295     }
296 
297 
298     protected void tearDown() {
299         clearState();
300     }
301 
302 
303     /**
304      * Clean state and avoid keeping dangling references to internal objects.
305      */
306     private void clearState() {
307         for (LevelInfo level : levels) {
308             level.clear();
309         }
310         currentHandler = null;
311         lastParsed = null;
312         currentLevel = -1;
313     }
314 
315 
316     LevelInfo getCurrentLevel() {
317         if (currentLevel < 0) return null;
318         return levels.get(currentLevel);
319     }
320 
321 
322     void increaseLevel(String uri, String localName) {
323         currentLevel++;
324         LevelInfo newLevel;
325         if (currentLevel < levels.size()) {
326             newLevel = levels.get(currentLevel);
327         } else if (currentLevel == levels.size()) {
328             newLevel = new LevelInfo();
329             levels.add(newLevel);
330         } else {
331             // should never happen
332             throw new IllegalStateException("Level mismatch");
333         }
334         newLevel.init(uri, localName);
335     }
336 
337 
338     void decreaseLevel(String uri, String localName) throws XMLParseException {
339         assert (currentLevel >= 0);
340         LevelInfo level = getCurrentLevel();
341         int handlerCount = level.getHandlerCount();
342         if (handlerCount > 0) {
343             for (int i = handlerCount - 1; i >= 0; i--) {
344                 XMLSerializable nextHandler = null;
345                 if (i > 0) nextHandler = level.getHandlers().get(i - 1);
346                 else nextHandler = getNextHandler(currentLevel - 1);
347                 // notify next handler
348                 lastParsed = currentHandler;
349                 currentHandler = nextHandler;
350                 currentlyInFirstOrLast = (i > 0);
351                 if (currentHandler != null) {
352                     currentHandler.endElement(level.getCurrentURI(), level.getCurrentElementName(), this);
353                 }
354             }
355         }
356         level.clear();
357         currentLevel--;
358     }
359 
360 
361     private void setupRootHandler(XMLSerializable root) {
362         assert (currentLevel == -1);
363         increaseLevel(null, null);
364         levels.get(0).addHandler(root);
365         currentLevel--;
366         currentHandler = root;
367     }
368 
369 
370     protected void pushPrefixMappingInNextLevel(String prefix, String namespaceURI) {
371         increaseLevel(null, null);
372         levels.get(currentLevel).getPrefixMappings().addPrefixMapping(prefix, namespaceURI);
373         currentLevel--;
374     }
375 
376 
377     private XMLSerializable getNextHandler(int fromLevel) {
378         for (int i = fromLevel; i >= 0; i--) {
379             LevelInfo level = levels.get(i);
380             int handlerCount = level.getHandlerCount();
381             if (handlerCount > 0) {
382                 return level.getHandlers().get(handlerCount - 1);
383             }
384         }
385         return null;
386     }
387 
388 
389     protected void startElementImpl(String uri, String localName) throws Exception {
390         increaseLevel(uri, localName);
391         currentlyInTagEnd = false;
392         currentHandler.startElement(uri, localName, BaseXMLEventParser.this);
393         currentlyInFirstOrLast = false;
394     }
395 
396 
397     protected void endElementImpl(String uri, String localName) throws Exception {
398         currentlyInTagEnd = true;
399         currentlyInFirstOrLast = getCurrentLevel().getHandlerCount() > 0;
400         currentHandler.endElement(uri, localName, BaseXMLEventParser.this);
401         decreaseLevel(uri, localName);
402     }
403 
404 
405     // --------------------- Level inner class -------------------------------
406 
407 
408     /**
409      * Info put in a stack while opening XML elements.
410      */
411     static class LevelInfo {
412 
413 
414         private List<XMLSerializable> handlers;
415         /** Defined prefix mappings. */
416         private MapPrefixResolver prefixMappings;
417         /** The namespace URI of this level element. */
418         private String currentElementURI;
419         /** The local name of this level element. */
420         private String currentElementName;
421 
422 
423         LevelInfo() {
424             handlers = new ArrayList<XMLSerializable>();
425             prefixMappings = new MapPrefixResolver();
426         }
427 
428 
429         public int getHandlerCount() {
430             return handlers.size();
431         }
432 
433 
434         void addHandler(XMLSerializable handler) {
435             handlers.add(handler);
436         }
437 
438 
439         List<XMLSerializable> getHandlers() {
440             return handlers;
441         }
442 
443 
444         void init(String uri, String localName) {
445             currentElementURI = uri;
446             currentElementName = localName;
447         }
448 
449 
450         void clear() {
451             handlers.clear();
452             prefixMappings.clear();
453             currentElementURI = null;
454             currentElementName = null;
455         }
456 
457 
458         String getCurrentURI() {
459             return currentElementURI;
460         }
461 
462 
463         String getCurrentElementName() {
464             return currentElementName;
465         }
466 
467 
468         MapPrefixResolver getPrefixMappings() {
469             return prefixMappings;
470         }
471 
472     }
473 
474 
475 }