View Javadoc
1   /**
2    * Redistribution and use of this software and associated documentation ("Software"), with or
3    * without modification, are permitted provided that the following conditions are met:
4    *
5    * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
6    * must also contain a copy of this document.
7    *
8    * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
9    * conditions and the following disclaimer in the documentation and/or other materials provided with
10   * the distribution.
11   *
12   * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
13   * without prior written permission of Intalio, Inc. For written permission, please contact
14   * info@exolab.org.
15   *
16   * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
17   * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
18   * Intalio, Inc.
19   *
20   * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
21   *
22   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
23   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
24   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
25   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
29   * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30   *
31   * Copyright 1999-2004 (C) Intalio, Inc. All Rights Reserved.
32   *
33   * This file was originally developed by Keith Visco during the course of employment at Intalio Inc.
34   * All portions of this file developed by Keith Visco after Jan 19 2005 are Copyright (C) 2005 Keith
35   * Visco. All Rights Reserved.
36   *
37   * $Id$
38   */
39  package org.exolab.castor.xml;
40  
41  import java.io.IOException;
42  import java.io.PrintWriter;
43  import java.io.Writer;
44  import java.lang.reflect.Array;
45  import java.lang.reflect.Method;
46  import java.math.BigDecimal;
47  import java.util.ArrayList;
48  import java.util.Enumeration;
49  import java.util.HashSet;
50  import java.util.Iterator;
51  import java.util.List;
52  import java.util.Set;
53  import java.util.Stack;
54  import java.util.StringTokenizer;
55  
56  import org.apache.commons.lang3.StringUtils;
57  import org.apache.commons.logging.Log;
58  import org.apache.commons.logging.LogFactory;
59  import org.castor.core.util.Base64Encoder;
60  import org.castor.core.util.HexDecoder;
61  import org.castor.core.util.Messages;
62  import org.castor.mapping.BindingType;
63  import org.castor.mapping.MappingUnmarshaller;
64  import org.castor.xml.InternalContext;
65  import org.castor.xml.XMLProperties;
66  import org.exolab.castor.mapping.ClassDescriptor;
67  import org.exolab.castor.mapping.CollectionHandler;
68  import org.exolab.castor.mapping.FieldHandler;
69  import org.exolab.castor.mapping.MapHandler;
70  import org.exolab.castor.mapping.MapItem;
71  import org.exolab.castor.mapping.Mapping;
72  import org.exolab.castor.mapping.MappingException;
73  import org.exolab.castor.mapping.MappingLoader;
74  import org.exolab.castor.mapping.handlers.MapHandlers;
75  import org.exolab.castor.mapping.loader.CollectionHandlers;
76  import org.exolab.castor.types.AnyNode;
77  import org.exolab.castor.util.SafeStack;
78  import org.exolab.castor.xml.descriptors.RootArrayDescriptor;
79  import org.exolab.castor.xml.descriptors.StringClassDescriptor;
80  import org.exolab.castor.xml.handlers.DateFieldHandler;
81  import org.exolab.castor.xml.handlers.EnumFieldHandler;
82  import org.exolab.castor.xml.util.AnyNode2SAX2;
83  import org.exolab.castor.xml.util.AttributeSetImpl;
84  import org.exolab.castor.xml.util.DocumentHandlerAdapter;
85  import org.exolab.castor.xml.util.SAX2DOMHandler;
86  import org.exolab.castor.xml.util.StaxEventHandler;
87  import org.exolab.castor.xml.util.StaxStreamHandler;
88  import org.exolab.castor.xml.util.XMLClassDescriptorAdapter;
89  import org.exolab.castor.xml.util.XMLClassDescriptorImpl;
90  import org.exolab.castor.xml.util.XMLFieldDescriptorImpl;
91  import org.w3c.dom.Node;
92  import org.xml.sax.ContentHandler;
93  import org.xml.sax.DocumentHandler;
94  import org.xml.sax.SAXException;
95  import org.xml.sax.helpers.AttributesImpl;
96  
97  import javax.xml.stream.XMLEventWriter;
98  import javax.xml.stream.XMLStreamWriter;
99  
100 import javax.xml.transform.Result;
101 import javax.xml.transform.dom.DOMResult;
102 import javax.xml.transform.sax.SAXResult;
103 import javax.xml.transform.stream.StreamResult;
104 
105 /**
106  * A Marshaller that serializes Java Object's to XML
107  *
108  * Note: This class is not thread safe, and not intended to be, so please create a new Marshaller
109  * for each thread if it is to be used in a multithreaded environment.
110  *
111  * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
112  * @version $Revision$ $Date: 2006-04-13 06:47:36 -0600 (Thu, 13 Apr 2006) $
113  */
114 public class Marshaller extends MarshalFramework {
115 
116 
117   // ---------------------------/
118   // - Private Class variables -/
119   // ---------------------------/
120 
121   /**
122    * Logger from commons-logging.
123    */
124   private static final Log LOG = LogFactory.getLog(Marshaller.class);
125 
126   /**
127    * The CDATA type..uses for SAX attributes.
128    **/
129   private static final String CDATA = "CDATA";
130 
131   /**
132    * Default prefix for use when creating namespace prefixes.
133    **/
134   private static final String DEFAULT_PREFIX = "ns";
135 
136 
137   /**
138    * Message name for a non sax capable serializer error.
139    **/
140   private static final String SERIALIZER_NOT_SAX_CAPABLE = "conf.serializerNotSaxCapable";
141 
142   /**
143    * Namespace declaration for xml schema instance.
144    */
145   private static final String XSI_PREFIX = "xsi";
146 
147   /**
148    * The xsi:type attribute.
149    */
150   private static final String XSI_TYPE = "xsi:type";
151 
152   /**
153    * Namespace prefix counter.
154    */
155   private int _namespaceCounter = 0;
156 
157   /**
158    * An instance of StringClassDescriptor.
159    */
160   private static final StringClassDescriptor STRING_CLASS_DESCRIPTOR = new StringClassDescriptor();
161 
162   // ----------------------------/
163   // - Private member variables -/
164   // ----------------------------/
165 
166   /**
167    * A boolean to indicate whether or not we are marshalling as a complete document or not.
168    **/
169   private boolean _asDocument = true;
170 
171   /**
172    * The depth of the sub tree, 0 denotes document level.
173    */
174   private int _depth = 0;
175 
176   /**
177    * The output format to use with the serializer. This will be null if the user passed in their own
178    * DocumentHandler.
179    **/
180   private OutputFormat _format = null;
181 
182   /**
183    * The ContentHandler we are marshaling to.
184    **/
185   private ContentHandler _handler = null;
186 
187   /**
188    * Indicates whether whether or not to use xsi:type declarations in the output.
189    **/
190   private boolean _marshalExtendedType = true;
191 
192   /**
193    * The registered {@link MarshalListener} to receive notifications of pre- and post marshal for
194    * each object in the tree being marshaled.
195    **/
196   private MarshalListener _marshalListener = null;
197 
198   /**
199    * The name space stack.
200    **/
201   private NamespacesStack namespacesStack = new NamespacesStack();
202 
203   /**
204    * Records Java packages being used during marshaling.
205    **/
206   private List<String> _packages = new ArrayList<String>();
207 
208   /**
209    * A stack of parent objects...to prevent circular references from being marshaled.
210    **/
211   private Stack _parents = new SafeStack();
212 
213   /**
214    * A list of ProcessingInstructions to output upon marshalling of the document.
215    **/
216   private List<ProcessingInstruction> _processingInstructions =
217       new ArrayList<ProcessingInstruction>();
218 
219   /**
220    * Name of the root element to use.
221    */
222   private String _rootElement = null;
223 
224   /**
225    * A boolean to indicate keys from a map should be saved when necessary.
226    */
227   private boolean _saveMapKeys = true;
228 
229   /**
230    * The serializer that is being used for marshalling. This may be null if the user passed in a
231    * DocumentHandler.
232    **/
233   private Serializer _serializer = null;
234 
235   /**
236    * A flag to allow suppressing namespaces.
237    */
238   private boolean _suppressNamespaces = false;
239 
240   /**
241    * A flag to allow suppressing the xsi:type attribute.
242    */
243   private boolean _suppressXSIType = false;
244 
245   private boolean _useXSITypeAtRoot = false;
246 
247   /**
248    * The set of optional top-level attributes set by the user.
249    **/
250   private AttributeSetImpl _topLevelAtts = new AttributeSetImpl();
251 
252   /**
253    * The AttributeList which is to be used during marshalling, instead of creating a bunch of new
254    * ones.
255    */
256   private AttributesImpl _attributes = new AttributesImpl();
257 
258   /**
259    * The validation flag.
260    */
261   private boolean _validate = false;
262 
263   /**
264    * Set of full class names of proxy interfaces. If the class to be marshalled implements one of
265    * them the superclass will be marshalled instead of the class itself.
266    */
267   private final Set<String> _proxyInterfaces = new HashSet<String>();
268 
269   /**
270    * Creates a new {@link Marshaller} with the given SAX {@link DocumentHandler}.
271    *
272    * @param handler the SAX {@link DocumentHandler} to "marshal" to.
273    *
274    * @throws IllegalArgumentException if the given {@link DocumentHandler} is null
275    * @deprecate Please use {@link XMLContext#createMarshaller()} and
276    *            {@link Marshaller#setDocumentHandler(DocumentHandler)} instead
277    * 
278    * @see {@link XMLContext#createMarshaller()}
279    * @see {@link Marshaller#setDocumentHandler(DocumentHandler)}
280    * @see XMLContext
281    * 
282    **/
283   public Marshaller(final DocumentHandler handler) {
284     super(null);
285     checkNotNull(handler, "The given 'org.sax.DocumentHandler' instance is null.");
286 
287     setContentHandler(new DocumentHandlerAdapter(handler));
288   }
289 
290   /**
291    * Sets the given SAX {@link DocumentHandler} to 'marshal' into.
292    *
293    * @param handler the SAX {@link DocumentHandler} to "marshal" to.
294    *
295    * @throws IllegalArgumentException if the given {@link DocumentHandler} is null
296    **/
297   public void setDocumentHandler(final DocumentHandler handler) {
298     checkNotNull(handler, "The given 'org.sax.DocumentHandler' instance is null.");
299 
300     setContentHandler(new DocumentHandlerAdapter(handler));
301   }
302 
303   /**
304    * Creates a new {@link Marshaller} with the given SAX {@link ContentHandler}.
305    *
306    * @param contentHandler the {@link ContentHandler} to "marshal" to.
307    * @throws IllegalArgumentException if the gievn {@link ContentHandler} is null
308    * @deprecate Please use {@link XMLContext#createMarshaller()} and
309    *            {@link Marshaller#setContentHandler(ContentHandler)} instead
310    * 
311    * @see {@link XMLContext#createMarshaller()}
312    * @see {@link Marshaller#setContentHandler(ContentHandler)}
313    * @see XMLContext
314    * 
315    **/
316   public Marshaller(final ContentHandler contentHandler) {
317     super(null);
318     checkNotNull(contentHandler, "The given 'org.sax.ContentHandler' is null.");
319 
320     setContentHandler(contentHandler);
321   }
322 
323   /**
324    * The one {@link Marshaller} constructor that is used by {@link XMLContext} which sets an
325    * {@link InternalContext} that comes from outside. Writer or {@link ContentHandler} have to be
326    * set in a second step.
327    * 
328    * @param internalContext the {@link InternalContext} to initialize the {@link Marshaller}
329    *        instance with
330    */
331   public Marshaller(final InternalContext internalContext) {
332     super(internalContext);
333   }
334 
335   /**
336    * Creates a default instance of Marshaller, where the sink needs to be set separately.
337    */
338   public Marshaller() {
339     super(null);
340   }
341 
342   /**
343    * Creates a new {@link Marshaller} with the given writer.
344    * 
345    * @param out the {@link Writer} to serialise to.
346    * @throws IllegalArgumentException if the given {@link Writer} is null
347    * @throws IOException If the given {@link Writer} instance cannot be opened.
348    * @deprecate Please use {@link XMLContext#createMarshaller()} and
349    *            {@link Marshaller#setWriter(Writer)} instead
350    * 
351    * @see {@link XMLContext#createMarshaller()}
352    * @see {@link Marshaller#setWriter(Writer)}
353    * @see XMLContext
354    * 
355    **/
356   public Marshaller(final Writer out) throws IOException {
357     super(null);
358     setWriter(out);
359   }
360 
361   /**
362    * Creates a new {@link Marshaller} with the given {@link XMLStreamWriter}.
363    *
364    * @param xmlStreamWriter the {@link XMLStreamWriter}
365    * @throws IllegalArgumentException if the given {@link XMLStreamWriter} is null
366    * @see {@link XMLContext#createMarshaller()}
367    * @see {@link Marshaller#setXmlStreamWriter(javax.xml.stream.XMLStreamWriter)}
368    * @see XMLContext
369    *
370    * @since 1.3.3
371    */
372   public Marshaller(XMLStreamWriter xmlStreamWriter) {
373     super(null);
374     setXmlStreamWriter(xmlStreamWriter);
375   }
376 
377   /**
378    * Creates a new {@link Marshaller} with the given {@link XMLEventWriter}.
379    *
380    * @param xmlEventWriter the {@link XMLEventWriter}
381    * @throws IllegalArgumentException if the given {@link XMLEventWriter} is null
382    * @see {@link XMLContext#createMarshaller()}
383    * @see {@link Marshaller#setXmlEventWriter(javax.xml.stream.XMLEventWriter)}
384    * @see XMLContext
385    *
386    * @since 1.3.3
387    */
388   public Marshaller(XMLEventWriter xmlEventWriter) {
389     super(null);
390     setXmlEventWriter(xmlEventWriter);
391   }
392 
393   /**
394    * Sets the java.io.Writer to be used during marshalling.
395    * 
396    * @param out The writer to use for marshalling
397    *
398    * @throws IllegalArgumentException if out is null
399    * @throws IOException If there's a problem accessing the java.io.Writer provided
400    */
401   public void setWriter(final Writer out) throws IOException {
402     checkNotNull(out, "The given 'java.io.Writer' instance is null.");
403 
404     configureSerializer(out);
405   }
406 
407   /**
408    * Sets the {@link Result} into which the output xml will be written. Currently this method
409    * supports {@link DOMResult}, {@link SAXResult} and {@link StreamResult}.
410    *
411    * @param result the {@link Result} instance to set
412    *
413    * @throws IllegalArgumentException if the result is null or it is not supported
414    *
415    * @since 1.3.3
416    */
417   public void setResult(Result result) throws IOException {
418     checkNotNull(result, "The given 'javax.xml.transform.Result' instance is null.");
419 
420     if (result instanceof DOMResult) {
421       DOMResult domResult = (DOMResult) result;
422 
423       if (domResult.getNode() != null) {
424         // sets the dom node
425         setNode(domResult.getNode());
426         return;
427       }
428     } else if (result instanceof SAXResult) {
429       SAXResult saxResult = (SAXResult) result;
430 
431       if (saxResult.getHandler() != null) {
432         // sets the content handler
433         setContentHandler(saxResult.getHandler());
434         return;
435         // TODO what to do with lexical handler ?
436       }
437     } else if (result instanceof StreamResult) {
438       StreamResult streamResult = (StreamResult) result;
439 
440       if (streamResult.getWriter() != null) {
441         // sets the writer
442         setWriter(streamResult.getWriter());
443         return;
444       } else if (streamResult.getOutputStream() != null) {
445         // sets the output stream, wrapping it into a print writer instance
446         setWriter(new PrintWriter(streamResult.getOutputStream()));
447         return;
448       }
449     }
450 
451     throw new IllegalArgumentException(
452         "The given 'javax.transofrm.xml.Result' is not supported, or were incorrectly instantiated.");
453   }
454 
455 
456   private void configureSerializer(Writer out) throws IOException {
457     _serializer = getInternalContext().getSerializer();
458 
459     if (_serializer == null)
460       throw new RuntimeException("Unable to obtain serializer");
461 
462     _serializer.setOutputCharStream(out);
463 
464     // -- Due to a Xerces Serializer bug that doesn't allow declaring
465     // -- multiple prefixes to the same namespace, we use the old
466     // -- DocumentHandler format and process namespaces ourselves
467     _handler = new DocumentHandlerAdapter(_serializer.asDocumentHandler());
468 
469     if (_handler == null) {
470       String err = Messages.format(SERIALIZER_NOT_SAX_CAPABLE, _serializer.getClass().getName());
471       throw new RuntimeException(err);
472     }
473   }
474 
475   /**
476    * Creates a new {@link Marshaller} for the given DOM {@link Node}.
477    *
478    * @param node the DOM {@link Node} to marshal into.
479    *
480    * @throws IllegalArgumentException if node is null
481    *
482    * @deprecate Please use {@link XMLContext#createMarshaller()} and
483    *            {@link Marshaller#setNode(Node)} instead
484    * 
485    * @see {@link XMLContext#createMarshaller()}
486    * @see {@link Marshaller#setNode(Node)}
487    * @see XMLContext
488    **/
489   public Marshaller(final Node node) {
490     super(null);
491     checkNotNull(node, "The given 'org.w3c.dom.Node' instance is null.");
492 
493     setContentHandler(new DocumentHandlerAdapter(new SAX2DOMHandler(node)));
494   }
495 
496   /**
497    * Sets the W3C {@link Node} instance to marshal to.
498    *
499    * @param node the DOM {@link Node} to marshal into.
500    *
501    * @throws IllegalArgumentException if node is null
502    **/
503   public void setNode(final Node node) {
504     checkNotNull(node, "The given 'org.w3c.dom.Node' instance is null.");
505 
506     setContentHandler(new DocumentHandlerAdapter(new SAX2DOMHandler(node)));
507   }
508 
509   /**
510    * Sets the {@link XMLStreamWriter} to use.
511    *
512    * @param xmlStreamWriter the {@link XMLStreamWriter} instance to use
513    *
514    * @throws IllegalArgumentException if the xmlStreamWriter is null
515    *
516    * @since 1.3.3
517    */
518   public void setXmlStreamWriter(XMLStreamWriter xmlStreamWriter) {
519     checkNotNull(xmlStreamWriter, "The given 'java.xml.stream.XMLStreamWriter' instance is null.");
520 
521     setContentHandler(new StaxStreamHandler(xmlStreamWriter));
522   }
523 
524   /**
525    * Sets the {@link XMLEventWriter} to use.
526    *
527    * @param xmlEventWriter the {@link XMLEventWriter} instance to use
528    *
529    * @throws IllegalArgumentException if the xmlEventReader is null
530    *
531    * @since 1.3.3
532    */
533   public void setXmlEventWriter(XMLEventWriter xmlEventWriter) {
534     checkNotNull(xmlEventWriter, "The given 'java.xml.stream.XMLEventWriter' instance is null.");
535 
536     setContentHandler(new StaxEventHandler(xmlEventWriter));
537   }
538 
539   /**
540    * To set the {@link InternalContext} to use, and to initialize {@link Marshaller} properties
541    * linked to it.
542    * 
543    * @param internalContext the {@link InternalContext} to use
544    */
545   public void setInternalContext(final InternalContext internalContext) {
546     super.setInternalContext(internalContext);
547     deriveProperties();
548   }
549 
550 
551   /**
552    * Derive class-level properties from {@link XMLProperties} as defined {@link InternalContext}.
553    * This method will be called after a new {@link InternalContext} or a property has been set.
554    * 
555    * @link #setInternalContext(InternalContext)
556    */
557   private void deriveProperties() {
558     _validate = getInternalContext().marshallingValidation();
559     _saveMapKeys =
560         getInternalContext().getBooleanProperty(XMLProperties.SAVE_MAP_KEYS).booleanValue();
561 
562     String prop = getInternalContext().getStringProperty(XMLProperties.PROXY_INTERFACES);
563     if (prop != null) {
564       StringTokenizer tokenizer = new StringTokenizer(prop, ", ");
565       while (tokenizer.hasMoreTokens()) {
566         _proxyInterfaces.add(tokenizer.nextToken());
567       }
568     }
569   }
570 
571   /**
572    * Adds the given processing instruction data to the set of processing instructions to output
573    * during marshalling.
574    *
575    * @param target the processing instruction target
576    * @param data the processing instruction data
577    *
578    * @throws IllegalArgumentException if target is null or empty string or data is null
579    **/
580   public void addProcessingInstruction(String target, String data) {
581 
582     checkNotEmpty(target, "The argument 'target' must not be null or empty.");
583     checkNotNull(data, "The argument 'data' must not be null.");
584 
585     _processingInstructions.add(new ProcessingInstruction(target, data));
586   } // -- addProcessingInstruction
587 
588   /**
589    * Sets the document type definition for the serializer. Note that this method cannot be called if
590    * you've passed in your own DocumentHandler.
591    *
592    * @param publicId the public identifier
593    * @param systemId the system identifier
594    */
595   public void setDoctype(String publicId, String systemId) {
596 
597     if (_serializer != null) {
598       if (_format == null) {
599         _format = getInternalContext().getOutputFormat();
600       }
601       _format.setDoctype(publicId, systemId);
602       // -- reset output format, this needs to be done
603       // -- any time a change occurs to the format.
604       _serializer.setOutputFormat(_format);
605 
606       setDocumentHandler();
607     } else {
608       String error = "doctype cannot be set if you've passed in " + "your own DocumentHandler";
609       throw new IllegalStateException(error);
610     }
611   } // -- setDoctype
612 
613   /**
614    * Sets whether or not to marshal as a document which includes the XML declaration, and if
615    * necessary the DOCTYPE declaration. By default the Marshaller will marshal as a well formed XML
616    * document including the XML Declaration.
617    *
618    * If the given boolean is true, the Marshaller will marshal as a well formed XML fragment (no XML
619    * declaration or DOCTYPE).
620    *
621    * This method is basically the same as calling #setMarshalAsDocument(false);
622    *
623    * @param supressXMLDeclaration a boolean that when true includes that generated XML should not
624    *        contain the XML declaration.
625    * @see #setMarshalAsDocument
626    */
627   public void setSupressXMLDeclaration(boolean supressXMLDeclaration) {
628     setMarshalAsDocument(!supressXMLDeclaration);
629   } // -- setSupressXMLDeclaration
630 
631   /**
632    * Sets whether or not to marshal as a document which includes the XML declaration, and if
633    * necessary the DOCTYPE declaration. By default the Marshaller will marshal as a well formed XML
634    * document including the XML Declaration.
635    *
636    * If the given boolean is false, the Marshaller will marshal as a well formed XML fragment (no
637    * XML declaration or DOCTYPE).
638    *
639    * This method is basically the same as calling #setSupressXMLDeclaration(true);
640    *
641    * @param asDocument a boolean, when true, indicating to marshal as a complete XML document.
642    * @see #setSupressXMLDeclaration
643    */
644   public void setMarshalAsDocument(boolean asDocument) {
645 
646     _asDocument = asDocument;
647 
648     if (_serializer != null) {
649 
650       if (_format == null) {
651         _format = getInternalContext().getOutputFormat();
652       }
653       _format.setOmitXMLDeclaration(!asDocument);
654       _format.setOmitDocumentType(!asDocument);
655 
656       // -- reset output format, this needs to be done
657       // -- any time a change occurs to the format.
658       _serializer.setOutputFormat(_format);
659 
660       setDocumentHandler();
661     }
662 
663   } // -- setMarshalAsDocument
664 
665   /**
666    * Sets the given mapping to be used by the marshalling Framework. If a resolver exists this
667    * mapping will be added to the existing ClassDescriptorResolver. Otherwise a new
668    * ClassDescriptorResolver will be created.
669    *
670    * @param mapping Mapping to using during marshalling.
671    */
672   public void setMapping(final Mapping mapping) throws MappingException {
673     // if (_cdResolver == null) {
674     // _cdResolver = (XMLClassDescriptorResolver) ClassDescriptorResolverFactory
675     // .createClassDescriptorResolver(BindingType.XML);
676     // }
677     if ((getInternalContext() == null)
678         || (getInternalContext().getXMLClassDescriptorResolver() == null)) {
679       String message = "No internal context or no class descriptor in context.";
680       LOG.warn(message);
681       throw new IllegalStateException(message);
682     }
683 
684     MappingUnmarshaller mum = new MappingUnmarshaller();
685     MappingLoader resolver = mum.getMappingLoader(mapping, BindingType.XML);
686     getInternalContext().getXMLClassDescriptorResolver().setMappingLoader(resolver);
687   }
688 
689   /**
690    * Sets an optional MarshalListener to recieve pre and post marshal notification for each Object
691    * in the tree. MarshalListener is only for complex objects that map into elements, simpleTypes
692    * and types that map into attributes do not cause any pre and post event notifications. Current
693    * only one (1) listener is allowed. If you need register multiple listeners, you will have to
694    * create your own master listener that will forward the event notifications and manage the
695    * multiple listeners.
696    *
697    * @param listener the MarshalListener to set.
698    **/
699   public void setMarshalListener(MarshalListener listener) {
700     _marshalListener = listener;
701   }
702 
703   /**
704    * Sets the mapping for the given Namespace prefix.
705    * 
706    * @param nsPrefix the namespace prefix
707    * @param nsURI the namespace that the prefix resolves to
708    *
709    * @throws IllegalArgumentException if nsURI is null or empty string
710    **/
711   public void setNamespaceMapping(final String nsPrefix, final String nsURI) {
712     checkNotEmpty(nsURI, "Namespace URI must be not null.");
713     namespacesStack.addNamespace(nsPrefix, nsURI);
714   }
715 
716   /**
717    * Sets the name of the root element to use.
718    *
719    * @param rootElement The name of the root element to use.
720    */
721   public void setRootElement(final String rootElement) {
722     _rootElement = rootElement;
723   }
724 
725   /**
726    * Returns the name of the root element to use
727    * 
728    * @return Returns the name of the root element to use
729    */
730   public String getRootElement() {
731     return _rootElement;
732   } // -- getRootElement
733 
734   /**
735    * Set to True to declare the given namespace mappings at the root node. Default is False.
736    * 
737    * @param nsPrefixAtRoot
738    * @deprecated
739    */
740   public void setNSPrefixAtRoot(boolean nsPrefixAtRoot) {
741     // leaving for now...backward compatability
742     // _nsPrefixAtRoot = nsPrefixAtRoot;
743   }
744 
745   /**
746    * Returns True if the given namespace mappings will be declared at the root node.
747    * 
748    * @return Returns True if the given namespace mappings will be declared at the root node.
749    * @deprecated
750    */
751   public boolean getNSPrefixAtRoot() {
752     return true;
753   }
754 
755   /**
756    * Returns the ClassDescriptorResolver for use during marshalling
757    *
758    * @return the ClassDescriptorResolver
759    * @see #setResolver
760    */
761   public XMLClassDescriptorResolver getResolver() {
762 
763     // if (_cdResolver == null) {
764     // _cdResolver = (XMLClassDescriptorResolver)
765     // ClassDescriptorResolverFactory.createClassDescriptorResolver(BindingType.XML);
766     // }
767     if ((getInternalContext() == null)
768         || (getInternalContext().getXMLClassDescriptorResolver() == null)) {
769       String message = "No internal context or no class descriptor in context.";
770       LOG.warn(message);
771       throw new IllegalStateException(message);
772     }
773     return getInternalContext().getXMLClassDescriptorResolver();
774 
775   } // -- getResolver
776 
777   /**
778    * Sets the ClassDescriptorResolver to use during marshalling.
779    *
780    * <BR />
781    * <B>Note:</B> This method will nullify any Mapping currently being used by this Marshaller
782    *
783    * @param cdr the ClassDescriptorResolver to use
784    * @see #setMapping
785    * @see #getResolver
786    */
787   public void setResolver(final XMLClassDescriptorResolver cdr) {
788 
789     if (cdr != null) {
790       getInternalContext().setXMLClassDescriptorResolver(cdr);
791       // _cdResolver = cdr;
792     }
793 
794   } // -- setResolver
795 
796   /**
797    * Sets whether or not to validate the object model before marshalling. By default validation is
798    * enabled. This method is really for debugging. I do not recommend turning off validation, since
799    * you could marshal a document, which you can then not unmarshal. If you know the object model is
800    * guaranteed to be valid, disabling validation will improve performace.
801    *
802    * @param validate the boolean indicating whether or not to validate the object model before
803    *        marshalling.
804    **/
805   public void setValidation(boolean validate) {
806     _validate = validate;
807   } // -- setValidation
808 
809   public boolean getValidation() {
810     return _validate;
811   }
812 
813   /**
814    * If True the marshaller will use the 'xsi:type' attribute to marshall a field value that
815    * extended the defined field type. Default is True.
816    */
817   public void setMarshalExtendedType(boolean marshalExtendedType) {
818     _marshalExtendedType = marshalExtendedType;
819   } // -- setMarshalExtendedType
820 
821 
822   /**
823    * If True the marshaller will use the 'xsi:type' attribute to marshall a field value that
824    * extended the defined field type. Default is True.
825    * 
826    * @return If True the marshaller will use the 'xsi:type' attribute to marshall a field value that
827    *         extended the defined field type. Default is True.
828    */
829   public boolean getMarshalExtendedType() {
830     return _marshalExtendedType;
831   } // -- setMarshallExtendedType
832 
833   /**
834    * Marshals the given Object as XML using the given writer.
835    *
836    * @param object The Object to marshal.
837    * @param out The writer to marshal to.
838    * @exception org.exolab.castor.xml.MarshalException
839    * @exception org.exolab.castor.xml.ValidationException
840    */
841   public static void marshal(Object object, Writer out)
842       throws MarshalException, ValidationException {
843     try {
844       staticMarshal(object, new Marshaller(out));
845     } catch (IOException e) {
846       throw new MarshalException(e);
847     }
848   } // -- marshal
849 
850   /**
851    * Marshals the given Object as XML using the given DocumentHandler to send events to.
852    *
853    * @param object The Object to marshal.
854    * @param handler The DocumentHandler to marshal to.
855    * @exception org.exolab.castor.xml.MarshalException
856    * @exception org.exolab.castor.xml.ValidationException
857    */
858   public static void marshal(Object object, DocumentHandler handler)
859       throws MarshalException, ValidationException {
860     staticMarshal(object, new Marshaller(handler));
861   } // -- marshal
862 
863   /**
864    * Marshals the given Object as XML using the given ContentHandler to send events to.
865    *
866    * @param object The Object to marshal.
867    * @param handler The ContentHandler to marshal to.
868    * @exception org.exolab.castor.xml.MarshalException
869    * @exception org.exolab.castor.xml.ValidationException
870    */
871   public static void marshal(Object object, ContentHandler handler)
872       throws MarshalException, ValidationException {
873     staticMarshal(object, new Marshaller(handler));
874   } // -- marshal
875 
876   /**
877    * Marshals the given Object as XML using the given DOM Node to send events to.
878    *
879    * @param object The Object to marshal.
880    * @param node The DOM Node to marshal to.
881    * @exception org.exolab.castor.xml.MarshalException
882    * @exception org.exolab.castor.xml.ValidationException
883    */
884   public static void marshal(Object object, Node node)
885       throws MarshalException, ValidationException {
886     staticMarshal(object, new Marshaller(node));
887   } // -- marshal
888 
889   /**
890    * Static helper method to marshal the given object using the Marshaller instance provided.
891    *
892    * @param object The Object to marshal.
893    * @param marshaller The {@link Marshaller} to use for marshalling.
894    * @throws MarshalException as thrown by marshal(Object)
895    * @throws ValidationException as thrown by marshal(Object)
896    */
897   private static void staticMarshal(final Object object, final Marshaller marshaller)
898       throws MarshalException, ValidationException {
899     if (object == null) {
900       throw new MarshalException("object must not be null");
901     }
902 
903     if (LOG.isInfoEnabled()) {
904       LOG.info("Marshaller called using one of the *static* "
905           + " marshal(Object, *) methods. This will ignore any "
906           + " mapping files as specified. Please consider switching to "
907           + " using Marshaller instances and calling one of the" + " marshal(*) methods.");
908     }
909 
910     marshaller.marshal(object);
911   } // -- staticMarshal
912 
913   /**
914    * Marshals the given Object as XML using the DocumentHandler for this Marshaller.
915    *
916    * @param object The Object to marshal.
917    * @exception org.exolab.castor.xml.MarshalException
918    * @exception org.exolab.castor.xml.ValidationException
919    */
920   public void marshal(Object object) throws MarshalException, ValidationException {
921     if (object == null)
922       throw new MarshalException("object must not be null");
923 
924     if (LOG.isDebugEnabled()) {
925       LOG.debug("Marshalling " + object.getClass().getName());
926     }
927 
928     if (object instanceof AnyNode) {
929       try {
930         AnyNode2SAX2.fireEvents((AnyNode) object, _handler, namespacesStack);
931       } catch (SAXException e) {
932         throw new MarshalException(e);
933       }
934     } else {
935       validate(object);
936       MarshalState mstate = new MarshalState(object, "root");
937       if (_asDocument) {
938         try {
939           _handler.startDocument();
940           // -- handle processing instructions
941           for (int i = 0; i < _processingInstructions.size(); i++) {
942             ProcessingInstruction pi = _processingInstructions.get(i);
943             _handler.processingInstruction(pi.getTarget(), pi.getData());
944           }
945           marshal(object, null, _handler, mstate);
946           _handler.endDocument();
947         } catch (SAXException sx) {
948           throw new MarshalException(sx);
949         }
950       } else {
951         marshal(object, null, _handler, mstate);
952       }
953     }
954 
955   } // -- marshal
956 
957   /**
958    * Marshals the given object, using the given descriptor and document handler.
959    *
960    * <BR/>
961    * <B>Note:</B> <I> It is an error if this method is called with an AttributeDescriptor. </I>
962    * 
963    * @param descriptor the XMLFieldDescriptor for the given object
964    * @param handler the DocumentHandler to marshal to
965    * @exception org.exolab.castor.xml.MarshalException
966    * @exception org.exolab.castor.xml.ValidationException during marshaling
967    **/
968   private void marshal(Object object, XMLFieldDescriptor descriptor, ContentHandler handler,
969       final MarshalState mstate) throws MarshalException, ValidationException {
970 
971 
972     if (object == null) {
973       String err = "Marshaller#marshal: null parameter: 'object'";
974       throw new MarshalException(err);
975     }
976 
977     if (descriptor != null && descriptor.isTransient())
978       return;
979 
980     // -- notify listener
981     if (_marshalListener != null) {
982       boolean toBeMarshalled = true;
983       try {
984         toBeMarshalled = _marshalListener.preMarshal(object);
985       } catch (RuntimeException e) {
986         LOG.error(
987             "Invoking #preMarshal() on your custom MarshalListener instance caused the following problem:",
988             e);
989       }
990       if (!toBeMarshalled) {
991         return;
992       }
993     }
994 
995     // -- handle AnyNode
996     if (object instanceof AnyNode) {
997       try {
998         AnyNode2SAX2.fireEvents((AnyNode) object, handler, namespacesStack);
999       } catch (SAXException e) {
1000         throw new MarshalException(e);
1001       }
1002       return;
1003     }
1004 
1005     boolean containerField = false;
1006 
1007     if (descriptor != null && descriptor.isContainer()) {
1008       containerField = true;
1009     }
1010 
1011     // -- add object to stack so we don't potentially get into
1012     // -- an endlessloop
1013     if (_parents.search(object) >= 0) {
1014       return;
1015     }
1016 
1017     _parents.push(object);
1018 
1019     final boolean isNil = (object instanceof NilObject);
1020 
1021     Class<?> cls = null;
1022 
1023     if (!isNil) {
1024       cls = object.getClass();
1025 
1026       if (!_proxyInterfaces.isEmpty()) {
1027         boolean isProxy = false;
1028 
1029         Class<?>[] interfaces = cls.getInterfaces();
1030         for (int i = 0; i < interfaces.length; i++) {
1031           if (_proxyInterfaces.contains(interfaces[i].getName())) {
1032             isProxy = true;
1033           }
1034         }
1035 
1036         if (isProxy) {
1037           cls = cls.getSuperclass();
1038         }
1039       }
1040     } else {
1041       cls = ((NilObject) object).getClassDescriptor().getJavaClass();
1042     }
1043 
1044     boolean byteArray = false;
1045     if (cls.isArray())
1046       byteArray = (cls.getComponentType() == Byte.TYPE);
1047 
1048     boolean atRoot = false;
1049     if (descriptor == null) {
1050       descriptor = new XMLFieldDescriptorImpl(cls, "root", null, null);
1051       atRoot = true;
1052     }
1053 
1054 
1055     // -- calculate Object's name
1056     String name = descriptor.getXMLName();
1057     if (atRoot && _rootElement != null)
1058       name = _rootElement;
1059 
1060     boolean autoNameByClass = false;
1061     if (name == null) {
1062       autoNameByClass = true;
1063       name = cls.getName();
1064       // -- remove package information from name
1065       int idx = name.lastIndexOf('.');
1066       if (idx >= 0) {
1067         name = name.substring(idx + 1);
1068       }
1069       // -- remove capitalization
1070       name = getInternalContext().getXMLNaming().toXMLName(name);
1071     }
1072 
1073     // -- obtain the class descriptor
1074     XMLClassDescriptor classDesc = null;
1075     boolean saveType = false; /* flag for xsi:type */
1076 
1077     if (object instanceof NilObject) {
1078       classDesc = ((NilObject) object).getClassDescriptor();
1079     } else if (cls == descriptor.getFieldType()) {
1080       classDesc = (XMLClassDescriptor) descriptor.getClassDescriptor();
1081     }
1082 
1083     if (classDesc == null) {
1084 
1085       // -- check for primitive or String, we need to use
1086       // -- the special #isPrimitive method of this class
1087       // -- so that we can check for the primitive wrapper
1088       // -- classes
1089       if (isPrimitive(cls) || byteArray) {
1090         classDesc = STRING_CLASS_DESCRIPTOR;
1091         // -- check to see if we need to save the xsi:type
1092         // -- for this class
1093         Class<?> fieldType = descriptor.getFieldType();
1094         if (cls != fieldType) {
1095           while (fieldType.isArray()) {
1096             fieldType = fieldType.getComponentType();
1097           }
1098           saveType = (!primitiveOrWrapperEquals(cls, fieldType));
1099         }
1100       } else {
1101         saveType = cls.isArray();
1102         // -- save package information for use when searching
1103         // -- for MarshalInfo classes
1104         String className = cls.getName();
1105         int idx = className.lastIndexOf(".");
1106         String pkgName = null;
1107         if (idx > 0) {
1108           pkgName = className.substring(0, idx + 1);
1109           if (!_packages.contains(pkgName))
1110             _packages.add(pkgName);
1111         }
1112 
1113         if (_marshalExtendedType) {
1114           // -- Check to see if we can determine the class or
1115           // -- ClassDescriptor from the type specified in the
1116           // -- FieldHandler or from the current CDR state
1117 
1118           if ((cls != descriptor.getFieldType()) || atRoot) {
1119 
1120             saveType = true;
1121 
1122             boolean containsDesc = false;
1123 
1124             // -- if we're not at the root, check to see if we can resolve name.
1125             // -- if we're at the root, the name will most likely be resolvable
1126             // -- due to the validation step, so in most cases, if we are not
1127             // -- using a mapping we need the xsi:type at the root
1128             if (!atRoot) {
1129               String nsURI = descriptor.getNameSpaceURI();
1130               XMLClassDescriptor tmpDesc = null;
1131               try {
1132                 tmpDesc = getResolver().resolveByXMLName(name, nsURI, null);
1133               } catch (ResolverException rx) {
1134                 // -- exception not important as we're simply
1135                 // -- testing to see if we can resolve during
1136                 // -- unmarshalling
1137                 if (LOG.isDebugEnabled()) {
1138                   LOG.debug("Error resolving", rx);
1139                 }
1140               }
1141 
1142               if (tmpDesc != null) {
1143                 Class<?> tmpType = tmpDesc.getJavaClass();
1144                 if (tmpType == cls) {
1145                   containsDesc = (!tmpType.isInterface());
1146                 }
1147               }
1148             }
1149 
1150             if (!containsDesc) {
1151               // -- check for class mapping, we don't use the
1152               // -- resolver directly because it will try to
1153               // -- load a compiled descriptor, or introspect
1154               // -- one
1155               if (atRoot) {
1156                 if (_useXSITypeAtRoot) {
1157                   XMLMappingLoader ml = (XMLMappingLoader) getResolver().getMappingLoader();
1158                   if (ml != null) {
1159                     containsDesc = (ml.getDescriptor(cls.getName()) != null);
1160                   }
1161                 } else {
1162                   // -- prevent xsi:type from appearing
1163                   // -- on root
1164                   containsDesc = true;
1165                 }
1166 
1167               }
1168 
1169               // -- The following logic needs to be expanded to use
1170               // -- namespace -to- package mappings
1171               if ((!containsDesc) && (pkgName == null)) {
1172                 // -- check to see if the class name is guessable
1173                 // -- from the xml name
1174                 classDesc = getClassDescriptor(cls);
1175                 if (classDesc != null) {
1176                   String tmpName = classDesc.getXMLName();
1177                   if (name.equals(tmpName))
1178                     saveType = false;
1179                 }
1180               }
1181             }
1182 
1183             if (containsDesc)
1184               saveType = false;
1185 
1186           }
1187 
1188           // marshal as the actual type
1189           if (classDesc == null)
1190             classDesc = getClassDescriptor(cls);
1191 
1192         } // -- end if (marshalExtendedType)
1193         else {
1194           // marshall as the base field type
1195           cls = descriptor.getFieldType();
1196           classDesc = getClassDescriptor(cls);
1197         }
1198 
1199         // -- If we are marshalling an array as the top
1200         // -- level object, or if we run into a multi
1201         // -- dimensional array, use the special
1202         // -- ArrayDescriptor
1203         if ((classDesc == null) && cls.isArray()) {
1204           classDesc = new RootArrayDescriptor(cls);
1205           if (atRoot) {
1206             containerField = (!_asDocument);
1207           }
1208         }
1209       } // -- end else not primitive
1210 
1211       if (classDesc == null) {
1212         // -- make sure we are allowed to marshal Object
1213         if ((cls == Void.class) || (cls == Object.class) || (cls == Class.class)) {
1214 
1215           throw new MarshalException(MarshalException.BASE_CLASS_OR_VOID_ERR);
1216         }
1217         _parents.pop();
1218         return;
1219       }
1220     }
1221 
1222     // -- handle auto-naming by class
1223     if (autoNameByClass) {
1224       if (classDesc.getXMLName() != null) {
1225         name = classDesc.getXMLName();
1226       }
1227     }
1228 
1229     // -- at this point naming should be done, update
1230     // -- MarshalState.xmlName if root element
1231     if (atRoot) {
1232       mstate._xmlName = name;
1233     }
1234 
1235     // ------------------------------------------------/
1236     // - Next few sections of code deal with xsi:type -/
1237     // - prevention, if necessary -/
1238     // ------------------------------------------------/
1239 
1240     // -- Allow user to prevent xsi:type
1241     saveType = (saveType && (!_suppressXSIType));
1242 
1243     // -- Suppress xsi:type for special types
1244     if (saveType) {
1245       // -- java.util.Enumeration and java.util.Date fix
1246       if (descriptor.getHandler() instanceof DateFieldHandler)
1247         saveType = false;
1248       else if (descriptor.getHandler() instanceof EnumFieldHandler)
1249         saveType = false;
1250       else if (isNil)
1251         saveType = false;
1252     }
1253 
1254     // -- Suppress 'xsi:type' attributes when Castor is able to infer
1255     // -- the correct type during unmarshalling
1256     if (saveType) {
1257       // When the type of the instance of the field is not the
1258       // type specified for the field, it might be necessary to
1259       // store the type explicitly (using xsi:type) to avoid
1260       // confusion during unmarshalling.
1261       //
1262       // However, it might be possible to use the XMLName of the
1263       // instance rather than the XMLName of the field. If
1264       // Castor could find back the type from the name of the
1265       // element, there is no need to add an xsi:type.
1266       //
1267       // In order to do that, there is two conditions:
1268       // 1. Castor should be able to find the right class to
1269       // instantiate form the XMLName of the instance.
1270       // 2. Castor should be sure than when using the XMLName of
1271       // the instance, there is only one field wich will match
1272       // that name (and that it is the current field)
1273 
1274       // XML Name associated with the class we are marshalling
1275       String xmlElementName = name;
1276       String xmlNamespace = descriptor.getNameSpaceURI();
1277 
1278       // We try to find if there is a XMLClassDescriptor associated
1279       // with the XML name of this class
1280       XMLClassDescriptor xmlElementNameClassDesc = null;
1281       try {
1282         xmlElementNameClassDesc = getResolver().resolveByXMLName(xmlElementName, null, null);
1283       } catch (ResolverException rx) {
1284         // -- exception not important as we're simply
1285         // -- testing to see if we can resolve during
1286         // -- unmarshalling
1287         if (LOG.isDebugEnabled()) {
1288           LOG.debug("Error resolving " + xmlElementName, rx);
1289         }
1290       }
1291 
1292       // Test if we are not dealing with a source generated vector
1293       if ((xmlElementName != null) && (xmlElementNameClassDesc != null)) {
1294         // More than one class can map to a given element name
1295         try {
1296           Iterator<ClassDescriptor> classDescriptorIter =
1297               getResolver().resolveAllByXMLName(xmlElementName, null, null);
1298           for (; classDescriptorIter.hasNext();) {
1299             xmlElementNameClassDesc = (XMLClassDescriptor) classDescriptorIter.next();
1300             if (cls == xmlElementNameClassDesc.getJavaClass()) {
1301               break;
1302             }
1303             // reset the classDescriptor --> none has been found
1304             xmlElementNameClassDesc = null;
1305           }
1306         } catch (ResolverException rx) {
1307           if (LOG.isDebugEnabled()) {
1308             LOG.debug("Error resolving " + xmlElementName, rx);
1309           }
1310           xmlElementNameClassDesc = null;
1311         }
1312 
1313         // make sure we only run into this logic if the classDescriptor
1314         // is coming from a mapping file.
1315         if (xmlElementNameClassDesc instanceof XMLClassDescriptorAdapter) {
1316 
1317           // Try to find a field descriptor directly in the parent object
1318           XMLClassDescriptor tempContaining =
1319               (XMLClassDescriptor) descriptor.getContainingClassDescriptor();
1320 
1321           // --if no containing class descriptor
1322           // --it means the container class could have been introspected
1323           // --so no need to enter the logic
1324           if (tempContaining != null) {
1325             XMLFieldDescriptor fieldDescMatch =
1326                 tempContaining.getFieldDescriptor(xmlElementName, xmlNamespace, NodeType.Element);
1327 
1328             // Try to find a field descriptor by inheritance in the parent object
1329             InheritanceMatch[] matches = searchInheritance(xmlElementName, null, tempContaining); // TODO:
1330                                                                                                   // Joachim,
1331                                                                                                   // _cdResolver);
1332 
1333             if (matches.length == 1) {
1334 
1335               boolean foundTheRightClass = ((xmlElementNameClassDesc != null)
1336                   && (cls == xmlElementNameClassDesc.getJavaClass()));
1337 
1338               boolean oneAndOnlyOneMatchedField =
1339                   ((fieldDescMatch != null) || (matches[0].parentFieldDesc == descriptor));
1340 
1341               // Can we remove the xsi:type ?
1342               if (foundTheRightClass && oneAndOnlyOneMatchedField) {
1343                 saveType = false;
1344                 // no name swapping for now
1345               }
1346             } // lengh is one
1347           }
1348         } // the classDesc comes from a mapping file
1349       }
1350     } // --- End of "if (saveType)"
1351 
1352 
1353     // ------------------------/
1354     // - Namespace Management -/
1355     // ------------------------/
1356 
1357     // -- Set a new namespace scoping
1358     // -- Note: We still need to declare a new scope even if
1359     // -- we are suppressing most namespaces. Certain elements
1360     // -- like xsi:type and xsi:nil will require a namespace
1361     // -- declaration and cannot be suppressed.
1362     if (!atRoot) {
1363       namespacesStack.addNewNamespaceScope();
1364     }
1365 
1366     String nsPrefix = "";
1367     String nsURI = "";
1368 
1369     if (!_suppressNamespaces) {
1370 
1371       // -- Must be done before any attributes are processed
1372       // -- since attributes can be namespaced as well.
1373 
1374       nsPrefix = descriptor.getNameSpacePrefix();
1375       if (nsPrefix == null)
1376         nsPrefix = classDesc.getNameSpacePrefix();
1377 
1378       nsURI = descriptor.getNameSpaceURI();
1379       if (nsURI == null)
1380         nsURI = classDesc.getNameSpaceURI();
1381 
1382       if ((nsURI == null) && (nsPrefix != null)) {
1383         nsURI = namespacesStack.getNamespaceURI(nsPrefix);
1384       } else if ((nsPrefix == null) && (nsURI != null)) {
1385         nsPrefix = namespacesStack.getNamespacePrefix(nsURI);
1386       }
1387       // -- declare namespace at this element scope?
1388       if (nsURI != null) {
1389         String defaultNamespace = namespacesStack.getDefaultNamespaceURI();
1390         if ((nsPrefix == null) && (!nsURI.equals(defaultNamespace))) {
1391           if ((defaultNamespace == null) && atRoot) {
1392             nsPrefix = "";
1393           } else
1394             nsPrefix = DEFAULT_PREFIX + (++_namespaceCounter);
1395         }
1396         declareNamespace(nsPrefix, nsURI);
1397       } else {
1398         nsURI = "";
1399         // -- redeclare default namespace as empty
1400         String defaultNamespace = namespacesStack.getNamespaceURI("");
1401         if ((defaultNamespace != null) && (!"".equals(defaultNamespace)))
1402           namespacesStack.addNamespace("", "");
1403       }
1404     }
1405 
1406 
1407 
1408     // ---------------------/
1409     // - handle attributes -/
1410     // ---------------------/
1411 
1412     AttributesImpl atts = new AttributesImpl();
1413 
1414     // -- user defined attributes
1415     if (atRoot) {
1416       // -- declare xsi prefix if necessary
1417       if (_topLevelAtts.getSize() > 0) {
1418         namespacesStack.addNamespace(XSI_PREFIX, XSI_NAMESPACE);
1419       }
1420 
1421       for (int i = 0; i < _topLevelAtts.getSize(); i++) {
1422         String localName = _topLevelAtts.getName(i);
1423         String qName = localName;
1424         String ns = "";
1425         if (!_suppressNamespaces) {
1426           ns = _topLevelAtts.getNamespace(i);
1427           String prefix = null;
1428           if (StringUtils.isNotEmpty(ns)) {
1429             prefix = namespacesStack.getNonDefaultNamespacePrefix(ns);
1430           }
1431           if (StringUtils.isNotEmpty(prefix)) {
1432             qName = prefix + ':' + qName;
1433           }
1434           if (ns == null)
1435             ns = "";
1436         }
1437         atts.addAttribute(ns, localName, qName, CDATA, _topLevelAtts.getValue(i));
1438       }
1439     }
1440 
1441     // ----------------------------
1442     // -- process attr descriptors
1443     // ----------------------------
1444 
1445     int nestedAttCount = 0;
1446     XMLFieldDescriptor[] nestedAtts = null;
1447     XMLFieldDescriptor[] descriptors = null;
1448     if ((!descriptor.isReference()) && (!isNil)) {
1449       descriptors = classDesc.getAttributeDescriptors();
1450     } else {
1451       // references don't have attributes
1452       descriptors = NO_FIELD_DESCRIPTORS;
1453     }
1454 
1455     for (int i = 0; i < descriptors.length; i++) {
1456       XMLFieldDescriptor attributeDescriptor = descriptors[i];
1457       if (attributeDescriptor == null) {
1458         continue;
1459       }
1460       String path = attributeDescriptor.getLocationPath();
1461       if (StringUtils.isNotEmpty(path)) {
1462         // -- save for later processing
1463         if (nestedAtts == null) {
1464           nestedAtts = new XMLFieldDescriptor[descriptors.length - i];
1465         }
1466         nestedAtts[nestedAttCount] = attributeDescriptor;
1467         nestedAttCount++;
1468       } else {
1469         processAttribute(object, attributeDescriptor, atts);
1470       }
1471     }
1472 
1473     // -- handle ancestor nested attributes
1474     if (mstate._nestedAttCount > 0) {
1475       for (int i = 0; i < mstate._nestedAtts.length; i++) {
1476         XMLFieldDescriptor attributeDescriptor = mstate._nestedAtts[i];
1477         if (attributeDescriptor == null) {
1478           continue;
1479         }
1480         String locationPath = attributeDescriptor.getLocationPath();
1481         if (name.equals(locationPath)) {
1482           // indicate that this 'nested' attribute has been processed
1483           mstate._nestedAtts[i] = null;
1484           // decrease number of unprocessed 'nested' attributes by one
1485           mstate._nestedAttCount--;
1486           processAttribute(mstate.getOwner(), attributeDescriptor, atts);
1487         }
1488       }
1489     }
1490 
1491     // -- Look for attributes in container fields,
1492     // -- (also handle container in container)
1493     if (!isNil)
1494       processContainerAttributes(object, classDesc, atts);
1495 
1496     // -- xml:space
1497     String attValue = descriptor.getXMLProperty(XMLFieldDescriptor.PROPERTY_XML_SPACE);
1498     if (attValue != null) {
1499       atts.addAttribute(Namespaces.XML_NAMESPACE, SPACE_ATTR, XML_SPACE_ATTR, CDATA, attValue);
1500     }
1501 
1502     // -- xml:lang
1503     attValue = descriptor.getXMLProperty(XMLFieldDescriptor.PROPERTY_XML_LANG);
1504     if (attValue != null) {
1505       atts.addAttribute(Namespaces.XML_NAMESPACE, LANG_ATTR, XML_LANG_ATTR, CDATA, attValue);
1506     }
1507 
1508 
1509     // ------------------/
1510     // - Create element -/
1511     // ------------------/
1512 
1513 
1514     // -- save xsi:type information, if necessary
1515 
1516     if (saveType) {
1517       // -- declare XSI namespace, if necessary
1518       declareNamespace(XSI_PREFIX, XSI_NAMESPACE);
1519 
1520       // -- calculate type name, either use class name or
1521       // -- schema type name. If XMLClassDescriptor is introspected,
1522       // -- or is the default XMLClassDescriptorImpl, then
1523       // -- use java:classname, otherwise use XML name.
1524       String typeName = classDesc.getXMLName();
1525 
1526       // -- Check for introspection...
1527       boolean introspected = false;
1528       if (classDesc instanceof InternalXMLClassDescriptor)
1529         introspected = ((InternalXMLClassDescriptor) classDesc).introspected();
1530       else
1531         introspected = Introspector.introspected(classDesc);
1532 
1533       boolean useJavaPrefix = false;
1534       if ((typeName == null) || introspected) {
1535         typeName = JAVA_PREFIX + cls.getName();
1536         useJavaPrefix = true;
1537       } else if (classDesc instanceof RootArrayDescriptor) {
1538         typeName = JAVA_PREFIX + cls.getName();
1539         useJavaPrefix = true;
1540       } else {
1541         String dcn = classDesc.getClass().getName();
1542         if (dcn.equals(XMLClassDescriptorImpl.class.getName())) {
1543           typeName = JAVA_PREFIX + cls.getName();
1544           useJavaPrefix = true;
1545         } else {
1546           // -- calculate proper prefix
1547           String tns = classDesc.getNameSpaceURI();
1548           String prefix = null;
1549           if (StringUtils.isNotEmpty(tns)) {
1550             prefix = namespacesStack.getNamespacePrefix(tns);
1551             if (StringUtils.isNotEmpty(prefix)) {
1552               typeName = prefix + ':' + typeName;
1553             }
1554           }
1555         }
1556       }
1557       // -- save type information
1558       atts.addAttribute(XSI_NAMESPACE, TYPE_ATTR, XSI_TYPE, CDATA, typeName);
1559       if (useJavaPrefix) {
1560         if (namespacesStack.getNamespaceURI("java") == null) {
1561           // -- declare Java namespace, if necessary
1562           declareNamespace("java", "http://java.sun.com");
1563         }
1564       }
1565     }
1566 
1567     if (isNil && !_suppressXSIType) {
1568       // -- declare XSI namespace, if necessary
1569       declareNamespace(XSI_PREFIX, XSI_NAMESPACE);
1570       // -- add xsi:nil="true"
1571       atts.addAttribute(XSI_NAMESPACE, NIL_ATTR, XSI_NIL_ATTR, CDATA, TRUE_VALUE);
1572     }
1573 
1574     // check if the value is a QName that needs to
1575     // be resolved ({URI}value -> ns:value)
1576     // This should be done BEFORE declaring the namespaces as attributes
1577     // because we can declare new namespace during the QName resolution
1578     String valueType = descriptor.getSchemaType();
1579     if ((valueType != null) && (valueType.equals(QNAME_NAME))) {
1580       object = resolveQName(object, descriptor);
1581     }
1582 
1583 
1584     String qName = null;
1585     if (nsPrefix != null) {
1586       int len = nsPrefix.length();
1587       if (len > 0) {
1588         qName = nsPrefix + ':' + name;
1589       } else
1590         qName = name;
1591     } else
1592       qName = name;
1593 
1594 
1595     Object firstNonNullValue = null;
1596     int firstNonNullIdx = 0;
1597 
1598     try {
1599 
1600 
1601       if (!containerField) {
1602 
1603 
1604         // -- isNillable?
1605         if ((!isNil) && descriptor.isNillable()) {
1606           XMLFieldDescriptor desc = classDesc.getContentDescriptor();
1607           descriptors = classDesc.getElementDescriptors();
1608           int descCount = descriptors.length;
1609           boolean isNilContent = (descCount > 0) || (desc != null);
1610 
1611           // -- check content descriptor for a valid value
1612           if (desc != null) {
1613             Object value = desc.getHandler().getValue(object);
1614             if (value != null) {
1615               isNilContent = false;
1616               descCount = 0;
1617             } else if (desc.isNillable() && desc.isRequired()) {
1618               isNilContent = false;
1619               descCount = 0;
1620             }
1621           }
1622 
1623 
1624           for (int i = 0; i < descCount; i++) {
1625             desc = descriptors[i];
1626             if (desc == null)
1627               continue;
1628             Object value = desc.getHandler().getValue(object);
1629             if (value != null) {
1630               isNilContent = false;
1631               firstNonNullIdx = i;
1632               firstNonNullValue = value;
1633               break;
1634             } else if (desc.isNillable() && desc.isRequired()) {
1635               isNilContent = false;
1636               firstNonNullIdx = i;
1637               firstNonNullValue = new NilObject(classDesc, desc);
1638               break;
1639             }
1640           }
1641 
1642           if (isNilContent) {
1643             declareNamespace(XSI_PREFIX, XSI_NAMESPACE);
1644             atts.addAttribute(XSI_NAMESPACE, NIL_ATTR, XSI_NIL_ATTR, CDATA, TRUE_VALUE);
1645           }
1646         }
1647 
1648         // -- declare all necesssary namespaces
1649         namespacesStack.getCurrentNamespaceScope().sendStartEvents(handler);
1650         // -- Make sure qName is not null
1651         if (qName == null) {
1652           // -- hopefully this never happens, but if it does, it means
1653           // -- we have a bug in our naming logic
1654           String err = "Error in deriving name for type: " + cls.getName()
1655               + ", please report bug to: " + "http://castor.exolab.org.";
1656           throw new IllegalStateException(err);
1657         }
1658 
1659 
1660         handler.startElement(nsURI, name, qName, atts);
1661       }
1662     } catch (org.xml.sax.SAXException sx) {
1663       throw new MarshalException(sx);
1664     }
1665 
1666 
1667     // ---------------------------------------
1668     // -- process all child content, including
1669     // -- text nodes + daughter elements
1670     // ---------------------------------------
1671 
1672     Stack<WrapperInfo> wrappers = null;
1673 
1674 
1675     // ----------------------
1676     // -- handle text content
1677     // ----------------------
1678 
1679     if (!isNil) {
1680 
1681       XMLFieldDescriptor cdesc = null;
1682       if (!descriptor.isReference()) {
1683         cdesc = classDesc.getContentDescriptor();
1684       }
1685       if (cdesc != null) {
1686         Object obj = null;
1687         try {
1688           obj = cdesc.getHandler().getValue(object);
1689         } catch (IllegalStateException ise) {
1690           LOG.warn("Error getting value from: " + object, ise);
1691         }
1692 
1693         if (obj != null) {
1694 
1695           // -- <Wrapper>
1696           // -- handle XML path
1697           String path = cdesc.getLocationPath();
1698           String currentLoc = null;
1699 
1700           if (path != null) {
1701             _attributes.clear();
1702             if (wrappers == null) {
1703               wrappers = new SafeStack<WrapperInfo>();
1704             }
1705             try {
1706               while (path != null) {
1707 
1708                 String elemName = null;
1709                 int idx = path.indexOf('/');
1710 
1711                 if (idx > 0) {
1712                   elemName = path.substring(0, idx);
1713                   path = path.substring(idx + 1);
1714                 } else {
1715                   elemName = path;
1716                   path = null;
1717                 }
1718 
1719                 // -- save current location without namespace
1720                 // -- information for now.
1721                 if (currentLoc == null)
1722                   currentLoc = elemName;
1723                 else
1724                   currentLoc = currentLoc + "/" + elemName;
1725 
1726                 String elemQName = elemName;
1727                 if (StringUtils.isNotEmpty(nsPrefix)) {
1728                   elemQName = nsPrefix + ':' + elemName;
1729                 }
1730                 wrappers.push(new WrapperInfo(elemName, elemQName, currentLoc));
1731 
1732 
1733                 _attributes.clear();
1734                 if (nestedAttCount > 0) {
1735                   for (int na = 0; na < nestedAtts.length; na++) {
1736                     if (nestedAtts[na] == null)
1737                       continue;
1738                     String tmpPath = nestedAtts[na].getLocationPath();
1739                     if (tmpPath.equals(currentLoc)) {
1740                       processAttribute(object, nestedAtts[na], _attributes);
1741                       nestedAtts[na] = null;
1742                       --nestedAttCount;
1743                     }
1744                   }
1745                 }
1746                 handler.startElement(nsURI, elemName, elemQName, _attributes);
1747               }
1748             } catch (SAXException sx) {
1749               throw new MarshalException(sx);
1750             }
1751           }
1752           // -- </Wrapper>
1753 
1754           char[] chars = null;
1755           Class<?> objType = obj.getClass();
1756           if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) {
1757             // -- handle base64/hexbinary content
1758             final String schemaType = descriptor.getSchemaType();
1759             if (HexDecoder.DATA_TYPE.equals(schemaType)) {
1760               chars = new String(HexDecoder.encode((byte[]) obj)).toCharArray();
1761             } else {
1762               chars = Base64Encoder.encode((byte[]) obj);
1763             }
1764           } else {
1765             // -- all other types
1766             String str = obj.toString();
1767             if (StringUtils.isNotEmpty(str)) {
1768               chars = str.toCharArray();
1769             }
1770           }
1771           if ((chars != null) && (chars.length > 0)) {
1772             try {
1773               handler.characters(chars, 0, chars.length);
1774             } catch (org.xml.sax.SAXException sx) {
1775               throw new MarshalException(sx);
1776             }
1777           }
1778         }
1779       }
1780       // -- element references
1781       else if (descriptor.isReference()) {
1782         Object id = getObjectID(object);
1783         if (id != null) {
1784           char[] chars = id.toString().toCharArray();
1785           try {
1786             handler.characters(chars, 0, chars.length);
1787           } catch (org.xml.sax.SAXException sx) {
1788             throw new MarshalException(sx);
1789           }
1790         }
1791       }
1792       // special case for byte[]
1793       else if (byteArray) {
1794         // -- Base64Encoding / HexBinary
1795         String schemaType = descriptor.getSchemaType();
1796         String componentType = descriptor.getComponentType();
1797         char[] chars = new char[0];
1798         if ((descriptor.isMultivalued() && HexDecoder.DATA_TYPE.equals(componentType))
1799             || HexDecoder.DATA_TYPE.equals(schemaType)) {
1800           chars = new String(HexDecoder.encode((byte[]) object)).toCharArray();
1801         } else {
1802           chars = Base64Encoder.encode((byte[]) object);
1803         }
1804         try {
1805           handler.characters(chars, 0, chars.length);
1806         } catch (org.xml.sax.SAXException sx) {
1807           throw new MarshalException(sx);
1808         }
1809       }
1810       /* special case for Strings and primitives */
1811       else if (isPrimitive(cls)) {
1812 
1813         char[] chars;
1814         if (cls == java.math.BigDecimal.class) {
1815           chars = convertBigDecimalToString(object).toCharArray();
1816         } else {
1817           chars = object.toString().toCharArray();
1818         }
1819         try {
1820           handler.characters(chars, 0, chars.length);
1821         } catch (org.xml.sax.SAXException sx) {
1822           throw new MarshalException(sx);
1823         }
1824       } else if (isEnum(cls)) {
1825         char[] chars = object.toString().toCharArray();
1826         try {
1827           handler.characters(chars, 0, chars.length);
1828         } catch (org.xml.sax.SAXException sx) {
1829           throw new MarshalException(sx);
1830         }
1831 
1832       }
1833     }
1834 
1835     // ---------------------------
1836     // -- handle daughter elements
1837     // ---------------------------
1838 
1839     if (isNil || descriptor.isReference()) {
1840       descriptors = NO_FIELD_DESCRIPTORS;
1841     } else {
1842       descriptors = classDesc.getElementDescriptors();
1843     }
1844 
1845     ++_depth;
1846 
1847     // -- marshal elements
1848     for (int i = firstNonNullIdx; i < descriptors.length; i++) {
1849 
1850       XMLFieldDescriptor elemDescriptor = descriptors[i];
1851       Object obj = null;
1852       boolean nil = false;
1853 
1854       // -- used previously cached value?
1855       if ((i == firstNonNullIdx) && (firstNonNullValue != null)) {
1856         obj = firstNonNullValue;
1857       } else {
1858         // -- obtain value from handler
1859         try {
1860           obj = elemDescriptor.getHandler().getValue(object);
1861         } catch (IllegalStateException ise) {
1862           LOG.warn("Error marshalling " + object, ise);
1863           continue;
1864         }
1865       }
1866 
1867       if (obj == null
1868           || (obj instanceof Enumeration && !((Enumeration<?>) obj).hasMoreElements())) {
1869         if (elemDescriptor.isNillable() && (elemDescriptor.isRequired())) {
1870           nil = true;
1871         } else {
1872           continue;
1873         }
1874       }
1875 
1876 
1877       // -- handle XML path
1878       String path = elemDescriptor.getLocationPath();
1879       String currentLoc = null;
1880       // -- Wrapper/Location cleanup
1881       if (wrappers != null) {
1882         try {
1883           while (!wrappers.empty()) {
1884             WrapperInfo wInfo = wrappers.peek();
1885             if (path != null) {
1886               if (wInfo._location.equals(path)) {
1887                 path = null;
1888                 break;
1889               } else if (path.startsWith(wInfo._location + "/")) {
1890                 path = path.substring(wInfo._location.length() + 1);
1891                 currentLoc = wInfo._location;
1892                 break;
1893               }
1894             }
1895             handler.endElement(nsURI, wInfo._localName, wInfo._qName);
1896             wrappers.pop();
1897           }
1898         } catch (SAXException sx) {
1899           throw new MarshalException(sx);
1900         }
1901       }
1902 
1903 
1904       if (path != null) {
1905         _attributes.clear();
1906         if (wrappers == null) {
1907           wrappers = new SafeStack<WrapperInfo>();
1908         }
1909         try {
1910           while (path != null) {
1911 
1912             String elemName = null;
1913             int idx = path.indexOf('/');
1914 
1915             if (idx > 0) {
1916               elemName = path.substring(0, idx);
1917               path = path.substring(idx + 1);
1918             } else {
1919               elemName = path;
1920               path = null;
1921             }
1922 
1923             // -- save current location without namespace
1924             // -- information for now.
1925             if (currentLoc == null)
1926               currentLoc = elemName;
1927             else
1928               currentLoc = currentLoc + "/" + elemName;
1929 
1930             String elemQName = elemName;
1931             if (StringUtils.isNotEmpty(nsPrefix)) {
1932               elemQName = nsPrefix + ':' + elemName;
1933             }
1934             wrappers.push(new WrapperInfo(elemName, elemQName, currentLoc));
1935 
1936 
1937             _attributes.clear();
1938             if (nestedAttCount > 0) {
1939               for (int na = 0; na < nestedAtts.length; na++) {
1940                 if (nestedAtts[na] == null)
1941                   continue;
1942                 String tmpPath = nestedAtts[na].getLocationPath();
1943                 if (tmpPath.equals(currentLoc)) {
1944                   processAttribute(object, nestedAtts[na], _attributes);
1945                   nestedAtts[na] = null;
1946                   --nestedAttCount;
1947                 }
1948               }
1949             }
1950             handler.startElement(nsURI, elemName, elemQName, _attributes);
1951           }
1952         } catch (SAXException sx) {
1953           throw new MarshalException(sx);
1954         }
1955       }
1956 
1957       if (nil) {
1958         obj = new NilObject(classDesc, elemDescriptor);
1959       }
1960 
1961       final Class<?> type = obj.getClass();
1962 
1963       MarshalState myState = mstate.createMarshalState(object, name);
1964       myState._nestedAtts = nestedAtts;
1965       myState._nestedAttCount = nestedAttCount;
1966 
1967 
1968       // -- handle byte arrays
1969       if (type.isArray() && (type.getComponentType() == Byte.TYPE)) {
1970         marshal(obj, elemDescriptor, handler, myState);
1971       } else if (type.isArray() && elemDescriptor.isDerivedFromXSList()) {
1972         Object buffer = processXSListType(obj, elemDescriptor);
1973         String elemName = elemDescriptor.getXMLName();
1974         String elemQName = elemName;
1975         if (StringUtils.isNotEmpty(nsPrefix)) {
1976           elemQName = nsPrefix + ':' + elemName;
1977         }
1978         char[] chars = buffer.toString().toCharArray();
1979         try {
1980           handler.startElement(nsURI, elemName, elemQName, _attributes);
1981           handler.characters(chars, 0, chars.length);
1982           handler.endElement(nsURI, elemName, elemQName);
1983         } catch (org.xml.sax.SAXException sx) {
1984           throw new MarshalException(sx);
1985         }
1986       }
1987       // -- handle all other collection types
1988       else if (isCollection(type)) {
1989         boolean processCollection = true;
1990         if (_saveMapKeys) {
1991           MapHandler mapHandler = MapHandlers.getHandler(type);
1992           if (mapHandler != null) {
1993             processCollection = false;
1994             MapItem item = new MapItem();
1995             Enumeration<?> keys = mapHandler.keys(obj);
1996             while (keys.hasMoreElements()) {
1997               item.setKey(keys.nextElement());
1998               item.setValue(mapHandler.get(obj, item.getKey()));
1999               marshal(item, elemDescriptor, handler, myState);
2000             }
2001           }
2002 
2003         }
2004         if (processCollection) {
2005           CollectionHandler<?> colHandler = getCollectionHandler(type);
2006           Enumeration<?> enumeration = colHandler.elements(obj);
2007           while (enumeration.hasMoreElements()) {
2008             Object item = enumeration.nextElement();
2009             if (item != null) {
2010               marshal(item, elemDescriptor, handler, myState);
2011             }
2012           }
2013         }
2014       }
2015       // -- otherwise just marshal object as is
2016       else {
2017         marshal(obj, elemDescriptor, handler, myState);
2018       }
2019 
2020       if (nestedAttCount > 0) {
2021         nestedAttCount = myState._nestedAttCount;
2022       }
2023 
2024     }
2025 
2026 
2027     // -- Wrapper/Location cleanup for elements
2028     if (wrappers != null) {
2029       try {
2030         while (!wrappers.empty()) {
2031           WrapperInfo wInfo = wrappers.peek();
2032           boolean popStack = true;
2033           if (nestedAttCount > 0) {
2034             for (int na = 0; na < nestedAtts.length; na++) {
2035               // TODO[LL]: refactor to avoid check against null
2036               if (nestedAtts[na] == null)
2037                 continue;
2038               String nestedAttributePath = nestedAtts[na].getLocationPath();
2039               if (nestedAttributePath.startsWith(wInfo._location + "/")) {
2040                 popStack = false;
2041                 break;
2042               }
2043             }
2044           }
2045           if (popStack) {
2046             handler.endElement(nsURI, wInfo._localName, wInfo._qName);
2047             wrappers.pop();
2048           } else {
2049             break;
2050           }
2051         }
2052       } catch (SAXException sx) {
2053         throw new MarshalException(sx);
2054       }
2055     }
2056 
2057     // TODO: Handling additional attributes at the end causes elements to be marshalled in the wrong
2058     // order when element 'text' is null, but their attribute value is not null. Can this be fixed
2059     // to process element attributes even when the element text value is null?
2060 
2061     if (wrappers != null && !wrappers.isEmpty()) {
2062       dealWithNestedAttributesNested(object, handler, nsPrefix, nsURI, nestedAttCount, nestedAtts,
2063           wrappers);
2064     }
2065 
2066     dealWithNestedAttributes(object, handler, nsPrefix, nsURI, nestedAttCount, nestedAtts,
2067         new SafeStack<WrapperInfo>());
2068 
2069     // -- finish element
2070     try {
2071       if (!containerField) {
2072         handler.endElement(nsURI, name, qName);
2073         // -- undeclare all necesssary namespaces
2074         namespacesStack.getCurrentNamespaceScope().sendEndEvents(handler);
2075       }
2076     } catch (org.xml.sax.SAXException sx) {
2077       throw new MarshalException(sx);
2078     }
2079 
2080     --_depth;
2081     _parents.pop();
2082     if (!atRoot) {
2083       namespacesStack.removeNamespaceScope();
2084     }
2085 
2086     // -- notify listener of post marshal
2087     if (_marshalListener != null) {
2088       try {
2089         _marshalListener.postMarshal(object);
2090       } catch (RuntimeException e) {
2091         LOG.error(
2092             "Invoking #postMarshal() on your custom MarshalListener instance caused the following problem:",
2093             e);
2094       }
2095     }
2096 
2097   }
2098 
2099   private void dealWithNestedAttributes(Object object, ContentHandler handler, String nsPrefix,
2100       String nsURI, int nestedAttCount, XMLFieldDescriptor[] nestedAtts,
2101       Stack<WrapperInfo> wrappers) throws MarshalException {
2102     // -- Handle any additional attribute locations that were
2103     // -- not handled when dealing with wrapper elements
2104     if (nestedAttCount > 0) {
2105       for (int i = 0; i < nestedAtts.length; i++) {
2106         if (nestedAtts[i] == null)
2107           continue;
2108         String path = nestedAtts[i].getLocationPath();
2109         String currentLoc = null;
2110 
2111 
2112         // -- Make sure attribute has value before continuing
2113         // -- We really could use a FieldHandler#hasValue()
2114         // -- method (since sometimes getValue() methods may
2115         // -- be expensive and we don't always want to call it
2116         // -- multiple times)
2117         if (nestedAtts[i].getHandler().getValue(object) == null) {
2118           nestedAtts[i] = null;
2119           --nestedAttCount;
2120           continue;
2121         }
2122         try {
2123           while (path != null) {
2124             int idx = path.indexOf('/');
2125             String elemName = null;
2126             if (idx > 0) {
2127               elemName = path.substring(0, idx);
2128               path = path.substring(idx + 1);
2129             } else {
2130               elemName = path;
2131               path = null;
2132             }
2133             if (currentLoc == null)
2134               currentLoc = elemName;
2135             else
2136               currentLoc = currentLoc + "/" + elemName;
2137 
2138             String elemQName = elemName;
2139             if (StringUtils.isNotEmpty(nsPrefix)) {
2140               elemQName = nsPrefix + ':' + elemName;
2141             }
2142             wrappers.push(new WrapperInfo(elemName, elemQName, null));
2143 
2144             _attributes.clear();
2145             if (path == null) {
2146               processAttribute(object, nestedAtts[i], _attributes);
2147               nestedAtts[i] = null;
2148               --nestedAttCount;
2149             }
2150             if (nestedAttCount > 0) {
2151               for (int na = i + 1; na < nestedAtts.length; na++) {
2152                 if (nestedAtts[na] == null)
2153                   continue;
2154                 String tmpPath = nestedAtts[na].getLocationPath();
2155                 if (tmpPath.equals(currentLoc)) {
2156                   processAttribute(object, nestedAtts[na], _attributes);
2157                   nestedAtts[na] = null;
2158                   --nestedAttCount;
2159                 }
2160               }
2161             }
2162             handler.startElement(nsURI, elemName, elemQName, _attributes);
2163           }
2164 
2165           while (!wrappers.empty()) {
2166             WrapperInfo wInfo = wrappers.pop();
2167             handler.endElement(nsURI, wInfo._localName, wInfo._qName);
2168           }
2169         } catch (Exception e) {
2170           throw new MarshalException(e);
2171         }
2172       }
2173     } // if (nestedAttCount > 0)
2174   }
2175 
2176   private void dealWithNestedAttributesNested(Object object, ContentHandler handler,
2177       String nsPrefix, String nsURI, int nestedAttCount, XMLFieldDescriptor[] nestedAtts,
2178       Stack<WrapperInfo> wrappers) throws MarshalException {
2179     // -- Handle any additional attribute locations that were
2180     // -- not handled when dealing with wrapper elements
2181 
2182     WrapperInfo wrapperInfo = wrappers.peek();
2183     String currentLocation = wrapperInfo._location;
2184 
2185     if (nestedAttCount > 0) {
2186       for (int i = 0; i < nestedAtts.length; i++) {
2187         if (nestedAtts[i] == null)
2188           continue;
2189         String nestedAttributePath = nestedAtts[i].getLocationPath();
2190 
2191         if (!nestedAttributePath.startsWith(currentLocation + "/")) {
2192           continue;
2193         }
2194 
2195         nestedAttributePath = nestedAttributePath.substring(wrapperInfo._location.length() + 1);
2196         String currentLoc = currentLocation;
2197 
2198 
2199         // -- Make sure attribute has value before continuing
2200         // -- We really could use a FieldHandler#hasValue()
2201         // -- method (since sometimes getValue() methods may
2202         // -- be expensive and we don't always want to call it
2203         // -- multiple times)
2204         if (nestedAtts[i].getHandler().getValue(object) == null) {
2205           nestedAtts[i] = null;
2206           --nestedAttCount;
2207           continue;
2208         }
2209         try {
2210           while (nestedAttributePath != null) {
2211             int idx = nestedAttributePath.indexOf('/');
2212             String elemName = null;
2213             if (idx > 0) {
2214               elemName = nestedAttributePath.substring(0, idx);
2215               nestedAttributePath = nestedAttributePath.substring(idx + 1);
2216             } else {
2217               elemName = nestedAttributePath;
2218               nestedAttributePath = null;
2219             }
2220             if (currentLoc == null)
2221               currentLoc = elemName;
2222             else
2223               currentLoc = currentLoc + "/" + elemName;
2224 
2225             String elemQName = elemName;
2226             if (StringUtils.isNotEmpty(nsPrefix)) {
2227               elemQName = nsPrefix + ':' + elemName;
2228             }
2229             wrappers.push(new WrapperInfo(elemName, elemQName, null));
2230 
2231             _attributes.clear();
2232             if (nestedAttributePath == null) {
2233               processAttribute(object, nestedAtts[i], _attributes);
2234               nestedAtts[i] = null;
2235               --nestedAttCount;
2236             }
2237             if (nestedAttCount > 0) {
2238               for (int na = i + 1; na < nestedAtts.length; na++) {
2239                 if (nestedAtts[na] == null)
2240                   continue;
2241                 String tmpPath = nestedAtts[na].getLocationPath();
2242                 if (tmpPath.equals(currentLoc)) {
2243                   processAttribute(object, nestedAtts[na], _attributes);
2244                   nestedAtts[na] = null;
2245                   --nestedAttCount;
2246                 }
2247               }
2248             }
2249             handler.startElement(nsURI, elemName, elemQName, _attributes);
2250           }
2251 
2252           while (!wrappers.empty()) {
2253             WrapperInfo wInfo = wrappers.pop();
2254             handler.endElement(nsURI, wInfo._localName, wInfo._qName);
2255           }
2256         } catch (Exception e) {
2257           throw new MarshalException(e);
2258         }
2259       }
2260     } // if (nestedAttCount > 0)
2261   }
2262 
2263 
2264   /**
2265    * Converts a {@link BigDecimal} instance value to its String representation. This method will
2266    * take into into account the Java version number, as the semantics of BigDecimal.toString() have
2267    * changed between Java 1.4 and Java 5.0 and above.
2268    * 
2269    * @param object The {@link BigDecimal} instance
2270    * @return The String representation of the {@link BigDecimal} instance
2271    * @throws MarshalException If invocation of BigDecimal#toPlainString() fails.
2272    */
2273   private String convertBigDecimalToString(Object object) throws MarshalException {
2274     String stringValue;
2275     float javaVersion = Float.parseFloat(System.getProperty("java.specification.version"));
2276     if (javaVersion >= 1.5) {
2277       // as of Java 5.0 and above, BigDecimal.toPlainString() should be used.
2278       // TODO: reconsider if we start using BigDecimal for XSTypes that can hold scientific values
2279       Method method;
2280       try {
2281         method = java.math.BigDecimal.class.getMethod("toPlainString", (Class[]) null);
2282         stringValue = (String) method.invoke(object, (Object[]) null);
2283       } catch (Exception e) {
2284         LOG.error("Problem accessing java.math.BigDecimal.toPlainString().", e);
2285         throw new MarshalException("Problem accessing java.math.BigDecimal.toPlainString().", e);
2286       }
2287     } else {
2288       // use BigDecimal.toString() with Java 1.4 and below
2289       stringValue = object.toString();
2290     }
2291     return stringValue;
2292   }
2293 
2294   /**
2295    * Retrieves the ID for the given Object
2296    *
2297    * @param object the Object to retrieve the ID for
2298    * @return the ID for the given Object
2299    **/
2300   private Object getObjectID(Object object) throws MarshalException {
2301     if (object == null)
2302       return null;
2303 
2304     Object id = null;
2305     XMLClassDescriptor cd = getClassDescriptor(object.getClass());
2306     String err = null;
2307     if (cd != null) {
2308       XMLFieldDescriptor fieldDesc = (XMLFieldDescriptor) cd.getIdentity();
2309       if (fieldDesc != null) {
2310         FieldHandler<?> fieldHandler = fieldDesc.getHandler();
2311         if (fieldHandler != null) {
2312           try {
2313             id = fieldHandler.getValue(object);
2314           } catch (IllegalStateException ise) {
2315             err = ise.toString();
2316           }
2317         } // fieldHandler != null
2318         else {
2319           err = "FieldHandler for Identity descriptor is null.";
2320         }
2321       } // fieldDesc != null
2322       else
2323         err = "No identity descriptor available";
2324     } // cd!=null
2325     else {
2326       err = "Unable to resolve ClassDescriptor.";
2327     }
2328     if (err != null) {
2329       String errMsg = "Unable to resolve ID for instance of class '";
2330       errMsg += object.getClass().getName();
2331       errMsg += "' due to the following error: ";
2332       throw new MarshalException(errMsg + err);
2333     }
2334     return id;
2335   } // -- getID
2336 
2337 
2338   /**
2339    * Declares the given namespace, if not already in scope
2340    *
2341    * @param nsPrefix the namespace prefix
2342    * @param nsURI the namespace URI to declare
2343    * @return true if the namespace was not in scope and was sucessfully declared, other false
2344    **/
2345   private boolean declareNamespace(String nsPrefix, String nsURI) {
2346     boolean declared = false;
2347 
2348     // -- make sure it's not already declared...
2349     if ((nsURI != null) && (nsURI.length() != 0)) {
2350 
2351       String tmpURI = namespacesStack.getNamespaceURI(nsPrefix);
2352       if ((tmpURI != null) && (tmpURI.equals(nsURI))) {
2353         return declared;
2354       }
2355       String tmpPrefix = namespacesStack.getNamespacePrefix(nsURI);
2356       if ((tmpPrefix == null) || (!tmpPrefix.equals(nsPrefix))) {
2357         namespacesStack.addNamespace(nsPrefix, nsURI);
2358         declared = true;
2359       }
2360     }
2361     return declared;
2362   } // -- declareNamespace
2363 
2364   /**
2365    * Sets the PrintWriter used for logging
2366    * 
2367    * @param printWriter the PrintWriter to use for logging
2368    **/
2369   public void setLogWriter(final PrintWriter printWriter) {}
2370 
2371   /**
2372    * Sets the encoding for the serializer. Note that this method cannot be called if you've passed
2373    * in your own DocumentHandler.
2374    *
2375    * @param encoding the encoding to set
2376    **/
2377   public void setEncoding(String encoding) {
2378 
2379     if (_serializer != null) {
2380       if (_format == null) {
2381         _format = getInternalContext().getOutputFormat();
2382       }
2383       _format.setEncoding(encoding);
2384       // -- reset output format, this needs to be done
2385       // -- any time a change occurs to the format.
2386       _serializer.setOutputFormat(_format);
2387       try {
2388         // -- Due to a Xerces Serializer bug that doesn't allow declaring
2389         // -- multiple prefixes to the same namespace, we use the old
2390         // -- DocumentHandler format and process namespaces ourselves
2391         _handler = new DocumentHandlerAdapter(_serializer.asDocumentHandler());
2392       } catch (java.io.IOException iox) {
2393         // -- we can ignore this exception since it shouldn't
2394         // -- happen. If _serializer is not null, it means
2395         // -- we've already called this method sucessfully
2396         // -- in the Marshaller() constructor
2397         if (LOG.isDebugEnabled()) {
2398           LOG.debug("Error setting encoding to " + encoding, iox);
2399         }
2400       }
2401     } else {
2402       String error = "encoding cannot be set if you've passed in " + "your own DocumentHandler";
2403       throw new IllegalStateException(error);
2404     }
2405   } // -- setEncoding
2406 
2407   /**
2408    * Sets the value for the xsi:noNamespaceSchemaLocation attribute. When set, this attribute will
2409    * appear on the root element of the marshalled document.
2410    *
2411    * @param schemaLocation the URI location of the schema to which the marshalled document is an
2412    *        instance of.
2413    **/
2414   public void setNoNamespaceSchemaLocation(String schemaLocation) {
2415     if (schemaLocation == null) {
2416       // -- remove if necessary
2417       // -- to be added later.
2418     } else {
2419       _topLevelAtts.setAttribute(XSI_NO_NAMESPACE_SCHEMA_LOCATION, schemaLocation, XSI_NAMESPACE);
2420     }
2421   } // -- setNoNamespaceSchemaLocation
2422 
2423   /**
2424    * Sets the value for the xsi:schemaLocation attribute. When set, this attribute will appear on
2425    * the root element of the marshalled document.
2426    *
2427    * @param schemaLocation the URI location of the schema to which the marshalled document is an
2428    *        instance of.
2429    **/
2430   public void setSchemaLocation(String schemaLocation) {
2431     if (schemaLocation == null) {
2432       // -- remove if necessary
2433       // -- to be added later.
2434     } else {
2435       _topLevelAtts.setAttribute(XSI_SCHEMA_LOCATION, schemaLocation, XSI_NAMESPACE);
2436     }
2437   } // -- setSchemaLocation
2438 
2439   /**
2440    * Sets whether or not namespaces are output. By default the Marshaller will output namespace
2441    * declarations and prefix elements and attributes with their respective namespace prefix. This
2442    * method can be used to prevent the usage of namespaces.
2443    *
2444    * @param suppressNamespaces a boolean that when true will prevent namespaces from being output.
2445    */
2446   public void setSuppressNamespaces(boolean suppressNamespaces) {
2447     _suppressNamespaces = suppressNamespaces;
2448   } // -- setSuppressNamespaces
2449 
2450   /**
2451    * Sets whether or not the xsi:type attribute should appear on the marshalled document.
2452    *
2453    * @param suppressXSIType a boolean that when true will prevent xsi:type attribute from being used
2454    *        in the marshalling process.
2455    */
2456   public void setSuppressXSIType(boolean suppressXSIType) {
2457     _suppressXSIType = suppressXSIType;
2458   } // -- setSuppressXSIType
2459 
2460   /**
2461    * Sets whether or not to output the xsi:type at the root element. This is usually needed when the
2462    * root element type cannot be determined by the element name alone. By default xsi:type will not
2463    * be output on the root element.
2464    *
2465    * @param useXSITypeAtRoot a boolean that when true indicates that the xsi:type should be output
2466    *        on the root element.
2467    */
2468   public void setUseXSITypeAtRoot(boolean useXSITypeAtRoot) {
2469     _useXSITypeAtRoot = useXSITypeAtRoot;
2470   } // -- setUseXSITypeAtRoot
2471 
2472   /**
2473    * Finds and returns an XMLClassDescriptor for the given class. If a XMLClassDescriptor could not
2474    * be found, this method will attempt to create one automatically using reflection.
2475    * 
2476    * @param cls the Class to get the XMLClassDescriptor for
2477    * @exception MarshalException when there is a problem retrieving or creating the
2478    *            XMLClassDescriptor for the given class
2479    **/
2480   private XMLClassDescriptor getClassDescriptor(final Class<?> cls) throws MarshalException {
2481     XMLClassDescriptor classDesc = null;
2482 
2483     try {
2484       if (!isPrimitive(cls))
2485         classDesc = (XMLClassDescriptor) getResolver().resolve(cls);
2486     } catch (ResolverException rx) {
2487       Throwable actual = rx.getCause();
2488       if (actual instanceof MarshalException) {
2489         throw (MarshalException) actual;
2490       }
2491       if (actual != null) {
2492         throw new MarshalException(actual);
2493       }
2494       throw new MarshalException(rx);
2495     }
2496 
2497     if (classDesc != null)
2498       classDesc = new InternalXMLClassDescriptor(classDesc);
2499 
2500     return classDesc;
2501   } // -- getClassDescriptor
2502 
2503   /**
2504    * Processes the attribute associated with the given attDescriptor and parent object.
2505    *
2506    * @param atts the SAX attribute list to add the attribute to
2507    */
2508   private void processAttribute(Object object, XMLFieldDescriptor attDescriptor,
2509       AttributesImpl atts) throws MarshalException {
2510     if (attDescriptor == null)
2511       return;
2512 
2513     // -- process Namespace nodes from Object Model,
2514     // -- if necessary.
2515     if (attDescriptor.getNodeType() == NodeType.Namespace) {
2516       if (!_suppressNamespaces) {
2517         Object map = attDescriptor.getHandler().getValue(object);
2518         MapHandler mapHandler = MapHandlers.getHandler(map);
2519         if (mapHandler != null) {
2520           Enumeration<?> keys = mapHandler.keys(map);
2521           while (keys.hasMoreElements()) {
2522             Object key = keys.nextElement();
2523             Object val = mapHandler.get(map, key);
2524             declareNamespace(key.toString(), val.toString());
2525           }
2526         }
2527       }
2528       return;
2529     }
2530 
2531     String localName = attDescriptor.getXMLName();
2532     String qName = localName;
2533 
2534     // -- handle attribute namespaces
2535     String namespace = "";
2536     if (!_suppressNamespaces) {
2537       namespace = attDescriptor.getNameSpaceURI();
2538       if (StringUtils.isNotEmpty(namespace)) {
2539         String prefix = attDescriptor.getNameSpacePrefix();
2540         if ((prefix == null) || (prefix.length() == 0))
2541           prefix = namespacesStack.getNonDefaultNamespacePrefix(namespace);
2542 
2543         if ((prefix == null) || (prefix.length() == 0)) {
2544           // -- automatically create namespace prefix?
2545           prefix = DEFAULT_PREFIX + (++_namespaceCounter);
2546         }
2547         declareNamespace(prefix, namespace);
2548         qName = prefix + ':' + qName;
2549       } else
2550         namespace = "";
2551     }
2552 
2553     Object value = null;
2554 
2555     try {
2556       value = attDescriptor.getHandler().getValue(object);
2557     } catch (IllegalStateException ise) {
2558       LOG.warn("Error getting value from " + object, ise);
2559       return;
2560     }
2561 
2562     // -- handle IDREF(S)
2563     if (attDescriptor.isReference() && (value != null)) {
2564 
2565       if (attDescriptor.isMultivalued()) {
2566         Enumeration<?> enumeration = null;
2567         if (value instanceof Enumeration) {
2568           enumeration = (Enumeration<?>) value;
2569         } else {
2570           CollectionHandler<?> colHandler = null;
2571           try {
2572             colHandler = CollectionHandlers.getHandler(value.getClass());
2573           } catch (MappingException mx) {
2574             throw new MarshalException(mx);
2575           }
2576           enumeration = colHandler.elements(value);
2577         }
2578         if (enumeration.hasMoreElements()) {
2579           StringBuilder sb = new StringBuilder();
2580           for (int v = 0; enumeration.hasMoreElements(); v++) {
2581             if (v > 0)
2582               sb.append(' ');
2583             sb.append(getObjectID(enumeration.nextElement()));
2584           }
2585           value = sb;
2586         } else
2587           value = null;
2588       } else {
2589         value = getObjectID(value);
2590       }
2591     }
2592     // -- handle multi-value attributes
2593     else if (attDescriptor.isMultivalued() && (value != null)) {
2594       value = processXSListType(value, attDescriptor);
2595     } else if (value != null) {
2596       // -- handle hex/base64 content
2597       Class<?> objType = value.getClass();
2598       if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) {
2599         value = encodeBinaryData(value, attDescriptor.getSchemaType());
2600       }
2601     }
2602 
2603     if (value != null) {
2604       // check if the value is a QName that needs to
2605       // be resolved ({URI}value -> ns:value).
2606       String valueType = attDescriptor.getSchemaType();
2607       if ((valueType != null) && (valueType.equals(QNAME_NAME)))
2608         value = resolveQName(value, attDescriptor);
2609 
2610       atts.addAttribute(namespace, localName, qName, CDATA, value.toString());
2611     }
2612   }
2613 
2614   private Object processXSListType(final Object value, XMLFieldDescriptor descriptor)
2615       throws MarshalException {
2616     Object returnValue = null;
2617     Enumeration<?> enumeration = null;
2618     if (value instanceof Enumeration) {
2619       enumeration = (Enumeration<?>) value;
2620     } else {
2621       CollectionHandler<?> colHandler = null;
2622       try {
2623         colHandler = CollectionHandlers.getHandler(value.getClass());
2624       } catch (MappingException mx) {
2625         throw new MarshalException(mx);
2626       }
2627       enumeration = colHandler.elements(value);
2628     }
2629     if (enumeration.hasMoreElements()) {
2630       StringBuilder sb = new StringBuilder();
2631       for (int v = 0; enumeration.hasMoreElements(); v++) {
2632         if (v > 0) {
2633           sb.append(' ');
2634         }
2635         Object collectionValue = enumeration.nextElement();
2636         // -- handle hex/base64 content
2637         Class<?> objType = collectionValue.getClass();
2638         if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) {
2639           collectionValue = encodeBinaryData(collectionValue, descriptor.getComponentType());
2640         }
2641 
2642         sb.append(collectionValue);
2643       }
2644       returnValue = sb;
2645     }
2646 
2647     return returnValue;
2648   }
2649 
2650 
2651   /**
2652    * Encode binary data.
2653    * 
2654    * @param valueToEncode The binary data to encode.
2655    * @param componentType The XML schema component type.
2656    * @return Encoded binary data in {@link String} form.
2657    */
2658   private Object encodeBinaryData(final Object valueToEncode, final String componentType) {
2659     String encodedValue;
2660     if (HexDecoder.DATA_TYPE.equals(componentType)) {
2661       encodedValue = HexDecoder.encode((byte[]) valueToEncode);
2662     } else {
2663       encodedValue = new String(Base64Encoder.encode((byte[]) valueToEncode));
2664     }
2665     return encodedValue;
2666   }
2667 
2668 
2669   /**
2670    * Processes the attributes for container objects
2671    *
2672    * @param target the object currently being marshalled.
2673    * @param classDesc the XMLClassDescriptor for the target object
2674    * @param atts the SAX attributes list to add attributes to
2675    */
2676   private void processContainerAttributes(Object target, XMLClassDescriptor classDesc,
2677       AttributesImpl atts) throws MarshalException {
2678     if (classDesc instanceof XMLClassDescriptorImpl) {
2679       if (!((XMLClassDescriptorImpl) classDesc).hasContainerFields())
2680         return;
2681     }
2682 
2683     XMLFieldDescriptor[] elemDescriptors = classDesc.getElementDescriptors();
2684     for (int i = 0; i < elemDescriptors.length; i++) {
2685       if (elemDescriptors[i] == null)
2686         continue;
2687       if (!elemDescriptors[i].isContainer())
2688         continue;
2689       processContainerAttributes(target, elemDescriptors[i], atts);
2690     }
2691   } // -- processContainerAttributes
2692 
2693   /**
2694    * Processes the attributes for container objects.
2695    *
2696    * @param target the object currently being marshalled.
2697    * @param containerFieldDesc the XMLFieldDescriptor for the containter to process
2698    * @param atts the SAX attributes list to add any necessary attributes to.
2699    * @throws MarshalException If there's a problem marshalling the attribute(s).
2700    */
2701   private void processContainerAttributes(final Object target,
2702       final XMLFieldDescriptor containerFieldDesc, final AttributesImpl atts)
2703       throws MarshalException {
2704     if (target.getClass().isArray()) {
2705       int length = Array.getLength(target);
2706       for (int j = 0; j < length; j++) {
2707         Object item = Array.get(target, j);
2708         if (item != null) {
2709           processContainerAttributes(item, containerFieldDesc, atts);
2710         }
2711       }
2712       return;
2713     } else if (target instanceof Enumeration) {
2714       Enumeration<?> enumeration = (Enumeration<?>) target;
2715       while (enumeration.hasMoreElements()) {
2716         Object item = enumeration.nextElement();
2717         if (item != null) {
2718           processContainerAttributes(item, containerFieldDesc, atts);
2719         }
2720       }
2721       return;
2722     }
2723 
2724     Object containerObject = containerFieldDesc.getHandler().getValue(target);
2725 
2726     if (containerObject == null) {
2727       return;
2728     }
2729 
2730     XMLClassDescriptor containerClassDesc =
2731         (XMLClassDescriptor) containerFieldDesc.getClassDescriptor();
2732 
2733     if (containerClassDesc == null) {
2734       containerClassDesc = getClassDescriptor(containerFieldDesc.getFieldType());
2735       if (containerClassDesc == null) {
2736         return;
2737       }
2738     }
2739 
2740     // Look for attributes
2741     XMLFieldDescriptor[] attrDescriptors = containerClassDesc.getAttributeDescriptors();
2742     for (int idx = 0; idx < attrDescriptors.length; idx++) {
2743       if (attrDescriptors[idx] == null) {
2744         continue;
2745       }
2746       if (attrDescriptors[idx].getLocationPath() == null
2747           || attrDescriptors[idx].getLocationPath().length() == 0) {
2748         processAttribute(containerObject, attrDescriptors[idx], atts);
2749       }
2750     }
2751 
2752     // recursively process containers
2753     processContainerAttributes(containerObject, containerClassDesc, atts);
2754   } // -- processContainerAttributes
2755 
2756   /**
2757    * Resolve a QName value ({URI}value) by declaring a namespace after having retrieved the prefix.
2758    */
2759   private Object resolveQName(Object value, XMLFieldDescriptor fieldDesc) {
2760     if ((value == null) || !(value instanceof String))
2761       return value;
2762     if (!(fieldDesc instanceof XMLFieldDescriptorImpl))
2763       return value;
2764 
2765     String result = (String) value;
2766 
2767     String nsURI = null;
2768     int idx = -1;
2769     if ((result.length() > 0) && (result.charAt(0) == '{')) {
2770       idx = result.indexOf('}');
2771       if (idx <= 0) {
2772         String err = "Bad QName value :'" + result + "', it should follow the pattern '{URI}value'";
2773         throw new IllegalArgumentException(err);
2774       }
2775       nsURI = result.substring(1, idx);
2776     } else
2777       return value;
2778 
2779     String prefix = ((XMLFieldDescriptorImpl) fieldDesc).getQNamePrefix();
2780     // no prefix provided, check if one has been previously defined
2781     if (prefix == null)
2782       prefix = namespacesStack.getNamespacePrefix(nsURI);
2783     // if still no prefix, use a naming algorithm (ns+counter).
2784     if (prefix == null)
2785       prefix = DEFAULT_PREFIX + (++_namespaceCounter);
2786     result = (prefix.length() != 0) ? prefix + ":" + result.substring(idx + 1)
2787         : result.substring(idx + 1);
2788     declareNamespace(prefix, nsURI);
2789     return result;
2790   }
2791 
2792   private void validate(final Object object) throws ValidationException {
2793     if (_validate) {
2794       // -- we must have a valid element before marshalling
2795       Validator validator = new Validator();
2796       ValidationContext context = new ValidationContext();
2797       context.setInternalContext(getInternalContext());
2798       // context.setConfiguration(_config);
2799       // context.setResolver(_cdResolver);
2800       validator.validate(object, context);
2801     }
2802   }
2803 
2804   /**
2805    * Returns the value of the given Castor XML-specific property.
2806    * 
2807    * @param name Qualified name of the CASTOR XML-specific property.
2808    * @return The current value of the given property.
2809    * @since 1.1.2
2810    */
2811   public String getProperty(final String name) {
2812     return getInternalContext().getStringProperty(name);
2813   }
2814 
2815   /**
2816    * Sets a custom value of a given Castor XML-specific property.
2817    * 
2818    * @param name Name of the Castor XML property
2819    * @param value Custom value to set.
2820    * @since 1.1.2
2821    */
2822   public void setProperty(final String name, final String value) {
2823     getInternalContext().setProperty(name, value);
2824     deriveProperties();
2825   }
2826 
2827   /**
2828    * Inner-class used for handling wrapper elements and locations.
2829    */
2830   static class WrapperInfo {
2831     private String _localName = null;
2832     private String _qName = null;
2833     private String _location = null;
2834 
2835     WrapperInfo(final String localName, final String qName, final String location) {
2836       _localName = localName;
2837       _qName = qName;
2838       _location = location;
2839     }
2840   }
2841 
2842 
2843   static class MarshalState {
2844     private String _xpath = null;
2845     private XMLFieldDescriptor[] _nestedAtts = null;
2846     private int _nestedAttCount = 0;
2847     private MarshalState _parent = null;
2848     private Object _owner = null;
2849     private String _xmlName = null;
2850 
2851     MarshalState(Object owner, String xmlName) {
2852       checkNotNull(owner, "The argument 'owner' must not be null.");
2853       checkNotNull(xmlName, "The argument 'xmlName' must not be null.");
2854 
2855       _owner = owner;
2856       _xmlName = xmlName;
2857     }
2858 
2859 
2860     MarshalState createMarshalState(Object owner, String xmlName) {
2861       MarshalState ms = new MarshalState(owner, xmlName);
2862       ms._parent = this;
2863       return ms;
2864     }
2865 
2866     String getXPath() {
2867       if (_xpath == null) {
2868         if (_parent != null) {
2869           _xpath = _parent.getXPath() + "/" + _xmlName;
2870         } else {
2871           _xpath = _xmlName;
2872         }
2873       }
2874       return _xpath;
2875     }
2876 
2877     Object getOwner() {
2878       return _owner;
2879     }
2880 
2881     MarshalState getParent() {
2882       return _parent;
2883     }
2884   }
2885 
2886   /**
2887    * A wrapper for a "Nil" object
2888    *
2889    */
2890   public static class NilObject {
2891 
2892     private XMLClassDescriptor _classDesc = null;
2893     private XMLFieldDescriptor _fieldDesc = null;
2894 
2895     NilObject(XMLClassDescriptor classDesc, XMLFieldDescriptor fieldDesc) {
2896       _classDesc = classDesc;
2897       _fieldDesc = fieldDesc;
2898     }
2899 
2900     /**
2901      * Returns the associated XMLClassDescriptor
2902      *
2903      * @return the XMLClassDescriptor
2904      */
2905     public XMLClassDescriptor getClassDescriptor() {
2906       return _classDesc;
2907     }
2908 
2909     /**
2910      * Returns the associated XMLFieldDescriptor
2911      *
2912      * @return the associated XMLFieldDescriptor
2913      */
2914     public XMLFieldDescriptor getFieldDescriptor() {
2915       return _fieldDesc;
2916     }
2917   }
2918 
2919   /**
2920    * To set the SAX {@link ContentHandler} which is used as destination at marshalling.
2921    * 
2922    * @param contentHandler the SAX {@link ContentHandler} to use as destination at marshalling
2923    */
2924   public void setContentHandler(final ContentHandler contentHandler) {
2925     _handler = contentHandler;
2926   }
2927 
2928   /**
2929    * Assigns the document handler, ignoring any possible exception.
2930    */
2931   private void setDocumentHandler() {
2932     try {
2933       // -- Due to a Xerces Serializer bug that doesn't allow declaring
2934       // -- multiple prefixes to the same namespace, we use the old
2935       // -- DocumentHandler format and process namespaces ourselves
2936       _handler = new DocumentHandlerAdapter(_serializer.asDocumentHandler());
2937     } catch (IOException iox) {
2938       // -- we can ignore this exception since it shouldn't
2939       // -- happen. If _serializer is not null, it means
2940       // -- we've already called this method successfully
2941       // -- in the Marshaller() constructor
2942       if (LOG.isDebugEnabled()) {
2943         LOG.debug("Error setting up document handler", iox);
2944       }
2945     }
2946   }
2947 
2948   /**
2949    * Checks if passed parameter is not null and not a empty string. In case it is, a
2950    * {@link IllegalArgumentException} is thrown.
2951    *
2952    * @param param the parameter to check
2953    * @param msg the error message to use for thrown exception
2954    *
2955    * @throws IllegalArgumentException if param is null
2956    */
2957   private static void checkNotEmpty(String param, String msg) {
2958     checkNotNull(param, msg);
2959 
2960     if (param.length() == 0) {
2961       throw new IllegalArgumentException(msg);
2962     }
2963   }
2964 
2965   /**
2966    * Checks if passed parameter is not null. In case it is, a {@link IllegalArgumentException} is
2967    * thrown.
2968    *
2969    * @param param the parameter to check
2970    * @param msg the error message to use for thrown exception
2971    *
2972    * @throws IllegalArgumentException if param is null
2973    */
2974   private static void checkNotNull(Object param, String msg) {
2975 
2976     if (param == null) {
2977       throw new IllegalArgumentException(msg);
2978     }
2979   }
2980 } // -- Marshaller
2981 
2982