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