001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */ 
017    package org.apache.commons.betwixt.io;
018    
019    import java.beans.IntrospectionException;
020    import java.io.IOException;
021    import java.util.ArrayList;
022    import java.util.Collection;
023    import java.util.Iterator;
024    
025    import org.apache.commons.betwixt.AttributeDescriptor;
026    import org.apache.commons.betwixt.BindingConfiguration;
027    import org.apache.commons.betwixt.Descriptor;
028    import org.apache.commons.betwixt.ElementDescriptor;
029    import org.apache.commons.betwixt.Options;
030    import org.apache.commons.betwixt.XMLBeanInfo;
031    import org.apache.commons.betwixt.XMLIntrospector;
032    import org.apache.commons.betwixt.expression.Context;
033    import org.apache.commons.betwixt.expression.Expression;
034    import org.apache.commons.betwixt.io.id.SequentialIDGenerator;
035    import org.apache.commons.collections.ArrayStack;
036    import org.apache.commons.logging.Log;
037    import org.apache.commons.logging.LogFactory;
038    import org.xml.sax.Attributes;
039    import org.xml.sax.InputSource;
040    import org.xml.sax.SAXException;
041    import org.xml.sax.helpers.AttributesImpl;
042    
043    /**
044      * <p>Abstract superclass for bean writers.
045      * This class encapsulates the processing logic. 
046      * Subclasses provide implementations for the actual expression of the xml.</p>
047      * <h5>SAX Inspired Writing API</h5>
048      * <p>
049      * This class is intended to be used by subclassing: 
050      * concrete subclasses perform the actual writing by providing
051      * suitable implementations for the following methods inspired 
052      * by <a href='http://www.saxproject.org'>SAX</a>:
053      * </p>
054      * <ul>
055      *     <li> {@link #start} - called when processing begins</li>
056      *     <li> {@link #startElement(WriteContext, String, String, String, Attributes)} 
057      *     - called when the start of an element 
058      *     should be written</li> 
059      *     <li> {@link #bodyText(WriteContext, String)} 
060      *     - called when the start of an element 
061      *     should be written</li> 
062      *     <li> {@link #endElement(WriteContext, String, String, String)} 
063      *     - called when the end of an element 
064      *     should be written</li> 
065      *     <li> {@link #end} - called when processing has been completed</li>
066      * </ul>
067      * <p>
068      * <strong>Note</strong> that this class contains many deprecated 
069      * versions of the writing API. These will be removed soon so care
070      * should be taken to use the latest version.
071      * </p>
072      * <p>
073      * <strong>Note</strong> that this class is designed to be used
074      * in a single threaded environment. When used in multi-threaded
075      * environments, use of a common <code>XMLIntrospector</code>
076      * and pooled writer instances should be considered. 
077      * </p>
078      *
079      * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a>
080      */
081    public abstract class AbstractBeanWriter {
082    
083        /** Introspector used */
084        private XMLIntrospector introspector = new XMLIntrospector();
085    
086        /** Log used for logging (Doh!) */
087        private Log log = LogFactory.getLog( AbstractBeanWriter.class );
088        /** Stack containing beans - used to detect cycles */
089        private ArrayStack beanStack = new ArrayStack();
090        /** Used to generate ID attribute values*/
091        private IDGenerator idGenerator = new SequentialIDGenerator();
092        /** Should empty elements be written out? */
093        private boolean writeEmptyElements = true;
094        /** Dynamic binding configuration settings */
095        private BindingConfiguration bindingConfiguration = new BindingConfiguration();
096        /** <code>WriteContext</code> implementation reused curing writing */
097        private WriteContextImpl writeContext = new WriteContextImpl();
098        /** Collection of namespaces which have already been declared */
099        private Collection namespacesDeclared = new ArrayList();
100        
101        /**
102         * Marks the start of the bean writing.
103         * By default doesn't do anything, but can be used
104         * to do extra start processing 
105         * @throws IOException if an IO problem occurs during writing 
106         * @throws SAXException if an SAX problem occurs during writing 
107         */
108        public void start() throws IOException, SAXException {
109        }
110        
111        /**
112         * Marks the start of the bean writing.
113         * By default doesn't do anything, but can be used
114         * to do extra end processing 
115         * @throws IOException if an IO problem occurs during writing
116         * @throws SAXException if an SAX problem occurs during writing 
117         */
118        
119        public void end() throws IOException, SAXException {
120        }
121            
122        /** 
123         * <p> Writes the given bean to the current stream using the XML introspector.</p>
124         * 
125         * <p> This writes an xml fragment representing the bean to the current stream.</p>
126         *
127         * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
128         * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
129         * setting of the </code>BindingConfiguration</code> is false.</p>
130         *
131         * @throws IOException if an IO problem occurs during writing 
132         * @throws SAXException if an SAX problem occurs during writing  
133         * @throws IntrospectionException if a java beans introspection problem occurs 
134         *
135         * @param bean write out representation of this bean
136         */
137        public void write(Object bean) throws 
138                                            IOException, 
139                                            SAXException, 
140                                            IntrospectionException {
141            if (log.isDebugEnabled()) {
142                log.debug( "Writing bean graph..." );
143                log.debug( bean );
144            }
145            start();
146            writeBean( null, null, null, bean, makeContext( bean ) );
147            end();
148            if (log.isDebugEnabled()) {
149                log.debug( "Finished writing bean graph." );
150            }
151        }
152        
153        /** 
154         * <p>Writes the given bean to the current stream 
155         * using the given <code>qualifiedName</code>.</p>
156         *
157         * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
158         * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
159         * setting of the <code>BindingConfiguration</code> is false.</p>
160         *
161         * @param qualifiedName the string naming root element
162         * @param bean the <code>Object</code> to write out as xml
163         * 
164         * @throws IOException if an IO problem occurs during writing
165         * @throws SAXException if an SAX problem occurs during writing 
166         * @throws IntrospectionException if a java beans introspection problem occurs
167         */
168        public void write(
169                    String qualifiedName, 
170                    Object bean) 
171                        throws 
172                            IOException, 
173                            SAXException,
174                            IntrospectionException {
175            start();
176            writeBean( "", qualifiedName, qualifiedName, bean, makeContext( bean ) );
177            end();
178        }
179        
180        /**
181         * <p>Writes the bean using the mapping specified in the <code>InputSource</code>.
182         * </p><p>
183         * <strong>Note:</strong> that the custom mapping will <em>not</em>
184         * be registered for later use. Please use {@link XMLIntrospector#register}
185         * to register the custom mapping for the class and then call
186         * {@link #write(Object)}.
187         * </p>
188         * @see #write(Object) since the standard notes also apply
189         * @since 0.7
190         * @param bean <code>Object</code> to be written as xml, not null
191         * @param source <code>InputSource/code> containing an xml document
192         * specifying the mapping to be used (in the usual way), not null
193         * @throws IOException
194         * @throws SAXException
195         * @throws IntrospectionException
196         */
197        public void write(Object bean, InputSource source) 
198                                            throws IOException, SAXException, IntrospectionException {
199            writeBean(
200                            null, 
201                            null, 
202                            null, 
203                            bean, 
204                            makeContext( bean ), 
205                            getXMLIntrospector().introspect(bean.getClass(), source));
206        }
207        
208        /** 
209         * <p>Writes the given bean to the current stream 
210         * using the given <code>qualifiedName</code>.</p>
211         *
212         * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
213         * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
214         * setting of the <code>BindingConfiguration</code> is false.</p>
215         *
216         * @param namespaceUri the namespace uri
217         * @param localName the local name
218         * @param qualifiedName the string naming root element
219         * @param introspectedBindType the <code>Class</code> of the bean 
220         * as resolved at introspection time, or null if the type has not been resolved
221         * @param bean the <code>Object</code> to write out as xml
222         * @param context not null
223         * 
224         * @throws IOException if an IO problem occurs during writing
225         * @throws SAXException if an SAX problem occurs during writing 
226         * @throws IntrospectionException if a java beans introspection problem occurs
227         */
228        private void writeBean (
229                    String namespaceUri,
230                    String localName,
231                    String qualifiedName, 
232                    Object bean,
233                    Context context) 
234                        throws 
235                            IOException, 
236                            SAXException,
237                            IntrospectionException {                    
238            
239            if ( log.isTraceEnabled() ) {
240                log.trace( "Writing bean graph (qualified name '" + qualifiedName + "'" );
241            }
242            
243            // introspect to obtain bean info
244            XMLBeanInfo beanInfo = introspector.introspect( bean );
245            writeBean(namespaceUri, localName, qualifiedName, bean, context, beanInfo);
246            
247            log.trace( "Finished writing bean graph." );
248        }
249        
250        
251        private void writeBean (
252                String namespaceUri,
253                String localName,
254                String qualifiedName, 
255                Object bean,
256                ElementDescriptor parentDescriptor,
257                Context context) 
258                    throws 
259                        IOException, 
260                        SAXException,
261                        IntrospectionException {                    
262        
263        if ( log.isTraceEnabled() ) {
264            log.trace( "Writing bean graph (qualified name '" + qualifiedName + "'" );
265        }
266        
267        // introspect to obtain bean info
268        XMLBeanInfo beanInfo = findXMLBeanInfo(bean, parentDescriptor);
269        writeBean(namespaceUri, localName, qualifiedName, bean, context, beanInfo);
270        
271        log.trace( "Finished writing bean graph." );
272    }
273        
274        /**
275         * Finds the appropriate bean info for the given (hollow) element.
276         * @param bean
277         * @param parentDescriptor <code>ElementDescriptor</code>, not null
278         * @return <code>XMLBeanInfo</code>, not null
279         * @throws IntrospectionException
280         */
281        private XMLBeanInfo findXMLBeanInfo(Object bean, ElementDescriptor parentDescriptor) throws IntrospectionException {
282            XMLBeanInfo beanInfo = null;
283            Class introspectedBindType = parentDescriptor.getSingularPropertyType();
284            if ( introspectedBindType == null ) {
285                introspectedBindType = parentDescriptor.getPropertyType();
286            }
287            if ( parentDescriptor.isUseBindTimeTypeForMapping() || introspectedBindType == null ) {
288                beanInfo = introspector.introspect( bean );
289            } else {
290                beanInfo = introspector.introspect( introspectedBindType );
291            }
292            return beanInfo;
293        }
294    
295        /**
296         * <p>Writes the given bean to the current stream 
297         * using the given mapping.</p>
298         *
299         * <p>This method will throw a <code>CyclicReferenceException</code> when a cycle
300         * is encountered in the graph <strong>only</strong> if the <code>getMapIDs()</code>
301         * setting of the <code>BindingConfiguration</code> is false.</p>
302         *
303         * @param namespaceUri the namespace uri, or null to use the automatic binding
304         * @param localName the local name  or null to use the automatic binding
305         * @param qualifiedName the <code>String</code> naming the root element 
306         *  or null to use the automatic binding
307         * @param bean <code>Object</code> to be written, not null
308         * @param context <code>Context</code>, not null
309         * @param beanInfo <code>XMLBeanInfo</code>, not null
310         * @throws IOException
311         * @throws SAXException
312         * @throws IntrospectionException
313         */
314        private void writeBean(
315                                                    String namespaceUri, 
316                                                    String localName, 
317                                                    String qualifiedName, 
318                                                    Object bean, 
319                                                    Context context, 
320                                                    XMLBeanInfo beanInfo) 
321                                                                            throws IOException, SAXException, IntrospectionException {
322            if ( beanInfo != null ) {
323                ElementDescriptor elementDescriptor = beanInfo.getElementDescriptor();
324                if ( elementDescriptor != null ) {
325                    
326                    // Construct the options
327                    Options combinedOptions = new Options();
328    
329                    // Add options defined by the current bean's element descriptor
330                    combinedOptions.addOptions(elementDescriptor.getOptions());
331                    
332                    // The parent descriptor may have defined options
333                    // for the current bean.  These options take precedence
334                    // over the options of the current class descriptor
335                    if( context.getOptions() != null) {
336                        combinedOptions.addOptions(context.getOptions());
337                    }
338                    context = context.newContext( bean );
339                    context.pushOptions(combinedOptions);
340                    
341                    if ( qualifiedName == null ) {
342                        qualifiedName = elementDescriptor.getQualifiedName();
343                    }
344                    if ( namespaceUri == null ) {
345                        namespaceUri = elementDescriptor.getURI();
346                    }
347                    if ( localName == null ) {
348                        localName = elementDescriptor.getLocalName();
349                    }
350    
351                    String ref = null;
352                    String id = null;
353                    
354                    // simple type should not have IDs
355                    if ( elementDescriptor.isSimple() ) {
356                        // write without an id
357                        writeElement( 
358                            namespaceUri,
359                            localName,
360                            qualifiedName, 
361                            elementDescriptor, 
362                            context );
363                            
364                    } else {
365                        pushBean ( context.getBean() );
366                        if ( getBindingConfiguration().getMapIDs() ) {
367                           ref = getBindingConfiguration().getIdMappingStrategy().getReferenceFor(context, context.getBean());
368                        }
369                        if ( ref == null ) {
370                            // this is the first time that this bean has be written
371                            AttributeDescriptor idAttribute = beanInfo.getIDAttribute();
372                            if (idAttribute == null) {
373                                // use a generated id
374                                id = idGenerator.nextId();
375                                getBindingConfiguration().getIdMappingStrategy().setReference(context, bean, id);
376                                
377                                if ( getBindingConfiguration().getMapIDs() ) {
378                                    // write element with id
379                                    writeElement(
380                                        namespaceUri,
381                                        localName,
382                                        qualifiedName, 
383                                        elementDescriptor, 
384                                        context , 
385                                        beanInfo.getIDAttributeName(),
386                                        id);
387                                        
388    
389                                } else {    
390                                    // write element without ID
391                                    writeElement( 
392                                        namespaceUri,
393                                        localName,
394                                        qualifiedName, 
395                                        elementDescriptor, 
396                                        context );
397                                }
398                                                            
399                            } else {
400                                // use id from bean property
401                                // it's up to the user to ensure uniqueness
402                                Expression idExpression = idAttribute.getTextExpression();
403                                if(idExpression == null) {
404                                       throw new IntrospectionException(
405                                             "The specified id property wasn't found in the bean ("
406                                            + idAttribute + ").");
407                                }
408                                Object exp = idExpression.evaluate( context );
409                                if (exp == null) {
410                                    // we'll use a random id
411                                    log.debug("Using random id");
412                                    id = idGenerator.nextId();
413                                    
414                                } else {
415                                    // convert to string
416                                    id = exp.toString();
417                                }
418                                getBindingConfiguration().getIdMappingStrategy().setReference(context, bean, id);
419                                
420                                // the ID attribute should be written automatically
421                                writeElement( 
422                                    namespaceUri,
423                                    localName,
424                                    qualifiedName, 
425                                    elementDescriptor, 
426                                    context );
427                            }
428                        } else {
429                            
430                            if ( !ignoreElement( elementDescriptor, namespaceUri, localName, qualifiedName, context )) {
431                                // we've already written this bean so write an IDREF
432                                writeIDREFElement( 
433                                                elementDescriptor,
434                                                namespaceUri,
435                                                localName,
436                                                qualifiedName,  
437                                                beanInfo.getIDREFAttributeName(), 
438                                                ref);
439                            }
440                        }
441                        popBean();
442                    }
443                    
444                    context.popOptions();
445                }
446            }
447        }
448    
449        /** 
450          * Get <code>IDGenerator</code> implementation used to 
451          * generate <code>ID</code> attribute values .
452          *
453          * @return implementation used for <code>ID</code> attribute generation
454          */
455        public IDGenerator getIdGenerator() {
456            return idGenerator;
457        }
458        
459        /** 
460          * Set <code>IDGenerator</code> implementation 
461          * used to generate <code>ID</code> attribute values.
462          * This property can be used to customize the algorithm used for generation.
463          *
464          * @param idGenerator use this implementation for <code>ID</code> attribute generation
465          */
466        public void setIdGenerator(IDGenerator idGenerator) {
467            this.idGenerator = idGenerator;
468        }
469        
470        
471        
472        /**
473         * Gets the dynamic configuration setting to be used for bean reading.
474         * @return the BindingConfiguration settings, not null
475         * @since 0.5
476         */
477        public BindingConfiguration getBindingConfiguration() {
478            return bindingConfiguration;
479        }
480        
481        /**
482         * Sets the dynamic configuration setting to be used for bean reading.
483         * @param bindingConfiguration the BindingConfiguration settings, not null
484         * @since 0.5
485         */
486        public void setBindingConfiguration(BindingConfiguration bindingConfiguration) {
487            this.bindingConfiguration = bindingConfiguration;
488        }
489        
490        /** 
491         * <p>Should generated <code>ID</code> attribute values be added to the elements?</p>
492         * 
493         * <p>If IDs are not being written then if a cycle is encountered in the bean graph, 
494         * then a {@link CyclicReferenceException} will be thrown by the write method.</p>
495         * 
496         * @return true if <code>ID</code> and <code>IDREF</code> attributes are to be written
497         * @deprecated 0.5 use {@link BindingConfiguration#getMapIDs}
498         */
499        public boolean getWriteIDs() {
500            return getBindingConfiguration().getMapIDs();
501        }
502    
503        /** 
504         * Set whether generated <code>ID</code> attribute values should be added to the elements 
505         * If this property is set to false, then <code>CyclicReferenceException</code> 
506         * will be thrown whenever a cyclic occurs in the bean graph.
507         *
508         * @param writeIDs true if <code>ID</code>'s and <code>IDREF</code>'s should be written
509         * @deprecated 0.5 use {@link BindingConfiguration#setMapIDs}
510         */
511        public void setWriteIDs(boolean writeIDs) {
512            getBindingConfiguration().setMapIDs( writeIDs );
513        }
514        
515        /**
516         * <p>Gets whether empty elements should be written into the output.</p>
517         *
518         * <p>An empty element is one that has no attributes, no child elements 
519         * and no body text.
520         * For example, <code>&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    }