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