KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javolution > xml > ObjectWriter


1 /*
2  * Javolution - Java(TM) Solution for Real-Time and Embedded Systems
3  * Copyright (C) 2005 - Javolution (http://javolution.org/)
4  * All rights reserved.
5  *
6  * Permission to use, copy, modify, and distribute this software is
7  * freely granted, provided that this notice is preserved.
8  */

9 package javolution.xml;
10
11 import j2me.lang.CharSequence;
12 import j2me.nio.ByteBuffer;
13
14 import java.io.IOException;
15 import java.io.OutputStream;
16 import java.io.Writer;
17
18 import org.xml.sax.SAXException;
19
20 import javolution.io.Utf8ByteBufferWriter;
21 import javolution.io.Utf8StreamWriter;
22 import javolution.lang.PersistentReference;
23 import javolution.lang.Reusable;
24 import javolution.lang.Text;
25 import javolution.lang.TextBuilder;
26 import javolution.realtime.ObjectFactory;
27 import javolution.util.FastComparator;
28 import javolution.util.FastList;
29 import javolution.util.FastMap;
30 import javolution.util.FastTable;
31 import javolution.xml.sax.ContentHandler;
32 import javolution.xml.sax.WriterHandler;
33
34 /**
35  * <p> This class takes an object and formats it to XML (SAX2 events or stream).
36  * Objects written using this facility may be read using
37  * the {@link ObjectReader} class.</p>
38  *
39  * <p> Namespaces are supported (including default namespace).</p>
40  *
41  * <p> For example, the following code creates an <code>ObjectWriter</code>
42  * using the default namespace for all <code>java.lang.*</code> classes and
43  * the <code>math</code> prefix for the <code>org.jscience.mathematics.*
44  * </code> classes.
45  * <pre>
46  * ObjectWriter ow = new ObjectWriter();
47  * ow.setNamespace("", "java.lang"); // Default namespace.
48  * ow.setNamespace("math", "org.jscience.mathematics");
49  * ...
50  * ow.write(obj, contentHandler); // SAX2 Events.
51  * <i>or</i> ow.write(obj, writer); // Writer encoding.
52  * <i>or</i> ow.write(obj, outputStream); // UTF-8 stream.
53  * <i>or</i> ow.write(obj, byteBuffer); // UTF-8 NIO ByteBuffer.
54  * </pre></p>
55  *
56  * <p> For more control over the xml document generated (e.g. indentation,
57  * prolog, etc.), applications may use the
58  * {@link #write(Object, ContentHandler)} method in conjonction with
59  * a custom {@link WriterHandler}. For example:<pre>
60  * OutputStream out = new FileOutputStream("C:/document.xml");
61  * Writer writer = new Utf8StreamWriter().setOuptutStream(out); // UTF-8 encoding.
62  * WriterHandler handler = new WriterHandler().setWriter(writer);
63  * handler.setIndent("\t"); // Indents with tabs.
64  * handler.setProlog("&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;");
65  * ...
66  * ow.write(obj, handler);</pre>
67  * </p>
68  *
69  * @author <a HREF="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
70  * @version 3.3, May 13, 2005
71  */

