View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */ 
17  package org.apache.commons.betwixt.io;
18  
19  import java.beans.IntrospectionException;
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Iterator;
24  
25  import org.apache.commons.betwixt.AttributeDescriptor;
26  import org.apache.commons.betwixt.BindingConfiguration;
27  import org.apache.commons.betwixt.Descriptor;
28  import org.apache.commons.betwixt.ElementDescriptor;
29  import org.apache.commons.betwixt.Options;
30  import org.apache.commons.betwixt.XMLBeanInfo;
31  import org.apache.commons.betwixt.XMLIntrospector;
32  import org.apache.commons.betwixt.expression.Context;
33  import org.apache.commons.betwixt.expression.Expression;
34  import org.apache.commons.betwixt.io.id.SequentialIDGenerator;
35  import org.apache.commons.collections.ArrayStack;
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  import org.xml.sax.Attributes;
39  import org.xml.sax.InputSource;
40  import org.xml.sax.SAXException;
41  import org.xml.sax.helpers.AttributesImpl;
42  
43  /**
44    * <p>Abstract superclass for bean writers.
45    * This class encapsulates the processing logic. 
46    * Subclasses provide implementations for the actual expression of the xml.</p>
47    * <h5>SAX Inspired Writing API</h5>
48    * <p>
49    * This class is intended to be used by subclassing: 
50    * concrete subclasses perform the actual writing by providing
51    * suitable implementations for the following methods inspired 
52    * by <a href='http://www.saxproject.org'>SAX</a>:
53    * </p>
54    * <ul>
55    *     <li> {@link #start} - called when processing begins</li>
56    *     <li> {@link #startElement(WriteContext, String, String, String, Attributes)} 
57    *     - called when the start of an element 
58    *     should be written</li> 
59    *     <li> {@link #bodyText(WriteContext, String)} 
60    *     - called when the start of an element 
61    *     should be written</li> 
62    *     <li> {@link #endElement(WriteContext, String, String, String)} 
63    *     - called when the end of an element 
64    *     should be written</li> 
65    *     <li> {@link #end} - called when processing has been completed</li>
66    * </ul>
67    * <p>
68    * <strong>Note</strong> that this class contains many deprecated 
69    * versions of the writing API. These will be removed soon so care
70    * should be taken to use the latest version.
71    * </p>
72    * <p>
73    * <strong>Note</strong> that this class is designed to be used
74    * in a single threaded environment. When used in multi-threaded
75    * environments, use of a common <code>XMLIntrospector</code>
76    * and pooled writer instances should be considered. 
77    * </p>
78    *
79    * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a>
80    */
81  public abstract class AbstractBeanWriter {
82  
83      /** Introspector used */
84      private XMLIntrospector introspector = new XMLIntrospector();
85  
86      /** Log used for logging (Doh!) */
87      private Log log = LogFactory.getLog( AbstractBeanWriter.class );
88      /** Stack containing beans - used to detect cycles */
89      private ArrayStack beanStack = new ArrayStack();
90      /** Used to generate ID attribute values*/
91      private IDGenerator idGenerator = new SequentialIDGenerator();
92      /** Should empty elements be written out? */
93      private boolean writeEmptyElements = true;
94      /** Dynamic binding configuration settings */
95      private BindingConfiguration bindingConfiguration = new BindingConfiguration();
96      /** <code>WriteContext</code> implementation reused curing writing */
97      private WriteContextImpl writeContext = new WriteContextImpl();
98      /** Collection of namespaces which have already been declared */
99      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>&lt;element/&gt;</code> is an empty element but
521      * <code>&lt;element attr='value'/&gt;</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>&lt;element/&gt;</code> is an empty element but
536      * <code>&lt;element attr='value'/&gt;</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>&lt;element/&gt;</code> is an empty element but
1087      * <code>&lt;element attr='value'/&gt;</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 }