001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.betwixt.io; 018 019 import java.beans.IntrospectionException; 020 import java.io.IOException; 021 import java.util.ArrayList; 022 import java.util.Collection; 023 import java.util.Iterator; 024 025 import org.apache.commons.betwixt.AttributeDescriptor; 026 import org.apache.commons.betwixt.BindingConfiguration; 027 import org.apache.commons.betwixt.Descriptor; 028 import org.apache.commons.betwixt.ElementDescriptor; 029 import org.apache.commons.betwixt.Options; 030 import org.apache.commons.betwixt.XMLBeanInfo; 031 import org.apache.commons.betwixt.XMLIntrospector; 032 import org.apache.commons.betwixt.expression.Context; 033 import org.apache.commons.betwixt.expression.Expression; 034 import org.apache.commons.betwixt.io.id.SequentialIDGenerator; 035 import org.apache.commons.collections.ArrayStack; 036 import org.apache.commons.logging.Log; 037 import org.apache.commons.logging.LogFactory; 038 import org.xml.sax.Attributes; 039 import org.xml.sax.InputSource; 040 import org.xml.sax.SAXException; 041 import org.xml.sax.helpers.AttributesImpl; 042 043 /** 044 * <p>Abstract superclass for bean writers. 045 * This class encapsulates the processing logic. 046 * Subclasses provide implementations for the actual expression of the xml.</p> 047 * <h5>SAX Inspired Writing API</h5> 048 * <p> 049 * This class is intended to be used by subclassing: 050 * concrete subclasses perform the actual writing by providing 051 * suitable implementations for the following methods inspired 052 * by <a href='http://www.saxproject.org'>SAX</a>: 053 * </p> 054 * <ul> 055 * <li> {@link #start} - called when processing begins</li> 056 * <li> {@link #startElement(WriteContext, String, String, String, Attributes)} 057 * - called when the start of an element 058 * should be written</li> 059 * <li> {@link #bodyText(WriteContext, String)} 060 * - called when the start of an element 061 * should be written</li> 062 * <li> {@link #endElement(WriteContext, String, String, String)} 063 * - called when the end of an element 064 * should be written</li> 065 * <li> {@link #end} - called when processing has been completed</li> 066 * </ul> 067 * <p> 068 * <strong>Note</strong> that this class contains many deprecated 069 * versions of the writing API. These will be removed soon so care 070 * should be taken to use the latest version. 071 * </p> 072 * <p> 073 * <strong>Note</strong> that this class is designed to be used 074 * in a single threaded environment. When used in multi-threaded 075 * environments, use of a common <code>XMLIntrospector</code> 076 * and pooled writer instances should be considered. 077 * </p> 078 * 079 * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a> 080 */ 081 public abstract class AbstractBeanWriter { 082 083 /** Introspector used */ 084 private XMLIntrospector introspector = new XMLIntrospector(); 085 086 /** Log used for logging (Doh!) */ 087 private Log log = LogFactory.getLog( AbstractBeanWriter.class ); 088 /** Stack containing beans - used to detect cycles */ 089 private ArrayStack beanStack = new ArrayStack(); 090 /** Used to generate ID attribute values*/ 091 private IDGenerator idGenerator = new SequentialIDGenerator(); 092 /** Should empty elements be written out? */ 093 private boolean writeEmptyElements = true; 094 /** Dynamic binding configuration settings */ 095 private BindingConfiguration bindingConfiguration = new BindingConfiguration(); 096 /** <code>WriteContext</code> implementation reused curing writing */ 097 private WriteContextImpl writeContext = new WriteContextImpl(); 098 /** Collection of namespaces which have already been declared */ 099 private Collection namespacesDeclared = new ArrayList(); 100 101 /** 102 * Marks the start of the bean writing. 103 * By default doesn't do anything, but can be used 104 * to do extra start processing 105 * @throws IOException if an IO problem occurs during writing 106 * @throws SAXException if an SAX problem occurs during writing 107 */ 108 public void start() throws IOException, SAXException { 109 } 110 111 /** 112 * Marks the start of the bean writing. 113 * By default doesn't do anything, but can be used 114 * to do extra end processing 115 * @throws IOException if an IO problem occurs during writing 116 * @throws SAXException if an SAX problem occurs during writing 117 */ 118 119 public void end() throws IOException, SAXException { 120 } 121 122 /** 123 * <p> Writes the given bean to the current stream using the XML introspector.</p> 124 * 125 * <p> This writes an xml fragment representing the bean to the current stream.</p> 126 * 127 * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle 128 * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code> 129 * setting of the </code>BindingConfiguration</code> is false.</p> 130 * 131 * @throws IOException if an IO problem occurs during writing 132 * @throws SAXException if an SAX problem occurs during writing 133 * @throws IntrospectionException if a java beans introspection problem occurs 134 * 135 * @param bean write out representation of this bean 136 */ 137 public void write(Object bean) throws 138 IOException, 139 SAXException, 140 IntrospectionException { 141 if (log.isDebugEnabled()) { 142 log.debug( "Writing bean graph..." ); 143 log.debug( bean ); 144 } 145 start(); 146 writeBean( null, null, null, bean, makeContext( bean ) ); 147 end(); 148 if (log.isDebugEnabled()) { 149 log.debug( "Finished writing bean graph." ); 150 } 151 } 152 153 /** 154 * <p>Writes the given bean to the current stream 155 * using the given <code>qualifiedName</code>.</p> 156 * 157 * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle 158 * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code> 159 * setting of the <code>BindingConfiguration</code> is false.</p> 160 * 161 * @param qualifiedName the string naming root element 162 * @param bean the <code>Object</code> to write out as xml 163 * 164 * @throws IOException if an IO problem occurs during writing 165 * @throws SAXException if an SAX problem occurs during writing 166 * @throws IntrospectionException if a java beans introspection problem occurs 167 */ 168 public void write( 169 String qualifiedName, 170 Object bean) 171 throws 172 IOException, 173 SAXException, 174 IntrospectionException { 175 start(); 176 writeBean( "", qualifiedName, qualifiedName, bean, makeContext( bean ) ); 177 end(); 178 } 179 180 /** 181 * <p>Writes the bean using the mapping specified in the <code>InputSource</code>. 182 * </p><p> 183 * <strong>Note:</strong> that the custom mapping will <em>not</em> 184 * be registered for later use. Please use {@link XMLIntrospector#register} 185 * to register the custom mapping for the class and then call 186 * {@link #write(Object)}. 187 * </p> 188 * @see #write(Object) since the standard notes also apply 189 * @since 0.7 190 * @param bean <code>Object</code> to be written as xml, not null 191 * @param source <code>InputSource/code> containing an xml document 192 * specifying the mapping to be used (in the usual way), not null 193 * @throws IOException 194 * @throws SAXException 195 * @throws IntrospectionException 196 */ 197 public void write(Object bean, InputSource source) 198 throws IOException, SAXException, IntrospectionException { 199 writeBean( 200 null, 201 null, 202 null, 203 bean, 204 makeContext( bean ), 205 getXMLIntrospector().introspect(bean.getClass(), source)); 206 } 207 208 /** 209 * <p>Writes the given bean to the current stream 210 * using the given <code>qualifiedName</code>.</p> 211 * 212 * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle 213 * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code> 214 * setting of the <code>BindingConfiguration</code> is false.</p> 215 * 216 * @param namespaceUri the namespace uri 217 * @param localName the local name 218 * @param qualifiedName the string naming root element 219 * @param introspectedBindType the <code>Class</code> of the bean 220 * as resolved at introspection time, or null if the type has not been resolved 221 * @param bean the <code>Object</code> to write out as xml 222 * @param context not null 223 * 224 * @throws IOException if an IO problem occurs during writing 225 * @throws SAXException if an SAX problem occurs during writing 226 * @throws IntrospectionException if a java beans introspection problem occurs 227 */ 228 private void writeBean ( 229 String namespaceUri, 230 String localName, 231 String qualifiedName, 232 Object bean, 233 Context context) 234 throws 235 IOException, 236 SAXException, 237 IntrospectionException { 238 239 if ( log.isTraceEnabled() ) { 240 log.trace( "Writing bean graph (qualified name '" + qualifiedName + "'" ); 241 } 242 243 // introspect to obtain bean info 244 XMLBeanInfo beanInfo = introspector.introspect( bean ); 245 writeBean(namespaceUri, localName, qualifiedName, bean, context, beanInfo); 246 247 log.trace( "Finished writing bean graph." ); 248 } 249 250 251 private void writeBean ( 252 String namespaceUri, 253 String localName, 254 String qualifiedName, 255 Object bean, 256 ElementDescriptor parentDescriptor, 257 Context context) 258 throws 259 IOException, 260 SAXException, 261 IntrospectionException { 262 263 if ( log.isTraceEnabled() ) { 264 log.trace( "Writing bean graph (qualified name '" + qualifiedName + "'" ); 265 } 266 267 // introspect to obtain bean info 268 XMLBeanInfo beanInfo = findXMLBeanInfo(bean, parentDescriptor); 269 writeBean(namespaceUri, localName, qualifiedName, bean, context, beanInfo); 270 271 log.trace( "Finished writing bean graph." ); 272 } 273 274 /** 275 * Finds the appropriate bean info for the given (hollow) element. 276 * @param bean 277 * @param parentDescriptor <code>ElementDescriptor</code>, not null 278 * @return <code>XMLBeanInfo</code>, not null 279 * @throws IntrospectionException 280 */ 281 private XMLBeanInfo findXMLBeanInfo(Object bean, ElementDescriptor parentDescriptor) throws IntrospectionException { 282 XMLBeanInfo beanInfo = null; 283 Class introspectedBindType = parentDescriptor.getSingularPropertyType(); 284 if ( introspectedBindType == null ) { 285 introspectedBindType = parentDescriptor.getPropertyType(); 286 } 287 if ( parentDescriptor.isUseBindTimeTypeForMapping() || introspectedBindType == null ) { 288 beanInfo = introspector.introspect( bean ); 289 } else { 290 beanInfo = introspector.introspect( introspectedBindType ); 291 } 292 return beanInfo; 293 } 294 295 /** 296 * <p>Writes the given bean to the current stream 297 * using the given mapping.</p> 298 * 299 * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle 300 * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code> 301 * setting of the <code>BindingConfiguration</code> is false.</p> 302 * 303 * @param namespaceUri the namespace uri, or null to use the automatic binding 304 * @param localName the local name or null to use the automatic binding 305 * @param qualifiedName the <code>String</code> naming the root element 306 * or null to use the automatic binding 307 * @param bean <code>Object</code> to be written, not null 308 * @param context <code>Context</code>, not null 309 * @param beanInfo <code>XMLBeanInfo</code>, not null 310 * @throws IOException 311 * @throws SAXException 312 * @throws IntrospectionException 313 */ 314 private void writeBean( 315 String namespaceUri, 316 String localName, 317 String qualifiedName, 318 Object bean, 319 Context context, 320 XMLBeanInfo beanInfo) 321 throws IOException, SAXException, IntrospectionException { 322 if ( beanInfo != null ) { 323 ElementDescriptor elementDescriptor = beanInfo.getElementDescriptor(); 324 if ( elementDescriptor != null ) { 325 326 // Construct the options 327 Options combinedOptions = new Options(); 328 329 // Add options defined by the current bean's element descriptor 330 combinedOptions.addOptions(elementDescriptor.getOptions()); 331 332 // The parent descriptor may have defined options 333 // for the current bean. These options take precedence 334 // over the options of the current class descriptor 335 if( context.getOptions() != null) { 336 combinedOptions.addOptions(context.getOptions()); 337 } 338 context = context.newContext( bean ); 339 context.pushOptions(combinedOptions); 340 341 if ( qualifiedName == null ) { 342 qualifiedName = elementDescriptor.getQualifiedName(); 343 } 344 if ( namespaceUri == null ) { 345 namespaceUri = elementDescriptor.getURI(); 346 } 347 if ( localName == null ) { 348 localName = elementDescriptor.getLocalName(); 349 } 350 351 String ref = null; 352 String id = null; 353 354 // simple type should not have IDs 355 if ( elementDescriptor.isSimple() ) { 356 // write without an id 357 writeElement( 358 namespaceUri, 359 localName, 360 qualifiedName, 361 elementDescriptor, 362 context ); 363 364 } else { 365 pushBean ( context.getBean() ); 366 if ( getBindingConfiguration().getMapIDs() ) { 367 ref = getBindingConfiguration().getIdMappingStrategy().getReferenceFor(context, context.getBean()); 368 } 369 if ( ref == null ) { 370 // this is the first time that this bean has be written 371 AttributeDescriptor idAttribute = beanInfo.getIDAttribute(); 372 if (idAttribute == null) { 373 // use a generated id 374 id = idGenerator.nextId(); 375 getBindingConfiguration().getIdMappingStrategy().setReference(context, bean, id); 376 377 if ( getBindingConfiguration().getMapIDs() ) { 378 // write element with id 379 writeElement( 380 namespaceUri, 381 localName, 382 qualifiedName, 383 elementDescriptor, 384 context , 385 beanInfo.getIDAttributeName(), 386 id); 387 388 389 } else { 390 // write element without ID 391 writeElement( 392 namespaceUri, 393 localName, 394 qualifiedName, 395 elementDescriptor, 396 context ); 397 } 398 399 } else { 400 // use id from bean property 401 // it's up to the user to ensure uniqueness 402 Expression idExpression = idAttribute.getTextExpression(); 403 if(idExpression == null) { 404 throw new IntrospectionException( 405 "The specified id property wasn't found in the bean (" 406 + idAttribute + ")."); 407 } 408 Object exp = idExpression.evaluate( context ); 409 if (exp == null) { 410 // we'll use a random id 411 log.debug("Using random id"); 412 id = idGenerator.nextId(); 413 414 } else { 415 // convert to string 416 id = exp.toString(); 417 } 418 getBindingConfiguration().getIdMappingStrategy().setReference(context, bean, id); 419 420 // the ID attribute should be written automatically 421 writeElement( 422 namespaceUri, 423 localName, 424 qualifiedName, 425 elementDescriptor, 426 context ); 427 } 428 } else { 429 430 if ( !ignoreElement( elementDescriptor, namespaceUri, localName, qualifiedName, context )) { 431 // we've already written this bean so write an IDREF 432 writeIDREFElement( 433 elementDescriptor, 434 namespaceUri, 435 localName, 436 qualifiedName, 437 beanInfo.getIDREFAttributeName(), 438 ref); 439 } 440 } 441 popBean(); 442 } 443 444 context.popOptions(); 445 } 446 } 447 } 448 449 /** 450 * Get <code>IDGenerator</code> implementation used to 451 * generate <code>ID</code> attribute values . 452 * 453 * @return implementation used for <code>ID</code> attribute generation 454 */ 455 public IDGenerator getIdGenerator() { 456 return idGenerator; 457 } 458 459 /** 460 * Set <code>IDGenerator</code> implementation 461 * used to generate <code>ID</code> attribute values. 462 * This property can be used to customize the algorithm used for generation. 463 * 464 * @param idGenerator use this implementation for <code>ID</code> attribute generation 465 */ 466 public void setIdGenerator(IDGenerator idGenerator) { 467 this.idGenerator = idGenerator; 468 } 469 470 471 472 /** 473 * Gets the dynamic configuration setting to be used for bean reading. 474 * @return the BindingConfiguration settings, not null 475 * @since 0.5 476 */ 477 public BindingConfiguration getBindingConfiguration() { 478 return bindingConfiguration; 479 } 480 481 /** 482 * Sets the dynamic configuration setting to be used for bean reading. 483 * @param bindingConfiguration the BindingConfiguration settings, not null 484 * @since 0.5 485 */ 486 public void setBindingConfiguration(BindingConfiguration bindingConfiguration) { 487 this.bindingConfiguration = bindingConfiguration; 488 } 489 490 /** 491 * <p>Should generated <code>ID</code> attribute values be added to the elements?</p> 492 * 493 * <p>If IDs are not being written then if a cycle is encountered in the bean graph, 494 * then a {@link CyclicReferenceException} will be thrown by the write method.</p> 495 * 496 * @return true if <code>ID</code> and <code>IDREF</code> attributes are to be written 497 * @deprecated 0.5 use {@link BindingConfiguration#getMapIDs} 498 */ 499 public boolean getWriteIDs() { 500 return getBindingConfiguration().getMapIDs(); 501 } 502 503 /** 504 * Set whether generated <code>ID</code> attribute values should be added to the elements 505 * If this property is set to false, then <code>CyclicReferenceException</code> 506 * will be thrown whenever a cyclic occurs in the bean graph. 507 * 508 * @param writeIDs true if <code>ID</code>'s and <code>IDREF</code>'s should be written 509 * @deprecated 0.5 use {@link BindingConfiguration#setMapIDs} 510 */ 511 public void setWriteIDs(boolean writeIDs) { 512 getBindingConfiguration().setMapIDs( writeIDs ); 513 } 514 515 /** 516 * <p>Gets whether empty elements should be written into the output.</p> 517 * 518 * <p>An empty element is one that has no attributes, no child elements 519 * and no body text. 520 * For example, <code><element/></code> is an empty element but 521 * <code><element attr='value'/></code> is not.</p> 522 * 523 * @return true if empty elements will be written into the output 524 * @since 0.5 525 */ 526 public boolean getWriteEmptyElements() { 527 return writeEmptyElements; 528 } 529 530 /** 531 * <p>Sets whether empty elements should be written into the output.</p> 532 * 533 * <p>An empty element is one that has no attributes, no child elements 534 * and no body text. 535 * For example, <code><element/></code> is an empty element but 536 * <code><element attr='value'/></code> is not. 537 * 538 * @param writeEmptyElements true if empty elements should be written into the output 539 * @since 0.5 540 */ 541 public void setWriteEmptyElements(boolean writeEmptyElements) { 542 this.writeEmptyElements = writeEmptyElements; 543 } 544 545 /** 546 * <p>Gets the introspector used.</p> 547 * 548 * <p>The {@link XMLBeanInfo} used to map each bean is 549 * created by the <code>XMLIntrospector</code>. 550 * One way in which the mapping can be customized is 551 * by altering the <code>XMLIntrospector</code>. </p> 552 * 553 * @return the <code>XMLIntrospector</code> used for introspection 554 */ 555 public XMLIntrospector getXMLIntrospector() { 556 return introspector; 557 } 558 559 560 /** 561 * <p>Sets the introspector to be used.</p> 562 * 563 * <p>The {@link XMLBeanInfo} used to map each bean is 564 * created by the <code>XMLIntrospector</code>. 565 * One way in which the mapping can be customized is by 566 * altering the <code>XMLIntrospector</code>. </p> 567 * 568 * @param introspector use this introspector 569 */ 570 public void setXMLIntrospector(XMLIntrospector introspector) { 571 this.introspector = introspector; 572 } 573 574 /** 575 * <p>Gets the current logging implementation.</p> 576 * 577 * @return the <code>Log</code> implementation which this class logs to 578 */ 579 public final Log getAbstractBeanWriterLog() { 580 return log; 581 } 582 583 /** 584 * <p> Set the current logging implementation. </p> 585 * 586 * @param log <code>Log</code> implementation to use 587 */ 588 public final void setAbstractBeanWriterLog(Log log) { 589 this.log = log; 590 } 591 592 // SAX-style methods 593 //------------------------------------------------------------------------- 594 595 /** 596 * Writes the start tag for an element. 597 * 598 * @param uri the element's namespace uri 599 * @param localName the element's local name 600 * @param qName the element's qualified name 601 * @param attr the element's attributes 602 * 603 * @throws IOException if an IO problem occurs during writing 604 * @throws SAXException if an SAX problem occurs during writing 605 * @since 0.5 606 */ 607 protected void startElement( 608 WriteContext context, 609 String uri, 610 String localName, 611 String qName, 612 Attributes attr) 613 throws 614 IOException, 615 SAXException { 616 // for backwards compatbility call older methods 617 startElement(uri, localName, qName, attr); 618 } 619 620 /** 621 * Writes the end tag for an element 622 * 623 * @param uri the element's namespace uri 624 * @param localName the element's local name 625 * @param qName the element's qualified name 626 * 627 * @throws IOException if an IO problem occurs during writing 628 * @throws SAXException if an SAX problem occurs during writing 629 * @since 0.5 630 */ 631 protected void endElement( 632 WriteContext context, 633 String uri, 634 String localName, 635 String qName) 636 throws 637 IOException, 638 SAXException { 639 // for backwards compatibility call older interface 640 endElement(uri, localName, qName); 641 } 642 643 /** 644 * Writes body text 645 * 646 * @param text the body text to be written 647 * 648 * @throws IOException if an IO problem occurs during writing 649 * @throws SAXException if an SAX problem occurs during writing 650 * @since 0.5 651 */ 652 protected void bodyText(WriteContext context, String text) 653 throws IOException, SAXException { 654 // for backwards compatibility call older interface 655 bodyText(text); 656 } 657 658 // Older SAX-style methods 659 //------------------------------------------------------------------------- 660 661 /** 662 * Writes the start tag for an element. 663 * 664 * @param uri the element's namespace uri 665 * @param localName the element's local name 666 * @param qName the element's qualified name 667 * @param attr the element's attributes 668 * 669 * @throws IOException if an IO problem occurs during writing 670 * @throws SAXException if an SAX problem occurs during writing 671 * @deprecated 0.5 use {@link #startElement(WriteContext, String, String, String, Attributes)} 672 */ 673 protected void startElement( 674 String uri, 675 String localName, 676 String qName, 677 Attributes attr) 678 throws 679 IOException, 680 SAXException {} 681 682 /** 683 * Writes the end tag for an element 684 * 685 * @param uri the element's namespace uri 686 * @param localName the element's local name 687 * @param qName the element's qualified name 688 * 689 * @throws IOException if an IO problem occurs during writing 690 * @throws SAXException if an SAX problem occurs during writing 691 * @deprecated 0.5 use {@link #endElement(WriteContext, String, String, String)} 692 */ 693 protected void endElement( 694 String uri, 695 String localName, 696 String qName) 697 throws 698 IOException, 699 SAXException {} 700 701 /** 702 * Writes body text 703 * 704 * @param text the body text to be written 705 * 706 * @throws IOException if an IO problem occurs during writing 707 * @throws SAXException if an SAX problem occurs during writing 708 * @deprecated 0.5 use {@link #bodyText(WriteContext, String)} 709 */ 710 protected void bodyText(String text) throws IOException, SAXException {} 711 712 // Implementation methods 713 //------------------------------------------------------------------------- 714 715 /** 716 * Writes the given element 717 * 718 * @param namespaceUri the namespace uri 719 * @param localName the local name 720 * @param qualifiedName qualified name to use for the element 721 * @param elementDescriptor the <code>ElementDescriptor</code> describing the element 722 * @param context the <code>Context</code> to use to evaluate the bean expressions 723 * @throws IOException if an IO problem occurs during writing 724 * @throws SAXException if an SAX problem occurs during writing 725 * @throws IntrospectionException if a java beans introspection problem occurs 726 */ 727 private void writeElement( 728 String namespaceUri, 729 String localName, 730 String qualifiedName, 731 ElementDescriptor elementDescriptor, 732 Context context ) 733 throws 734 IOException, 735 SAXException, 736 IntrospectionException { 737 if( log.isTraceEnabled() ) { 738 log.trace( "Writing: " + qualifiedName + " element: " + elementDescriptor ); 739 } 740 741 if ( !ignoreElement( elementDescriptor, namespaceUri, localName, qualifiedName, context )) { 742 if ( log.isTraceEnabled() ) { 743 log.trace( "Element " + elementDescriptor + " is empty." ); 744 } 745 746 Attributes attributes = addNamespaceDeclarations( 747 new ElementAttributes( elementDescriptor, context ), namespaceUri); 748 writeContext.setCurrentDescriptor(elementDescriptor); 749 startElement( 750 writeContext, 751 namespaceUri, 752 localName, 753 qualifiedName, 754 attributes); 755 756 writeElementContent( elementDescriptor, context ) ; 757 writeContext.setCurrentDescriptor(elementDescriptor); 758 endElement( writeContext, namespaceUri, localName, qualifiedName ); 759 } 760 } 761 762 /** 763 * Adds namespace declarations (if any are needed) to the given attributes. 764 * @param attributes Attributes, not null 765 * @param elementNamespaceUri the URI for the enclosing element, possibly null 766 * @return Attributes, not null 767 */ 768 private Attributes addNamespaceDeclarations(Attributes attributes, String elementNamespaceUri) { 769 Attributes result = attributes; 770 AttributesImpl withDeclarations = null; 771 for (int i=-1, size=attributes.getLength(); i<size ; i++) { 772 String uri = null; 773 if (i == -1) { 774 uri = elementNamespaceUri; 775 } else { 776 uri = attributes.getURI(i); 777 } 778 if (uri != null && !"".equals(uri) && !namespacesDeclared.contains(uri)) { 779 if (withDeclarations == null) { 780 withDeclarations = new AttributesImpl(attributes); 781 } 782 withDeclarations.addAttribute("", "", "xmlns:" 783 + getXMLIntrospector().getConfiguration().getPrefixMapper().getPrefix(uri), "NOTATION", uri); 784 namespacesDeclared.add(uri); 785 } 786 } 787 788 if (withDeclarations != null) { 789 result = withDeclarations; 790 } 791 return result; 792 } 793 794 795 /** 796 * Writes the given element adding an ID attribute 797 * 798 * @param namespaceUri the namespace uri 799 * @param localName the local name 800 * @param qualifiedName the qualified name 801 * @param elementDescriptor the ElementDescriptor describing this element 802 * @param context the context being evaliated against 803 * @param idAttribute the qualified name of the <code>ID</code> attribute 804 * @param idValue the value for the <code>ID</code> attribute 805 * @throws IOException if an IO problem occurs during writing 806 * @throws SAXException if an SAX problem occurs during writing 807 * @throws IntrospectionException if a java beans introspection problem occurs 808 */ 809 private void writeElement( 810 String namespaceUri, 811 String localName, 812 String qualifiedName, 813 ElementDescriptor elementDescriptor, 814 Context context, 815 String idAttribute, 816 String idValue ) 817 throws 818 IOException, 819 SAXException, 820 IntrospectionException { 821 822 if ( !ignoreElement( elementDescriptor, namespaceUri, localName, qualifiedName, context ) ) { 823 writeContext.setCurrentDescriptor(elementDescriptor); 824 Attributes attributes = new IDElementAttributes( 825 elementDescriptor, 826 context, 827 idAttribute, 828 idValue ); 829 startElement( 830 writeContext, 831 namespaceUri, 832 localName, 833 qualifiedName, 834 addNamespaceDeclarations(attributes, namespaceUri)); 835 836 writeElementContent( elementDescriptor, context ) ; 837 writeContext.setCurrentDescriptor(elementDescriptor); 838 endElement( writeContext, namespaceUri, localName, qualifiedName ); 839 } else if ( log.isTraceEnabled() ) { 840 log.trace( "Element " + qualifiedName + " is empty." ); 841 } 842 } 843 844 845 /** 846 * Write attributes, child elements and element end 847 * 848 * @param uri the element namespace uri 849 * @param localName the local name of the element 850 * @param qualifiedName the qualified name of the element 851 * @param elementDescriptor the descriptor for this element 852 * @param context evaluate against this context 853 * @throws IOException if an IO problem occurs during writing 854 * @throws SAXException if an SAX problem occurs during writing 855 * @throws IntrospectionException if a java beans introspection problem occurs 856 */ 857 private void writeRestOfElement( 858 String uri, 859 String localName, 860 String qualifiedName, 861 ElementDescriptor elementDescriptor, 862 Context context ) 863 throws 864 IOException, 865 SAXException, 866 IntrospectionException { 867 868 writeElementContent( elementDescriptor, context ); 869 } 870 871 /** 872 * Writes an element with a <code>IDREF</code> attribute 873 * 874 * @param uri the namespace uri 875 * @param localName the local name 876 * @param qualifiedName of the element with <code>IDREF</code> attribute 877 * @param idrefAttributeName the qualified name of the <code>IDREF</code> attribute 878 * @param idrefAttributeValue the value for the <code>IDREF</code> attribute 879 * @throws IOException if an IO problem occurs during writing 880 * @throws SAXException if an SAX problem occurs during writing 881 * @throws IntrospectionException if a java beans introspection problem occurs 882 */ 883 private void writeIDREFElement( 884 ElementDescriptor elementDescriptor, 885 String uri, 886 String localName, 887 String qualifiedName, 888 String idrefAttributeName, 889 String idrefAttributeValue ) 890 throws 891 IOException, 892 SAXException, 893 IntrospectionException { 894 895 896 897 // write IDREF element 898 AttributesImpl attributes = new AttributesImpl(); 899 // XXX for the moment, assign IDREF to default namespace 900 attributes.addAttribute( 901 "", 902 idrefAttributeName, 903 idrefAttributeName, 904 "IDREF", 905 idrefAttributeValue); 906 writeContext.setCurrentDescriptor(elementDescriptor); 907 startElement( writeContext, uri, localName, qualifiedName, addNamespaceDeclarations(attributes, uri)); 908 endElement( writeContext, uri, localName, qualifiedName ); 909 } 910 911 /** 912 * Writes the element content. 913 * 914 * @param elementDescriptor the <code>ElementDescriptor</code> to write as xml 915 * @param context the <code>Context</code> to use to evaluate the bean expressions 916 * 917 * @throws IOException if an IO problem occurs during writing 918 * @throws SAXException if an SAX problem occurs during writing 919 * @throws IntrospectionException if a java beans introspection problem occurs 920 */ 921 private void writeElementContent( 922 ElementDescriptor elementDescriptor, 923 Context context ) 924 throws 925 IOException, 926 SAXException, 927 IntrospectionException { 928 writeContext.setCurrentDescriptor( elementDescriptor ); 929 Descriptor[] childDescriptors = elementDescriptor.getContentDescriptors(); 930 if ( childDescriptors != null && childDescriptors.length > 0 ) { 931 // process child elements 932 for ( int i = 0, size = childDescriptors.length; i < size; i++ ) { 933 if (childDescriptors[i] instanceof ElementDescriptor) { 934 // Element content 935 ElementDescriptor childDescriptor = (ElementDescriptor) childDescriptors[i]; 936 Context childContext = context; 937 childContext.pushOptions(childDescriptor.getOptions()); 938 Expression childExpression = childDescriptor.getContextExpression(); 939 if ( childExpression != null ) { 940 Object childBean = childExpression.evaluate( context ); 941 if ( childBean != null ) { 942 String qualifiedName = childDescriptor.getQualifiedName(); 943 String namespaceUri = childDescriptor.getURI(); 944 String localName = childDescriptor.getLocalName(); 945 // XXXX: should we handle nulls better 946 if ( childBean instanceof Iterator ) { 947 for ( Iterator iter = (Iterator) childBean; iter.hasNext(); ) { 948 Object object = iter.next(); 949 if (object == null) { 950 continue; 951 } 952 writeBean( 953 namespaceUri, 954 localName, 955 qualifiedName, 956 object, 957 childDescriptor, 958 context ); 959 } 960 } else { 961 writeBean( 962 namespaceUri, 963 localName, 964 qualifiedName, 965 childBean, 966 childDescriptor, 967 context ); 968 } 969 } 970 } else { 971 writeElement( 972 childDescriptor.getURI(), 973 childDescriptor.getLocalName(), 974 childDescriptor.getQualifiedName(), 975 childDescriptor, 976 childContext ); 977 } 978 childContext.popOptions(); 979 } else { 980 // Mixed text content 981 // evaluate the body text 982 Expression expression = childDescriptors[i].getTextExpression(); 983 if ( expression != null ) { 984 Object value = expression.evaluate( context ); 985 String text = convertToString( 986 value, 987 childDescriptors[i], 988 context ); 989 if ( text != null && text.length() > 0 ) {; 990 bodyText( writeContext, text ); 991 } 992 } 993 } 994 } 995 } else { 996 // evaluate the body text 997 Expression expression = elementDescriptor.getTextExpression(); 998 if ( expression != null ) { 999 Object value = expression.evaluate( context ); 1000 String text = convertToString( value, elementDescriptor, context ); 1001 if ( text != null && text.length() > 0 ) { 1002 bodyText( writeContext, text ); 1003 } 1004 } 1005 } 1006 } 1007 1008 /** 1009 * Pushes the bean onto the ancestry stack. 1010 * If IDs are not being written, then check for cyclic references. 1011 * 1012 * @param bean push this bean onto the ancester stack 1013 */ 1014 protected void pushBean( Object bean ) { 1015 // check that we don't have a cyclic reference when we're not writing IDs 1016 if ( !getBindingConfiguration().getMapIDs() ) { 1017 Iterator it = beanStack.iterator(); 1018 while ( it.hasNext() ) { 1019 Object next = it.next(); 1020 // use absolute equality rather than equals 1021 // we're only really bothered if objects are actually the same 1022 if ( bean == next ) { 1023 final String message = "Cyclic reference at bean: " + bean; 1024 log.error(message); 1025 StringBuffer buffer = new StringBuffer(message); 1026 buffer.append(" Stack: "); 1027 Iterator errorStack = beanStack.iterator(); 1028 while ( errorStack.hasNext() ) { 1029 Object errorObj = errorStack.next(); 1030 if(errorObj != null) { 1031 buffer.append(errorObj.getClass().getName()); 1032 buffer.append(": "); 1033 } 1034 buffer.append(errorObj); 1035 buffer.append(";"); 1036 } 1037 final String debugMessage = buffer.toString(); 1038 log.info( debugMessage ); 1039 throw new CyclicReferenceException( debugMessage ); 1040 } 1041 } 1042 } 1043 if (log.isTraceEnabled()) { 1044 log.trace( "Pushing onto object stack: " + bean ); 1045 } 1046 beanStack.push( bean ); 1047 } 1048 1049 /** 1050 * Pops the top bean off from the ancestry stack 1051 * 1052 * @return the last object pushed onto the ancester stack 1053 */ 1054 protected Object popBean() { 1055 Object bean = beanStack.pop(); 1056 if (log.isTraceEnabled()) { 1057 log.trace( "Popped from object stack: " + bean ); 1058 } 1059 return bean; 1060 } 1061 1062 /** 1063 * Should this element (and children) be written out? 1064 * 1065 * @param descriptor the <code>ElementDescriptor</code> to evaluate 1066 * @param context the <code>Context</code> against which the element will be evaluated 1067 * @return true if this element should be written out 1068 * @throws IntrospectionException 1069 */ 1070 private boolean ignoreElement( ElementDescriptor descriptor, String namespaceUri, String localName, String qualifiedName, Context context ) throws IntrospectionException { 1071 if (getBindingConfiguration().getValueSuppressionStrategy().suppressElement(descriptor, namespaceUri, localName, qualifiedName, context.getBean())) { 1072 return true; 1073 } 1074 1075 if ( ! getWriteEmptyElements() ) { 1076 return isEmptyElement( descriptor, context ); 1077 } 1078 return false; 1079 } 1080 1081 /** 1082 * <p>Will evaluating this element against this context result in an empty element?</p> 1083 * 1084 * <p>An empty element is one that has no attributes, no child elements 1085 * and no body text. 1086 * For example, <code><element/></code> is an empty element but 1087 * <code><element attr='value'/></code> is not.</p> 1088 * 1089 * @param descriptor the <code>ElementDescriptor</code> to evaluate 1090 * @param context the <code>Context</code> against which the element will be evaluated 1091 * @return true if this element is empty on evaluation 1092 * @throws IntrospectionException 1093 */ 1094 private boolean isEmptyElement( ElementDescriptor descriptor, Context context ) throws IntrospectionException { 1095 //TODO: this design isn't too good 1096 // to would be much better to render just once 1097 if ( log.isTraceEnabled() ) { 1098 log.trace( "Is " + descriptor + " empty?" ); 1099 } 1100 1101 // an element which has attributes is not empty 1102 if ( descriptor.hasAttributes() ) { 1103 log.trace( "Element has attributes." ); 1104 return false; 1105 } 1106 1107 // an element is not empty if it has a non-empty body 1108 Expression expression = descriptor.getTextExpression(); 1109 if ( expression != null ) { 1110 Object value = expression.evaluate( context ); 1111 String text = convertToString( value, descriptor, context ); 1112 if ( text != null && text.length() > 0 ) { 1113 log.trace( "Element has body text which isn't empty." ); 1114 return false; 1115 } 1116 } 1117 1118 // always write out loops - even when they have no elements 1119 if ( descriptor.isCollective() ) { 1120 log.trace("Loop type so not empty."); 1121 return false; 1122 } 1123 1124 // now test child elements 1125 // an element is empty if it has no non-empty child elements 1126 if ( descriptor.hasChildren() ) { 1127 for ( int i=0, size=descriptor.getElementDescriptors().length; i<size; i++ ) { 1128 if ( ! isEmptyElement( descriptor.getElementDescriptors()[i], context ) ) { 1129 log.trace( "Element has child which isn't empty." ); 1130 return false; 1131 } 1132 } 1133 } 1134 1135 if ( descriptor.isHollow() ) 1136 { 1137 Expression contentExpression = descriptor.getContextExpression(); 1138 if (contentExpression != null) { 1139 Object childBean = contentExpression.evaluate(context); 1140 if (childBean != null) 1141 { 1142 XMLBeanInfo xmlBeanInfo = findXMLBeanInfo(childBean, descriptor); 1143 Object currentBean = context.getBean(); 1144 context.setBean(childBean); 1145 boolean result = isEmptyElement(xmlBeanInfo.getElementDescriptor(), context); 1146 context.setBean(currentBean); 1147 return result; 1148 } 1149 } 1150 } 1151 1152 log.trace( "Element is empty." ); 1153 return true; 1154 } 1155 1156 1157 /** 1158 * Attributes backed by attribute descriptors. 1159 * ID/IDREFs not set. 1160 */ 1161 private class ElementAttributes implements Attributes { 1162 /** Attribute descriptors backing the <code>Attributes</code> */ 1163 private AttributeDescriptor[] attributes; 1164 /** Context to be evaluated when finding values */ 1165 private Context context; 1166 /** Cached attribute values */ 1167 private String[] values; 1168 /** The number of unsuppressed attributes */ 1169 private int length; 1170 1171 1172 /** 1173 * Construct attributes for element and context. 1174 * 1175 * @param descriptor the <code>ElementDescriptor</code> describing the element 1176 * @param context evaluate against this context 1177 */ 1178 ElementAttributes( ElementDescriptor descriptor, Context context ) { 1179 this.context = context; 1180 init(descriptor.getAttributeDescriptors()); 1181 } 1182 1183 private void init(AttributeDescriptor[] baseAttributes) { 1184 attributes = new AttributeDescriptor[baseAttributes.length]; 1185 values = new String[baseAttributes.length]; 1186 int index = 0; 1187 for (int i=0, size=baseAttributes.length; i<size; i++) { 1188 AttributeDescriptor baseAttribute = baseAttributes[i]; 1189 String attributeValue = valueAttribute(baseAttribute); 1190 if (attributeValue != null 1191 && !context.getValueSuppressionStrategy() 1192 .suppressAttribute(baseAttribute, attributeValue)) { 1193 values[index] = attributeValue; 1194 attributes[index] = baseAttribute; 1195 index++; 1196 } 1197 } 1198 length = index; 1199 } 1200 1201 private String valueAttribute(AttributeDescriptor attribute) { 1202 Expression expression = attribute.getTextExpression(); 1203 if ( expression != null ) { 1204 Object value = expression.evaluate( context ); 1205 return convertToString( value, attribute, context ); 1206 } 1207 1208 return ""; 1209 } 1210 1211 /** 1212 * Gets the index of an attribute by qualified name. 1213 * 1214 * @param qName the qualified name of the attribute 1215 * @return the index of the attribute - or -1 if there is no matching attribute 1216 */ 1217 public int getIndex( String qName ) { 1218 for ( int i=0; i<attributes.length; i++ ) { 1219 if (attributes[i].getQualifiedName() != null 1220 && attributes[i].getQualifiedName().equals( qName )) { 1221 return i; 1222 } 1223 } 1224 return -1; 1225 } 1226 1227 /** 1228 * Gets the index of an attribute by namespace name. 1229 * 1230 * @param uri the namespace uri of the attribute 1231 * @param localName the local name of the attribute 1232 * @return the index of the attribute - or -1 if there is no matching attribute 1233 */ 1234 public int getIndex( String uri, String localName ) { 1235 for ( int i=0; i<attributes.length; i++ ) { 1236 if ( 1237 attributes[i].getURI() != null 1238 && attributes[i].getURI().equals(uri) 1239 && attributes[i].getLocalName() != null 1240 && attributes[i].getURI().equals(localName)) { 1241 return i; 1242 } 1243 } 1244 1245 return -1; 1246 } 1247 1248 /** 1249 * Gets the number of attributes in the list. 1250 * 1251 * @return the number of attributes in this list 1252 */ 1253 public int getLength() { 1254 return length; 1255 } 1256 1257 /** 1258 * Gets the local name by index. 1259 * 1260 * @param index the attribute index (zero based) 1261 * @return the attribute local name - or null if the index is out of range 1262 */ 1263 public String getLocalName( int index ) { 1264 if ( indexInRange( index ) ) { 1265 return attributes[index].getLocalName(); 1266 } 1267 1268 return null; 1269 } 1270 1271 /** 1272 * Gets the qualified name by index. 1273 * 1274 * @param index the attribute index (zero based) 1275 * @return the qualified name of the element - or null if the index is our of range 1276 */ 1277 public String getQName( int index ) { 1278 if ( indexInRange( index ) ) { 1279 return attributes[index].getQualifiedName(); 1280 } 1281 1282 return null; 1283 } 1284 1285 /** 1286 * Gets the attribute SAX type by namespace name. 1287 * 1288 * @param index the attribute index (zero based) 1289 * @return the attribute type (as a string) or null if the index is out of range 1290 */ 1291 public String getType( int index ) { 1292 if ( indexInRange( index ) ) { 1293 return "CDATA"; 1294 } 1295 return null; 1296 } 1297 1298 /** 1299 * Gets the attribute SAX type by qualified name. 1300 * 1301 * @param qName the qualified name of the attribute 1302 * @return the attribute type (as a string) or null if the attribute is not in the list 1303 */ 1304 public String getType( String qName ) { 1305 return getType( getIndex( qName ) ); 1306 } 1307 1308 /** 1309 * Gets the attribute SAX type by namespace name. 1310 * 1311 * @param uri the namespace uri of the attribute 1312 * @param localName the local name of the attribute 1313 * @return the attribute type (as a string) or null if the attribute is not in the list 1314 */ 1315 public String getType( String uri, String localName ) { 1316 return getType( getIndex( uri, localName )); 1317 } 1318 1319 /** 1320 * Gets the namespace URI for attribute at the given index. 1321 * 1322 * @param index the attribute index (zero-based) 1323 * @return the namespace URI (empty string if none is available) 1324 * or null if the index is out of range 1325 */ 1326 public String getURI( int index ) { 1327 if ( indexInRange( index ) ) { 1328 return attributes[index].getURI(); 1329 } 1330 return null; 1331 } 1332 1333 /** 1334 * Gets the value for the attribute at given index. 1335 * 1336 * @param index the attribute index (zero based) 1337 * @return the attribute value or null if the index is out of range 1338 * @todo add value caching 1339 */ 1340 public String getValue( int index ) { 1341 if ( indexInRange( index ) ) { 1342 return values[index]; 1343 } 1344 return null; 1345 } 1346 1347 /** 1348 * Gets the value for the attribute by qualified name. 1349 * 1350 * @param qName the qualified name 1351 * @return the attribute value or null if there are no attributes 1352 * with the given qualified name 1353 * @todo add value caching 1354 */ 1355 public String getValue( String qName ) { 1356 return getValue( getIndex( qName ) ); 1357 } 1358 1359 /** 1360 * Gets the value for the attribute by namespace name. 1361 * 1362 * @param uri the namespace URI of the attribute 1363 * @param localName the local name of the attribute 1364 * @return the attribute value or null if there are not attributes 1365 * with the given namespace and local name 1366 * @todo add value caching 1367 */ 1368 public String getValue( String uri, String localName ) { 1369 return getValue( getIndex( uri, localName ) ); 1370 } 1371 1372 /** 1373 * Is the given index within the range of the attribute list 1374 * 1375 * @param index the index whose range will be checked 1376 * @return true if the index with within the range of the attribute list 1377 */ 1378 private boolean indexInRange( int index ) { 1379 return ( index >= 0 && index < getLength() ); 1380 } 1381 } 1382 1383 /** 1384 * Attributes with generate ID/IDREF attributes 1385 * //TODO: refactor the ID/REF generation so that it's fixed at introspection 1386 * and the generators are placed into the Context. 1387 * @author <a href='http://commons.apache.org/'>Apache Commons Team</a> 1388 * @version $Revision: 561314 $ 1389 */ 1390 private class IDElementAttributes extends ElementAttributes { 1391 /** ID attribute value */ 1392 private String idValue; 1393 /** ID attribute name */ 1394 private String idAttributeName; 1395 1396 private boolean matchingAttribute = false; 1397 private int length; 1398 private int idIndex; 1399 1400 /** 1401 * Construct attributes for element and context. 1402 * 1403 * @param descriptor the <code>ElementDescriptor</code> describing the element 1404 * @param context evaluate against this context 1405 * @param idAttributeName the name of the id attribute 1406 * @param idValue the ID attribute value 1407 */ 1408 IDElementAttributes( 1409 ElementDescriptor descriptor, 1410 Context context, 1411 String idAttributeName, 1412 String idValue) { 1413 super(descriptor, context); 1414 this.idValue = idValue; 1415 this.idAttributeName = idAttributeName; 1416 1417 // see if we have already have a matching attribute descriptor 1418 AttributeDescriptor[] attributeDescriptors = descriptor.getAttributeDescriptors(); 1419 length = super.getLength(); 1420 for (int i=0; i<length; i++) { 1421 if (idAttributeName.equals(attributeDescriptors[i].getQualifiedName())) { 1422 matchingAttribute = true; 1423 idIndex = i; 1424 break; 1425 } 1426 } 1427 if (!matchingAttribute) { 1428 length += 1; 1429 idIndex = length-1; 1430 } 1431 } 1432 1433 public int getIndex(String uri, String localName) { 1434 if (localName.equals(idAttributeName)) { 1435 return idIndex; 1436 } 1437 1438 return super.getIndex(uri, localName); 1439 } 1440 1441 public int getIndex(String qName) { 1442 if (qName.equals(idAttributeName)) { 1443 return idIndex; 1444 } 1445 1446 return super.getIndex(qName); 1447 } 1448 1449 public int getLength() { 1450 return length; 1451 } 1452 1453 public String getLocalName(int index) { 1454 if (index == idIndex) { 1455 return idAttributeName; 1456 } 1457 return super.getLocalName(index); 1458 } 1459 1460 public String getQName(int index) { 1461 if (index == idIndex) { 1462 return idAttributeName; 1463 } 1464 return super.getQName(index); 1465 } 1466 1467 public String getType(int index) { 1468 if (index == idIndex) { 1469 return "ID"; 1470 } 1471 return super.getType(index); 1472 } 1473 1474 public String getType(String uri, String localName) { 1475 return getType(getIndex(uri, localName)); 1476 } 1477 1478 public String getType(String qName) { 1479 return getType(getIndex(qName)); 1480 } 1481 1482 public String getURI(int index) { 1483 //TODO: this is probably wrong 1484 // probably need to move ID management into introspection 1485 // before we can handle this namespace bit correctly 1486 if (index == idIndex) { 1487 return ""; 1488 } 1489 return super.getURI(index); 1490 } 1491 1492 public String getValue(int index) { 1493 if (index == idIndex) { 1494 return idValue; 1495 } 1496 return super.getValue(index); 1497 } 1498 1499 public String getValue(String uri, String localName) { 1500 return getValue(getIndex(uri, localName)); 1501 } 1502 1503 public String getValue(String qName) { 1504 return getValue(getIndex(qName)); 1505 } 1506 1507 } 1508 1509 1510 // OLD API (DEPRECATED) 1511 // -------------------------------------------------------------------------------------- 1512 1513 1514 /** 1515 * Get the indentation for the current element. 1516 * Used for pretty priting. 1517 * 1518 * @return the amount that the current element is indented 1519 * @deprecated 0.5 replaced by new SAX inspired API 1520 */ 1521 protected int getIndentLevel() { 1522 return 0; 1523 } 1524 1525 // Expression methods 1526 //------------------------------------------------------------------------- 1527 1528 /** 1529 * Express an element tag start using given qualified name. 1530 * 1531 * @param qualifiedName the qualified name of the element to be expressed 1532 * @throws IOException if an IO problem occurs during writing 1533 * @throws SAXException if an SAX problem occurs during writing 1534 * @deprecated 0.5 replaced by new SAX inspired API 1535 */ 1536 protected void expressElementStart(String qualifiedName) 1537 throws IOException, SAXException { 1538 // do nothing 1539 } 1540 1541 /** 1542 * Express an element tag start using given qualified name. 1543 * 1544 * @param uri the namespace uri 1545 * @param localName the local name for this element 1546 * @param qualifiedName the qualified name of the element to be expressed 1547 * @throws IOException if an IO problem occurs during writing 1548 * @throws SAXException if an SAX problem occurs during writing 1549 * @deprecated 0.5 replaced by new SAX inspired API 1550 */ 1551 protected void expressElementStart(String uri, String localName, String qualifiedName) 1552 throws IOException, SAXException { 1553 expressElementStart( qualifiedName ); 1554 } 1555 1556 /** 1557 * Express a closing tag. 1558 * 1559 * @throws IOException if an IO problem occurs during writing 1560 * @throws SAXException if an SAX problem occurs during writing 1561 * @deprecated 0.5 replaced by new SAX inspired API 1562 */ 1563 protected void expressTagClose() throws IOException, SAXException {} 1564 1565 /** 1566 * Express an element end tag (with given name) 1567 * 1568 * @param qualifiedName the qualified name for the element to be closed 1569 * 1570 * @throws IOException if an IO problem occurs during writing 1571 * @throws SAXException if an SAX problem occurs during writing 1572 * @deprecated 0.5 replaced by new SAX inspired API 1573 */ 1574 protected void expressElementEnd(String qualifiedName) 1575 throws IOException, SAXException { 1576 // do nothing 1577 } 1578 1579 /** 1580 * Express an element end tag (with given name) 1581 * 1582 * @param uri the namespace uri of the element close tag 1583 * @param localName the local name of the element close tag 1584 * @param qualifiedName the qualified name for the element to be closed 1585 * 1586 * @throws IOException if an IO problem occurs during writing 1587 * @throws SAXException if an SAX problem occurs during writing 1588 * @deprecated 0.5 replaced by new SAX inspired API 1589 */ 1590 protected void expressElementEnd( 1591 String uri, 1592 String localName, 1593 String qualifiedName) 1594 throws 1595 IOException, 1596 SAXException { 1597 expressElementEnd(qualifiedName); 1598 } 1599 1600 1601 /** 1602 * Express an empty element end. 1603 * 1604 * @throws IOException if an IO problem occurs during writing 1605 * @throws SAXException if an SAX problem occurs during writing 1606 * @deprecated 0.5 replaced by new SAX inspired API 1607 */ 1608 protected void expressElementEnd() throws IOException, SAXException {} 1609 1610 /** 1611 * Express body text 1612 * 1613 * @param text the string to write out as the body of the current element 1614 * 1615 * @throws IOException if an IO problem occurs during writing 1616 * @throws SAXException if an SAX problem occurs during writing 1617 * @deprecated 0.5 replaced by new SAX inspired API 1618 */ 1619 protected void expressBodyText(String text) throws IOException, SAXException {} 1620 1621 /** 1622 * Express an attribute 1623 * 1624 * @param qualifiedName the qualified name of the attribute 1625 * @param value the attribute value 1626 * @throws IOException if an IO problem occurs during writing 1627 * @throws SAXException if an SAX problem occurs during writing 1628 * @deprecated 0.5 replaced by new SAX inspired API 1629 */ 1630 protected void expressAttribute( 1631 String qualifiedName, 1632 String value) 1633 throws 1634 IOException, 1635 SAXException { 1636 // Do nothing 1637 } 1638 1639 /** 1640 * Express an attribute 1641 * 1642 * @param namespaceUri the namespace uri 1643 * @param localName the local name 1644 * @param qualifiedName the qualified name of the attribute 1645 * @param value the attribute value 1646 * @throws IOException if an IO problem occurs during writing 1647 * @throws SAXException if an SAX problem occurs during writing 1648 * @deprecated 0.5 replaced by new SAX inspired API 1649 */ 1650 protected void expressAttribute( 1651 String namespaceUri, 1652 String localName, 1653 String qualifiedName, 1654 String value) 1655 throws 1656 IOException, 1657 SAXException { 1658 expressAttribute(qualifiedName, value); 1659 } 1660 1661 1662 /** 1663 * Writes the given element 1664 * 1665 * @param qualifiedName qualified name to use for the element 1666 * @param elementDescriptor the <code>ElementDescriptor</code> describing the element 1667 * @param context the <code>Context</code> to use to evaluate the bean expressions 1668 * @throws IOException if an IO problem occurs during writing 1669 * @throws SAXException if an SAX problem occurs during writing 1670 * @throws IntrospectionException if a java beans introspection problem occurs 1671 * @deprecated 0.5 replaced by new SAX inspired API 1672 */ 1673 protected void write( 1674 String qualifiedName, 1675 ElementDescriptor elementDescriptor, 1676 Context context ) 1677 throws 1678 IOException, 1679 SAXException, 1680 IntrospectionException { 1681 writeElement( "", qualifiedName, qualifiedName, elementDescriptor, context ); 1682 } 1683 1684 /** 1685 * Writes the given element adding an ID attribute 1686 * 1687 * @param qualifiedName qualified name to use for the element 1688 * @param elementDescriptor the <code>ElementDescriptor</code> describing the element 1689 * @param context the <code>Context</code> to use to evaluate the bean expressions 1690 * @param idAttribute the qualified name of the <code>ID</code> attribute 1691 * @param idValue the value for the <code>ID</code> attribute 1692 * @throws IOException if an IO problem occurs during writing 1693 * @throws SAXException if an SAX problem occurs during writing 1694 * @throws IntrospectionException if a java beans introspection problem occurs 1695 * @deprecated 0.5 replaced by new SAX inspired API 1696 */ 1697 protected void write( 1698 String qualifiedName, 1699 ElementDescriptor elementDescriptor, 1700 Context context, 1701 String idAttribute, 1702 String idValue ) 1703 throws 1704 IOException, 1705 SAXException, 1706 IntrospectionException { 1707 writeElement( 1708 "", 1709 qualifiedName, 1710 qualifiedName, 1711 elementDescriptor, 1712 context, 1713 idAttribute, 1714 idValue ); 1715 } 1716 1717 /** 1718 * Write attributes, child elements and element end 1719 * 1720 * @param qualifiedName qualified name to use for the element 1721 * @param elementDescriptor the <code>ElementDescriptor</code> describing the element 1722 * @param context the <code>Context</code> to use to evaluate the bean expressions 1723 * @throws IOException if an IO problem occurs during writing 1724 * @throws SAXException if an SAX problem occurs during writing 1725 * @throws IntrospectionException if a java beans introspection problem occurs 1726 * @deprecated 0.5 replaced by new SAX inspired API 1727 */ 1728 protected void writeRestOfElement( 1729 String qualifiedName, 1730 ElementDescriptor elementDescriptor, 1731 Context context ) 1732 throws 1733 IOException, 1734 SAXException, 1735 IntrospectionException { 1736 writeRestOfElement( "", qualifiedName, qualifiedName, elementDescriptor, context ); 1737 } 1738 1739 /** 1740 * Writes an element with a <code>IDREF</code> attribute 1741 * 1742 * @param qualifiedName of the element with <code>IDREF</code> attribute 1743 * @param idrefAttributeName the qualified name of the <code>IDREF</code> attribute 1744 * @param idrefAttributeValue the value for the <code>IDREF</code> attribute 1745 * @throws IOException if an IO problem occurs during writing 1746 * @throws SAXException if an SAX problem occurs during writing 1747 * @throws IntrospectionException if a java beans introspection problem occurs 1748 * @deprecated 0.5 replaced by new SAX inspired API 1749 */ 1750 protected void writeIDREFElement( 1751 String qualifiedName, 1752 String idrefAttributeName, 1753 String idrefAttributeValue ) 1754 throws 1755 IOException, 1756 SAXException, 1757 IntrospectionException { 1758 // deprecated 1759 AttributesImpl attributes = new AttributesImpl(); 1760 attributes.addAttribute( 1761 "", 1762 idrefAttributeName, 1763 idrefAttributeName, 1764 "IDREF", 1765 idrefAttributeValue); 1766 startElement( "", qualifiedName, qualifiedName, attributes); 1767 endElement( "", qualifiedName, qualifiedName ); 1768 } 1769 1770 1771 /** 1772 * Writes the element content. 1773 * 1774 * @param elementDescriptor the <code>ElementDescriptor</code> to write as xml 1775 * @param context the <code>Context</code> to use to evaluate the bean expressions 1776 * @return true if some content was written 1777 * @throws IOException if an IO problem occurs during writing 1778 * @throws SAXException if an SAX problem occurs during writing 1779 * @throws IntrospectionException if a java beans introspection problem occurs 1780 * @deprecated 0.5 replaced by new SAX inspired API 1781 */ 1782 protected boolean writeContent( 1783 ElementDescriptor elementDescriptor, 1784 Context context ) 1785 throws 1786 IOException, 1787 SAXException, 1788 IntrospectionException { 1789 return false; 1790 } 1791 1792 1793 /** 1794 * Writes the attribute declarations 1795 * 1796 * @param elementDescriptor the <code>ElementDescriptor</code> to be written out as xml 1797 * @param context the <code>Context</code> to use to evaluation bean expressions 1798 * @throws IOException if an IO problem occurs during writing 1799 * @throws SAXException if an SAX problem occurs during writing 1800 * @deprecated 0.5 replaced by new SAX inspired API 1801 */ 1802 protected void writeAttributes( 1803 ElementDescriptor elementDescriptor, 1804 Context context ) 1805 throws 1806 IOException, SAXException { 1807 if (!elementDescriptor.isWrapCollectionsInElement()) { 1808 return; 1809 } 1810 1811 AttributeDescriptor[] attributeDescriptors = elementDescriptor.getAttributeDescriptors(); 1812 if ( attributeDescriptors != null ) { 1813 for ( int i = 0, size = attributeDescriptors.length; i < size; i++ ) { 1814 AttributeDescriptor attributeDescriptor = attributeDescriptors[i]; 1815 writeAttribute( attributeDescriptor, context ); 1816 } 1817 } 1818 } 1819 1820 1821 /** 1822 * Writes an attribute declaration 1823 * 1824 * @param attributeDescriptor the <code>AttributeDescriptor</code> to be written as xml 1825 * @param context the <code>Context</code> to use to evaluation bean expressions 1826 * @throws IOException if an IO problem occurs during writing 1827 * @throws SAXException if an SAX problem occurs during writing 1828 * @deprecated 0.5 replaced by new SAX inspired API 1829 */ 1830 protected void writeAttribute( 1831 AttributeDescriptor attributeDescriptor, 1832 Context context ) 1833 throws 1834 IOException, SAXException { 1835 Expression expression = attributeDescriptor.getTextExpression(); 1836 if ( expression != null ) { 1837 Object value = expression.evaluate( context ); 1838 if ( value != null ) { 1839 String text = value.toString(); 1840 if ( text != null && text.length() > 0 ) { 1841 expressAttribute( 1842 attributeDescriptor.getURI(), 1843 attributeDescriptor.getLocalName(), 1844 attributeDescriptor.getQualifiedName(), 1845 text); 1846 } 1847 } 1848 } 1849 } 1850 /** 1851 * Writes a empty line. 1852 * This implementation does nothing but can be overridden by subclasses. 1853 * 1854 * @throws IOException if the line cannot be written 1855 * @deprecated 0.5 replaced by new SAX inspired API 1856 */ 1857 protected void writePrintln() throws IOException {} 1858 1859 /** 1860 * Writes an indentation. 1861 * This implementation does nothing but can be overridden by subclasses. 1862 * 1863 * @throws IOException if the indent cannot be written 1864 * @deprecated 0.5 replaced by new BeanWriter API 1865 */ 1866 protected void writeIndent() throws IOException {} 1867 1868 /** 1869 * Converts an object to a string. 1870 * 1871 * @param value the Object to represent as a String, possibly null 1872 * @param descriptor writing out this descriptor not null 1873 * @param context not null 1874 * @return String representation, not null 1875 */ 1876 private String convertToString( Object value , Descriptor descriptor, Context context ) { 1877 return getBindingConfiguration() 1878 .getObjectStringConverter() 1879 .objectToString( value, descriptor.getPropertyType(), context ); 1880 } 1881 1882 /** 1883 * Factory method for new contexts. 1884 * Ensure that they are correctly configured. 1885 * @param bean make a new Context for this bean 1886 * @return not null 1887 */ 1888 private Context makeContext(Object bean) { 1889 return new Context( bean, log, bindingConfiguration ); 1890 } 1891 1892 1893 /** 1894 * Basic mutable implementation of <code>WriteContext</code>. 1895 */ 1896 private static class WriteContextImpl extends WriteContext { 1897 1898 private ElementDescriptor currentDescriptor; 1899 1900 /** 1901 * @see org.apache.commons.betwixt.io.WriteContext#getCurrentDescriptor() 1902 */ 1903 public ElementDescriptor getCurrentDescriptor() { 1904 return currentDescriptor; 1905 } 1906 1907 /** 1908 * Sets the descriptor for the current element. 1909 * @param currentDescriptor 1910 */ 1911 public void setCurrentDescriptor(ElementDescriptor currentDescriptor) { 1912 this.currentDescriptor = currentDescriptor; 1913 } 1914 1915 } 1916 }