1 /*
2 * $Header: /home/cvs/jakarta-commons/betwixt/src/java/org/apache/commons/betwixt/io/BeanWriter.java,v 1.13 2003/01/07 22:32:57 rdonkin Exp $
3 * $Revision: 1.13 $
4 * $Date: 2003/01/07 22:32:57 $
5 *
6 * ====================================================================
7 *
8 * The Apache Software License, Version 1.1
9 *
10 * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
11 * reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 *
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in
22 * the documentation and/or other materials provided with the
23 * distribution.
24 *
25 * 3. The end-user documentation included with the redistribution, if
26 * any, must include the following acknowlegement:
27 * "This product includes software developed by the
28 * Apache Software Foundation (http://www.apache.org/)."
29 * Alternately, this acknowlegement may appear in the software itself,
30 * if and wherever such third-party acknowlegements normally appear.
31 *
32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
33 * Foundation" must not be used to endorse or promote products derived
34 * from this software without prior written permission. For written
35 * permission, please contact apache@apache.org.
36 *
37 * 5. Products derived from this software may not be called "Apache"
38 * nor may "Apache" appear in their names without prior written
39 * permission of the Apache Group.
40 *
41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52 * SUCH DAMAGE.
53 * ====================================================================
54 *
55 * This software consists of voluntary contributions made by many
56 * individuals on behalf of the Apache Software Foundation. For more
57 * information on the Apache Software Foundation, please see
58 * <http://www.apache.org/>.
59 *
60 * $Id: BeanWriter.java,v 1.13 2003/01/07 22:32:57 rdonkin Exp $
61 */
62 package org.apache.commons.betwixt.io;
63
64 import java.beans.IntrospectionException;
65 import java.io.BufferedWriter;
66 import java.io.IOException;
67 import java.io.OutputStream;
68 import java.io.OutputStreamWriter;
69 import java.io.Writer;
70
71 import org.apache.commons.logging.Log;
72 import org.apache.commons.logging.LogFactory;
73 import org.xml.sax.SAXException;
74
75 /*** <p><code>BeanWriter</code> outputs beans as XML to an io stream.</p>
76 *
77 * <p>The output for each bean is an xml fragment
78 * (rather than a well-formed xml-document).
79 * This allows bean representations to be appended to a document
80 * by writing each in turn to the stream.
81 * So to create a well formed xml document,
82 * you'll need to write the prolog to the stream first.
83 * If you append more than one bean to the stream,
84 * then you'll need to add a wrapping root element as well.
85 *
86 * <p> The line ending to be used is set by {@link #setEndOfLine}.
87 *
88 * <p> The output can be formatted (with whitespace) for easy reading
89 * by calling {@link #enablePrettyPrint}.
90 * The output will be indented.
91 * The indent string used is set by {@link #setIndent}.
92 *
93 * <p> Bean graphs can sometimes contain cycles.
94 * Care must be taken when serializing cyclic bean graphs
95 * since this can lead to infinite recursion.
96 * The approach taken by <code>BeanWriter</code> is to automatically
97 * assign an <code>ID</code> attribute value to beans.
98 * When a cycle is encountered,
99 * an element is written that has the <code>IDREF</code> attribute set to the
100 * id assigned earlier.
101 *
102 * <p> The names of the <code>ID</code> and <code>IDREF</code> attributes used
103 * can be customized by the <code>XMLBeanInfo</code>.
104 * The id's used can also be customized by the user
105 * via <code>IDGenerator</code> subclasses.
106 * The implementation used can be set by the <code>IdGenerator</code> property.
107 * BeanWriter defaults to using <code>SequentialIDGenerator</code>
108 * which supplies id values in numeric sequence.
109 *
110 * <p>If generated <code>ID</code> attribute values are not acceptable in the output,
111 * then this can be disabled by setting the <code>WriteIDs</code> property to false.
112 * If a cyclic reference is encountered in this case then a
113 * <code>CyclicReferenceException</code> will be thrown.
114 * When the <code>WriteIDs</code> property is set to false,
115 * it is recommended that this exception is caught by the caller.
116 *
117 *
118 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
119 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
120 * @version $Revision: 1.13 $
121 */
122 public class BeanWriter extends AbstractBeanWriter {
123
124 /*** Escaped <code><</code> entity */
125 private static final String LESS_THAN_ENTITY = "<";
126 /*** Escaped <code>></code> entity */
127 private static final String GREATER_THAN_ENTITY = ">";
128 /*** Escaped <code>&</code> entity */
129 private static final String AMPERSAND_ENTITY = "&";
130 /*** Escaped <code>'</code> entity */
131 private static final String APOSTROPHE_ENTITY = "'";
132 /*** Escaped <code>"</code> entity */
133 private static final String QUOTE_ENTITY = """;
134
135 /*** Where the output goes */
136 private Writer writer;
137 /*** text used for end of lines. Defaults to <code>\n</code>*/
138 private static final String EOL = "\n";
139 /*** text used for end of lines. Defaults to <code>\n</code>*/
140 private String endOfLine = EOL;
141 /*** indentation text */
142 private String indent;
143
144 /*** should we flush after writing bean */
145 private boolean autoFlush;
146 /*** Log used for logging (Doh!) */
147 private Log log = LogFactory.getLog( BeanWriter.class );
148
149 /***
150 * <p> Constructor uses <code>System.out</code> for output.</p>
151 */
152 public BeanWriter() {
153 this( System.out );
154 }
155
156 /***
157 * <p> Constuctor uses given <code>OutputStream</code> for output.</p>
158 *
159 * @param out write out representations to this stream
160 */
161 public BeanWriter(OutputStream out) {
162 this.writer = new BufferedWriter( new OutputStreamWriter( out ) );
163 this.autoFlush = true;
164 }
165
166 /***
167 * <p> Constructor sets writer used for output.</p>
168 *
169 * @param writer write out representations to this writer
170 */
171 public BeanWriter(Writer writer) {
172 this.writer = writer;
173 }
174
175 /***
176 * A helper method that allows you to write the XML Declaration.
177 * This should only be called once before you output any beans.
178 *
179 * @param xmlDeclaration is the XML declaration string typically of
180 * the form "<xml version='1.0' encoding='UTF-8' ?>
181 *
182 * @throws IOException when declaration cannot be written
183 */
184 public void writeXmlDeclaration(String xmlDeclaration) throws IOException {
185 writer.write( xmlDeclaration );
186 writePrintln();
187 }
188
189 /***
190 * Allows output to be flushed on the underlying output stream
191 *
192 * @throws IOException when the flush cannot be completed
193 */
194 public void flush() throws IOException {
195 writer.flush();
196 }
197
198 /***
199 * Closes the underlying output stream
200 *
201 * @throws IOException when writer cannot be closed
202 */
203 public void close() throws IOException {
204 writer.close();
205 }
206
207 /***
208 * Write the given object to the stream (and then flush).
209 *
210 * @param bean write this <code>Object</code> to the stream
211 * @throws IOException if an IO problem causes failure
212 * @throws SAXException if a SAX problem causes failure
213 * @throws IntrospectionException if bean cannot be introspected
214 */
215 public void write(Object bean) throws IOException, SAXException, IntrospectionException {
216
217 super.write(bean);
218
219 if ( autoFlush ) {
220 writer.flush();
221 }
222 }
223
224
225 /***
226 * <p> Switch on formatted output.
227 * This sets the end of line and the indent.
228 * The default is adding 2 spaces and a newline
229 */
230 public void enablePrettyPrint() {
231 endOfLine = EOL;
232 indent = " ";
233 }
234
235 /***
236 * Gets the string used to mark end of lines.
237 *
238 * @return the string used for end of lines
239 */
240 public String getEndOfLine() {
241 return endOfLine;
242 }
243
244 /***
245 * Sets the string used for end of lines
246 * Produces a warning the specified value contains an invalid whitespace character
247 *
248 * @param endOfLine the <code>String</code to use
249 */
250 public void setEndOfLine(String endOfLine) {
251 this.endOfLine = endOfLine;
252 for (int i = 0; i < endOfLine.length(); i++) {
253 if (!Character.isWhitespace(endOfLine.charAt(i))) {
254 log.warn("Invalid EndOfLine character(s)");
255 break;
256 }
257 }
258
259 }
260
261 /***
262 * Gets the indent string
263 *
264 * @return the string used for indentation
265 */
266 public String getIndent() {
267 return indent;
268 }
269
270 /***
271 * Sets the string used for pretty print indents
272 * @param indent use this <code>string</code> for indents
273 */
274 public void setIndent(String indent) {
275 this.indent = indent;
276 }
277
278 /***
279 * <p> Set the log implementation used. </p>
280 *
281 * @return a <code>org.apache.commons.logging.Log</code> level constant
282 */
283 public Log getLog() {
284 return log;
285 }
286
287 /***
288 * <p> Set the log implementation used. </p>
289 *
290 * @param log <code>Log</code> implementation to use
291 */
292 public void setLog( Log log ) {
293 this.log = log;
294 }
295
296
297 // Expression methods
298 //-------------------------------------------------------------------------
299
300 /***
301 * Express an element tag start using given qualified name
302 *
303 * @param qualifiedName the fully qualified name of the element to write
304 * @throws IOException when stream write fails
305 */
306 protected void expressElementStart(String qualifiedName) throws IOException {
307 if ( qualifiedName == null ) {
308 // XXX this indicates a programming error
309 log.fatal( "[expressElementStart]Qualified name is null." );
310 throw new RuntimeException( "Qualified name is null." );
311 }
312
313 writePrintln();
314 writeIndent();
315 writer.write( '<' );
316 writer.write( qualifiedName );
317 }
318
319 /***
320 * Write a tag close to the stream
321 *
322 * @throws IOException when stream write fails
323 */
324 protected void expressTagClose() throws IOException {
325 writer.write( '>' );
326 }
327
328 /***
329 * Write an element end tag to the stream
330 *
331 * @param qualifiedName the name of the element
332 * @throws IOException when stream write fails
333 */
334 protected void expressElementEnd(String qualifiedName) throws IOException {
335 if (qualifiedName == null) {
336 // XXX this indicates a programming error
337 log.fatal( "[expressElementEnd]Qualified name is null." );
338 throw new RuntimeException( "Qualified name is null." );
339 }
340
341 writer.write( "</" );
342 writer.write( qualifiedName );
343 writer.write( '>' );
344 }
345
346 /***
347 * Write an empty element end to the stream
348 *
349 * @throws IOException when stream write fails
350 */
351 protected void expressElementEnd() throws IOException {
352 writer.write( "/>" );
353 }
354
355 /***
356 * Write element body text
357 *
358 * @param text write out this body text
359 * @throws IOException when the stream write fails
360 */
361 protected void expressBodyText(String text) throws IOException {
362 if ( text == null ) {
363 // XXX This is probably a programming error
364 log.error( "[expressBodyText]Body text is null" );
365
366 } else {
367 writer.write( escapeBodyValue(text) );
368 }
369 }
370
371 /***
372 * Writes an attribute to the stream.
373 *
374 * @param qualifiedName fully qualified attribute name
375 * @param value attribute value
376 * @throws IOException when the stream write fails
377 */
378 protected void expressAttribute(
379 String qualifiedName,
380 String value)
381 throws
382 IOException{
383 if ( value == null ) {
384 // XXX probably a programming error
385 log.error( "Null attribute value." );
386 return;
387 }
388
389 if ( qualifiedName == null ) {
390 // XXX probably a programming error
391 log.error( "Null attribute value." );
392 return;
393 }
394
395 writer.write( ' ' );
396 writer.write( qualifiedName );
397 writer.write( "=\"" );
398 writer.write( escapeAttributeValue(value) );
399 writer.write( '\"' );
400 }
401
402
403 // Implementation methods
404 //-------------------------------------------------------------------------
405
406 /*** Writes out an empty line.
407 * Uses current <code>endOfLine</code>.
408 *
409 * @throws IOException when stream write fails
410 */
411 protected void writePrintln() throws IOException {
412 if ( endOfLine != null ) {
413 writer.write( endOfLine );
414 }
415 }
416
417 /***
418 * Writes out <code>indent</code>'s to the current <code>indentLevel</code>
419 *
420 * @throws IOException when stream write fails
421 */
422 protected void writeIndent() throws IOException {
423 if ( indent != null ) {
424 for ( int i = 0; i < getIndentLevel(); i++ ) {
425 writer.write( getIndent() );
426 }
427 }
428 }
429
430 /***
431 * <p>Escape the <code>toString</code> of the given object.
432 * For use as body text.</p>
433 *
434 * @param value escape <code>value.toString()</code>
435 * @return text with escaped delimiters
436 */
437 protected String escapeBodyValue(Object value) {
438 StringBuffer buffer = new StringBuffer(value.toString());
439 for (int i=0, size = buffer.length(); i <size; i++) {
440 switch (buffer.charAt(i)) {
441 case '<':
442 buffer.replace(i, i+1, LESS_THAN_ENTITY);
443 size += 3;
444 i+=3;
445 break;
446 case '>':
447 buffer.replace(i, i+1, GREATER_THAN_ENTITY);
448 size += 3;
449 i += 3;
450 break;
451 case '&':
452 buffer.replace(i, i+1, AMPERSAND_ENTITY);
453 size += 4;
454 i += 4;
455 break;
456 }
457 }
458 return buffer.toString();
459 }
460
461 /***
462 * <p>Escape the <code>toString</code> of the given object.
463 * For use in an attribute value.</p>
464 *
465 * @param value escape <code>value.toString()</code>
466 * @return text with characters restricted (for use in attributes) escaped
467 */
468 protected String escapeAttributeValue(Object value) {
469 StringBuffer buffer = new StringBuffer(value.toString());
470 for (int i=0, size = buffer.length(); i <size; i++) {
471 switch (buffer.charAt(i)) {
472 case '<':
473 buffer.replace(i, i+1, LESS_THAN_ENTITY);
474 size += 3;
475 i+=3;
476 break;
477 case '>':
478 buffer.replace(i, i+1, GREATER_THAN_ENTITY);
479 size += 3;
480 i += 3;
481 break;
482 case '&':
483 buffer.replace(i, i+1, AMPERSAND_ENTITY);
484 size += 4;
485 i += 4;
486 break;
487 case '\'':
488 buffer.replace(i, i+1, APOSTROPHE_ENTITY);
489 size += 4;
490 i += 4;
491 break;
492 case '\"':
493 buffer.replace(i, i+1, QUOTE_ENTITY);
494 size += 5;
495 i += 5;
496 break;
497 }
498 }
499 return buffer.toString();
500 }
501
502 }
This page was automatically generated by Maven