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