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