KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > betwixt > io > BeanWriter


1 /*
2  * Copyright 2001-2004 The Apache Software Foundation.
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 org.apache.commons.betwixt.io;
17
18 import java.beans.IntrospectionException JavaDoc;
19 import java.io.BufferedWriter JavaDoc;
20 import java.io.IOException JavaDoc;
21 import java.io.OutputStream JavaDoc;
22 import java.io.OutputStreamWriter JavaDoc;
23 import java.io.UnsupportedEncodingException JavaDoc;
24 import java.io.Writer JavaDoc;
25
26 import org.apache.commons.betwixt.XMLUtils;
27 import org.apache.commons.betwixt.strategy.MixedContentEncodingStrategy;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.xml.sax.Attributes JavaDoc;
31 import org.xml.sax.SAXException JavaDoc;
32
33 /** <p><code>BeanWriter</code> outputs beans as XML to an io stream.</p>
34   *
35   * <p>The output for each bean is an xml fragment
36   * (rather than a well-formed xml-document).
37   * This allows bean representations to be appended to a document
38   * by writing each in turn to the stream.
39   * So to create a well formed xml document,
40   * you'll need to write the prolog to the stream first.
41   * If you append more than one bean to the stream,
42   * then you'll need to add a wrapping root element as well.
43   *
44   * <p> The line ending to be used is set by {@link #setEndOfLine}.
45   *
46   * <p> The output can be formatted (with whitespace) for easy reading
47   * by calling {@link #enablePrettyPrint}.
48   * The output will be indented.
49   * The indent string used is set by {@link #setIndent}.
50   *
51   * <p> Bean graphs can sometimes contain cycles.
52   * Care must be taken when serializing cyclic bean graphs
53   * since this can lead to infinite recursion.
54   * The approach taken by <code>BeanWriter</code> is to automatically
55   * assign an <code>ID</code> attribute value to beans.
56   * When a cycle is encountered,
57   * an element is written that has the <code>IDREF</code> attribute set to the
58   * id assigned earlier.
59   *
60   * <p> The names of the <code>ID</code> and <code>IDREF</code> attributes used
61   * can be customized by the <code>XMLBeanInfo</code>.
62   * The id's used can also be customized by the user
63   * via <code>IDGenerator</code> subclasses.
64   * The implementation used can be set by the <code>IdGenerator</code> property.
65   * BeanWriter defaults to using <code>SequentialIDGenerator</code>
66   * which supplies id values in numeric sequence.
67   *
68   * <p>If generated <code>ID</code> attribute values are not acceptable in the output,
69   * then this can be disabled by setting the <code>WriteIDs</code> property to false.
70   * If a cyclic reference is encountered in this case then a
71   * <code>CyclicReferenceException</code> will be thrown.
72   * When the <code>WriteIDs</code> property is set to false,
73   * it is recommended that this exception is caught by the caller.
74   *
75   *
76   * @author <a HREF="mailto:jstrachan@apache.org">James Strachan</a>
77   * @author <a HREF="mailto:martin@mvdb.net">Martin van den Bemt</a>
78   */

