View Javadoc
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