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