View Javadoc

1   /*
2    * Copyright 2001-2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */ 
16  package org.apache.commons.betwixt.io;
17  
18  import java.beans.IntrospectionException;
19  import java.io.IOException;
20  import java.util.HashMap;
21  import java.util.Iterator;
22  
23  import org.apache.commons.betwixt.AttributeDescriptor;
24  import org.apache.commons.betwixt.BindingConfiguration;
25  import org.apache.commons.betwixt.Descriptor;
26  import org.apache.commons.betwixt.ElementDescriptor;
27  import org.apache.commons.betwixt.XMLBeanInfo;
28  import org.apache.commons.betwixt.XMLIntrospector;
29  import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
30  import org.apache.commons.betwixt.expression.Context;
31  import org.apache.commons.betwixt.expression.Expression;
32  import org.apache.commons.betwixt.io.id.SequentialIDGenerator;
33  import org.apache.commons.collections.ArrayStack;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.xml.sax.Attributes;
37  import org.xml.sax.SAXException;
38  import org.xml.sax.helpers.AttributesImpl;
39  
40  // FIX ME!!!
41  // Logging logic!
42  
43  // FIX ME!!
44  // Error handling strategy!
45  // i'm going to add SAXExceptions everywhere since it's the easiest way to make things work quick
46  // but this is a poor strategy
47  
48  /***
49    * <p>Abstract superclass for bean writers.
50    * This class encapsulates the processing logic. 
51    * Subclasses provide implementations for the actual expression of the xml.</p>
52    * <h5>SAX Inspired Writing API</h5>
53    * <p>
54    * This class is intended to be used by subclassing: 
55    * concrete subclasses perform the actual writing by providing
56    * suitable implementations for the following methods inspired 
57    * by <a href='http://www.saxproject.org'>SAX</a>:
58    * </p>
59    * <ul>
60    *     <li> {@link #start} - called when processing begins</li>
61    *     <li> {@link #startElement(WriteContext, String, String, String, Attributes)} 
62    *     - called when the start of an element 
63    *     should be written</li> 
64    *     <li> {@link #bodyText(WriteContext, String)} 
65    *     - called when the start of an element 
66    *     should be written</li> 
67    *     <li> {@link #endElement(WriteContext, String, String, String)} 
68    *     - called when the end of an element 
69    *     should be written</li> 
70    *     <li> {@link #end} - called when processing has been completed</li>
71    * </ul>
72    * <p>
73    * <strong>Note</strong> that this class contains many deprecated 
74    * versions of the writing API. These will be removed soon so care
75    * should be taken to use the latest version.
76    * </p>
77    * <p>
78    * <strong>Note</strong> that this class is designed to be used
79    * in a single threaded environment. When used in multi-threaded
80    * environments, use of a common <code>XMLIntrospector</code>
81    * and pooled writer instances should be considered. 
82    * </p>
83    *
84    * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a>
85    * @version $Revision: 1.26.2.1 $
86    */
87  public abstract class AbstractBeanWriter {
88  
89      /*** Introspector used */
90      private XMLIntrospector introspector = new XMLIntrospector();
91  
92      /*** Log used for logging (Doh!) */
93      private Log log = LogFactory.getLog( AbstractBeanWriter.class );
94      /*** Map containing ID attribute values for beans */
95      private HashMap idMap = new HashMap();
96      /*** Stack containing beans - used to detect cycles */
97      private ArrayStack beanStack = new ArrayStack();
98      /*** Used to generate ID attribute values*/
99      private IDGenerator idGenerator = new SequentialIDGenerator();
100     /*** Should empty elements be written out? */
101     private boolean writeEmptyElements = true;
102     /*** Dynamic binding configuration settings */
103     private BindingConfiguration bindingConfiguration = new BindingConfiguration();
104     /*** <code>WriteContext</code> implementation reused curing writing */
105     private WriteContextImpl writeContext = new WriteContextImpl();
106     
107     /***
108      * Marks the start of the bean writing.
109      * By default doesn't do anything, but can be used
110      * to do extra start processing 
111      * @throws IOException if an IO problem occurs during writing 
112      * @throws SAXException if an SAX problem occurs during writing 
113      */
114     public void start() throws IOException, SAXException {
115     }
116     
117     /***
118      * Marks the start of the bean writing.
119      * By default doesn't do anything, but can be used
120      * to do extra end processing 
121      * @throws IOException if an IO problem occurs during writing
122      * @throws SAXException if an SAX problem occurs during writing 
123      */
124     
125     public void end() throws IOException, SAXException {
126     }
127         
128     /*** 
129      * <p> Writes the given bean to the current stream using the XML introspector.</p>
130      * 
131      * <p> This writes an xml fragment representing the bean to the current stream.</p>
132      *
133      * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
134      * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
135      * setting of the </code>BindingConfiguration</code> is false.</p>
136      *
137      * @throws IOException if an IO problem occurs during writing 
138      * @throws SAXException if an SAX problem occurs during writing  
139      * @throws IntrospectionException if a java beans introspection problem occurs 
140      *
141      * @param bean write out representation of this bean
142      */
143     public void write(Object bean) throws 
144                                         IOException, 
145                                         SAXException, 
146                                         IntrospectionException {
147         if (log.isDebugEnabled()) {
148             log.debug( "Writing bean graph..." );
149             log.debug( bean );
150         }
151         start();
152         write( null, bean );
153         end();
154         if (log.isDebugEnabled()) {
155             log.debug( "Finished writing bean graph." );
156         }
157     }
158     
159     /*** 
160      * <p>Writes the given bean to the current stream 
161      * using the given <code>qualifiedName</code>.</p>
162      *
163      * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
164      * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
165      * setting of the <code>BindingConfiguration</code> is false.</p>
166      *
167      * @param qualifiedName the string naming root element
168      * @param bean the <code>Object</code> to write out as xml
169      * 
170      * @throws IOException if an IO problem occurs during writing
171      * @throws SAXException if an SAX problem occurs during writing 
172      * @throws IntrospectionException if a java beans introspection problem occurs
173      */
174     public void write(
175                 String qualifiedName, 
176                 Object bean) 
177                     throws 
178                         IOException, 
179                         SAXException,
180                         IntrospectionException {
181         writeBean( "", qualifiedName, qualifiedName, bean, makeContext( bean ) );
182     }
183     
184     /*** 
185      * <p>Writes the given bean to the current stream 
186      * using the given <code>qualifiedName</code>.</p>
187      *
188      * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
189      * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
190      * setting of the <code>BindingConfiguration</code> is false.</p>
191      *
192      * @param namespaceUri the namespace uri
193      * @param localName the local name
194      * @param qualifiedName the string naming root element
195      * @param bean the <code>Object</code> to write out as xml
196      * @param context not null
197      * 
198      * @throws IOException if an IO problem occurs during writing
199      * @throws SAXException if an SAX problem occurs during writing 
200      * @throws IntrospectionException if a java beans introspection problem occurs
201      */
202     private void writeBean (
203                 String namespaceUri,
204                 String localName,
205                 String qualifiedName, 
206                 Object bean,
207                 Context context) 
208                     throws 
209                         IOException, 
210                         SAXException,
211                         IntrospectionException {                    
212         
213         if ( log.isTraceEnabled() ) {
214             log.trace( "Writing bean graph (qualified name '" + qualifiedName + "'" );
215         }
216         
217         // introspect to obtain bean info
218         XMLBeanInfo beanInfo = introspector.introspect( bean );
219         if ( beanInfo != null ) {
220             ElementDescriptor elementDescriptor = beanInfo.getElementDescriptor();
221             if ( elementDescriptor != null ) {
222                 context = context.newContext( bean );
223                 if ( qualifiedName == null ) {
224                     qualifiedName = elementDescriptor.getQualifiedName();
225                 }
226                 if ( namespaceUri == null ) {
227                     namespaceUri = elementDescriptor.getURI();
228                 }
229                 if ( localName == null ) {
230                     localName = elementDescriptor.getLocalName();
231                 }
232 
233                 String ref = null;
234                 String id = null;
235                 
236                 // only give id's to non-primatives
237                 if ( elementDescriptor.isPrimitiveType() ) {
238                     // write without an id
239                     writeElement( 
240                         namespaceUri,
241                         localName,
242                         qualifiedName, 
243                         elementDescriptor, 
244                         context );
245                         
246                 } else {
247                     pushBean ( context.getBean() );
248                     if ( getBindingConfiguration().getMapIDs() ) {
249                         ref = (String) idMap.get( context.getBean() );
250                     }
251                     if ( ref == null ) {
252                         // this is the first time that this bean has be written
253                         AttributeDescriptor idAttribute = beanInfo.getIDAttribute();
254                         if (idAttribute == null) {
255                             // use a generated id
256                             id = idGenerator.nextId();
257                             idMap.put( bean, id );
258                             
259                             if ( getBindingConfiguration().getMapIDs() ) {
260                                 // write element with id
261                                 writeElement(
262                                     namespaceUri,
263                                     localName,
264                                     qualifiedName, 
265                                     elementDescriptor, 
266                                     context , 
267                                     beanInfo.getIDAttributeName(),
268                                     id);
269                                     
270                             } else {    
271                                 // write element without ID
272                                 writeElement( 
273                                     namespaceUri,
274                                     localName,
275                                     qualifiedName, 
276                                     elementDescriptor, 
277                                     context );
278                             }
279                                                         
280                         } else {
281                             // use id from bean property
282                             // it's up to the user to ensure uniqueness
283                             // XXX should we trap nulls?
284                             Object exp = idAttribute.getTextExpression().evaluate( context );
285                             if (exp == null) {
286                                 // we'll use a random id
287                                 log.debug("Using random id");
288                                 id = idGenerator.nextId();
289                                 
290                             } else {
291                                 // convert to string
292                                 id = exp.toString();
293                             }
294                             idMap.put( bean, id);
295                             
296                             // the ID attribute should be written automatically
297                             writeElement( 
298                                 namespaceUri,
299                                 localName,
300                                 qualifiedName, 
301                                 elementDescriptor, 
302                                 context );
303                         }
304                     } else {
305                         
306                         if ( !ignoreElement( elementDescriptor, context )) {
307                             // we've already written this bean so write an IDREF
308                             writeIDREFElement( 
309                                             elementDescriptor,
310                                             namespaceUri,
311                                             localName,
312                                             qualifiedName,  
313                                             beanInfo.getIDREFAttributeName(), 
314                                             ref);
315                         }
316                     }
317                     popBean();
318                 }
319             }
320         }
321         
322         log.trace( "Finished writing bean graph." );
323     }
324     
325     /*** 
326       * Get <code>IDGenerator</code> implementation used to 
327       * generate <code>ID</code> attribute values .
328       *
329       * @return implementation used for <code>ID</code> attribute generation
330       */
331     public IDGenerator getIdGenerator() {
332         return idGenerator;
333     }
334     
335     /*** 
336       * Set <code>IDGenerator</code> implementation 
337       * used to generate <code>ID</code> attribute values.
338       * This property can be used to customize the algorithm used for generation.
339       *
340       * @param idGenerator use this implementation for <code>ID</code> attribute generation
341       */
342     public void setIdGenerator(IDGenerator idGenerator) {
343         this.idGenerator = idGenerator;
344     }
345     
346     /***
347      * Gets the dynamic configuration setting to be used for bean reading.
348      * @return the BindingConfiguration settings, not null
349      * @since 0.5
350      */
351     public BindingConfiguration getBindingConfiguration() {
352         return bindingConfiguration;
353     }
354     
355     /***
356      * Sets the dynamic configuration setting to be used for bean reading.
357      * @param bindingConfiguration the BindingConfiguration settings, not null
358      * @since 0.5
359      */
360     public void setBindingConfiguration(BindingConfiguration bindingConfiguration) {
361         this.bindingConfiguration = bindingConfiguration;
362     }
363     
364     /*** 
365      * <p>Should generated <code>ID</code> attribute values be added to the elements?</p>
366      * 
367      * <p>If IDs are not being written then if a cycle is encountered in the bean graph, 
368      * then a {@link CyclicReferenceException} will be thrown by the write method.</p>
369      * 
370      * @return true if <code>ID</code> and <code>IDREF</code> attributes are to be written
371      * @deprecated 0.5 use {@link BindingConfiguration#getMapIDs}
372      */
373     public boolean getWriteIDs() {
374         return getBindingConfiguration().getMapIDs();
375     }
376 
377     /*** 
378      * Set whether generated <code>ID</code> attribute values should be added to the elements 
379      * If this property is set to false, then <code>CyclicReferenceException</code> 
380      * will be thrown whenever a cyclic occurs in the bean graph.
381      *
382      * @param writeIDs true if <code>ID</code>'s and <code>IDREF</code>'s should be written
383      * @deprecated 0.5 use {@link BindingConfiguration#setMapIDs}
384      */
385     public void setWriteIDs(boolean writeIDs) {
386         getBindingConfiguration().setMapIDs( writeIDs );
387     }
388     
389     /***
390      * <p>Gets whether empty elements should be written into the output.</p>
391      *
392      * <p>An empty element is one that has no attributes, no child elements 
393      * and no body text.
394      * For example, <code>&lt;element/&gt;</code> is an empty element but
395      * <code>&lt;element attr='value'/&gt;</code> is not.</p>
396      *
397      * @return true if empty elements will be written into the output
398      * @since 0.5
399      */
400     public boolean getWriteEmptyElements() {
401         return writeEmptyElements;
402     }
403     
404     /***
405      * <p>Sets whether empty elements should be written into the output.</p>
406      *
407      * <p>An empty element is one that has no attributes, no child elements 
408      * and no body text.
409      * For example, <code>&lt;element/&gt;</code> is an empty element but
410      * <code>&lt;element attr='value'/&gt;</code> is not.
411      *
412      * @param writeEmptyElements true if empty elements should be written into the output 
413      * @since 0.5
414      */
415     public void setWriteEmptyElements(boolean writeEmptyElements) {
416         this.writeEmptyElements = writeEmptyElements;
417     }
418 
419     /***
420      * <p>Gets the introspector used.</p>
421      *
422      * <p>The {@link XMLBeanInfo} used to map each bean is 
423      * created by the <code>XMLIntrospector</code>.
424      * One way in which the mapping can be customized is 
425      * by altering the <code>XMLIntrospector</code>. </p>
426      *
427      * @return the <code>XMLIntrospector</code> used for introspection
428      */
429     public XMLIntrospector getXMLIntrospector() {
430         return introspector;
431     }
432     
433 
434     /***
435      * <p>Sets the introspector to be used.</p>
436      *
437      * <p>The {@link XMLBeanInfo} used to map each bean is 
438      * created by the <code>XMLIntrospector</code>.
439      * One way in which the mapping can be customized is by 
440      * altering the <code>XMLIntrospector</code>. </p>
441      *
442      * @param introspector use this introspector
443      */
444     public void  setXMLIntrospector(XMLIntrospector introspector) {
445         this.introspector = introspector;
446     }
447 
448     /***
449      * <p>Gets the current logging implementation.</p>
450      *
451      * @return the <code>Log</code> implementation which this class logs to
452      */ 
453     public final Log getAbstractBeanWriterLog() {
454         return log;
455     }
456 
457     /***
458      * <p> Set the current logging implementation. </p>
459      *
460      * @param log <code>Log</code> implementation to use
461      */ 
462     public final void setAbstractBeanWriterLog(Log log) {
463         this.log = log;
464     }
465         
466     // SAX-style methods
467     //-------------------------------------------------------------------------    
468         
469     /***
470      * Writes the start tag for an element.
471      *
472      * @param uri the element's namespace uri
473      * @param localName the element's local name 
474      * @param qName the element's qualified name
475      * @param attr the element's attributes
476      *
477      * @throws IOException if an IO problem occurs during writing
478      * @throws SAXException if an SAX problem occurs during writing 
479      * @since 0.5
480      */
481     protected void startElement(
482                                 WriteContext context,
483                                 String uri, 
484                                 String localName, 
485                                 String qName, 
486                                 Attributes attr)
487                                     throws
488                                         IOException,
489                                         SAXException {
490         // for backwards compatbility call older methods
491         startElement(uri, localName, qName, attr);                                    
492     }
493     
494     /***
495      * Writes the end tag for an element
496      *
497      * @param uri the element's namespace uri
498      * @param localName the element's local name 
499      * @param qName the element's qualified name
500      *
501      * @throws IOException if an IO problem occurs during writing
502      * @throws SAXException if an SAX problem occurs during writing 
503      * @since 0.5
504      */
505     protected void endElement(
506                                 WriteContext context,
507                                 String uri, 
508                                 String localName, 
509                                 String qName)
510                                     throws
511                                         IOException,
512                                         SAXException {
513         // for backwards compatibility call older interface
514         endElement(uri, localName, qName);                                    
515     }
516     
517     /*** 
518      * Writes body text
519      *
520      * @param text the body text to be written
521      *
522      * @throws IOException if an IO problem occurs during writing
523      * @throws SAXException if an SAX problem occurs during writing 
524      * @since 0.5
525      */
526     protected void bodyText(WriteContext context, String text) 
527                                 throws IOException, SAXException {
528         // for backwards compatibility call older interface
529         bodyText(text);                            
530     }
531         
532     // Older SAX-style methods
533     //-------------------------------------------------------------------------    
534         
535     /***
536      * Writes the start tag for an element.
537      *
538      * @param uri the element's namespace uri
539      * @param localName the element's local name 
540      * @param qName the element's qualified name
541      * @param attr the element's attributes
542      *
543      * @throws IOException if an IO problem occurs during writing
544      * @throws SAXException if an SAX problem occurs during writing 
545      * @deprecated 0.5 use {@link #startElement(WriteContext, String, String, String, Attributes)}
546      */
547     protected void startElement(
548                                 String uri, 
549                                 String localName, 
550                                 String qName, 
551                                 Attributes attr)
552                                     throws
553                                         IOException,
554                                         SAXException {}
555     
556     /***
557      * Writes the end tag for an element
558      *
559      * @param uri the element's namespace uri
560      * @param localName the element's local name 
561      * @param qName the element's qualified name
562      *
563      * @throws IOException if an IO problem occurs during writing
564      * @throws SAXException if an SAX problem occurs during writing 
565      * @deprecated 0.5 use {@link #endElement(WriteContext, String, String, String)}
566      */
567     protected void endElement(
568                                 String uri, 
569                                 String localName, 
570                                 String qName)
571                                     throws
572                                         IOException,
573                                         SAXException {}
574     
575     /*** 
576      * Writes body text
577      *
578      * @param text the body text to be written
579      *
580      * @throws IOException if an IO problem occurs during writing
581      * @throws SAXException if an SAX problem occurs during writing 
582      * @deprecated 0.5 use {@link #bodyText(WriteContext, String)}
583      */
584     protected void bodyText(String text) throws IOException, SAXException {}
585     
586     // Implementation methods
587     //-------------------------------------------------------------------------    
588 
589     /*** 
590      * Writes the given element 
591      *
592      * @param namespaceUri the namespace uri
593      * @param localName the local name
594      * @param qualifiedName qualified name to use for the element
595      * @param elementDescriptor the <code>ElementDescriptor</code> describing the element
596      * @param context the <code>Context</code> to use to evaluate the bean expressions
597      * @throws IOException if an IO problem occurs during writing
598      * @throws SAXException if an SAX problem occurs during writing 
599      * @throws IntrospectionException if a java beans introspection problem occurs
600      */
601     private void writeElement(
602                             String namespaceUri,
603                             String localName,
604                             String qualifiedName, 
605                             ElementDescriptor elementDescriptor, 
606                             Context context ) 
607                                 throws 
608                                     IOException, 
609                                     SAXException,
610                                     IntrospectionException {
611         if( log.isTraceEnabled() ) {
612             log.trace( "Writing: " + qualifiedName + " element: " + elementDescriptor );
613         }
614                 
615         if ( !ignoreElement( elementDescriptor, context )) {
616             if ( log.isTraceEnabled() ) {
617                 log.trace( "Element " + elementDescriptor + " is empty." );
618             }
619         
620             if (elementDescriptor.isWrapCollectionsInElement()) {
621                 writeContext.setCurrentDescriptor(elementDescriptor);
622                 startElement( 
623                             writeContext,
624                             namespaceUri, 
625                             localName, 
626                             qualifiedName,
627                             new ElementAttributes( elementDescriptor, context ));
628             }
629     
630             writeElementContent( elementDescriptor, context ) ;
631             if ( elementDescriptor.isWrapCollectionsInElement() ) {
632                 writeContext.setCurrentDescriptor(elementDescriptor);
633                 endElement( writeContext, namespaceUri, localName, qualifiedName );
634             }
635         }
636     }
637     
638     /*** 
639      * Writes the given element adding an ID attribute 
640      *
641      * @param namespaceUri the namespace uri
642      * @param localName the local name
643      * @param qualifiedName the qualified name
644      * @param elementDescriptor the ElementDescriptor describing this element
645      * @param context the context being evaliated against
646      * @param idAttribute the qualified name of the <code>ID</code> attribute 
647      * @param idValue the value for the <code>ID</code> attribute 
648      * @throws IOException if an IO problem occurs during writing
649      * @throws SAXException if an SAX problem occurs during writing 
650      * @throws IntrospectionException if a java beans introspection problem occurs
651      */
652     private void writeElement( 
653                             String namespaceUri,
654                             String localName,
655                             String qualifiedName, 
656                             ElementDescriptor elementDescriptor, 
657                             Context context,
658                             String idAttribute,
659                             String idValue ) 
660                                 throws 
661                                     IOException, 
662                                     SAXException,
663                                     IntrospectionException {
664                    
665         if ( !ignoreElement( elementDescriptor, context ) ) {
666             writeContext.setCurrentDescriptor(elementDescriptor);
667             startElement( 
668                         writeContext,
669                         namespaceUri, 
670                         localName, 
671                         qualifiedName,
672                         new ElementAttributes( 
673                                                 elementDescriptor, 
674                                                 context, 
675                                                 idAttribute, 
676                                                 idValue ));
677     
678             writeElementContent( elementDescriptor, context ) ;
679             writeContext.setCurrentDescriptor(elementDescriptor);
680             endElement( writeContext, namespaceUri, localName, qualifiedName );
681 
682         } else if ( log.isTraceEnabled() ) {
683             log.trace( "Element " + qualifiedName + " is empty." );
684         }
685     }
686     
687 
688     /***
689      * Write attributes, child elements and element end 
690      * 
691      * @param uri the element namespace uri 
692      * @param localName the local name of the element
693      * @param qualifiedName the qualified name of the element
694      * @param elementDescriptor the descriptor for this element
695      * @param context evaluate against this context
696      * @throws IOException if an IO problem occurs during writing
697      * @throws SAXException if an SAX problem occurs during writing 
698      * @throws IntrospectionException if a java beans introspection problem occurs
699      */
700     private void writeRestOfElement( 
701                             String uri,
702                             String localName,
703                             String qualifiedName, 
704                             ElementDescriptor elementDescriptor, 
705                             Context context ) 
706                                 throws 
707                                     IOException, 
708                                     SAXException,
709                                     IntrospectionException {
710 
711         if ( elementDescriptor.isWrapCollectionsInElement() ) {
712             writeAttributes( elementDescriptor, context );
713         }
714 
715         writeElementContent( elementDescriptor, context );
716         if ( elementDescriptor.isWrapCollectionsInElement() ) {
717             endElement( writeContext, uri, localName, qualifiedName );
718         }
719     }
720 
721     /***
722      * Writes an element with a <code>IDREF</code> attribute 
723      *
724      * @param uri the namespace uri
725      * @param localName the local name
726      * @param qualifiedName of the element with <code>IDREF</code> attribute 
727      * @param idrefAttributeName the qualified name of the <code>IDREF</code> attribute 
728      * @param idrefAttributeValue the value for the <code>IDREF</code> attribute 
729      * @throws IOException if an IO problem occurs during writing
730      * @throws SAXException if an SAX problem occurs during writing 
731      * @throws IntrospectionException if a java beans introspection problem occurs
732      */
733     private void writeIDREFElement( 
734                                     ElementDescriptor elementDescriptor,
735                                     String uri,
736                                     String localName,
737                                     String qualifiedName, 
738                                     String idrefAttributeName,
739                                     String idrefAttributeValue ) 
740                                         throws 
741                                             IOException, 
742                                             SAXException,
743                                             IntrospectionException {
744 
745         
746         
747         // write IDREF element
748         AttributesImpl attributes = new AttributesImpl();
749         // XXX for the moment, assign IDREF to default namespace
750         attributes.addAttribute( 
751                                 "",
752                                 idrefAttributeName, 
753                                 idrefAttributeName,
754                                 "IDREF",
755                                 idrefAttributeValue);
756         writeContext.setCurrentDescriptor(elementDescriptor);
757         startElement( writeContext, uri, localName, qualifiedName, attributes);        
758         endElement( writeContext, uri, localName, qualifiedName );
759     }
760     
761     /*** 
762      * Writes the element content.
763      *
764      * @param elementDescriptor the <code>ElementDescriptor</code> to write as xml 
765      * @param context the <code>Context</code> to use to evaluate the bean expressions
766      * 
767      * @throws IOException if an IO problem occurs during writing
768      * @throws SAXException if an SAX problem occurs during writing 
769      * @throws IntrospectionException if a java beans introspection problem occurs
770      */
771     private void writeElementContent( 
772                         ElementDescriptor elementDescriptor, 
773                         Context context ) 
774                             throws 
775                                 IOException, 
776                                 SAXException,
777                                 IntrospectionException {     
778         writeContext.setCurrentDescriptor( elementDescriptor );              
779         Descriptor[] childDescriptors = elementDescriptor.getContentDescriptors();
780         if ( childDescriptors != null && childDescriptors.length > 0 ) {
781             // process child elements
782             for ( int i = 0, size = childDescriptors.length; i < size; i++ ) {
783                 if (childDescriptors[i] instanceof ElementDescriptor) {
784                     // Element content
785                     ElementDescriptor childDescriptor = (ElementDescriptor) childDescriptors[i];
786                     Context childContext = context;
787                     Expression childExpression = childDescriptor.getContextExpression();
788                     if ( childExpression != null ) {
789                         Object childBean = childExpression.evaluate( context );
790                         if ( childBean != null ) {
791                             String qualifiedName = childDescriptor.getQualifiedName();
792                             String namespaceUri = childDescriptor.getURI();
793                             String localName = childDescriptor.getLocalName();
794                             // XXXX: should we handle nulls better
795                             if ( childBean instanceof Iterator ) {
796                                 for ( Iterator iter = (Iterator) childBean; iter.hasNext(); ) {
797                                     Object object = iter.next();
798                                     if (object == null) {
799                                         continue;
800                                     }
801                                     writeBean( 
802                                             namespaceUri, 
803                                             localName, 
804                                             qualifiedName, 
805                                             object, 
806                                             context );
807                                 }
808                             } else {
809                                 writeBean( 
810                                             namespaceUri, 
811                                             localName, 
812                                             qualifiedName, 
813                                             childBean, 
814                                             context );
815                             }
816                         }                    
817                     } else {
818                         writeElement(
819                                     childDescriptor.getURI(), 
820                                     childDescriptor.getLocalName(), 
821                                     childDescriptor.getQualifiedName(), 
822                                     childDescriptor, 
823                                     childContext );
824                     }
825                 } else {
826                     // Mixed text content
827                     // evaluate the body text 
828                     Expression expression = childDescriptors[i].getTextExpression();
829                     if ( expression != null ) {
830                         Object value = expression.evaluate( context );
831                         String text = convertToString( 
832                                                         value, 
833                                                         childDescriptors[i], 
834                                                         context );
835                         if ( text != null && text.length() > 0 ) {;
836                             bodyText( writeContext, text );
837                         }               
838                     }
839                 }
840             }
841         } else {
842             // evaluate the body text 
843             Expression expression = elementDescriptor.getTextExpression();
844             if ( expression != null ) {
845                 Object value = expression.evaluate( context );
846                 String text = convertToString( value, elementDescriptor, context );
847                 if ( text != null && text.length() > 0 ) {
848                     bodyText( writeContext, text );
849                 }
850             }
851         }
852     }
853 
854     /***
855      * Pushes the bean onto the ancestry stack.
856      * If IDs are not being written, then check for cyclic references.
857      *
858      * @param bean push this bean onto the ancester stack
859      */
860     protected void pushBean( Object bean ) {
861         // check that we don't have a cyclic reference when we're not writing IDs
862         if ( !getBindingConfiguration().getMapIDs() ) {
863             Iterator it = beanStack.iterator();
864             while ( it.hasNext() ) {
865                 Object next = it.next();
866                 // use absolute equality rather than equals
867                 // we're only really bothered if objects are actually the same
868                 if ( bean == next ) {
869                     if ( log.isDebugEnabled() ) {
870                         log.debug("Element stack: ");
871                         Iterator debugStack = beanStack.iterator();
872                         while ( debugStack.hasNext() ) {
873                             log.debug(debugStack.next());
874                         }
875                     }
876                     log.error("Cyclic reference at bean: " + bean);
877                     throw new CyclicReferenceException();
878                 }
879             }
880         }
881         if (log.isTraceEnabled()) {
882             log.trace( "Pushing onto object stack: " + bean );
883         }
884         beanStack.push( bean );
885     }
886     
887     /*** 
888      * Pops the top bean off from the ancestry stack 
889      *
890      * @return the last object pushed onto the ancester stack
891      */
892     protected Object popBean() {
893         Object bean = beanStack.pop();
894         if (log.isTraceEnabled()) {
895             log.trace( "Popped from object stack: " + bean );
896         }
897         return bean;
898     }
899     
900     /*** 
901      * Should this element (and children) be written out?
902      *
903      * @param descriptor the <code>ElementDescriptor</code> to evaluate
904      * @param context the <code>Context</code> against which the element will be evaluated
905      * @return true if this element should be written out
906      */
907     private boolean ignoreElement( ElementDescriptor descriptor, Context context ) {
908         if ( ! getWriteEmptyElements() ) {
909             return isEmptyElement( descriptor, context );
910         }
911         return false;
912     }
913     
914     /*** 
915      * <p>Will evaluating this element against this context result in an empty element?</p>
916      *
917      * <p>An empty element is one that has no attributes, no child elements 
918      * and no body text.
919      * For example, <code>&lt;element/&gt;</code> is an empty element but
920      * <code>&lt;element attr='value'/&gt;</code> is not.</p>
921      *
922      * @param descriptor the <code>ElementDescriptor</code> to evaluate
923      * @param context the <code>Context</code> against which the element will be evaluated
924      * @return true if this element is empty on evaluation
925      */
926     private boolean isEmptyElement( ElementDescriptor descriptor, Context context ) {
927         if ( log.isTraceEnabled() ) {
928             log.trace( "Is " + descriptor + " empty?" );
929         }
930         
931         // an element which has attributes is not empty
932         if ( descriptor.hasAttributes() ) {
933             log.trace( "Element has attributes." );
934             return false;
935         }
936         
937         // an element is not empty if it has a non-empty body
938         Expression expression = descriptor.getTextExpression();
939         if ( expression != null ) {
940             Object value = expression.evaluate( context );
941             String text = convertToString( value, descriptor, context );
942             if ( text != null && text.length() > 0 ) {
943                 log.trace( "Element has body text which isn't empty." );
944                 return false;
945             }
946         }
947         
948         // always write out loops - even when they have no elements
949         if ( XMLIntrospectorHelper.isLoopType( descriptor.getPropertyType() ) ) {
950             log.trace("Loop type so not empty.");
951             return false;
952         }
953         
954         // now test child elements
955         // an element is empty if it has no non-empty child elements
956         if ( descriptor.hasChildren() ) {
957             for ( int i=0, size=descriptor.getElementDescriptors().length; i<size; i++ ) {
958                 if ( ! isEmptyElement( descriptor.getElementDescriptors()[i], context ) ) {
959                     log.trace( "Element has child which isn't empty." );
960                     return false;
961                 }
962             }
963         }
964         
965         log.trace( "Element is empty." );
966         return true;
967     }
968     
969     
970     
971     
972     /***
973      * Attributes backed by attribute descriptors
974      */
975     private class ElementAttributes implements Attributes {
976         /*** Attribute descriptors backing the <code>Attributes</code> */
977         private AttributeDescriptor[] attributes;
978         /*** Context to be evaluated when finding values */
979         private Context context;
980         /*** ID attribute value */
981         private String idValue;
982         /*** ID attribute name */
983         private String idAttributeName;
984         
985         
986         /*** 
987          * Construct attributes for element and context.
988          *
989          * @param descriptor the <code>ElementDescriptor</code> describing the element
990          * @param context evaluate against this context
991          */
992         ElementAttributes( ElementDescriptor descriptor, Context context ) {
993             attributes = descriptor.getAttributeDescriptors();
994             this.context = context;
995         }
996         
997         /*** 
998          * Construct attributes for element and context.
999          *
1000          * @param descriptor the <code>ElementDescriptor</code> describing the element
1001          * @param context evaluate against this context
1002          * @param idAttributeName the name of the id attribute 
1003          * @param idValue the ID attribute value
1004          */
1005         ElementAttributes( 
1006                             ElementDescriptor descriptor, 
1007                             Context context, 
1008                             String idAttributeName,
1009                             String idValue) {
1010             attributes = descriptor.getAttributeDescriptors();
1011             this.context = context;
1012             this.idValue = idValue;
1013             this.idAttributeName = idAttributeName;
1014         }
1015         
1016         /***
1017          * Gets the index of an attribute by qualified name.
1018          * 
1019          * @param qName the qualified name of the attribute
1020          * @return the index of the attribute - or -1 if there is no matching attribute
1021          */
1022         public int getIndex( String qName ) {
1023             for ( int i=0; i<attributes.length; i++ ) {
1024                 if (attributes[i].getQualifiedName() != null 
1025                        && attributes[i].getQualifiedName().equals( qName )) {
1026                     return i;
1027                 }
1028             }
1029             return -1;
1030         }
1031         
1032         /***
1033          * Gets the index of an attribute by namespace name.
1034          *
1035          * @param uri the namespace uri of the attribute
1036          * @param localName the local name of the attribute
1037          * @return the index of the attribute - or -1 if there is no matching attribute
1038          */
1039         public int getIndex( String uri, String localName ) {
1040             for ( int i=0; i<attributes.length; i++ ) {
1041                 if (
1042                         attributes[i].getURI() != null 
1043                         && attributes[i].getURI().equals(uri)
1044                         && attributes[i].getLocalName() != null 
1045                         && attributes[i].getURI().equals(localName)) {
1046                     return i;
1047                 }
1048             } 
1049             
1050             return -1;
1051         }
1052         
1053         /***
1054          * Gets the number of attributes in the list.
1055          *
1056          * @return the number of attributes in this list
1057          */
1058         public int getLength() {
1059             return attributes.length;
1060         }
1061         
1062         /*** 
1063          * Gets the local name by index.
1064          * 
1065          * @param index the attribute index (zero based)
1066          * @return the attribute local name - or null if the index is out of range
1067          */
1068         public String getLocalName( int index ) {
1069             if ( indexInRange( index ) ) {
1070                 return attributes[index].getLocalName();
1071             }
1072             
1073             return null;
1074         }
1075         
1076         /***
1077          * Gets the qualified name by index.
1078          *
1079          * @param index the attribute index (zero based)
1080          * @return the qualified name of the element - or null if the index is our of range
1081          */
1082         public String getQName( int index ) {
1083             if ( indexInRange( index ) ) {
1084                 return attributes[index].getQualifiedName();
1085             }
1086             
1087             return null;
1088         }
1089         
1090         /***
1091          * Gets the attribute SAX type by namespace name.
1092          *
1093          * @param index the attribute index (zero based)
1094          * @return the attribute type (as a string) or null if the index is out of range
1095          */
1096         public String getType( int index ) {
1097             if ( indexInRange( index ) ) {
1098                 return "CDATA";
1099             }
1100             return null;
1101         }
1102         
1103         /***
1104          * Gets the attribute SAX type by qualified name.
1105          *
1106          * @param qName the qualified name of the attribute
1107          * @return the attribute type (as a string) or null if the attribute is not in the list
1108          */
1109         public String getType( String qName ) {
1110             return getType( getIndex( qName ) );
1111         }
1112         
1113         /***
1114          * Gets the attribute SAX type by namespace name.
1115          *
1116          * @param uri the namespace uri of the attribute
1117          * @param localName the local name of the attribute
1118          * @return the attribute type (as a string) or null if the attribute is not in the list
1119          */
1120         public String getType( String uri, String localName ) {
1121             return getType( getIndex( uri, localName ));
1122         }
1123         
1124         /***
1125          * Gets the namespace URI for attribute at the given index.
1126          *
1127          * @param index the attribute index (zero-based)
1128          * @return the namespace URI (empty string if none is available) 
1129          * or null if the index is out of range
1130          */
1131         public String getURI( int index ) {
1132             if ( indexInRange( index ) ) {
1133                 return attributes[index].getURI();
1134             }
1135             return null;
1136         }
1137         
1138         /***
1139          * Gets the value for the attribute at given index.
1140          * 
1141          * @param index the attribute index (zero based)
1142          * @return the attribute value or null if the index is out of range
1143          * @todo add value caching
1144          */
1145         public String getValue( int index ) {
1146             if ( indexInRange( index )) {
1147                 if (
1148                     idAttributeName != null 
1149                     && idAttributeName.equals(attributes[index].getLocalName())) {
1150                         
1151                     return idValue;
1152                     
1153                 } else {
1154                     Expression expression = attributes[index].getTextExpression();
1155                     if ( expression != null ) {
1156                         Object value = expression.evaluate( context );
1157                         return convertToString( value, attributes[index], context );
1158                     }
1159                 }
1160                 return "";
1161             }
1162             return null;
1163         }
1164         
1165         /***
1166          * Gets the value for the attribute by qualified name.
1167          * 
1168          * @param qName the qualified name 
1169          * @return the attribute value or null if there are no attributes 
1170          * with the given qualified name
1171          * @todo add value caching
1172          */
1173         public String getValue( String qName ) {
1174             return getValue( getIndex( qName ) );
1175         }
1176         
1177         /***
1178          * Gets the value for the attribute by namespace name.
1179          * 
1180          * @param uri the namespace URI of the attribute
1181          * @param localName the local name of the attribute
1182          * @return the attribute value or null if there are not attributes 
1183          * with the given namespace and local name
1184          * @todo add value caching
1185          */
1186         public String getValue( String uri, String localName ) {
1187             return getValue( getIndex( uri, localName ) );
1188         }
1189         
1190         /***
1191          * Is the given index within the range of the attribute list
1192          *
1193          * @param index the index whose range will be checked
1194          * @return true if the index with within the range of the attribute list
1195          */
1196         private boolean indexInRange( int index ) {
1197             return ( index >= 0 && index < attributes.length );
1198         }
1199     }
1200     
1201     
1202     // OLD API (DEPRECATED)
1203     // --------------------------------------------------------------------------------------
1204     
1205     
1206     /*** 
1207      * Get the indentation for the current element. 
1208      * Used for pretty priting.
1209      *
1210      * @return the amount that the current element is indented
1211      * @deprecated 0.5 replaced by new SAX inspired API
1212      */
1213     protected int getIndentLevel() {
1214         return 0;
1215     }
1216     
1217     // Expression methods
1218     //-------------------------------------------------------------------------    
1219 
1220     /*** 
1221      * Express an element tag start using given qualified name.
1222      *
1223      * @param qualifiedName the qualified name of the element to be expressed
1224      * @throws IOException if an IO problem occurs during writing
1225      * @throws SAXException if an SAX problem occurs during writing 
1226      * @deprecated 0.5 replaced by new SAX inspired API
1227      */
1228     protected void expressElementStart(String qualifiedName) 
1229                                         throws IOException, SAXException {
1230         // do nothing
1231     }
1232                                         
1233     /*** 
1234      * Express an element tag start using given qualified name.
1235      *
1236      * @param uri the namespace uri 
1237      * @param localName the local name for this element
1238      * @param qualifiedName the qualified name of the element to be expressed
1239      * @throws IOException if an IO problem occurs during writing
1240      * @throws SAXException if an SAX problem occurs during writing 
1241      * @deprecated 0.5 replaced by new SAX inspired API
1242      */
1243     protected void expressElementStart(String uri, String localName, String qualifiedName) 
1244                                         throws IOException, SAXException {
1245         expressElementStart( qualifiedName );
1246     }
1247     
1248      /***
1249      * Express a closing tag.
1250      *
1251      * @throws IOException if an IO problem occurs during writing
1252      * @throws SAXException if an SAX problem occurs during writing 
1253      * @deprecated 0.5 replaced by new SAX inspired API
1254      */
1255     protected void expressTagClose() throws IOException, SAXException {}
1256     
1257     /*** 
1258      * Express an element end tag (with given name) 
1259      *
1260      * @param qualifiedName the qualified name for the element to be closed
1261      *
1262      * @throws IOException if an IO problem occurs during writing
1263      * @throws SAXException if an SAX problem occurs during writing
1264      * @deprecated 0.5 replaced by new SAX inspired API
1265      */
1266     protected void expressElementEnd(String qualifiedName) 
1267                                               throws IOException, SAXException {
1268         // do nothing
1269     }
1270     
1271     /*** 
1272      * Express an element end tag (with given name) 
1273      *
1274      * @param uri the namespace uri of the element close tag
1275      * @param localName the local name of the element close tag
1276      * @param qualifiedName the qualified name for the element to be closed
1277      *
1278      * @throws IOException if an IO problem occurs during writing
1279      * @throws SAXException if an SAX problem occurs during writing
1280      * @deprecated 0.5 replaced by new SAX inspired API
1281      */
1282     protected void expressElementEnd(
1283                                                 String uri,
1284                                                 String localName,
1285                                                 String qualifiedName) 
1286                                                     throws 
1287                                                         IOException, 
1288                                                         SAXException {
1289         expressElementEnd(qualifiedName);
1290     }
1291                                               
1292     
1293     /*** 
1294      * Express an empty element end.
1295      * 
1296      * @throws IOException if an IO problem occurs during writing
1297      * @throws SAXException if an SAX problem occurs during writing
1298      * @deprecated 0.5 replaced by new SAX inspired API
1299      */
1300     protected void expressElementEnd() throws IOException, SAXException {}
1301 
1302     /*** 
1303      * Express body text 
1304      *
1305      * @param text the string to write out as the body of the current element
1306      * 
1307      * @throws IOException if an IO problem occurs during writing
1308      * @throws SAXException if an SAX problem occurs during writing
1309      * @deprecated 0.5 replaced by new SAX inspired API
1310      */
1311     protected void expressBodyText(String text) throws IOException, SAXException {}
1312     
1313     /*** 
1314      * Express an attribute 
1315      *
1316      * @param qualifiedName the qualified name of the attribute
1317      * @param value the attribute value
1318      * @throws IOException if an IO problem occurs during writing
1319      * @throws SAXException if an SAX problem occurs during writing
1320      * @deprecated 0.5 replaced by new SAX inspired API
1321      */
1322     protected void expressAttribute(
1323                                 String qualifiedName, 
1324                                 String value) 
1325                                     throws
1326                                         IOException, 
1327                                         SAXException {
1328         // Do nothing
1329     }
1330 
1331     /*** 
1332      * Express an attribute 
1333      *
1334      * @param namespaceUri the namespace uri
1335      * @param localName the local name
1336      * @param qualifiedName the qualified name of the attribute
1337      * @param value the attribute value
1338      * @throws IOException if an IO problem occurs during writing
1339      * @throws SAXException if an SAX problem occurs during writing
1340      * @deprecated 0.5 replaced by new SAX inspired API
1341      */
1342     protected void expressAttribute(
1343                                 String namespaceUri,
1344                                 String localName,
1345                                 String qualifiedName, 
1346                                 String value) 
1347                                     throws
1348                                         IOException, 
1349                                         SAXException {
1350         expressAttribute(qualifiedName, value);
1351     }
1352     
1353     
1354     /*** 
1355      * Writes the given element 
1356      *
1357      * @param qualifiedName qualified name to use for the element
1358      * @param elementDescriptor the <code>ElementDescriptor</code> describing the element
1359      * @param context the <code>Context</code> to use to evaluate the bean expressions
1360      * @throws IOException if an IO problem occurs during writing
1361      * @throws SAXException if an SAX problem occurs during writing 
1362      * @throws IntrospectionException if a java beans introspection problem occurs
1363      * @deprecated 0.5 replaced by new SAX inspired API
1364      */
1365     protected void write( 
1366                             String qualifiedName, 
1367                             ElementDescriptor elementDescriptor, 
1368                             Context context ) 
1369                                 throws 
1370                                     IOException, 
1371                                     SAXException,
1372                                     IntrospectionException {
1373         writeElement( "", qualifiedName, qualifiedName, elementDescriptor, context );
1374     }
1375     
1376     /*** 
1377      * Writes the given element adding an ID attribute 
1378      *
1379      * @param qualifiedName qualified name to use for the element
1380      * @param elementDescriptor the <code>ElementDescriptor</code> describing the element
1381      * @param context the <code>Context</code> to use to evaluate the bean expressions
1382      * @param idAttribute the qualified name of the <code>ID</code> attribute 
1383      * @param idValue the value for the <code>ID</code> attribute 
1384      * @throws IOException if an IO problem occurs during writing
1385      * @throws SAXException if an SAX problem occurs during writing 
1386      * @throws IntrospectionException if a java beans introspection problem occurs
1387      * @deprecated 0.5 replaced by new SAX inspired API
1388      */
1389     protected void write( 
1390                             String qualifiedName, 
1391                             ElementDescriptor elementDescriptor, 
1392                             Context context,
1393                             String idAttribute,
1394                             String idValue ) 
1395                                 throws 
1396                                     IOException, 
1397                                     SAXException,
1398                                     IntrospectionException {
1399         writeElement( 
1400                     "", 
1401                     qualifiedName, 
1402                     qualifiedName, 
1403                     elementDescriptor, 
1404                     context, 
1405                     idAttribute, 
1406                     idValue );
1407     }
1408     
1409     /*** 
1410      * Write attributes, child elements and element end 
1411      *
1412      * @param qualifiedName qualified name to use for the element
1413      * @param elementDescriptor the <code>ElementDescriptor</code> describing the element
1414      * @param context the <code>Context</code> to use to evaluate the bean expressions
1415      * @throws IOException if an IO problem occurs during writing
1416      * @throws SAXException if an SAX problem occurs during writing 
1417      * @throws IntrospectionException if a java beans introspection problem occurs
1418      * @deprecated 0.5 replaced by new SAX inspired API
1419      */
1420     protected void writeRestOfElement( 
1421                             String qualifiedName, 
1422                             ElementDescriptor elementDescriptor, 
1423                             Context context ) 
1424                                 throws 
1425                                     IOException, 
1426                                     SAXException,
1427                                     IntrospectionException {
1428         writeRestOfElement( "", qualifiedName, qualifiedName, elementDescriptor, context );
1429     }
1430 
1431     /***
1432      * Writes an element with a <code>IDREF</code> attribute 
1433      *
1434      * @param qualifiedName of the element with <code>IDREF</code> attribute 
1435      * @param idrefAttributeName the qualified name of the <code>IDREF</code> attribute 
1436      * @param idrefAttributeValue the value for the <code>IDREF</code> attribute 
1437      * @throws IOException if an IO problem occurs during writing
1438      * @throws SAXException if an SAX problem occurs during writing 
1439      * @throws IntrospectionException if a java beans introspection problem occurs
1440      * @deprecated 0.5 replaced by new SAX inspired API
1441      */
1442     protected void writeIDREFElement( 
1443                                     String qualifiedName, 
1444                                     String idrefAttributeName,
1445                                     String idrefAttributeValue ) 
1446                                         throws 
1447                                             IOException, 
1448                                             SAXException,
1449                                             IntrospectionException {
1450         // deprecated
1451        AttributesImpl attributes = new AttributesImpl();
1452        attributes.addAttribute( 
1453                                "",
1454                                idrefAttributeName, 
1455                                idrefAttributeName,
1456                                "IDREF",
1457                                idrefAttributeValue);
1458        startElement( "", qualifiedName, qualifiedName, attributes);        
1459        endElement( "", qualifiedName, qualifiedName );
1460     }
1461 
1462         
1463     /*** 
1464      * Writes the element content.
1465      *
1466      * @param elementDescriptor the <code>ElementDescriptor</code> to write as xml 
1467      * @param context the <code>Context</code> to use to evaluate the bean expressions
1468      * @return true if some content was written
1469      * @throws IOException if an IO problem occurs during writing
1470      * @throws SAXException if an SAX problem occurs during writing 
1471      * @throws IntrospectionException if a java beans introspection problem occurs
1472      * @deprecated 0.5 replaced by new SAX inspired API
1473      */
1474     protected boolean writeContent( 
1475                         ElementDescriptor elementDescriptor, 
1476                         Context context ) 
1477                             throws 
1478                                 IOException, 
1479                                 SAXException,
1480                                 IntrospectionException {     
1481         return false;
1482     }
1483 
1484     
1485     /***  
1486      * Writes the attribute declarations 
1487      *
1488      * @param elementDescriptor the <code>ElementDescriptor</code> to be written out as xml
1489      * @param context the <code>Context</code> to use to evaluation bean expressions
1490      * @throws IOException if an IO problem occurs during writing
1491      * @throws SAXException if an SAX problem occurs during writing 
1492      * @deprecated 0.5 replaced by new SAX inspired API
1493      */
1494     protected void writeAttributes( 
1495                     ElementDescriptor elementDescriptor, 
1496                     Context context ) 
1497                         throws 
1498                             IOException, SAXException {
1499         if (!elementDescriptor.isWrapCollectionsInElement()) {
1500             return;
1501         }
1502             
1503         AttributeDescriptor[] attributeDescriptors = elementDescriptor.getAttributeDescriptors();
1504         if ( attributeDescriptors != null ) {
1505             for ( int i = 0, size = attributeDescriptors.length; i < size; i++ ) {
1506                 AttributeDescriptor attributeDescriptor = attributeDescriptors[i];
1507                 writeAttribute( attributeDescriptor, context );
1508             }
1509         }
1510     }
1511 
1512     
1513     /*** 
1514      * Writes an attribute declaration 
1515      *
1516      * @param attributeDescriptor the <code>AttributeDescriptor</code> to be written as xml
1517      * @param context the <code>Context</code> to use to evaluation bean expressions
1518      * @throws IOException if an IO problem occurs during writing
1519      * @throws SAXException if an SAX problem occurs during writing 
1520      * @deprecated 0.5 replaced by new SAX inspired API
1521      */
1522     protected void writeAttribute( 
1523                         AttributeDescriptor attributeDescriptor, 
1524                         Context context ) 
1525                             throws 
1526                                 IOException, SAXException {
1527         Expression expression = attributeDescriptor.getTextExpression();
1528         if ( expression != null ) {
1529             Object value = expression.evaluate( context );
1530             if ( value != null ) {
1531                 String text = value.toString();
1532                 if ( text != null && text.length() > 0 ) {
1533                     expressAttribute(
1534                                     attributeDescriptor.getURI(),
1535                                     attributeDescriptor.getLocalName(),
1536                                     attributeDescriptor.getQualifiedName(), 
1537                                     text);
1538                 }
1539             }                
1540         }
1541     }
1542     /*** 
1543      * Writes a empty line.  
1544      * This implementation does nothing but can be overridden by subclasses.
1545      *
1546      * @throws IOException if the line cannot be written
1547      * @deprecated 0.5 replaced by new SAX inspired API
1548      */
1549     protected void writePrintln() throws IOException {}
1550     
1551     /*** 
1552      * Writes an indentation.
1553      * This implementation does nothing but can be overridden by subclasses.
1554      * 
1555      * @throws IOException if the indent cannot be written
1556      * @deprecated 0.5 replaced by new BeanWriter API
1557      */
1558     protected void writeIndent() throws IOException {}
1559     
1560     /***
1561       * Converts an object to a string.
1562       *
1563       * @param value the Object to represent as a String, possibly null
1564       * @param descriptor writing out this descriptor not null
1565       * @param context not null
1566       * @return String representation, not null
1567       */
1568     private String convertToString( Object value , Descriptor descriptor, Context context ) {
1569         return getBindingConfiguration()
1570             .getObjectStringConverter()
1571                 .objectToString( value, descriptor.getPropertyType(), null, context );
1572     }
1573     
1574     /***
1575       * Factory method for new contexts.
1576       * Ensure that they are correctly configured.
1577       * @param bean make a new Context for this bean
1578       * @return not null
1579       */
1580     private Context makeContext(Object bean) {
1581         return new Context( bean, log, bindingConfiguration );
1582     }
1583     
1584     /***
1585      * Basic mutable implementation of <code>WriteContext</code>.
1586      */
1587     private static class WriteContextImpl extends WriteContext {
1588 
1589         private ElementDescriptor currentDescriptor;
1590 
1591         /***
1592          * @see org.apache.commons.betwixt.io.WriteContext#getCurrentDescriptor()
1593          */
1594         public ElementDescriptor getCurrentDescriptor() {
1595             return currentDescriptor;
1596         }
1597         
1598         /***
1599          * Sets the descriptor for the current element.
1600          * @param currentDescriptor
1601          */
1602         public void setCurrentDescriptor(ElementDescriptor currentDescriptor) {
1603             this.currentDescriptor = currentDescriptor;
1604         }
1605         
1606     }
1607 }