001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.xml;
014
015import static org.apache.juneau.internal.ArrayUtils.*;
016import static org.apache.juneau.xml.XmlSerializer.*;
017import static org.apache.juneau.xml.XmlSerializerSession.ContentResult.*;
018import static org.apache.juneau.xml.XmlSerializerSession.JsonType.*;
019import static org.apache.juneau.xml.annotation.XmlFormat.*;
020
021import java.lang.reflect.*;
022import java.util.*;
023
024import org.apache.juneau.*;
025import org.apache.juneau.internal.*;
026import org.apache.juneau.serializer.*;
027import org.apache.juneau.transform.*;
028import org.apache.juneau.xml.annotation.*;
029import org.apache.juneau.xmlschema.*;
030
031/**
032 * Session object that lives for the duration of a single use of {@link XmlSerializer}.
033 *
034 * <p>
035 * This class is NOT thread safe.
036 * It is typically discarded after one-time use although it can be reused within the same thread.
037 */
038@SuppressWarnings({"unchecked","rawtypes"})
039public class XmlSerializerSession extends WriterSerializerSession {
040
041   private final XmlSerializer ctx;
042   private Namespace
043      defaultNamespace;
044   private Namespace[] namespaces = new Namespace[0];
045
046   /**
047    * Create a new session using properties specified in the context.
048    *
049    * @param ctx
050    *    The context creating this session object.
051    *    The context contains all the configuration settings for this object.
052    * @param args
053    *    Runtime arguments.
054    *    These specify session-level information such as locale and URI context.
055    *    It also include session-level properties that override the properties defined on the bean and
056    *    serializer contexts.
057    */
058   protected XmlSerializerSession(XmlSerializer ctx, SerializerSessionArgs args) {
059      super(ctx, args);
060      this.ctx = ctx;
061      namespaces = getInstanceArrayProperty(XML_namespaces, Namespace.class, ctx.getNamespaces());
062      defaultNamespace = findDefaultNamespace(getInstanceProperty(XML_defaultNamespace, Namespace.class, ctx.getDefaultNamespace()));
063   }
064
065   private Namespace findDefaultNamespace(Namespace n) {
066      if (n == null)
067         return null;
068      if (n.name != null && n.uri != null)
069         return n;
070      if (n.uri == null) {
071         for (Namespace n2 : getNamespaces())
072            if (n2.name.equals(n.name))
073               return n2;
074      }
075      if (n.name == null) {
076         for (Namespace n2 : getNamespaces())
077            if (n2.uri.equals(n.uri))
078               return n2;
079      }
080      return n;
081   }
082
083   @Override /* Session */
084   public ObjectMap asMap() {
085      return super.asMap()
086         .append("XmlSerializerSession", new ObjectMap()
087         );
088   }
089
090   /*
091    * Add a namespace to this session.
092    *
093    * @param ns The namespace being added.
094    */
095   private void addNamespace(Namespace ns) {
096      if (ns == defaultNamespace)
097         return;
098
099      for (Namespace n : namespaces)
100         if (n == ns)
101            return;
102
103      if (defaultNamespace != null && (ns.uri.equals(defaultNamespace.uri) || ns.name.equals(defaultNamespace.name)))
104         defaultNamespace = ns;
105      else
106         namespaces = append(namespaces, ns);
107   }
108
109   /**
110    * Returns <jk>true</jk> if we're serializing HTML.
111    *
112    * <p>
113    * The difference in behavior is how empty non-void elements are handled.
114    * The XML serializer will produce a collapsed tag, whereas the HTML serializer will produce a start and end tag.
115    *
116    * @return <jk>true</jk> if we're generating HTML.
117    */
118   protected boolean isHtmlMode() {
119      return false;
120   }
121
122   /**
123    * Converts the specified output target object to an {@link XmlWriter}.
124    *
125    * @param out The output target object.
126    * @return The output target object wrapped in an {@link XmlWriter}.
127    * @throws Exception
128    */
129   public final XmlWriter getXmlWriter(SerializerPipe out) throws Exception {
130      Object output = out.getRawOutput();
131      if (output instanceof XmlWriter)
132         return (XmlWriter)output;
133      XmlWriter w = new XmlWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isTrimStrings(), getQuoteChar(), getUriResolver(), isEnableNamespaces(), defaultNamespace);
134      out.setWriter(w);
135      return w;
136   }
137
138   @Override /* Serializer */
139   protected void doSerialize(SerializerPipe out, Object o) throws Exception {
140      if (isEnableNamespaces() && isAutoDetectNamespaces())
141         findNsfMappings(o);
142      serializeAnything(getXmlWriter(out), o, getExpectedRootType(o), null, null, isEnableNamespaces() && isAddNamespaceUrlsToRoot(), XmlFormat.DEFAULT, false, false, null);
143   }
144
145   /**
146    * Recursively searches for the XML namespaces on the specified POJO and adds them to the serializer context object.
147    *
148    * @param o The POJO to check.
149    * @throws SerializeException
150    */
151   protected final void findNsfMappings(Object o) throws SerializeException {
152      ClassMeta<?> aType = null;                // The actual type
153
154      try {
155         aType = push(null, o, null);
156      } catch (BeanRecursionException e) {
157         throw new SerializeException(e);
158      }
159
160      if (aType != null) {
161         Namespace ns = cXml(aType).getNamespace();
162         if (ns != null) {
163            if (ns.uri != null)
164               addNamespace(ns);
165            else
166               ns = null;
167         }
168      }
169
170      // Handle recursion
171      if (aType != null && ! aType.isPrimitive()) {
172
173         BeanMap<?> bm = null;
174         if (aType.isBeanMap()) {
175            bm = (BeanMap<?>)o;
176         } else if (aType.isBean()) {
177            bm = toBeanMap(o);
178         } else if (aType.isDelegate()) {
179            ClassMeta<?> innerType = ((Delegate<?>)o).getClassMeta();
180            Namespace ns = cXml(innerType).getNamespace();
181            if (ns != null) {
182               if (ns.uri != null)
183                  addNamespace(ns);
184               else
185                  ns = null;
186            }
187
188            if (innerType.isBean()) {
189               for (BeanPropertyMeta bpm : innerType.getBeanMeta().getPropertyMetas()) {
190                  if (bpm.canRead()) {
191                     ns = bpXml(bpm).getNamespace();
192                     if (ns != null && ns.uri != null)
193                        addNamespace(ns);
194                  }
195               }
196
197            } else if (innerType.isMap()) {
198               for (Object o2 : ((Map<?,?>)o).values())
199                  findNsfMappings(o2);
200            } else if (innerType.isCollection()) {
201               for (Object o2 : ((Collection<?>)o))
202                  findNsfMappings(o2);
203            }
204
205         } else if (aType.isMap()) {
206            for (Object o2 : ((Map<?,?>)o).values())
207               findNsfMappings(o2);
208         } else if (aType.isCollection()) {
209            for (Object o2 : ((Collection<?>)o))
210               findNsfMappings(o2);
211         } else if (aType.isArray() && ! aType.getElementType().isPrimitive()) {
212            for (Object o2 : ((Object[])o))
213               findNsfMappings(o2);
214         }
215         if (bm != null) {
216            for (BeanPropertyValue p : bm.getValues(isTrimNullProperties())) {
217
218               Namespace ns = bpXml(p.getMeta()).getNamespace();
219               if (ns != null && ns.uri != null)
220                  addNamespace(ns);
221
222               try {
223                  findNsfMappings(p.getValue());
224               } catch (Throwable x) {
225                  // Ignore
226               }
227            }
228         }
229      }
230
231      pop();
232   }
233
234   /**
235    * Workhorse method.
236    *
237    * @param out The writer to send the output to.
238    * @param o The object to serialize.
239    * @param eType The expected type if this is a bean property value being serialized.
240    * @param elementName The root element name.
241    * @param elementNamespace The namespace of the element.
242    * @param addNamespaceUris Flag indicating that namespace URIs need to be added.
243    * @param format The format to serialize the output to.
244    * @param isMixed We're serializing mixed content, so don't use whitespace.
245    * @param preserveWhitespace
246    *    <jk>true</jk> if we're serializing {@link XmlFormat#MIXED_PWS} or {@link XmlFormat#TEXT_PWS}.
247    * @param pMeta The bean property metadata if this is a bean property being serialized.
248    * @return The same writer passed in so that calls to the writer can be chained.
249    * @throws Exception If a problem occurred trying to convert the output.
250    */
251   protected ContentResult serializeAnything(
252         XmlWriter out,
253         Object o,
254         ClassMeta<?> eType,
255         String elementName,
256         Namespace elementNamespace,
257         boolean addNamespaceUris,
258         XmlFormat format,
259         boolean isMixed,
260         boolean preserveWhitespace,
261         BeanPropertyMeta pMeta) throws Exception {
262
263      JsonType type = null;              // The type string (e.g. <type> or <x x='type'>
264      int i = isMixed ? 0 : indent;       // Current indentation
265      ClassMeta<?> aType = null;     // The actual type
266      ClassMeta<?> wType = null;     // The wrapped type (delegate)
267      ClassMeta<?> sType = object(); // The serialized type
268
269      aType = push(elementName, o, eType);
270
271      if (eType == null)
272         eType = object();
273
274      // Handle recursion
275      if (aType == null) {
276         o = null;
277         aType = object();
278      }
279
280      if (o != null) {
281
282         if (aType.isDelegate()) {
283            wType = aType;
284            eType = aType = ((Delegate<?>)o).getClassMeta();
285         }
286
287         sType = aType;
288
289         // Swap if necessary
290         PojoSwap swap = aType.getPojoSwap(this);
291         if (swap != null) {
292            o = swap.swap(this, o);
293            sType = swap.getSwapClassMeta(this);
294
295            // If the getSwapClass() method returns Object, we need to figure out
296            // the actual type now.
297            if (sType.isObject())
298               sType = getClassMetaForObject(o);
299         }
300      } else {
301         sType = eType.getSerializedClassMeta(this);
302      }
303
304      // Does the actual type match the expected type?
305      boolean isExpectedType = true;
306      if (o == null || ! eType.same(aType)) {
307         if (eType.isNumber())
308            isExpectedType = aType.isNumber();
309         else if (eType.isMap())
310            isExpectedType = aType.isMap();
311         else if (eType.isCollectionOrArray())
312            isExpectedType = aType.isCollectionOrArray();
313         else
314            isExpectedType = false;
315      }
316
317      String resolvedDictionaryName = isExpectedType ? null : aType.getDictionaryName();
318
319      // Note that the dictionary name may be specified on the actual type or the serialized type.
320      // HTML templates will have them defined on the serialized type.
321      String dictionaryName = aType.getDictionaryName();
322      if (dictionaryName == null)
323         dictionaryName = sType.getDictionaryName();
324
325      // char '\0' is interpreted as null.
326      if (o != null && sType.isChar() && ((Character)o).charValue() == 0)
327         o = null;
328
329      boolean isCollapsed = false;     // If 'true', this is a collection and we're not rendering the outer element.
330      boolean isRaw = (sType.isReader() || sType.isInputStream()) && o != null;
331
332      // Get the JSON type string.
333      if (o == null) {
334         type = NULL;
335      } else if (sType.isCharSequence() || sType.isChar()) {
336         type = STRING;
337      } else if (sType.isNumber()) {
338         type = NUMBER;
339      } else if (sType.isBoolean()) {
340         type = BOOLEAN;
341      } else if (sType.isMapOrBean()) {
342         isCollapsed = cXml(sType).getFormat() == COLLAPSED;
343         type = OBJECT;
344      } else if (sType.isCollectionOrArray()) {
345         isCollapsed = (format == COLLAPSED && ! addNamespaceUris);
346         type = ARRAY;
347      } else {
348         type = STRING;
349      }
350
351      if (format.isOneOf(MIXED,MIXED_PWS,TEXT,TEXT_PWS,XMLTEXT) && type.isOneOf(NULL,STRING,NUMBER,BOOLEAN))
352         isCollapsed = true;
353
354      // Is there a name associated with this bean?
355      if (elementName == null && dictionaryName != null) {
356         elementName = dictionaryName;
357         isExpectedType = true;
358      }
359
360      if (isEnableNamespaces()) {
361         if (elementNamespace == null)
362            elementNamespace = cXml(sType).getNamespace();
363         if (elementNamespace == null)
364            elementNamespace = cXml(aType).getNamespace();
365         if (elementNamespace != null && elementNamespace.uri == null)
366            elementNamespace = null;
367         if (elementNamespace == null)
368            elementNamespace = defaultNamespace;
369      } else {
370         elementNamespace = null;
371      }
372
373      // Do we need a carriage return after the start tag?
374      boolean cr = o != null && (sType.isMapOrBean() || sType.isCollectionOrArray()) && ! isMixed;
375
376      String en = elementName;
377      if (en == null && ! isRaw) {
378         en = type.toString();
379         type = null;
380      }
381      boolean encodeEn = elementName != null;
382      String ns = (elementNamespace == null ? null : elementNamespace.name);
383      String dns = null, elementNs = null;
384      if (isEnableNamespaces()) {
385         dns = elementName == null && defaultNamespace != null ? defaultNamespace.name : null;
386         elementNs = elementName == null ? dns : ns;
387         if (elementName == null)
388            elementNamespace = null;
389      }
390
391      // Render the start tag.
392      if (! isCollapsed) {
393         if (en != null) {
394            out.oTag(i, elementNs, en, encodeEn);
395            if (addNamespaceUris) {
396               out.attr((String)null, "xmlns", defaultNamespace.getUri());
397
398               for (Namespace n : namespaces)
399                  out.attr("xmlns", n.getName(), n.getUri());
400            }
401            if (! isExpectedType) {
402               if (resolvedDictionaryName != null)
403                  out.attr(dns, getBeanTypePropertyName(eType), resolvedDictionaryName);
404               else if (type != null && type != STRING)
405                  out.attr(dns, getBeanTypePropertyName(eType), type);
406            }
407         } else {
408            out.i(i);
409         }
410         if (o == null) {
411            if ((sType.isBoolean() || sType.isNumber()) && ! sType.isNullable())
412               o = sType.getPrimitiveDefault();
413         }
414
415         if (o != null && ! (sType.isMapOrBean() || en == null))
416            out.append('>');
417
418         if (cr && ! (sType.isMapOrBean()))
419            out.nl(i+1);
420      }
421
422      ContentResult rc = CR_ELEMENTS;
423
424      // Render the tag contents.
425      if (o != null) {
426         if (sType.isUri() || (pMeta != null && pMeta.isUri())) {
427            out.textUri(o);
428         } else if (sType.isCharSequence() || sType.isChar()) {
429            if (isXmlText(format, sType))
430               out.append(o);
431            else
432               out.text(o, preserveWhitespace);
433         } else if (sType.isNumber() || sType.isBoolean()) {
434            out.append(o);
435         } else if (sType.isMap() || (wType != null && wType.isMap())) {
436            if (o instanceof BeanMap)
437               rc = serializeBeanMap(out, (BeanMap)o, elementNamespace, isCollapsed, isMixed);
438            else
439               rc = serializeMap(out, (Map)o, sType, eType.getKeyType(), eType.getValueType(), isMixed);
440         } else if (sType.isBean()) {
441            rc = serializeBeanMap(out, toBeanMap(o), elementNamespace, isCollapsed, isMixed);
442         } else if (sType.isCollection() || (wType != null && wType.isCollection())) {
443            if (isCollapsed)
444               this.indent--;
445            serializeCollection(out, o, sType, eType, pMeta, isMixed);
446            if (isCollapsed)
447               this.indent++;
448         } else if (sType.isArray()) {
449            if (isCollapsed)
450               this.indent--;
451            serializeCollection(out, o, sType, eType, pMeta, isMixed);
452            if (isCollapsed)
453               this.indent++;
454         } else if (sType.isReader() || sType.isInputStream()) {
455            IOUtils.pipe(o, out);
456         } else {
457            if (isXmlText(format, sType))
458               out.append(toString(o));
459            else
460               out.text(toString(o));
461         }
462      }
463
464      pop();
465
466      // Render the end tag.
467      if (! isCollapsed) {
468         if (en != null) {
469            if (rc == CR_EMPTY) {
470               if (isHtmlMode())
471                  out.append('>').eTag(elementNs, en, encodeEn);
472               else
473                  out.append('/').append('>');
474            } else if (rc == CR_VOID || o == null) {
475               out.append('/').append('>');
476            }
477            else
478               out.ie(cr && rc != CR_MIXED ? i : 0).eTag(elementNs, en, encodeEn);
479         }
480         if (! isMixed)
481            out.nl(i);
482      }
483
484      return rc;
485   }
486
487   private boolean isXmlText(XmlFormat format, ClassMeta<?> sType) {
488      if (format == XMLTEXT)
489         return true;
490      XmlClassMeta xcm = sType.getExtendedMeta(XmlClassMeta.class);
491      if (xcm == null)
492         return false;
493      return xcm.getFormat() == XMLTEXT;
494   }
495
496   private ContentResult serializeMap(XmlWriter out, Map m, ClassMeta<?> sType,
497         ClassMeta<?> eKeyType, ClassMeta<?> eValueType, boolean isMixed) throws Exception {
498
499      m = sort(m);
500
501      ClassMeta<?> keyType = eKeyType == null ? sType.getKeyType() : eKeyType;
502      ClassMeta<?> valueType = eValueType == null ? sType.getValueType() : eValueType;
503
504      boolean hasChildren = false;
505      for (Iterator i = m.entrySet().iterator(); i.hasNext();) {
506         Map.Entry e = (Map.Entry)i.next();
507
508         Object k = e.getKey();
509         if (k == null) {
510            k = "\u0000";
511         } else {
512            k = generalize(k, keyType);
513            if (isTrimStrings() && k instanceof String)
514               k = k.toString().trim();
515         }
516
517         Object value = e.getValue();
518
519         if (! hasChildren) {
520            hasChildren = true;
521            out.append('>').nlIf(! isMixed, indent);
522         }
523         serializeAnything(out, value, valueType, toString(k), null, false, XmlFormat.DEFAULT, isMixed, false, null);
524      }
525      return hasChildren ? CR_ELEMENTS : CR_EMPTY;
526   }
527
528   private ContentResult serializeBeanMap(XmlWriter out, BeanMap<?> m,
529         Namespace elementNs, boolean isCollapsed, boolean isMixed) throws Exception {
530      boolean hasChildren = false;
531      BeanMeta<?> bm = m.getMeta();
532
533      List<BeanPropertyValue> lp = m.getValues(isTrimNullProperties());
534
535      XmlBeanMeta xbm = bXml(bm);
536
537      Set<String>
538         attrs = xbm.getAttrPropertyNames(),
539         elements = xbm.getElementPropertyNames(),
540         collapsedElements = xbm.getCollapsedPropertyNames();
541      String
542         attrsProperty = xbm.getAttrsPropertyName(),
543         contentProperty = xbm.getContentPropertyName();
544
545      XmlFormat cf = null;
546
547      Object content = null;
548      ClassMeta<?> contentType = null;
549      for (BeanPropertyValue p : lp) {
550         String n = p.getName();
551         if (attrs.contains(n) || attrs.contains("*") || n.equals(attrsProperty)) {
552            BeanPropertyMeta pMeta = p.getMeta();
553            if (pMeta.canRead()) {
554               ClassMeta<?> cMeta = p.getClassMeta();
555
556               String key = p.getName();
557               Object value = p.getValue();
558               Throwable t = p.getThrown();
559               if (t != null)
560                  onBeanGetterException(pMeta, t);
561
562               if (canIgnoreValue(cMeta, key, value))
563                  continue;
564
565               XmlBeanPropertyMeta bpXml = bpXml(pMeta);
566               Namespace ns = (isEnableNamespaces() && bpXml.getNamespace() != elementNs ? bpXml.getNamespace() : null);
567
568               if (pMeta.isUri()  ) {
569                  out.attrUri(ns, key, value);
570               } else if (n.equals(attrsProperty)) {
571                  if (value instanceof BeanMap) {
572                     BeanMap<?> bm2 = (BeanMap)value;
573                     for (BeanPropertyValue p2 : bm2.getValues(true)) {
574                        String key2 = p2.getName();
575                        Object value2 = p2.getValue();
576                        Throwable t2 = p2.getThrown();
577                        if (t2 != null)
578                           onBeanGetterException(pMeta, t);
579                        out.attr(ns, key2, value2);
580                     }
581                  } else /* Map */ {
582                     Map m2 = (Map)value;
583                     for (Map.Entry e : (Set<Map.Entry>)(m2.entrySet())) {
584                        out.attr(ns, toString(e.getKey()), e.getValue());
585                     }
586                  }
587               } else {
588                  out.attr(ns, key, value);
589               }
590            }
591         }
592      }
593
594      boolean
595         hasContent = false,
596         preserveWhitespace = false,
597         isVoidElement = xbm.getContentFormat() == VOID;
598
599      for (BeanPropertyValue p : lp) {
600         BeanPropertyMeta pMeta = p.getMeta();
601         if (pMeta.canRead()) {
602            ClassMeta<?> cMeta = p.getClassMeta();
603
604            String n = p.getName();
605            if (n.equals(contentProperty)) {
606               content = p.getValue();
607               contentType = p.getClassMeta();
608               hasContent = true;
609               cf = xbm.getContentFormat();
610               if (cf.isOneOf(MIXED,MIXED_PWS,TEXT,TEXT_PWS,XMLTEXT))
611                  isMixed = true;
612               if (cf.isOneOf(MIXED_PWS, TEXT_PWS))
613                  preserveWhitespace = true;
614               if (contentType.isCollection() && ((Collection)content).isEmpty())
615                  hasContent = false;
616               else if (contentType.isArray() && Array.getLength(content) == 0)
617                  hasContent = false;
618            } else if (elements.contains(n) || collapsedElements.contains(n) || elements.contains("*") || collapsedElements.contains("*") ) {
619               String key = p.getName();
620               Object value = p.getValue();
621               Throwable t = p.getThrown();
622               if (t != null)
623                  onBeanGetterException(pMeta, t);
624
625               if (canIgnoreValue(cMeta, key, value))
626                  continue;
627
628               if (! hasChildren) {
629                  hasChildren = true;
630                  out.appendIf(! isCollapsed, '>').nlIf(! isMixed, indent);
631               }
632
633               XmlBeanPropertyMeta bpXml = bpXml(pMeta);
634               serializeAnything(out, value, cMeta, key, bpXml.getNamespace(), false, bpXml.getXmlFormat(), isMixed, false, pMeta);
635            }
636         }
637      }
638      if (! hasContent)
639         return (hasChildren ? CR_ELEMENTS : isVoidElement ? CR_VOID : CR_EMPTY);
640      out.append('>').nlIf(! isMixed, indent);
641
642      // Serialize XML content.
643      if (content != null) {
644         if (contentType == null) {
645         } else if (contentType.isCollection()) {
646            Collection c = (Collection)content;
647            for (Iterator i = c.iterator(); i.hasNext();) {
648               Object value = i.next();
649               serializeAnything(out, value, contentType.getElementType(), null, null, false, cf, isMixed, preserveWhitespace, null);
650            }
651         } else if (contentType.isArray()) {
652            Collection c = toList(Object[].class, content);
653            for (Iterator i = c.iterator(); i.hasNext();) {
654               Object value = i.next();
655               serializeAnything(out, value, contentType.getElementType(), null, null, false, cf, isMixed, preserveWhitespace, null);
656            }
657         } else {
658            serializeAnything(out, content, contentType, null, null, false, cf, isMixed, preserveWhitespace, null);
659         }
660      } else {
661         if (! isTrimNullProperties()) {
662            if (! isMixed)
663               out.i(indent);
664            out.text(content);
665            if (! isMixed)
666               out.nl(indent);
667         }
668      }
669      return isMixed ? CR_MIXED : CR_ELEMENTS;
670   }
671
672   private XmlWriter serializeCollection(XmlWriter out, Object in, ClassMeta<?> sType,
673         ClassMeta<?> eType, BeanPropertyMeta ppMeta, boolean isMixed) throws Exception {
674
675      ClassMeta<?> eeType = eType.getElementType();
676
677      Collection c = (sType.isCollection() ? (Collection)in : toList(sType.getInnerClass(), in));
678
679      c = sort(c);
680
681      String type2 = null;
682
683      String eName = type2;
684      Namespace eNs = null;
685
686      if (ppMeta != null) {
687         XmlBeanPropertyMeta bpXml = bpXml(ppMeta);
688         eName = bpXml.getChildName();
689         eNs = bpXml.getNamespace();
690      }
691
692      for (Iterator i = c.iterator(); i.hasNext();) {
693         Object value = i.next();
694         serializeAnything(out, value, eeType, eName, eNs, false, XmlFormat.DEFAULT, isMixed, false, null);
695      }
696      return out;
697   }
698
699   private static XmlClassMeta cXml(ClassMeta<?> cm) {
700      return cm.getExtendedMeta(XmlClassMeta.class);
701   }
702
703   private static XmlBeanPropertyMeta bpXml(BeanPropertyMeta pMeta) {
704      return pMeta == null ? XmlBeanPropertyMeta.DEFAULT : pMeta.getExtendedMeta(XmlBeanPropertyMeta.class);
705   }
706
707   private static XmlBeanMeta bXml(BeanMeta bm) {
708      return (XmlBeanMeta)bm.getExtendedMeta(XmlBeanMeta.class);
709   }
710
711   static enum JsonType {
712      STRING("string"),BOOLEAN("boolean"),NUMBER("number"),ARRAY("array"),OBJECT("object"),NULL("null");
713
714      private final String value;
715      private JsonType(String value) {
716         this.value = value;
717      }
718
719      @Override
720      public String toString() {
721         return value;
722      }
723
724      boolean isOneOf(JsonType...types) {
725         for (JsonType type : types)
726            if (type == this)
727               return true;
728         return false;
729      }
730   }
731
732   /**
733    * Identifies what the contents were of a serialized bean.
734    */
735   @SuppressWarnings("javadoc")
736   public static enum ContentResult {
737      CR_VOID,      // No content...append "/>" to the start tag.
738      CR_EMPTY,     // No content...append "/>" to the start tag if XML, "/></end>" if HTML.
739      CR_MIXED,     // Mixed content...don't add whitespace.
740      CR_ELEMENTS   // Elements...use normal whitespace rules.
741   }
742
743   //-----------------------------------------------------------------------------------------------------------------
744   // Properties
745   //-----------------------------------------------------------------------------------------------------------------
746
747   /**
748    * Configuration property:  Auto-detect namespace usage.
749    *
750    * @see XmlSerializer#XML_autoDetectNamespaces
751    * @return
752    *    <jk>true</jk> if namespace usage is detected before serialization.
753    */
754   protected final boolean isAutoDetectNamespaces() {
755      return ctx.isAutoDetectNamespaces();
756   }
757
758   /**
759    * Configuration property:  Enable support for XML namespaces.
760    *
761    * @see XmlSerializer#XML_enableNamespaces
762    * @return
763    *    <jk>false</jk> if XML output will not contain any namespaces regardless of any other settings.
764    */
765   protected final boolean isEnableNamespaces() {
766      return ctx.isEnableNamespaces();
767   }
768
769   /**
770    * Configuration property:  Add namespace URLs to the root element.
771    *
772    * @see XmlSerializer#XML_addNamespaceUrisToRoot
773    * @return
774    *    <jk>true</jk> if {@code xmlns:x} attributes are added to the root element for the default and all mapped namespaces.
775    */
776   protected final boolean isAddNamespaceUrlsToRoot() {
777      return ctx.isAddNamespaceUrlsToRoot();
778   }
779
780   /**
781    * Configuration property:  Add <js>"_type"</js> properties when needed.
782    *
783    * @see XmlSerializer#XML_addBeanTypes
784    * @return
785    *    <jk>true</jk> if<js>"_type"</js> properties will be added to beans if their type cannot be inferred
786    *    through reflection.
787    */
788   @Override
789   protected boolean isAddBeanTypes() {
790      return ctx.isAddBeanTypes();
791   }
792
793   /**
794    * Configuration property:  Default namespace.
795    *
796    * @see XmlSerializer#XML_defaultNamespace
797    * @return
798    *    The default namespace URI for this document.
799    */
800   protected final Namespace getDefaultNamespace() {
801      return defaultNamespace;
802   }
803
804   /**
805    * Configuration property:  XMLSchema namespace.
806    *
807    * @see XmlSerializer#XML_xsNamespace
808    * @return
809    *    The namespace for the <code>XMLSchema</code> namespace, used by the schema generated by the
810    *    {@link XmlSchemaSerializer} class.
811    */
812   protected final Namespace getXsNamespace() {
813      return ctx.getXsNamespace();
814   }
815
816   /**
817    * Configuration property:  Default namespaces.
818    *
819    * @see XmlSerializer#XML_namespaces
820    * @return
821    *    The default list of namespaces associated with this serializer.
822    */
823   protected final Namespace[] getNamespaces() {
824      return namespaces;
825   }
826}