79 public class BeanWriter extends AbstractBeanWriter {
80
81     /** Where the output goes */
82     private Writer JavaDoc writer;
83     /** text used for end of lines. Defaults to <code>\n</code>*/
84     private static final String JavaDoc EOL = "\n";
85     /** text used for end of lines. Defaults to <code>\n</code>*/
86     private String JavaDoc endOfLine = EOL;
87     /** indentation text */
88     private String JavaDoc indent;
89
90     /** should we flush after writing bean */
91     private boolean autoFlush;
92     /** Log used for logging (Doh!) */
93     private Log log = LogFactory.getLog( BeanWriter.class );
94     /** Has any content (excluding attributes) been written to the current element */
95     private boolean currentElementIsEmpty = false;
96     /** Has the current element written any body text */
97     private boolean currentElementHasBodyText = false;
98     /** Has the last start tag been closed */
99     private boolean closedStartTag = true;
100     /** Should an end tag be added for empty elements? */
101     private boolean addEndTagForEmptyElement = false;
102     /** Current level of indentation (starts at 1 with the first element) */
103     private int indentLevel;
104     /** USed to determine how body content should be encoded before being output*/
105     private MixedContentEncodingStrategy mixedContentEncodingStrategy
106         = MixedContentEncodingStrategy.DEFAULT;
107     
108     /**
109      * <p> Constructor uses <code>System.out</code> for output.</p>
110      */

111     public BeanWriter() {
112         this( System.out );
113     }
114     
115     /**
116      * <p> Constuctor uses given <code>OutputStream</code> for output.</p>
117      *
118      * @param out write out representations to this stream
119      */

120     public BeanWriter(OutputStream JavaDoc out) {
121         this.writer = new BufferedWriter JavaDoc( new OutputStreamWriter JavaDoc( out ) );
122         this.autoFlush = true;
123     }
124
125     /**
126      * <p>Constuctor uses given <code>OutputStream</code> for output
127      * and allows encoding to be set.</p>
128      *
129      * @param out write out representations to this stream
130      * @param enc the name of the encoding to be used. This should be compatible
131      * with the encoding types described in <code>java.io</code>
132      * @throws UnsupportedEncodingException if the given encoding is not supported
133      */

134     public BeanWriter(OutputStream JavaDoc out, String JavaDoc enc) throws UnsupportedEncodingException JavaDoc {
135         this.writer = new BufferedWriter JavaDoc( new OutputStreamWriter JavaDoc( out, enc ) );
136         this.autoFlush = true;
137     }
138
139     /**
140      * <p> Constructor sets writer used for output.</p>
141      *
142      * @param writer write out representations to this writer
143      */

144     public BeanWriter(Writer JavaDoc writer) {
145         this.writer = writer;
146     }
147
148     /**
149      * A helper method that allows you to write the XML Declaration.
150      * This should only be called once before you output any beans.
151      *
152      * @param xmlDeclaration is the XML declaration string typically of
153      * the form "&lt;xml version='1.0' encoding='UTF-8' ?&gt;
154      *
155      * @throws IOException when declaration cannot be written
156      */

157     public void writeXmlDeclaration(String JavaDoc xmlDeclaration) throws IOException JavaDoc {
158         writer.write( xmlDeclaration );
159         printLine();
160     }
161     
162     /**
163      * Allows output to be flushed on the underlying output stream
164      *
165      * @throws IOException when the flush cannot be completed
166      */

167     public void flush() throws IOException JavaDoc {
168         writer.flush();
169     }
170     
171     /**
172      * Closes the underlying output stream
173      *
174      * @throws IOException when writer cannot be closed
175      */

176     public void close() throws IOException JavaDoc {
177         writer.close();
178     }
179     
180     /**
181      * Write the given object to the stream (and then flush).
182      *
183      * @param bean write this <code>Object</code> to the stream
184      * @throws IOException if an IO problem causes failure
185      * @throws SAXException if a SAX problem causes failure
186      * @throws IntrospectionException if bean cannot be introspected
187      */

188     public void write(Object JavaDoc bean) throws IOException JavaDoc, SAXException JavaDoc, IntrospectionException JavaDoc {
189
190         super.write(bean);
191
192         if ( autoFlush ) {
193             writer.flush();
194         }
195     }
196     
197  
198     /**
199      * <p> Switch on formatted output.
200      * This sets the end of line and the indent.
201      * The default is adding 2 spaces and a newline
202      */

203     public void enablePrettyPrint() {
204         endOfLine = EOL;
205         indent = " ";
206     }
207
208     /**
209      * Gets the string used to mark end of lines.
210      *
211      * @return the string used for end of lines
212      */

213     public String JavaDoc getEndOfLine() {
214         return endOfLine;
215     }
216     
217     /**
218      * Sets the string used for end of lines
219      * Produces a warning the specified value contains an invalid whitespace character
220      *
221      * @param endOfLine the <code>String</code to use
222      */

223     public void setEndOfLine(String JavaDoc endOfLine) {
224         this.endOfLine = endOfLine;
225         for (int i = 0; i < endOfLine.length(); i++) {
226             if (!Character.isWhitespace(endOfLine.charAt(i))) {
227                 log.warn("Invalid EndOfLine character(s)");
228                 break;
229             }
230         }
231         
232     }
233
234     /**
235      * Gets the indent string
236      *
237      * @return the string used for indentation
238      */

239     public String JavaDoc getIndent() {
240         return indent;
241     }
242     
243     /**
244      * Sets the string used for pretty print indents
245      * @param indent use this <code>string</code> for indents
246      */

247     public void setIndent(String JavaDoc indent) {
248         this.indent = indent;
249     }
250
251     /**
252      * <p> Set the log implementation used. </p>
253      *
254      * @return a <code>org.apache.commons.logging.Log</code> level constant
255      */

256     public Log getLog() {
257         return log;
258     }
259
260     /**
261      * <p> Set the log implementation used. </p>
262      *
263      * @param log <code>Log</code> implementation to use
264      */

265     public void setLog( Log log ) {
266         this.log = log;
267     }
268     
269     /**
270      * Gets the encoding strategy for mixed content.
271      * This is used to process body content
272      * before it is written to the textual output.
273      * @return the <code>MixedContentEncodingStrategy</code>, not null
274      * @since 0.5
275      */

276     public MixedContentEncodingStrategy getMixedContentEncodingStrategy() {
277         return mixedContentEncodingStrategy;
278     }
279
280     /**
281      * Sets the encoding strategy for mixed content.
282      * This is used to process body content
283      * before it is written to the textual output.
284      * @param strategy the <code>MixedContentEncodingStrategy</code>
285      * used to process body content, not null
286      * @since 0.5
287      */

288     public void setMixedContentEncodingStrategy(MixedContentEncodingStrategy strategy) {
289         mixedContentEncodingStrategy = strategy;
290     }
291     
292     /**
293      * Should an end tag be added for each empty element?
294      * When this property is false then empty elements will
295      * be written as <code>&lt;<em>element-name</em>/gt;</code>.
296      * When this property is true then empty elements will
297      * be written as <code>&lt;<em>element-name</em>gt;
298      * &lt;/<em>element-name</em>gt;</code>.
299      * @return true if an end tag should be added
300      */

301     public boolean isEndTagForEmptyElement() {
302         return addEndTagForEmptyElement;
303     }
304     
305     /**
306      * Sets when an an end tag be added for each empty element?
307      * When this property is false then empty elements will
308      * be written as <code>&lt;<em>element-name</em>/gt;</code>.
309      * When this property is true then empty elements will
310      * be written as <code>&lt;<em>element-name</em>gt;
311      * &lt;/<em>element-name</em>gt;</code>.
312      * @param addEndTagForEmptyElement true if an end tag should be
313      * written for each empty element, false otherwise
314      */

315     public void setEndTagForEmptyElement(boolean addEndTagForEmptyElement) {
316         this.addEndTagForEmptyElement = addEndTagForEmptyElement;
317     }
318     
319     
320     
321     // New API
322
//------------------------------------------------------------------------------
323

324     
325     /**
326      * Writes the start tag for an element.
327      *
328      * @param uri the element's namespace uri
329      * @param localName the element's local name
330      * @param qualifiedName the element's qualified name
331      * @param attr the element's attributes
332      * @throws IOException if an IO problem occurs during writing
333      * @throws SAXException if an SAX problem occurs during writing
334      * @since 0.5
335      */

336     protected void startElement(
337                                 WriteContext context,
338                                 String JavaDoc uri,
339                                 String JavaDoc localName,
340                                 String JavaDoc qualifiedName,
341                                 Attributes JavaDoc attr)
342                                     throws
343                                         IOException JavaDoc,
344                                         SAXException JavaDoc {
345         if ( !closedStartTag ) {
346             writer.write( '>' );
347             printLine();
348         }
349         
350         indentLevel++;
351         
352         indent();
353         writer.write( '<' );
354         writer.write( qualifiedName );
355         
356         for ( int i=0; i< attr.getLength(); i++ ) {
357             writer.write( ' ' );
358             writer.write( attr.getQName(i) );
359             writer.write( "=\"" );
360             writer.write( XMLUtils.escapeAttributeValue( attr.getValue(i) ) );
361             writer.write( '\"' );
362         }
363         closedStartTag = false;
364         currentElementIsEmpty = true;
365         currentElementHasBodyText = false;
366     }
367     
368     /**
369      * Writes the end tag for an element
370      *
371      * @param uri the element's namespace uri
372      * @param localName the element's local name
373      * @param qualifiedName the element's qualified name
374      *
375      * @throws IOException if an IO problem occurs during writing
376      * @throws SAXException if an SAX problem occurs during writing
377      * @since 0.5
378      */

379     protected void endElement(
380                                 WriteContext context,
381                                 String JavaDoc uri,
382                                 String JavaDoc localName,
383                                 String JavaDoc qualifiedName)
384                                     throws
385                                         IOException JavaDoc,
386                                         SAXException JavaDoc {
387         if (
388             !addEndTagForEmptyElement
389             && !closedStartTag
390             && currentElementIsEmpty ) {
391         
392             writer.write( "/>" );
393             closedStartTag = true;
394             
395         } else {
396             if (!currentElementHasBodyText) {
397                 indent();
398             }
399             if (
400                     addEndTagForEmptyElement
401                     && !closedStartTag ) {
402                  writer.write( ">" );
403                  closedStartTag = true;
404             }
405             writer.write( "</" );
406             writer.write( qualifiedName );
407             writer.write( '>' );
408             
409         }
410         
411         indentLevel--;
412         printLine();
413         
414         currentElementHasBodyText = false;
415     }
416
417     /**
418      * Write element body text
419      *
420      * @param text write out this body text
421      * @throws IOException when the stream write fails
422      * @since 0.5
423      */

424     protected void bodyText(WriteContext context, String JavaDoc text) throws IOException JavaDoc {
425         if ( text == null ) {
426             // XXX This is probably a programming error
427
log.error( "[expressBodyText]Body text is null" );
428             
429         } else {
430             if ( !closedStartTag ) {
431                 writer.write( '>' );
432                 closedStartTag = true;
433             }
434             writer.write(
435                 mixedContentEncodingStrategy.encode(
436                     text,
437                     context.getCurrentDescriptor()) );
438             currentElementIsEmpty = false;
439             currentElementHasBodyText = true;
440         }
441     }
442     
443     /** Writes out an empty line.
444      * Uses current <code>endOfLine</code>.
445      *
446      * @throws IOException when stream write fails
447      */

448     private void printLine() throws IOException JavaDoc {
449         if ( endOfLine != null ) {
450             writer.write( endOfLine );
451         }
452     }
453     
454     /**
455      * Writes out <code>indent</code>'s to the current <code>indentLevel</code>
456      *
457      * @throws IOException when stream write fails
458      */

459     private void indent() throws IOException JavaDoc {
460         if ( indent != null ) {
461             for ( int i = 0; i < indentLevel; i++ ) {
462                 writer.write( getIndent() );
463             }
464         }
465     }
466
467     // OLD API (DEPRECATED)
468
//----------------------------------------------------------------------------
469

470             
471     /** Writes out an empty line.
472      * Uses current <code>endOfLine</code>.
473      *
474      * @throws IOException when stream write fails
475      * @deprecated 0.5 replaced by new SAX inspired API
476      */

477     protected void writePrintln() throws IOException JavaDoc {
478         if ( endOfLine != null ) {
479             writer.write( endOfLine );
480         }
481     }
482     
483     /**
484      * Writes out <code>indent</code>'s to the current <code>indentLevel</code>
485      *
486      * @throws IOException when stream write fails
487      * @deprecated 0.5 replaced by new SAX inspired API
488      */

489     protected void writeIndent() throws IOException JavaDoc {
490         if ( indent != null ) {
491             for ( int i = 0; i < indentLevel; i++ ) {
492                 writer.write( getIndent() );
493             }
494         }
495     }
496     
497     /**
498      * <p>Escape the <code>toString</code> of the given object.
499      * For use as body text.</p>
500      *
501      * @param value escape <code>value.toString()</code>
502      * @return text with escaped delimiters
503      * @deprecated 0.5 moved into utility class {@link XMLUtils#escapeBodyValue}
504      */

505     protected String JavaDoc escapeBodyValue(Object JavaDoc value) {
506         return XMLUtils.escapeBodyValue(value);
507     }
508
509     /**
510      * <p>Escape the <code>toString</code> of the given object.
511      * For use in an attribute value.</p>
512      *
513      * @param value escape <code>value.toString()</code>
514      * @return text with characters restricted (for use in attributes) escaped
515      *
516      * @deprecated 0.5 moved into utility class {@link XMLUtils#escapeAttributeValue}
517      */

518     protected String JavaDoc escapeAttributeValue(Object JavaDoc value) {
519         return XMLUtils.escapeAttributeValue(value);
520     }
521
522     /**
523      * Express an element tag start using given qualified name
524      *
525      * @param qualifiedName the fully qualified name of the element to write
526      * @throws IOException when stream write fails
527      * @deprecated 0.5 replaced by new SAX inspired API
528      */

529     protected void expressElementStart(String JavaDoc qualifiedName) throws IOException JavaDoc {
530         if ( qualifiedName == null ) {
531             // XXX this indicates a programming error
532
log.fatal( "[expressElementStart]Qualified name is null." );
533             throw new RuntimeException JavaDoc( "Qualified name is null." );
534         }
535         
536         writePrintln();
537         writeIndent();
538         writer.write( '<' );
539         writer.write( qualifiedName );
540     }
541     
542     /**
543      * Write a tag close to the stream
544      *
545      * @throws IOException when stream write fails
546      * @deprecated 0.5 replaced by new SAX inspired API
547      */

548     protected void expressTagClose() throws IOException JavaDoc {
549         writer.write( '>' );
550     }
551     
552     /**
553      * Write an element end tag to the stream
554      *
555      * @param qualifiedName the name of the element
556      * @throws IOException when stream write fails
557      * @deprecated 0.5 replaced by new SAX inspired API
558      */

559     protected void expressElementEnd(String JavaDoc qualifiedName) throws IOException JavaDoc {
560         if (qualifiedName == null) {
561             // XXX this indicates a programming error
562
log.fatal( "[expressElementEnd]Qualified name is null." );
563             throw new RuntimeException JavaDoc( "Qualified name is null." );
564         }
565         
566         writer.write( "</" );
567         writer.write( qualifiedName );
568         writer.write( '>' );
569     }
570     
571     /**
572      * Write an empty element end to the stream
573      *
574      * @throws IOException when stream write fails
575      * @deprecated 0.5 replaced by new SAX inspired API
576      */

577     protected void expressElementEnd() throws IOException JavaDoc {
578         writer.write( "/>" );
579     }
580
581     /**
582      * Write element body text
583      *
584      * @param text write out this body text
585      * @throws IOException when the stream write fails
586      * @deprecated 0.5 replaced by new SAX inspired API
587      */

588     protected void expressBodyText(String JavaDoc text) throws IOException JavaDoc {
589         if ( text == null ) {
590             // XXX This is probably a programming error
591
log.error( "[expressBodyText]Body text is null" );
592             
593         } else {
594             writer.write( XMLUtils.escapeBodyValue(text) );
595         }
596     }
597     
598     /**
599      * Writes an attribute to the stream.
600      *
601      * @param qualifiedName fully qualified attribute name
602      * @param value attribute value
603      * @throws IOException when the stream write fails
604      * @deprecated 0.5 replaced by new SAX inspired API
605      */

606     protected void expressAttribute(
607                                 String JavaDoc qualifiedName,
608                                 String JavaDoc value)
609                                     throws
610                                         IOException JavaDoc{
611         if ( value == null ) {
612             // XXX probably a programming error
613
log.error( "Null attribute value." );
614             return;
615         }
616         
617         if ( qualifiedName == null ) {
618             // XXX probably a programming error
619
log.error( "Null attribute value." );
620             return;
621         }
622                 
623         writer.write( ' ' );
624         writer.write( qualifiedName );
625         writer.write( "=\"" );
626         writer.write( XMLUtils.escapeAttributeValue(value) );
627         writer.write( '\"' );
628     }
629
630
631 }
632
Popular Tags