001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.xml;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021import static org.apache.juneau.commons.utils.SystemUtils.*;
022
023import java.io.*;
024import java.util.*;
025import java.util.concurrent.*;
026
027import org.apache.juneau.commons.bean.*;
028import org.apache.juneau.commons.collections.*;
029import org.apache.juneau.marshall.*;
030import org.apache.juneau.marshall.json.*;
031import org.apache.juneau.marshall.serializer.*;
032import org.apache.juneau.marshall.stream.*;
033
034/**
035 * Serializes POJO models to XML.
036 *
037 * <h5 class='topic'>Media types</h5>
038 * <p>
039 * Handles <c>Accept</c> types:  <bc>text/xml</bc>
040 * <p>
041 * Produces <c>Content-Type</c> types:  <bc>text/xml</bc>
042 *
043 * <h5 class='topic'>Description</h5>
044 * <p>
045 * See the {@link JsonSerializer} class for details on how Java models map to JSON.
046 *
047 * <p>
048 * For example, the following JSON...
049 * <p class='bjson'>
050 *    {
051 *       name:<js>'John Smith'</js>,
052 *       address: {
053 *          streetAddress: <js>'21 2nd Street'</js>,
054 *          city: <js>'New York'</js>,
055 *          state: <js>'NY'</js>,
056 *          postalCode: <js>10021</js>
057 *       },
058 *       phoneNumbers: [
059 *          <js>'212 555-1111'</js>,
060 *          <js>'212 555-2222'</js>
061 *       ],
062 *       additionalInfo: <jk>null</jk>,
063 *       remote: <jk>false</jk>,
064 *       height: <js>62.4</js>,
065 *       <js>'fico score'</js>:  <js>' &gt; 640'</js>
066 *    }
067 * <p>
068 *    ...maps to the following XML using the default serializer...
069 * <p class='bxml'>
070 *    <xt>&lt;object&gt;</xt>
071 *       <xt>&lt;name&gt;</xt>John Smith<xt>&lt;/name&gt;</xt>
072 *       <xt>&lt;address&gt;</xt>
073 *          <xt>&lt;streetAddress&gt;</xt>21 2nd Street<xt>&lt;/streetAddress&gt;</xt>
074 *          <xt>&lt;city&gt;</xt>New York<xt>&lt;/city&gt;</xt>
075 *          <xt>&lt;state&gt;</xt>NY<xt>&lt;/state&gt;</xt>
076 *          <xt>&lt;postalCode&gt;</xt>10021<xt>&lt;/postalCode&gt;</xt>
077 *       <xt>&lt;/address&gt;</xt>
078 *       <xt>&lt;phoneNumbers&gt;</xt>
079 *          <xt>&lt;string&gt;</xt>212 555-1111<xt>&lt;/string&gt;</xt>
080 *          <xt>&lt;string&gt;</xt>212 555-2222<xt>&lt;/string&gt;</xt>
081 *       <xt>&lt;/phoneNumbers&gt;</xt>
082 *       <xt>&lt;additionalInfo</xt> <xa>_type</xa>=<xs>'null'</xs><xt>&gt;&lt;/additionalInfo&gt;</xt>
083 *       <xt>&lt;remote&gt;</xt>false<xt>&lt;/remote&gt;</xt>
084 *       <xt>&lt;height&gt;</xt>62.4<xt>&lt;/height&gt;</xt>
085 *       <xt>&lt;fico_x0020_score&gt;</xt> &amp;gt; 640<xt>&lt;/fico_x0020_score&gt;</xt>
086 *    <xt>&lt;/object&gt;</xt>
087 *
088 * <p>
089 * An additional "add-json-properties" mode is also provided to prevent loss of JSON data types...
090 * <p class='bxml'>
091 *    <xt>&lt;object&gt;</xt>
092 *       <xt>&lt;name</xt> <xa>_type</xa>=<xs>'string'</xs><xt>&gt;</xt>John Smith<xt>&lt;/name&gt;</xt>
093 *       <xt>&lt;address</xt> <xa>_type</xa>=<xs>'object'</xs><xt>&gt;</xt>
094 *          <xt>&lt;streetAddress</xt> <xa>_type</xa>=<xs>'string'</xs><xt>&gt;</xt>21 2nd Street<xt>&lt;/streetAddress&gt;</xt>
095 *          <xt>&lt;city</xt> <xa>_type</xa>=<xs>'string'</xs><xt>&gt;</xt>New York<xt>&lt;/city&gt;</xt>
096 *          <xt>&lt;state</xt> <xa>_type</xa>=<xs>'string'</xs><xt>&gt;</xt>NY<xt>&lt;/state&gt;</xt>
097 *          <xt>&lt;postalCode</xt> <xa>_type</xa>=<xs>'number'</xs><xt>&gt;</xt>10021<xt>&lt;/postalCode&gt;</xt>
098 *       <xt>&lt;/address&gt;</xt>
099 *       <xt>&lt;phoneNumbers</xt> <xa>_type</xa>=<xs>'array'</xs><xt>&gt;</xt>
100 *          <xt>&lt;string&gt;</xt>212 555-1111<xt>&lt;/string&gt;</xt>
101 *          <xt>&lt;string&gt;</xt>212 555-2222<xt>&lt;/string&gt;</xt>
102 *       <xt>&lt;/phoneNumbers&gt;</xt>
103 *       <xt>&lt;additionalInfo</xt> <xa>_type</xa>=<xs>'null'</xs><xt>&gt;&lt;/additionalInfo&gt;</xt>
104 *       <xt>&lt;remote</xt> <xa>_type</xa>=<xs>'boolean'</xs><xt>&gt;</xt>false<xt>&lt;/remote&gt;</xt>
105 *       <xt>&lt;height</xt> <xa>_type</xa>=<xs>'number'</xs><xt>&gt;</xt>62.4<xt>&lt;/height&gt;</xt>
106 *       <xt>&lt;fico_x0020_score</xt> <xa>_type</xa>=<xs>'string'</xs><xt>&gt;</xt> &amp;gt; 640<xt>&lt;/fico_x0020_score&gt;</xt>
107 *    <xt>&lt;/object&gt;</xt>
108 * </p>
109 *
110 * <p>
111 * This serializer provides several serialization options.
112 * Typically, one of the predefined <jsf>DEFAULT</jsf> serializers will be sufficient.
113 * However, custom serializers can be constructed to fine-tune behavior.
114 *
115 * <p>
116 * If an attribute name contains any non-valid XML element characters, they will be escaped using standard
117 * {@code _x####_} notation.
118 *
119 * <h5 class='topic'>Behavior-specific subclasses</h5>
120 * <p>
121 * The following direct subclasses are provided for convenience:
122 * <ul>
123 *    <li>{@link Sq} - Default serializer, single quotes.
124 *    <li>{@link SqReadable} - Default serializer, single quotes, whitespace added.
125 * </ul>
126 *
127 * <h5 class='section'>Notes:</h5><ul>
128 *    <li class='note'>This class is thread safe and reusable.
129 * </ul>
130 *
131 * <h5 class='section'>See Also:</h5><ul>
132 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/XmlSupport">XML</a>
133
134 * </ul>
135 */
136@SuppressWarnings({
137   "java:S110", // Inheritance depth acceptable for this class hierarchy
138   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
139   "java:S9149", // Per-format static factories intentionally shadow the parent's.
140   "resource" // Closeable resources are owned by the caller's serializer session; Eclipse JDT @Owning warning is by design.
141})
142public class XmlSerializer extends WriterSerializer implements XmlMetaProvider, RecordWritable {
143
144   /**
145    * Builder class.
146    */
147   @SuppressWarnings({
148      "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name.
149   })
150   public abstract static class Builder<SELF extends Builder<SELF>> extends WriterSerializer.Builder<SELF> {
151
152      private static final Cache<HashKey,XmlSerializer> CACHE = Cache.of(HashKey.class, XmlSerializer.class).build();
153
154      private boolean addBeanTypesXml;
155      private boolean addNamespaceUrisToRoot;
156      private boolean disableAutoDetectNamespaces;
157      private boolean disableJsonTags;
158      private boolean enableNamespaces;
159      private Namespace defaultNamespace;
160      private List<Namespace> namespaces;
161      private String textNodeDelimiter;
162
163      /**
164       * Constructor, default settings.
165       */
166      protected Builder() {
167         produces("text/xml");
168         addBeanTypesXml = env("XmlSerializer.addBeanTypes", false);
169         addNamespaceUrisToRoot = env("XmlSerializer.addNamespaceUrisToRoot", false);
170         disableAutoDetectNamespaces = env("XmlSerializer.disableAutoDetectNamespaces", false);
171         disableJsonTags = env("XmlSerializer.disableJsonTags", false);
172         enableNamespaces = env("XmlSerializer.enableNamespaces", false);
173         defaultNamespace = null;
174         namespaces = null;
175         textNodeDelimiter = env("XmlSerializer.textNodeDelimiter", "");
176      }
177
178      /**
179       * Copy constructor.
180       *
181       * @param copyFrom The builder to copy from.
182       *    <br>Cannot be <jk>null</jk>.
183       */
184      protected Builder(Builder<?> copyFrom) {
185         super(reqnn("copyFrom", copyFrom));
186         addBeanTypesXml = copyFrom.addBeanTypesXml;
187         addNamespaceUrisToRoot = copyFrom.addNamespaceUrisToRoot;
188         defaultNamespace = copyFrom.defaultNamespace;
189         disableAutoDetectNamespaces = copyFrom.disableAutoDetectNamespaces;
190         disableJsonTags = copyFrom.disableJsonTags;
191         enableNamespaces = copyFrom.enableNamespaces;
192         namespaces = copyFrom.namespaces == null ? null : new ArrayList<>(copyFrom.namespaces);
193         textNodeDelimiter = copyFrom.textNodeDelimiter;
194      }
195
196      /**
197       * Copy constructor.
198       *
199       * @param copyFrom The bean to copy from.
200       *    <br>Cannot be <jk>null</jk>.
201       */
202      protected Builder(XmlSerializer copyFrom) {
203         super(reqnn("copyFrom", copyFrom));
204         addBeanTypesXml = copyFrom.addBeanTypesXml;
205         addNamespaceUrisToRoot = copyFrom.addNamespaceUrlsToRoot;
206         defaultNamespace = copyFrom.getDefaultNamespace();
207         disableAutoDetectNamespaces = ! copyFrom.autoDetectNamespaces;
208         disableJsonTags = ! copyFrom.addJsonTags;
209         enableNamespaces = copyFrom.enableNamespaces;
210         var ctxNamespaces = copyFrom.getNamespaces();
211         namespaces = ctxNamespaces.isEmpty() ? null : new ArrayList<>(ctxNamespaces);
212         textNodeDelimiter = copyFrom.getTextNodeDelimiter();
213      }
214
215      /**
216       * Add <js>"_type"</js> properties when needed.
217       *
218       * <p>
219       * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred
220       * through reflection.
221       *
222       * <p>
223       * When present, this value overrides the {@link org.apache.juneau.marshall.serializer.Serializer.Builder#addBeanTypes() Serializer.Builder#addBeanTypes()} setting and is
224       * provided to customize the behavior of specific serializers in a {@link SerializerSet}.
225       *
226       * @return This object.
227       */
228      public SELF addBeanTypesXml() {
229         return addBeanTypesXml(true);
230      }
231
232      /**
233       * Same as {@link #addBeanTypesXml()} but allows you to explicitly specify the value.
234       *
235       * @param value The value for this setting.
236       * @return This object.
237       */
238      public SELF addBeanTypesXml(boolean value) {
239         addBeanTypesXml = value;
240         return self();
241      }
242
243      /**
244       * Add namespace URLs to the root element.
245       *
246       * <p>
247       * Use this setting to add {@code xmlns:x} attributes to the root element for the default and all mapped namespaces.
248       *
249       * <p>
250       * This setting is ignored if {@link #enableNamespaces()} is not enabled.
251       *
252       * <h5 class='section'>See Also:</h5><ul>
253       *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/XmlNamespaces">Namespaces</a>
254       * </ul>
255       *
256       * @return This object.
257       */
258      public SELF addNamespaceUrisToRoot() {
259         return addNamespaceUrisToRoot(true);
260      }
261
262      /**
263       * Same as {@link #addNamespaceUrisToRoot()} but allows you to explicitly specify the value.
264       *
265       * @param value The value for this setting.
266       * @return This object.
267       */
268      public SELF addNamespaceUrisToRoot(boolean value) {
269         addNamespaceUrisToRoot = value;
270         return self();
271      }
272
273      @Override /* Overridden from Context.Builder<?> */
274      public XmlSerializer build() {
275         return cache(CACHE).build(XmlSerializer.class);
276      }
277
278      @Override /* Overridden from Context.Builder<?> */
279      public abstract SELF copy();
280
281      /**
282       * Default namespace.
283       *
284       * <p>
285       * Specifies the default namespace URI for this document.
286       *
287       * <h5 class='section'>See Also:</h5><ul>
288       *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/XmlNamespaces">Namespaces</a>
289       * </ul>
290       *
291       * @param value
292       *    The new value for this property.
293       *    <br>The default is <js>"juneau: http://www.apache.org/2013/Juneau"</js>.
294       *    <br>Can be <jk>null</jk> to specify no namespace.
295       * @return This object.
296       */
297      public SELF defaultNamespace(Namespace value) {
298         defaultNamespace = value;
299         return self();
300      }
301
302      /**
303       * Don't auto-detect namespace usage.
304       *
305       * <p>
306       * Don't detect namespace usage before serialization.
307       *
308       * <p>
309       * Used in conjunction with {@link Builder#addNamespaceUrisToRoot()} to reduce the list of namespace URLs appended to the
310       * root element to only those that will be used in the resulting document.
311       *
312       * <p>
313       * If disabled, then the data structure will first be crawled looking for namespaces that will be encountered before
314       * the root element is serialized.
315       *
316       * <p>
317       * This setting is ignored if {@link Builder#enableNamespaces()} is not enabled.
318       *
319       * <h5 class='section'>Notes:</h5><ul>
320       *    <li class='note'>
321       *       Auto-detection of namespaces can be costly performance-wise.
322       *       <br>In high-performance environments, it's recommended that namespace detection be
323       *       disabled, and that namespaces be manually defined through the {@link Builder#namespaces(Namespace...)} property.
324       * </ul>
325       *
326       * <h5 class='section'>See Also:</h5><ul>
327       *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/XmlNamespaces">Namespaces</a>
328       * </ul>
329       *
330       * @return This object.
331       */
332      public SELF disableAutoDetectNamespaces() {
333         return disableAutoDetectNamespaces(true);
334      }
335
336      /**
337       * Same as {@link #disableAutoDetectNamespaces()} but allows you to explicitly specify the value.
338       *
339       * @param value The value for this setting.
340       * @return This object.
341       */
342      public SELF disableAutoDetectNamespaces(boolean value) {
343         disableAutoDetectNamespaces = value;
344         return self();
345      }
346
347      /**
348       * <i><l>XmlSerializer</l> configuration property:&emsp;</i>  Disable use of JSON type identifier tags.
349       *
350       * <p>
351       * When enabled, JSON type tags (e.g. <js>"&lt;string&gt;"</js>) tags and attributes will not be added to the output.
352       * Note that JSON type tags are used to ensure parsers are able to recreate the original data types passed
353       * into the serializer.  Disabling JSON tags can cause different data to be parsed (e.g. strings instead of numbers).
354       *
355       * @return This object.
356       */
357      public SELF disableJsonTags() {
358         return disableJsonTags(true);
359      }
360
361      /**
362       * Same as {@link #disableJsonTags()} but allows you to explicitly specify the value.
363       *
364       * @param value The value for this setting.
365       * @return This object.
366       */
367      public SELF disableJsonTags(boolean value) {
368         disableJsonTags = value;
369         return self();
370      }
371
372      /**
373       * Enable support for XML namespaces.
374       *
375       * <p>
376       * If not enabled, XML output will not contain any namespaces regardless of any other settings.
377       *
378       * <h5 class='section'>See Also:</h5><ul>
379       *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/XmlNamespaces">Namespaces</a>
380       * </ul>
381       *
382       * @return This object.
383       */
384      public SELF enableNamespaces() {
385         return enableNamespaces(true);
386      }
387
388      /**
389       * Same as {@link #enableNamespaces()} but allows you to explicitly specify the value.
390       *
391       * @param value The value for this setting.
392       * @return This object.
393       */
394      public SELF enableNamespaces(boolean value) {
395         enableNamespaces = value;
396         return self();
397      }
398
399      @Override /* Overridden from Context.Builder<?> */
400      public HashKey hashKey() {
401         // @formatter:off
402         return HashKey.of(
403            super.hashKey(),
404            addBeanTypesXml,
405            addNamespaceUrisToRoot,
406            disableAutoDetectNamespaces,
407            disableJsonTags,
408            enableNamespaces,
409            defaultNamespace,
410            namespaces,
411            textNodeDelimiter
412         );
413         // @formatter:on
414      }
415
416      /**
417       * Default namespaces.
418       *
419       * <p>
420       * The default list of namespaces associated with this serializer.
421       *
422       * @param values The new value for this property.
423       *    <br>Cannot contain <jk>null</jk> values.
424       * @return This object.
425       */
426      public SELF namespaces(Namespace...values) {
427         reqnns("values", values);
428         namespaces = addAll(namespaces, values);
429         return self();
430      }
431
432      /**
433       * Enable support for XML namespaces.
434       *
435       * <p>
436       * Shortcut for calling <code>enableNamespaces(<jk>true</jk>)</code>.
437       *
438       * @return This object.
439       */
440      public SELF ns() {
441         return enableNamespaces();
442      }
443
444      /**
445       * Text node delimiter.
446       *
447       * <p>
448       * Specifies the delimiter string to insert between consecutive text nodes.
449       * This is useful for adding spacing between text elements to improve readability.
450       *
451       * <p>
452       * The default value is an empty string (no delimiter).
453       *
454       * <h5 class='section'>Example:</h5>
455       * <p class='bjava'>
456       *    XmlSerializer.<jsm>create</jsm>()
457       *       .textNodeDelimiter(<js>" "</js>)
458       *       .build();
459       * </p>
460       *
461       * <p>
462       * With this setting, serializing:
463       * <p class='bjava'>
464       *    <jk>new</jk> Audio().children(<js>"a"</js>, <js>"b"</js>, <jk>new</jk> Strong(<js>"c"</js>));
465       * </p>
466       *
467       * <p>
468       * Will produce: <code>&lt;audio&gt;a b&lt;strong&gt;c&lt;/strong&gt;&lt;/audio&gt;</code>
469       * <br>Instead of: <code>&lt;audio&gt;ab&lt;strong&gt;c&lt;/strong&gt;&lt;/audio&gt;</code>
470       *
471       * @param value
472       *    The delimiter string.
473       *    <br>Can be <jk>null</jk> (interpreted as empty string).
474       * @return This object.
475       */
476      public SELF textNodeDelimiter(String value) {
477         textNodeDelimiter = value == null ? "" : value;
478         return self();
479      }
480
481
482   }
483
484   /**
485    * Concrete default builder leaf for the non-subclassed {@link XmlSerializer#create()} / {@link XmlSerializer#copy()} path.
486    */
487   public static final class DefaultBuilder extends Builder<DefaultBuilder> {
488
489      DefaultBuilder() {}
490
491      DefaultBuilder(XmlSerializer copyFrom) {
492         super(copyFrom);
493      }
494
495      DefaultBuilder(Builder<?> copyFrom) {
496         super(copyFrom);
497      }
498
499      @Override /* Overridden from Context.Builder<?> */
500      public DefaultBuilder copy() {
501         return new DefaultBuilder(this);
502      }
503   }
504
505   /** Default serializer without namespaces. */
506   public static class Ns extends XmlSerializer {
507
508      /**
509       * Constructor.
510       *
511       * @param builder The builder for this object.
512       *    <br>Cannot be <jk>null</jk>.
513       */
514      public Ns(Builder<?> builder) {
515         super(reqnn("builder", builder).enableNamespaces());
516      }
517   }
518
519   /** Default serializer without namespaces, single quotes. */
520   public static class NsSq extends XmlSerializer {
521
522      /**
523       * Constructor.
524       *
525       * @param builder The builder for this object.
526       *    <br>Cannot be <jk>null</jk>.
527       */
528      public NsSq(Builder<?> builder) {
529         super(reqnn("builder", builder).enableNamespaces().quoteChar('\''));
530      }
531   }
532
533   /** Default serializer without namespaces, single quotes, with whitespace. */
534   public static class NsSqReadable extends XmlSerializer {
535
536      /**
537       * Constructor.
538       *
539       * @param builder The builder for this object.
540       *    <br>Cannot be <jk>null</jk>.
541       */
542      public NsSqReadable(Builder<?> builder) {
543         super(reqnn("builder", builder).enableNamespaces().quoteChar('\'').useWhitespace());
544      }
545   }
546
547   /** Default serializer, single quotes. */
548   public static class Sq extends XmlSerializer {
549
550      /**
551       * Constructor.
552       *
553       * @param builder The builder for this object.
554       *    <br>Cannot be <jk>null</jk>.
555       */
556      public Sq(Builder<?> builder) {
557         super(reqnn("builder", builder).quoteChar('\''));
558      }
559   }
560
561   /** Default serializer, single quotes, whitespace added. */
562   public static class SqReadable extends XmlSerializer {
563
564      /**
565       * Constructor.
566       *
567       * @param builder The builder for this object.
568       *    <br>Cannot be <jk>null</jk>.
569       */
570      public SqReadable(Builder<?> builder) {
571         super(reqnn("builder", builder).quoteChar('\'').useWhitespace());
572      }
573   }
574
575
576   /** Default serializer without namespaces. */
577   public static final XmlSerializer DEFAULT = new XmlSerializer(create());
578   /** Default serializer without namespaces, with single quotes. */
579   public static final XmlSerializer DEFAULT_SQ = new Sq(create());
580
581   /** Default serializer without namespaces, with single quotes, whitespace added. */
582   public static final XmlSerializer DEFAULT_SQ_READABLE = new SqReadable(create());
583
584   /** Default serializer, all default settings. */
585   public static final XmlSerializer DEFAULT_NS = new Ns(create());
586
587   /** Default serializer, single quotes. */
588   public static final XmlSerializer DEFAULT_NS_SQ = new NsSq(create());
589
590   /** Default serializer, single quotes, whitespace added. */
591   public static final XmlSerializer DEFAULT_NS_SQ_READABLE = new NsSqReadable(create());
592
593   protected static final Namespace DEFAULT_JUNEAU_NAMESPACE = Namespace.of("juneau", "http://www.apache.org/2013/Juneau");
594
595   /**
596    * Creates a new builder for this object.
597    *
598    * @return A new builder.
599    */
600   @SuppressWarnings({
601      "java:S1452" // Builder<?> wildcard return intentional; callers chain via fluent API without needing the concrete type
602   })
603   public static Builder<?> create() {
604      return new DefaultBuilder();
605   }
606
607   protected final boolean addBeanTypesXml;
608   protected final boolean addJsonTags;
609   protected final boolean addNamespaceUrlsToRoot;
610   protected final boolean autoDetectNamespaces;
611   protected final boolean enableNamespaces;
612   private final Namespace defaultNamespace;
613   private final List<Namespace> namespaces;
614   private final String textNodeDelimiter;
615
616   private final boolean addBeanTypes2;
617   private final Map<ClassMeta<?>,XmlClassMeta> xmlClassMetas = new ConcurrentHashMap<>();
618   private final Map<BeanMeta<?>,XmlBeanMeta> xmlBeanMetas = new ConcurrentHashMap<>();
619   private final Map<BeanPropertyMeta,XmlBeanPropertyMeta> xmlBeanPropertyMetas = new ConcurrentHashMap<>();
620
621   /**
622    * Constructor.
623    *
624    * @param builder
625    *    The builder for this object.
626    */
627   public XmlSerializer(Builder<?> builder) {
628      super(builder);
629      addBeanTypesXml = builder.addBeanTypesXml;
630      addJsonTags = ! builder.disableJsonTags;
631      addNamespaceUrlsToRoot = builder.addNamespaceUrisToRoot;
632      autoDetectNamespaces = ! builder.disableAutoDetectNamespaces;
633      defaultNamespace = nn(builder.defaultNamespace) ? builder.defaultNamespace : DEFAULT_JUNEAU_NAMESPACE;
634      enableNamespaces = builder.enableNamespaces;
635      namespaces = u(nn(builder.namespaces) ? new ArrayList<>(builder.namespaces) : l());
636      textNodeDelimiter = builder.textNodeDelimiter;
637      addBeanTypes2 = addBeanTypesXml || super.isAddBeanTypes();
638   }
639
640   @Override /* Overridden from Context */
641   public Builder<?> copy() {
642      return new DefaultBuilder(this);
643   }
644
645   @Override /* Overridden from Context */
646   public XmlSerializerSession.Builder<?> createSession() {
647      return XmlSerializerSession.create(this);
648   }
649
650   @Override /* Overridden from Context */
651   public XmlSerializerSession getSession() { return createSession().build(); }
652
653   /**
654    * Convenience delegator for the whole-value {@link RecordWriter} using <b>default session
655    * arguments</b>.  The real implementation lives on
656    * {@link XmlSerializerSession#writeRecords(Object)}.
657    *
658    * @param output The output.
659    * @return A new {@link RecordWriter}.
660    * @throws IOException If a problem occurred opening the underlying output.
661    */
662   @Override /* RecordWritable */
663   public RecordWriter writeRecords(Object output) throws IOException {
664      return getSession().writeRecords(output);
665   }
666
667   /**
668    * The XML record writer is buffered/{@link RecordAdapter}-backed, not O(1) streaming.
669    *
670    * @return Always <jk>false</jk>.
671    */
672   @Override /* RecordWritable */
673   public boolean isRecordStreaming() { return false; }
674
675   @Override /* Overridden from XmlMetaProvider */
676   public XmlBeanMeta getXmlBeanMeta(BeanMeta<?> bm) {
677      return xmlBeanMetas.computeIfAbsent(bm, k -> new XmlBeanMeta(k, this));
678   }
679
680   @Override /* Overridden from XmlMetaProvider */
681   public XmlBeanPropertyMeta getXmlBeanPropertyMeta(BeanPropertyMeta bpm) {
682      return xmlBeanPropertyMetas.computeIfAbsent(bpm, k -> new XmlBeanPropertyMeta(k.getDelegateFor(), this));
683   }
684
685   @Override /* Overridden from XmlMetaProvider */
686   public XmlClassMeta getXmlClassMeta(ClassMeta<?> cm) {
687      return xmlClassMetas.computeIfAbsent(cm, k -> new XmlClassMeta(k, this));
688   }
689
690   /**
691    * Default namespace.
692    *
693    * @see Builder#defaultNamespace(Namespace)
694    * @return
695    *    The default namespace URI for this document.
696    */
697   protected final Namespace getDefaultNamespace() { return defaultNamespace; }
698
699   /**
700    * Default namespaces.
701    *
702    * @see Builder#namespaces(Namespace...)
703    * @return
704    *    The default list of namespaces associated with this serializer.
705    *    <br>Never <jk>null</jk>.
706    *    <br>List is unmodifiable.
707    */
708   protected final List<Namespace> getNamespaces() { return namespaces; }
709
710   /**
711    * Text node delimiter.
712    *
713    * @see Builder#textNodeDelimiter(String)
714    * @return
715    *    The delimiter string to insert between consecutive text nodes.
716    *    <br>Never <jk>null</jk>.
717    */
718   protected final String getTextNodeDelimiter() { return textNodeDelimiter; }
719
720   /**
721    * Add <js>"_type"</js> properties when needed.
722    *
723    * @see Builder#addBeanTypesXml()
724    * @return
725    *    <jk>true</jk> if<js>"_type"</js> properties will be added to beans if their type cannot be inferred
726    *    through reflection.
727    */
728   @Override
729   protected boolean isAddBeanTypes() { return addBeanTypes2; }
730
731   /**
732    * Add namespace URLs to the root element.
733    *
734    * @see Builder#addNamespaceUrisToRoot()
735    * @return
736    *    <jk>true</jk> if {@code xmlns:x} attributes are added to the root element for the default and all mapped namespaces.
737    */
738   protected final boolean isAddNamespaceUrlsToRoot() { return addNamespaceUrlsToRoot; }
739
740   /**
741    * Auto-detect namespace usage.
742    *
743    * @see Builder#disableAutoDetectNamespaces()
744    * @return
745    *    <jk>true</jk> if namespace usage is detected before serialization.
746    */
747   protected final boolean isAutoDetectNamespaces() { return autoDetectNamespaces; }
748
749   /**
750    * Enable support for XML namespaces.
751    *
752    * @see Builder#enableNamespaces()
753    * @return
754    *    <jk>false</jk> if XML output will not contain any namespaces regardless of any other settings.
755    */
756   protected final boolean isEnableNamespaces() { return enableNamespaces; }
757
758   @Override /* Overridden from WriterSerializer */
759   protected FluentMap<String,Object> properties() {
760      return super.properties()
761         .a("addBeanTypes", addBeanTypes2)
762         .a("addNamespaceUrlsToRoot", addNamespaceUrlsToRoot)
763         .a("autoDetectNamespaces", autoDetectNamespaces)
764         .a("defaultNamespace", defaultNamespace)
765         .a("enableNamespaces", enableNamespaces)
766         .a("namespaces", namespaces);
767   }
768}