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