View Javadoc

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