72 public class ObjectWriter/*<T>*/ implements Reusable {
73
74     /**
75      * Holds the configurable nominal length for the CDATA buffer.
76      */

77     private static final PersistentReference CDATA_SIZE = new PersistentReference(
78             "javolution.xml.ObjectWriter#CDATA_SIZE", new Integer(256));
79
80     /**
81      * Holds Javolution prefix ("j").
82      */

83     private static final Text J = Text.valueOf("j").intern();
84
85     /**
86      * Holds Javolution prefix declaration ("xmlns:j").
87      */

88     private static final Text XMLNS_J = Text.valueOf("xmlns:j").intern();
89
90     /**
91      * The "xmlns" prefix is reserved, and is used to declare other prefixes.
92      */

93     private static final Text XMLNS = Text.valueOf("xmlns").intern();
94
95     /**
96      * Holds the namespace that the "xmlns" prefix refers to.
97      */

98     private static final Text XMLNS_URI = Text.valueOf(
99             "http://www.w3.org/2000/xmlns/").intern();
100
101     /**
102      * Holds Javolution uri.
103      */

104     public static final Text JAVOLUTION_URI = Text.valueOf(
105             "http://javolution.org").intern();
106
107     /**
108      * Holds the Java scheme for package identification.
109      */

110     private static final Text JAVA_ = Text.valueOf("java:").intern();
111
112     /**
113      * Holds the class identifier local name.
114      */

115     private static final Text CLASS = Text.valueOf("class").intern();
116
117     /**
118      * Holds the class identifier prefix.
119      */

120     private static final Text J_CLASS = Text.valueOf("j:class").intern();
121
122     /**
123      * Defines bit flag that causes the "j:class" attribute to always be included
124      * every element; ordinarily, this attribute is not output if the
125      * class is aliased or if the format has an intrinsic element name to
126      * class {@link XmlFormat#classFor(CharSequence) mapping}).
127      */

128     public static int OUTPUT_JCLASS = 0x01;
129
130     /**
131      * Defines bit flag that causes "xmlns:<prefix>" attributes to be output
132      * with every element; sometimes this is required for SAX sources that
133      * are used for transformations.
134      */

135     public static int OUTPUT_XMLNS = 0x02;
136
137     /**
138      * Defines bit flag the prevents ID and IDREF attributes from being output,
139      * even if they are defined by an <code>XmlFormat</code> instance.
140      */

141     public static int NO_IDENTIFIER = 0x04;
142
143     /**
144      * Defines bit flag that causes the IDREF attribute to be expanded
145      * unless infinite recursion would occur. This feature is useful if you
146      * would like to process the serialization object using XSLT, and would
147      * like all IDREF objects to be expanded in the SAX stream.
148      * This flag has no effect if OUTPUT_IDENTIFIERS is not set.
149      */

150     public static int EXPAND_REFERENCES = 0x08;
151
152     /**
153      * Holds bit flag that defines default output features (no feature set).
154      */

155     public static int DEFAULT_FEATURES = 0;
156
157     /**
158      * Holds prefix-package pairs (String).
159      */

160     private FastList _namespaces = new FastList();
161
162     /**
163      * Holds the current features.
164      */

165     private int _features = DEFAULT_FEATURES;
166
167     /**
168      * Holds the root name.
169      */

170     private CharSequence _rootName;
171
172     /**
173      * Holds the package associated to default namespace.
174      */

175     private String _defaultPkg = "";
176
177     /**
178      * Holds the class info mapping (Class to ClassInfo).
179      */

180     private final FastMap _classInfo = new FastMap();
181
182     /**
183      * Holds the stack of XML elements.
184      */

185     private final FastTable _stack = new FastTable();
186
187     /**
188      * Holds the stream writer.
189      */

190     private final Utf8StreamWriter _utf8StreamWriter = new Utf8StreamWriter();
191
192     /**
193      * Holds the byte buffer writer.
194      */

195     private final Utf8ByteBufferWriter _utf8ByteBufferWriter = new Utf8ByteBufferWriter();
196
197     /**
198      * Holds the writer handler for stream output.
199      */

200     private final WriterHandler _writerHandler = new WriterHandler();
201
202     /**
203      * Holds the cdata buffer for characters notification.
204      */

205     private char[] _cdata = (char[]) new char[((Integer) CDATA_SIZE.get())
206             .intValue()];
207
208     /**
209      * Holds the object to id mapping (persistent).
210      */

211     private final FastMap _objectToId = new FastMap()
212             .setKeyComparator(FastComparator.IDENTITY);
213
214     /**
215      * The counter to use to generate id automatically.
216      */

217     private int _idCount;
218
219     /**
220      * Default constructor.
221      */

222     public ObjectWriter() {
223         _stack.addLast(new XmlElement());
224     }
225
226     /**
227      * Maps a namespace to a Java package. The specified prefix is used to
228      * shorten the tag name of the object being serialized. For example:
229      * <code>setNamespace("math", "org.jscience.mathematics")</code> associates
230      * the namespace prefix <code>math</code> with the namespace name
231      * <code>java:org.jscience.mathematics</code>. Classes within the package
232      * <code>org.jscience.mathematics.*</code> now use the <code>math</code>
233      * prefix. The default namespace (represented by the prefix <code>""</code>)
234      * can be set as well.
235      *
236      * @param prefix the namespace prefix or empty sequence to set
237      * the default namespace.
238      * @param packageName of the package associated to the specified prefix.
239      * @throws IllegalArgumentException if the prefix is "j" (reserved for
240      * the "http://javolution.org" uri).
241      */

242     public void setNamespace(String prefix, String packageName) {
243         if (prefix.equals("j"))
244             throw new IllegalArgumentException("Prefix: \"j\" is reserved.");
245         if (prefix.length() == 0) { // Default namespace.
246
_defaultPkg = packageName;
247         }
248         _namespaces.addLast(prefix);
249         _namespaces.addLast(packageName);
250     }
251
252     /**
253      * Sets the features bit flags that controls output.
254      *
255      * @param features a combination of bit flags.
256      */

257     public void setFeature(int features) {
258         _features = features;
259     }
260
261     /**
262      * Sets the element name for the root element (default <code>null</code>
263      * the element name is the class name).
264      *
265      * @param rootName the name of the root element.
266      */

267     public void setRootName(CharSequence rootName) {
268         _rootName = rootName;
269     }
270
271     /**
272      * Writes the specified object to the given writer in XML format.
273      * The writer is closed after serialization. To serialize multiple
274      * objects over a persistent connection {@link XmlOutputStream}
275      * should be used instead.
276      *
277      * @param obj the object to format.
278      * @param writer the writer to write to.
279      * @throws IOException if there's any problem writing.
280      */

281     public void write(Object/*T*/obj, Writer writer) throws IOException {
282         try {
283             _writerHandler.setWriter(writer);
284             write(obj, _writerHandler);
285         } catch (SAXException e) {
286             if (e.getException() instanceof IOException) {
287                 throw (IOException) e.getException();
288             }
289         } finally {
290             _writerHandler.reset();
291         }
292     }
293
294     /**
295      * Writes the specified object to the given output stream in XML format.
296      * The characters are written using UTF-8 encoding.
297      * The output streamwriter is closed after serialization. To serialize
298      * multiple objects over a persistent connection {@link XmlOutputStream}
299      * should be used instead.
300      *
301      * @param obj the object to format.
302      * @param out the output stream to write to.
303      * @throws IOException if there's any problem writing.
304      */

305     public void write(Object/*T*/obj, OutputStream out) throws IOException {
306         try {
307             _utf8StreamWriter.setOutputStream(out);
308             _writerHandler.setWriter(_utf8StreamWriter);
309             write(obj, _writerHandler);
310         } catch (SAXException e) {
311             if (e.getException() instanceof IOException) {
312                 throw (IOException) e.getException();
313             }
314         } finally {
315             _utf8StreamWriter.reset();
316             _writerHandler.reset();
317         }
318     }
319
320     /**
321      * Writes the specified object to the given byte buffer in XML format.
322      * The characters are written using UTF-8 encoding.
323      *
324      * @param obj the object to format.
325      * @param byteBuffer the byte buffer to write to.
326      * @throws IOException if there's any problem writing.
327      */

328     public void write(Object/*T*/obj, ByteBuffer byteBuffer)
329             throws IOException {
330         try {
331             _utf8ByteBufferWriter.setByteBuffer(byteBuffer);
332             _writerHandler.setWriter(_utf8ByteBufferWriter);
333             write(obj, _writerHandler);
334         } catch (SAXException e) {
335             if (e.getException() instanceof IOException) {
336                 throw (IOException) e.getException();
337             }
338         } finally {
339             _utf8ByteBufferWriter.reset();
340             _writerHandler.reset();
341         }
342     }
343
344     /**
345      * Generates the SAX events corresponding to the serialization of the
346      * specified object.
347      *
348      * @param obj the object to format.
349      * @param handler the SAX event handler.
350      */

351     public void write(Object/*T*/obj, ContentHandler handler)
352             throws SAXException {
353         handler.startDocument();
354         try {
355             handler.startPrefixMapping(J, JAVOLUTION_URI);
356             for (FastList.Node n = _namespaces.headNode(), end = _namespaces
357                     .tailNode(); (n = n.getNextNode()) != end;) {
358                 Object prefix = n.getValue();
359                 String pkg = (String) (n = n.getNextNode()).getValue();
360                 Text uri = JAVA_.concat(Text.valueOf(pkg));
361                 handler.startPrefixMapping(toCharSeq(prefix), uri);
362             }
363             writeElement(obj, handler, 0, _rootName);
364         } finally {
365             handler.endPrefixMapping(J);
366             for (FastList.Node n = _namespaces.headNode(), end = _namespaces
367                     .tailNode(); (n = n.getNextNode()) != end;) {
368                 Object prefix = n.getValue();
369                 n = n.getNextNode(); // Ignores package.
370
handler.endPrefixMapping(toCharSeq(prefix));
371             }
372             handler.endDocument();
373         }
374     }
375
376     private void writeElement(Object obj, ContentHandler handler, int level,
377             CharSequence tagName) throws SAXException {
378
379         // Replaces null value with Null object.
380
if (obj == null) {
381             writeElement(XmlFormat.NULL, handler, level, null);
382             return;
383         }
384
385         // Checks for Character Data
386
if (obj instanceof CharacterData) {
387             CharacterData charData = (CharacterData) obj;
388             final int length = charData.length();
389             if (length > _cdata.length) { // Resizes.
390
char[] tmp = new char[_cdata.length * 2];
391                 System.arraycopy(_cdata, 0, tmp, 0, _cdata.length);
392                 _cdata = tmp;
393                 CDATA_SIZE.set(new Integer(_cdata.length));
394             }
395             charData.getChars(0, length, _cdata, 0);
396             handler.characters(_cdata, 0, length);
397             return;
398         }
399
400         // Retrieves info for the class (info changes when namespace changes).
401
Class clazz = obj.getClass();
402         ClassInfo ci = (ClassInfo) _classInfo.get(clazz);
403         if (ci == null) { // First occurence of this class ever.
404
ci = (ClassInfo) ClassInfo.FACTORY.newObject();
405             _classInfo.put(clazz, ci);
406         }
407         if (ci.format == null) {
408             ci.className = clazz.getName();
409             ci.alias = XmlFormat.nameFor(clazz);
410             ci.format = XmlFormat.getInstance(clazz);
411
412             // Search for a package for the className (or alias).
413
String prefix = null;
414             String pkg = "";
415             for (FastList.Node n = _namespaces.headNode(), end = _namespaces
416                     .tailNode(); (n = n.getNextNode()) != end;) {
417                 String pkgStr = (String) (n = n.getNextNode()).getValue();
418                 if (ci.alias.startsWith(pkgStr)
419                         && (pkgStr.length() > pkg.length())) {
420                     prefix = (String) n.getPreviousNode().getValue();
421                     pkg = pkgStr;
422                 }
423             }
424
425             // URI is one of:
426
// - "" (default namespace)
427
// - "http://javolution.org"
428
// - "java:xxx.yyy.zzz" (xxx.yyy.zzz package name)
429
if (prefix == null) { // Not found.
430
if (_defaultPkg.length() == 0) { // Use default namespace.
431
prefix = "";
432                 } else {
433                     prefix = "j";
434                     ci.uri.append(JAVOLUTION_URI);
435                 }
436             } else {
437                 ci.uri.append(JAVA_).append(pkg);
438             }
439
440             // Sets local name.
441
if (pkg.length() == 0) {
442                 ci.localName.append(ci.alias);
443             } else { // Remove package prefix from class name.
444
ci.localName.append(ci.alias, pkg.length() + 1, ci.alias
445                         .length());
446             }
447
448             // Sets qualified name.
449
if (prefix.length() == 0) { // Default namespace.
450
ci.qName.append(ci.localName);
451             } else {
452                 ci.qName.append(prefix).append(":").append(ci.localName);
453             }
454         }
455
456         // Formats.
457
if (level >= _stack.size()) {
458             XmlElement tmp = (XmlElement) XmlElement.FACTORY.newObject();
459             tmp._parent = (XmlElement) _stack.get(level - 1);
460             _stack.addLast(tmp);
461         }
462         XmlElement xml = (XmlElement) _stack.get(level);
463         xml._object = obj;
464         xml._objectClass = clazz;
465         xml._format = ci.format;
466         
467         if ((ci.format._idName != null) && ((_features & NO_IDENTIFIER) == 0)) {
468             // Writes identifier attribute.
469
CharSequence idValue = (CharSequence) _objectToId.get(obj);
470             if (idValue != null) { // Already has an identifier.
471
if (((_features & EXPAND_REFERENCES) != 0) && !xml.isRecursion()) {
472                     // Expands object.
473
xml._format.format(obj, xml);
474                 } else { // Write the reference.
475
xml.setAttribute(ci.format._idRef.toString(), idValue);
476                 }
477             } else { // Creates identifier.
478
xml._format.format(obj, xml);
479
480                 // Sets idValue if not set already
481
idValue = xml.getAttribute(ci.format._idName.toString());
482                 if (idValue == null) { // Generates idValue.
483
idValue = newTextBuilder().append(++_idCount);
484                     xml.setAttribute(ci.format._idName.toString(), idValue);
485                 }
486                 _objectToId.put(obj, idValue);
487             }
488         } else { // No object identifier.
489
xml._format.format(obj, xml);
490         }
491
492         if (((_features & OUTPUT_JCLASS) != 0) ||
493             ((tagName != null) && (xml.classFor(tagName) == null))) {
494             // Outputs j:class attribute.
495
xml._attributes.addAttribute(JAVOLUTION_URI, CLASS, J, J_CLASS,
496                     "CDATA", toCharSeq(ci.className));
497         }
498
499         if ((_features & OUTPUT_XMLNS) != 0) {
500             xml._attributes.addAttribute(XMLNS_URI, J, XMLNS, XMLNS_J,
501                   "CDATA", JAVOLUTION_URI);
502             // TODO: Output all current namespace mapping.
503
}
504
505         // Writes start tag.
506
if (tagName != null) { // Custom name
507
handler.startElement(Text.EMPTY, tagName, tagName, xml._attributes);
508         } else {
509             handler.startElement(ci.uri, ci.localName, ci.qName,
510                     xml._attributes);
511         }
512
513         // Writes named elements first.
514
for (FastMap.Entry e = xml._nameToChild.headEntry(), end = xml._nameToChild
515                 .tailEntry(); (e = e.getNextEntry()) != end;) {
516             writeElement(e.getValue(), handler, level + 1,
517                     toCharSeq(e.getKey()));
518         }
519         //Writes anonymous content.
520
for (FastList.Node n = xml._content.headNode(), end = xml._content
521                 .tailNode(); (n = n.getNextNode()) != end;) {
522             writeElement(n.getValue(), handler, level + 1, null);
523         }
524
525         // Writes end tag.
526
if (tagName != null) {
527             handler.endElement(Text.EMPTY, tagName, tagName);
528         } else {
529             handler.endElement(ci.uri, ci.localName, ci.qName);
530         }
531
532         xml.reset();
533    }
534
535     /**
536      * Resets all internal data maintained by this writer including any
537      * namespace associations; objects previously written will not be
538      * referred to, they will be send again.
539      */

540     public void reset() {
541         for (FastMap.Entry e = _classInfo.headEntry(), end = _classInfo
542                 .tailEntry(); (e = e.getNextEntry()) != end;) {
543             ((ClassInfo) e.getValue()).reset(); // Clears class info.
544
}
545         _textBuilderPool.addAll(_objectToId.values());
546         _objectToId.clear();
547         _namespaces.clear();
548         _defaultPkg = "";
549         _idCount = 0;
550         _features = DEFAULT_FEATURES;
551         _rootName = null;
552     }
553
554     /**
555      * Holds custom entries.
556      */

557     private static final class ClassInfo {
558         private static ObjectFactory FACTORY = new ObjectFactory() {
559             protected Object create() {
560                 return new ClassInfo();
561             }
562         };
563
564         String className; // The class name.
565

566         String alias; // The class name, possibly an alias (when obfuscating).
567

568         XmlFormat format;
569
570         TextBuilder uri = new TextBuilder(); // e.g. java:org.jscience.mathematics.numbers
571

572         TextBuilder qName = new TextBuilder(); // e.g. num:Complex
573

574         TextBuilder localName = new TextBuilder(); // e.g. Complex
575

576         void reset() {
577             format = null;
578             uri.reset();
579             qName.reset();
580             localName.reset();
581         }
582     }
583
584     /**
585      * Returns a persistent mutable character sequence from a local pool.
586      *
587      * @return a new or recycled text builder instance.
588      */

589     private TextBuilder newTextBuilder() {
590         if (_textBuilderPool.isEmpty())
591             return (TextBuilder) TextBuilder.newInstance().moveHeap();
592         TextBuilder tb = (TextBuilder) _textBuilderPool.removeLast();
593         tb.reset();
594         return tb;
595     }
596
597     private FastList _textBuilderPool = new FastList();
598
599     /**
600      * Converts a String to CharSequence (for J2ME compatibility)
601      *
602      * @param str the String to convert.
603      * @return the corresponding CharSequence instance.
604      */

605     private static CharSequence toCharSeq(Object str) {
606         return (str instanceof CharSequence) ? (CharSequence) str : Text
607                 .valueOf((String) str);
608     }
609 }
Popular Tags