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.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  
25  import net.sf.xolite.XMLSerializeException;
26  import net.sf.xolite.XMLSerializer;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  
32  /**
33   * Base for XMLSerializer implementations. This class hold all the complex state of XML serialization (like mapped prefixes,
34   * elements currently started, ...). It verify the coherence of the state (like checking that the ended element is the element
35   * currently started) and generate simpler serialization events (the abstract methods). Those new events will be implemented by
36   * concrete sub-classes.
37   */
38  public abstract class BaseXMLSerializer implements XMLSerializer {
39  
40  
41      private static Log log = LogFactory.getLog(BaseXMLSerializer.class);
42  
43      protected static final int STATE_INIT = 0;
44      protected static final int STATE_ELEMENT_START_OPENED = 1;
45      protected static final int STATE_ELEMENT_START_CLOSED = 2;
46      protected static final int STATE_TEXT = 3;
47      protected static final int STATE_TEXT_MULTI_LINE = 4;
48      protected static final int STATE_ELEMENT_CLOSED = 5;
49      protected static final int STATE_FINISHED = -1;
50  
51      private int state = STATE_INIT;
52      private int level = -1;
53      private List<LevelState> levelStates;
54      private int attributeIndex;
55      private Map<Object, Object> customObjects;
56  
57  
58      protected BaseXMLSerializer() {
59          levelStates = new ArrayList<LevelState>();
60      }
61  
62  
63      protected int getLevel() {
64          return level;
65      }
66  
67  
68      public void startDocument() throws XMLSerializeException {
69          state = STATE_INIT;
70          level = -1;
71          try {
72              startDocumentImpl();
73          } catch (XMLSerializeException xse) {
74              throw xse;
75          } catch (Exception e) {
76              throw new XMLSerializeException("Cannot start document", e);
77          }
78      }
79  
80  
81      public void endDocument() throws XMLSerializeException {
82          if (level >= 0) throw new XMLSerializeException("All tags are not closed at the end of the document");
83          try {
84              endDocumentImpl();
85          } catch (XMLSerializeException xse) {
86              throw xse;
87          } catch (Exception e) {
88              throw new XMLSerializeException("Cannot end document", e);
89          }
90          state = STATE_FINISHED;
91      }
92  
93  
94      private LevelState increaseLevel() throws XMLSerializeException {
95          if ((level < 0) && (state != STATE_INIT))
96              throw new XMLSerializeException("Bad documents state: " + state + ", should be STATE_INIT");
97          LevelState levelSt;
98          level++;
99          int len = levelStates.size();
100         if (level < len) levelSt = levelStates.get(level);
101         else if (level == len) {
102             LevelState parent = (level == 0) ? null : (LevelState) levelStates.get(level - 1);
103             levelSt = new LevelState(parent);
104             levelStates.add(levelSt);
105         } else {
106             throw new IllegalStateException("Invalid level=" + level + " when levelStates.size()=" + len);
107         }
108         return levelSt;
109     }
110 
111 
112     private void decreaseLevel() throws XMLSerializeException {
113         if (level < 0) throw new XMLSerializeException("No more XML element open");
114         LevelState levelSt = levelStates.get(level);
115         levelSt.clear();
116         level--;
117     }
118 
119 
120     private LevelState getLevelState() {
121         return levelStates.get(level);
122     }
123 
124 
125     private LevelState getNextLevelState() throws XMLSerializeException {
126         LevelState levelSt = increaseLevel();
127         level--;
128         return levelSt;
129     }
130 
131 
132     /**
133      * Get the element name of the last opened level (to be used for debug output only).
134      */
135     protected String getLastElementName() {
136         return getLevelState().getCurrentTag();
137     }
138 
139 
140     /**
141      * Get the tag length (prefix+name) of the last opened level.
142      */
143     protected int getLastElementLength() {
144         LevelState lvlState = getLevelState();
145         int len = lvlState.getCurrentTag().length();
146         String prefix = lvlState.getCurrentPrefix();
147         if ((prefix != null) && !prefix.equals("")) {
148             len += prefix.length() + 1;
149         }
150         return len;
151     }
152 
153 
154     private void closeElementStart(boolean complexContent) throws XMLSerializeException {
155         if (state == STATE_ELEMENT_START_OPENED) {
156             try {
157                 closeElementStartImpl(complexContent);
158             } catch (XMLSerializeException xse) {
159                 throw xse;
160             } catch (Exception e) {
161                 throw new XMLSerializeException("Cannot finalize element start", e);
162             }
163             state = STATE_ELEMENT_START_CLOSED;
164         }
165     }
166 
167 
168     // ------------------------ XMLSerializer interface implementation -------------------------
169 
170 
171     public void startPrefixMapping(String prefix, String namespaceUri) throws XMLSerializeException {
172         if (prefix == null)
173             throw new XMLSerializeException("Cannot define mapping for null prefix, use \"\" to define default prefix");
174         if ((namespaceUri == null) || "".equals(namespaceUri))
175             throw new XMLSerializeException("Cannot define mapping for null namespaceUri");
176 
177         if (state == STATE_ELEMENT_START_OPENED) {
178             // define the prefix in this level (and possibly add definition directly)
179             try {
180                 LevelState levelSt = getLevelState();
181                 if (levelSt.mapPrefix(prefix, namespaceUri)) definePrefixMapping(prefix, namespaceUri);
182             } catch (XMLSerializeException xse) {
183                 throw xse;
184             } catch (Exception e) {
185                 throw new XMLSerializeException("Cannot define prefix mapping " + prefix + "=" + namespaceUri, e);
186             }
187         } else {
188             // define the prefix for the next level
189             LevelState levelSt = getNextLevelState();
190             levelSt.mapPrefix(prefix, namespaceUri);
191         }
192     }
193 
194 
195     public void startElement(String uri, String tag) throws XMLSerializeException {
196         if (tag == null) throw new XMLSerializeException("Element Tag cannot be null");
197         if (tag.length() == 0) throw new XMLSerializeException("Element Tag cannot be empty string");
198         closeElementStart(true);
199         LevelState levelSt = increaseLevel();
200         if (log.isDebugEnabled()) log.debug("Start element <" + tag + "> level = " + level);
201         if (uri == null) uri = levelSt.getDefaultNamespaceUri();
202         String prefix = levelSt.getPrefix(uri);
203         levelSt.setCurrentValues(uri, prefix, tag);
204         attributeIndex = 0;
205         try {
206             startElementImpl(uri, prefix, tag);
207             // write all namespace definitions of this level
208             for (Iterator<String> iter = levelSt.getNamespaceDefinitons(); iter.hasNext();) {
209                 String defUri = iter.next();
210                 String defPrefix = levelSt.getPrefix(defUri);
211                 definePrefixMapping(defPrefix, defUri);
212             }
213         } catch (XMLSerializeException xse) {
214             throw xse;
215         } catch (Exception e) {
216             throw new XMLSerializeException("Cannot start element", e);
217         }
218         state = STATE_ELEMENT_START_OPENED;
219     }
220 
221 
222     public void attribute(String name, String value) throws XMLSerializeException {
223         attribute(null, name, value);
224     }
225 
226 
227     public void attribute(String uri, String name, String value) throws XMLSerializeException {
228         if (state != STATE_ELEMENT_START_OPENED)
229             throw new XMLSerializeException("writeAttribute must be called just after writeElementStart");
230         if (value == null) value = "";
231         LevelState levelSt = getLevelState();
232         String prefix = levelSt.getPrefix(uri);
233         try {
234             attributeImpl(uri, prefix, name, value, attributeIndex);
235             attributeIndex++;
236         } catch (XMLSerializeException xse) {
237             throw xse;
238         } catch (Exception e) {
239             throw new XMLSerializeException("Cannot serialize attribute", e);
240         }
241     }
242 
243 
244     public void characters(String text) throws XMLSerializeException {
245         if ((text != null) && (!text.equals(""))) {
246             closeElementStart(false);
247             try {
248                 charactersImpl(text, false);
249             } catch (XMLSerializeException xse) {
250                 throw xse;
251             } catch (Exception e) {
252                 throw new XMLSerializeException("Cannot serialize text", e);
253             }
254             state = STATE_TEXT;
255         }
256     }
257 
258 
259     public void charactersMultiLine(String text) throws XMLSerializeException {
260         if ((text != null) && (!text.equals(""))) {
261             closeElementStart(false);
262             try {
263                 charactersImpl(text, true);
264             } catch (XMLSerializeException xse) {
265                 throw xse;
266             } catch (Exception e) {
267                 throw new XMLSerializeException("Cannot serialize text", e);
268             }
269             state = STATE_TEXT_MULTI_LINE;
270         }
271     }
272 
273 
274     public void endElement(String uri, String tag) throws XMLSerializeException {
275         LevelState levelSt = getLevelState();
276         levelSt.checkCurrentValues(uri, tag);
277         String prefix = levelSt.getPrefix(uri);
278         try {
279             if (state == STATE_ELEMENT_START_OPENED) {
280                 endInlineElementImpl(uri, prefix, tag);
281             } else if ((state == STATE_ELEMENT_START_CLOSED) || (state == STATE_TEXT) || (state == STATE_TEXT_MULTI_LINE)) {
282                 endTextElementImpl(uri, prefix, tag, state == STATE_TEXT_MULTI_LINE);
283             } else {
284                 endComplexElementImpl(uri, prefix, tag);
285             }
286         } catch (XMLSerializeException xse) {
287             throw xse;
288         } catch (Exception e) {
289             throw new XMLSerializeException("Cannot end element", e);
290         }
291         decreaseLevel();
292         if (log.isDebugEnabled()) log.debug("End element <" + tag + "> leval = " + level);
293         state = STATE_ELEMENT_CLOSED;
294     }
295 
296 
297     public void simpleElement(String namespaceUri, String localName, String text) throws XMLSerializeException {
298         startElement(namespaceUri, localName);
299         characters(text);
300         endElement(namespaceUri, localName);
301     }
302 
303 
304     // ----------------------- Abstract methods ----------------------
305 
306 
307     protected abstract void startDocumentImpl() throws Exception;
308 
309 
310     protected abstract void endDocumentImpl() throws Exception;
311 
312 
313     protected abstract void startElementImpl(String uri, String prefix, String tag) throws Exception;
314 
315 
316     protected abstract void attributeImpl(String uri, String prefix, String name, String value, int index) throws Exception;
317 
318 
319     protected abstract void closeElementStartImpl(boolean complexContent) throws Exception;
320 
321 
322     protected abstract void charactersImpl(String text, boolean multilineContent) throws Exception;
323 
324 
325     protected abstract void endInlineElementImpl(String uri, String prefix, String tag) throws Exception;
326 
327 
328     protected abstract void endTextElementImpl(String uri, String prefix, String tag, boolean multilineContent)
329             throws Exception;
330 
331 
332     protected abstract void endComplexElementImpl(String uri, String prefix, String tag) throws Exception;
333 
334 
335     protected void definePrefixMapping(String prefix, String uri) throws Exception {
336         String xmlns = "xmlns";
337         if ("".equals(prefix)) {
338             prefix = "xmlns";
339             xmlns = null;
340         }
341         attributeImpl(uri, xmlns, prefix, uri, attributeIndex);
342         attributeIndex++;
343     }
344 
345 
346     public Object getCustomObject(Object key) {
347         return (customObjects == null) ? null : customObjects.get(key);
348     }
349 
350 
351     public void putCustomObject(Object key, Object value) {
352         if (customObjects == null) customObjects = new HashMap<Object, Object>();
353         customObjects.put(key, value);
354     }
355 
356 
357     // ----------------------- Inner class maintaining XML level state information ----------------------
358 
359 
360     /**
361      * Class holding the current state of a level in the path to the current element.
362      */
363     private static class LevelState {
364 
365 
366         private String defaultNamespaceUri;
367         private String currentNamespaceUri;
368         private String currentPrefix;
369         private String currentTag;
370         private Map<String, String> prefixMappings;
371         private LevelState parent;
372 
373 
374         LevelState(LevelState parentState) {
375             parent = parentState;
376             prefixMappings = new HashMap<String, String>();
377         }
378 
379 
380         String getCurrentPrefix() {
381             return currentPrefix;
382         }
383 
384 
385         String getCurrentTag() {
386             return currentTag;
387         }
388 
389 
390         void clear() {
391             defaultNamespaceUri = null;
392             currentNamespaceUri = null;
393             currentPrefix = null;
394             currentTag = null;
395             prefixMappings.clear();
396         }
397 
398 
399         void setCurrentValues(String uri, String prefix, String tag) {
400             currentNamespaceUri = uri;
401             currentPrefix = prefix;
402             currentTag = tag;
403         }
404 
405 
406         void checkCurrentValues(String uri, String tag) throws XMLSerializeException {
407             if (!checkEquals(currentNamespaceUri, uri))
408                 throw new XMLSerializeException("The closed element namespace URI is not the same as the opened one: "
409                         + currentNamespaceUri + " != " + uri);
410             if (!checkEquals(currentTag, tag))
411                 throw new XMLSerializeException("The closed element tag is not the same as the opened one: " + currentTag
412                         + " != " + tag);
413         }
414 
415 
416         private boolean checkEquals(String str1, String str2) {
417             if (str1 == str2) return true;
418             if (str1 == null) return str2 != null;
419             return str1.equals(str2);
420         }
421 
422 
423         String getPrefix(String namespaceUri) throws XMLSerializeException {
424             String prefix = null;
425             if (namespaceUri != null) {
426                 prefix = searchPrefix(namespaceUri);
427                 if (prefix == null) {
428                     char nsChar = 'a';
429                     while (prefix == null) {
430                         String candidatePrefix = "" + nsChar + nsChar;
431                         String uri = searchNamespaceUri(candidatePrefix);
432                         if (uri == null) {
433                             prefix = candidatePrefix;
434                         }
435                     }
436                     mapPrefix(prefix, namespaceUri);
437                 }
438             }
439             return prefix;
440         }
441 
442 
443         boolean mapPrefix(String prefix, String namespaceUri) {
444             boolean added = false;
445             String existingPrefix = searchPrefix(namespaceUri);
446             if (existingPrefix == null) {
447                 prefixMappings.put(namespaceUri, prefix);
448                 if (prefix.equals("")) defaultNamespaceUri = namespaceUri;
449                 added = true;
450             }
451             return added;
452         }
453 
454 
455         private String searchPrefix(String namespaceUri) {
456             String prefix = null;
457             if (checkEquals(currentNamespaceUri, namespaceUri)) prefix = currentPrefix;
458             if (prefix == null) {
459                 prefix = prefixMappings.get(namespaceUri);
460                 if ((prefix == null) && (parent != null)) prefix = parent.searchPrefix(namespaceUri);
461             }
462             return prefix;
463         }
464 
465 
466         private String searchNamespaceUri(String prefix) {
467             String namespaceUri = null;
468             if (checkEquals(currentPrefix, prefix)) namespaceUri = currentNamespaceUri;
469             if (namespaceUri == null) {
470                 for (String ns : prefixMappings.keySet()) {
471                     if (prefix.equals(prefixMappings.get(ns))) {
472                         namespaceUri = ns;
473                         break;
474                     }
475                 }
476                 if ((namespaceUri == null) && (parent != null)) namespaceUri = parent.searchNamespaceUri(prefix);
477             }
478             return namespaceUri;
479         }
480 
481 
482         Iterator<String> getNamespaceDefinitons() {
483             return prefixMappings.keySet().iterator();
484         }
485 
486 
487         String getDefaultNamespaceUri() {
488             if ((defaultNamespaceUri == null) && (parent != null)) defaultNamespaceUri = parent.getDefaultNamespaceUri();
489             return defaultNamespaceUri;
490         }
491 
492     }
493 
494 }