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 javax.xml.stream.XMLStreamConstants.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022import static org.apache.juneau.commons.utils.StringUtils.*;
023import static org.apache.juneau.marshall.xml.XmlFormat.*;
024
025import java.io.*;
026import java.lang.reflect.*;
027import java.util.*;
028
029import javax.xml.stream.*;
030import javax.xml.stream.util.*;
031
032import org.apache.juneau.commons.bean.*;
033import org.apache.juneau.commons.reflect.*;
034import org.apache.juneau.marshall.*;
035import org.apache.juneau.marshall.collections.*;
036import org.apache.juneau.marshall.parser.*;
037import org.apache.juneau.marshall.stream.*;
038import org.apache.juneau.marshall.swap.spi.*;
039
040/**
041 * Session object that lives for the duration of a single use of {@link XmlParser}.
042 *
043 * <h5 class='section'>Notes:</h5><ul>
044 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
045 * </ul>
046 *
047 * <h5 class='section'>See Also:</h5><ul>
048 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/XmlSupport">XML</a>
049
050 * </ul>
051 */
052@SuppressWarnings({
053   "java:S110", // Deep inheritance inherent to the serializer/parser session hierarchy
054   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
055   "java:S3776", // readAnything(), readIntoBean(), getUnknown() and readIntoCollection() are branch-heavy XML event state machines; splitting them would obscure the grammar
056   "java:S6541", // readAnything() and readIntoBean() are long single-pass dispatches over XML element/attribute forms
057   "null", // Null handling verified by context or framework
058   "rawtypes", // Raw types necessary for generic type handling
059   "resource", // RecordReader returned by RecordAdapter is a Closeable owned by the caller; Eclipse JDT @Owning warning is by design.
060   "unchecked" // Type erasure requires unchecked casts
061})
062public class XmlParserSession extends ReaderParserSession implements RecordReadable, ArrayRecordReadable {
063
064   /**
065    * Builder class.
066    */
067   @SuppressWarnings({
068      "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name.
069   })
070   public abstract static class Builder<SELF extends Builder<SELF>> extends ReaderParserSession.Builder<SELF> {
071
072      private boolean preserveRootElement;
073      private boolean validating;
074      private final XmlParser ctx;
075
076      /**
077       * Constructor
078       *
079       * @param ctx The context creating this session.
080       *    <br>Cannot be <jk>null</jk>.
081       */
082      protected Builder(XmlParser ctx) {
083         super(ctx);
084         this.ctx = ctx;
085         preserveRootElement = ctx.isPreserveRootElement();
086         validating = ctx.isValidating();
087      }
088
089      @Override
090      public XmlParserSession build() {
091         return new XmlParserSession(this);
092      }
093
094      /**
095       * Preserve root element during XML parsing.
096       *
097       * @param value The new value for this setting.
098       * @return This object.
099       */
100      public SELF preserveRootElement(boolean value) {
101         preserveRootElement = value;
102         return self();
103      }
104
105      /**
106       * Validate XML during parsing.
107       *
108       * @param value The new value for this setting.
109       * @return This object.
110       */
111      public SELF validating(boolean value) {
112         validating = value;
113         return self();
114      }
115
116      @Override /* Overridden from Builder */
117      public SELF property(String key, Object value) {
118         if (key == null) { super.property(key, value); return self(); }
119         switch (key) {
120            case "preserveRootElement", "XmlParserSession.preserveRootElement":
121               return preserveRootElement(cvt(value, Boolean.class));
122            case "validating", "XmlParserSession.validating":
123               return validating(cvt(value, Boolean.class));
124            default:
125               super.property(key, value);
126               return self();
127         }
128      }
129
130   }
131
132   /**
133    * Concrete default builder leaf for the non-subclassed {@code create()} path (CRTP terminal).
134    */
135   public static final class DefaultBuilder extends Builder<DefaultBuilder> {
136
137      DefaultBuilder(XmlParser ctx) {
138         super(ctx);
139      }
140   }
141
142   private static final int UNKNOWN = 0;
143   private static final int OBJECT = 1;
144   private static final int ARRAY = 2;
145   private static final int STRING = 3;
146   private static final int NUMBER = 4;
147   private static final int BOOLEAN = 5;
148   private static final int NULL = 6;
149
150   /**
151    * Creates a new builder for this object.
152    *
153    * @param ctx The context creating this session.
154    *    <br>Cannot be <jk>null</jk>.
155    * @return A new builder.
156    */
157   @SuppressWarnings({
158      "java:S1452" // Builder<?> wildcard return intentional; callers use it to construct session instances polymorphically
159   })
160   public static Builder<?> create(XmlParser ctx) {
161      return new DefaultBuilder(ctx);
162   }
163
164   private static int getJsonType(String s) {
165      if (s == null)
166         return UNKNOWN;
167      var c = s.charAt(0);
168      return switch (c) {
169         case 'o' -> (eq(s, "object") ? OBJECT : UNKNOWN);
170         case 'a' -> (eq(s, "array") ? ARRAY : UNKNOWN);
171         case 's' -> (eq(s, "string") ? STRING : UNKNOWN);
172         case 'b' -> (eq(s, "boolean") ? BOOLEAN : UNKNOWN);
173         case 'n' -> {
174            c = s.charAt(2);
175            yield switch (c) {
176               case 'm' -> (eq(s, "number") ? NUMBER : UNKNOWN);
177               case 'l' -> (eq(s, "null") ? NULL : UNKNOWN);
178               default -> NUMBER;
179            };
180         }
181         default -> UNKNOWN;
182      };
183   }
184
185   private final XmlParser ctx;
186   private final boolean preserveRootElement;
187   private final boolean validating;
188
189   private final StringBuilder rsb = new StringBuilder();  // Reusable string builder used in this class.
190
191   /**
192    * Constructor.
193    *
194    * @param builder The builder for this object.
195    */
196   protected XmlParserSession(Builder<?> builder) {
197      super(builder);
198      ctx = builder.ctx;
199      preserveRootElement = builder.preserveRootElement;
200      validating = builder.validating;
201   }
202
203   /*
204    * Returns the name of the specified attribute on the current XML element.
205    * Any <js>'_x####_'</js> sequences in the string will be decoded.
206    */
207   private String getAttributeName(XmlReader r, int i) {
208      return decodeString(r.getAttributeLocalName(i));
209   }
210
211   /*
212    * Returns the value of the specified attribute on the current XML element.
213    * Any <js>'_x####_'</js> sequences in the string will be decoded.
214    */
215   private String getAttributeValue(XmlReader r, int i) {
216      return decodeString(r.getAttributeValue(i));
217   }
218
219   /**
220    * Takes the element being read from the XML stream reader and reconstructs it as XML.
221    * Used when reconstructing bean properties of type {@link XmlFormat#XMLTEXT}.
222    */
223   private String getElementAsString(XmlReader r) {
224      int t = r.getEventType();
225      if (t > 2)
226         throw rex("Invalid event type on stream reader for elementToString() method: '%s'", XmlUtils.toReadableEvent(r));
227      rsb.setLength(0);
228      rsb.append("<").append(t == 1 ? "" : "/").append(r.getLocalName());
229      if (t == 1)
230         for (var i = 0; i < r.getAttributeCount(); i++)
231            rsb.append(' ').append(r.getAttributeName(i)).append('=').append('\'').append(r.getAttributeValue(i)).append('\'');
232      rsb.append('>');
233      return rsb.toString();
234   }
235
236   /*
237    * Returns the name of the current XML element.
238    * Any <js>'_x####_'</js> sequences in the string will be decoded.
239    */
240   private String getElementName(XmlReader r) {
241      return decodeString(r.getLocalName());
242   }
243
244   /*
245    * Returns the _name attribute value.
246    * Any <js>'_x####_'</js> sequences in the string will be decoded.
247    */
248   private String getNameProperty(XmlReader r) {
249      return decodeString(r.getAttributeValue(null, MarshallingSession.NAME_PROPERTY_NAME));
250   }
251
252   /**
253    * Shortcut for calling <code>getText(r, <jk>true</jk>);</code>.
254    */
255   private String getText(XmlReader r) {
256      return getText(r, true);
257   }
258
259   /*
260    * Returns the content of the current CHARACTERS node.
261    * Any <js>'_x####_'</js> sequences in the string will be decoded.
262    * Leading and trailing whitespace (unencoded) will be trimmed from the result.
263    */
264   private String getText(XmlReader r, boolean trim) {
265      var s = r.getText();
266      if (trim)
267         s = s.trim();
268      if (s.isEmpty())
269         return null;
270      return decodeString(s);
271   }
272
273   private Object getUnknown(XmlReader r) throws IOException, ParseException, ExecutableException, XMLStreamException {
274      if (r.getEventType() != START_ELEMENT) {
275         throw new ParseException(this, "Parser must be on START_ELEMENT to read next text.");
276      }
277      MarshalledMap m = null;
278
279      // If this element has attributes, then it's always a MarshalledMap.
280      if (r.getAttributeCount() > 0) {
281         m = newGenericMap();
282         for (var i = 0; i < r.getAttributeCount(); i++) {
283            var key = getAttributeName(r, i);
284            var val = r.getAttributeValue(i);
285            if (! isSpecialAttr(key))
286               m.put(key, val);
287         }
288      }
289      int eventType = r.next();
290      var sb = getStringBuilder();
291      while (eventType != END_ELEMENT) {
292         if (eventType == CHARACTERS || eventType == CDATA || eventType == XMLStreamConstants.SPACE || eventType == ENTITY_REFERENCE) {
293            sb.append(r.getText());
294         } else if (eventType == PROCESSING_INSTRUCTION || eventType == COMMENT) {
295            // skipping
296         } else if (eventType == END_DOCUMENT) {
297            throw new ParseException(this, "Unexpected end of document when reading element text content");
298         } else if (eventType == START_ELEMENT) {
299            // Oops...this has an element in it.
300            // Parse it as a map.
301            if (m == null)
302               m = newGenericMap();
303            int depth = 0;
304            do {
305               int event = (eventType == -1 ? r.nextTag() : eventType);
306               String currAttr;
307               if (event == START_ELEMENT) {
308                  depth++;
309                  currAttr = getNameProperty(r);
310                  if (currAttr == null)
311                     currAttr = getElementName(r);
312                  String key = convertAttrToType(null, currAttr, string());
313                  var value = readAnything(object(), currAttr, r, null, false, null);
314                  if (m.containsKey(key)) {
315                     var o = m.get(key);
316                     if (o instanceof MarshalledList o2)
317                        o2.add(value);
318                     else
319                        m.put(key, new JsonList(o, value).setBeanSession(this));
320                  } else {
321                     m.put(key, value);
322                  }
323
324               } else if (event == END_ELEMENT) {
325                  break;
326               }
327               eventType = -1;
328            } while (depth > 0);
329            break;
330         } else {
331            throw new ParseException(this, "Unexpected event type '%s'", eventType);
332         }
333         eventType = r.next();
334      }
335      var s = sb.toString().trim();
336      returnStringBuilder(sb);
337      s = decodeString(s);
338      if (nn(m)) {
339         if (! s.isEmpty())
340            m.put("contents", s);
341         return m;
342      }
343      return s;
344   }
345
346   private boolean isSpecialAttr(String key) {
347      return key.equals(getBeanTypePropertyName(null)) || key.equals(MarshallingSession.NAME_PROPERTY_NAME);
348   }
349
350   private <T> BeanMap<T> readIntoBean(XmlReader r, BeanMap<T> m, boolean isNil) throws IOException, ParseException, ExecutableException, XMLStreamException {
351      var bMeta = m.getMeta();
352      var xmlMeta = getXmlBeanMeta(bMeta);
353
354      for (var i = 0; i < r.getAttributeCount(); i++) {
355         String key = getAttributeName(r, i);
356         if (! (eq(key, "nil") || isSpecialAttr(key))) {
357            var val = r.getAttributeValue(i);
358            var ns = r.getAttributeNamespace(i);
359            var bpm = xmlMeta.getPropertyMeta(key);
360            if (bpm == null) {
361               if (nn(xmlMeta.getAttrsProperty())) {
362                  xmlMeta.getAttrsProperty().add(m, key, key, val);
363               } else if (ns == null) {
364                  onUnknownProperty(key, m, val);
365               }
366            } else {
367               try {
368                  bpm.set(m, key, val);
369               } catch (BeanRuntimeException e) {
370                  onBeanSetterException(bpm, e);
371                  throw e;
372               }
373            }
374         }
375      }
376
377      var cp = xmlMeta.getContentProperty();
378      var cpf = xmlMeta.getContentFormat();
379      var trim = cp == null || ! cpf.isOneOf(MIXED_PWS, TEXT_PWS, RAWTEXT);
380      ClassMeta<?> cpcm = (cp == null ? object() : (ClassMeta<?>) cp.getBeanInfo());
381      StringBuilder sb = null;
382      var breg = cp == null ? null : cp.getBeanRegistry();
383      List<Object> l = null;
384
385      int depth = 0;
386      do {
387         var event = r.next();
388         String currAttr;
389         // We only care about text in MIXED mode.
390         // Ignore if in FREEFORM mode.
391         if (event == CHARACTERS) {
392            if (nn(cp) && cpf.isOneOf(MIXED, MIXED_PWS)) {
393               if (cpcm.isCollectionOrArray()) {
394                  if (l == null)
395                     l = ll();
396                  l.add(getText(r, false));
397               } else {
398                  cp.set(m, null, getText(r, trim));
399               }
400            } else if (nn(cp) && cpf == RAWTEXT) {
401               // Verbatim: no trim, no _x####_ decoding (mirrors RAWTEXT serialize which bypasses encoding).
402               if (sb == null)
403                  sb = getStringBuilder();
404               sb.append(r.getText());
405            } else if (cpf != FREEFORM) {
406               var s = getText(r, trim);
407               if (nn(s)) {
408                  if (sb == null)
409                     sb = getStringBuilder();
410                  sb.append(s);
411               }
412            } else {
413               // Do nothing...we're in FREEFORM mode.
414            }
415         } else if (event == START_ELEMENT) {
416            if (nn(cp) && cpf.isOneOf(TEXT, TEXT_PWS)) {
417               var s = readText(r);
418               if (nn(s)) {
419                  if (sb == null)
420                     sb = getStringBuilder();
421                  sb.append(s);
422               }
423               depth--;
424            } else if (cpf == XMLTEXT) {
425               if (sb == null)
426                  sb = getStringBuilder();
427               sb.append(getElementAsString(r));
428               depth++;
429            } else if (nn(cp) && cpf.isOneOf(MIXED, MIXED_PWS)) {
430               if (isWhitespaceElement(r) && (breg == null || ! breg.hasName(r.getLocalName()))) {
431                  if (cpcm.isCollectionOrArray()) {
432                     if (l == null)
433                        l = ll();
434                     l.add(readWhitespaceElement(r));
435                  } else {
436                     cp.set(m, null, readWhitespaceElement(r));
437                  }
438               } else {
439                  if (cpcm.isCollectionOrArray()) {
440                     if (l == null)
441                        l = ll();
442                     l.add(readAnything(cpcm.getElementType(), cp.getName(), r, m.getBean(false), false, cp));
443                  } else {
444                     cp.set(m, null, readAnything(cpcm, cp.getName(), r, m.getBean(false), false, cp));
445                  }
446               }
447            } else if (nn(cp) && cpf == FREEFORM) {
448               cp.add(m, null, readAnything(cpcm.getElementType(), cp.getName(), r, m.getBean(false), false, cp));
449            } else {
450               currAttr = getNameProperty(r);
451               if (currAttr == null)
452                  currAttr = getElementName(r);
453               var pMeta = xmlMeta.getPropertyMeta(currAttr);
454               if (pMeta == null) {
455                  var value = readAnything(object(), currAttr, r, m.getBean(false), false, null);
456                  onUnknownProperty(currAttr, m, value);
457               } else {
458                  setCurrentProperty(pMeta);
459                  var xf = getXmlBeanPropertyMeta(pMeta).getXmlFormat();
460                  if (xf == COLLAPSED) {
461                     var et = ((ClassMeta<?>) pMeta.getBeanInfo()).getElementType();
462                     var value = readAnything(et, currAttr, r, m.getBean(false), false, pMeta);
463                     setName(et, value, currAttr);
464                     pMeta.add(m, currAttr, value);
465                  } else if (xf == ATTR) {
466                     pMeta.set(m, currAttr, getAttributeValue(r, 0));
467                     r.nextTag();
468                  } else {
469                     var cm = (ClassMeta<?>) pMeta.getBeanInfo();
470                     var value = readAnything(cm, currAttr, r, m.getBean(false), false, pMeta);
471                     setName(cm, value, currAttr);
472                     pMeta.set(m, currAttr, value);
473                  }
474                  setCurrentProperty(null);
475               }
476            }
477         } else if (event == END_ELEMENT) {
478            if (depth > 0) {
479               if (cpf == XMLTEXT) {
480                  if (sb == null)
481                     sb = getStringBuilder();
482                  sb.append(getElementAsString(r));
483               } else
484                  throw new ParseException("End element found where one was not expected.  %s", XmlUtils.toReadableEvent(r));
485            }
486            depth--;
487         } else if (event == COMMENT) {
488            // Ignore comments.
489         } else {
490            throw new ParseException("Unexpected event type: %s", XmlUtils.toReadableEvent(r));
491         }
492      } while (depth >= 0);
493
494      if (nn(cp) && ! isNil) {
495         if (nn(sb))
496            cp.set(m, null, sb.toString());
497         else if (nn(l))
498            cp.set(m, null, XmlUtils.collapseTextNodes(l));
499         else if (cpcm.isCollectionOrArray()) {
500            var o = cp.get(m, null);
501            if (o == null)
502               cp.set(m, cp.getName(), list());
503         }
504      }
505
506      returnStringBuilder(sb);
507      return m;
508   }
509
510   private <E> Collection<E> readIntoCollection(XmlReader r, Collection<E> l, ClassMeta<?> type, BeanPropertyMeta pMeta) throws IOException, ParseException, ExecutableException, XMLStreamException {
511      int depth = 0;
512      int argIndex = 0;
513      do {
514         var event = r.nextTag();
515         if (event == START_ELEMENT) {
516            depth++;
517            ClassMeta<?> elementType;
518            if (type == null) {
519               elementType = object();
520            } else if (type.isArgs()) {
521               elementType = type.getArg(argIndex++);
522            } else {
523               elementType = type.getElementType();
524            }
525            E value = (E)readAnything(elementType, null, r, l, false, pMeta);
526            l.add(value);
527         } else if (event == END_ELEMENT) {
528            return l;
529         }
530      } while (depth > 0);
531      return l;
532   }
533
534   private <K,V> Map<K,V> readIntoMap(XmlReader r, Map<K,V> m, ClassMeta<K> keyType, ClassMeta<V> valueType, BeanPropertyMeta pMeta)
535      throws IOException, ParseException, ExecutableException, XMLStreamException {
536      int depth = 0;
537      for (var i = 0; i < r.getAttributeCount(); i++) {
538         var a = r.getAttributeLocalName(i);
539         if (! isSpecialAttr(a)) {
540            K key = trim(convertAttrToType(m, a, keyType));
541            V value = trim(convertAttrToType(m, r.getAttributeValue(i), valueType));
542            setName(valueType, value, key);
543            m.put(key, value);
544         }
545      }
546      do {
547         var event = r.nextTag();
548         String currAttr;
549         if (event == START_ELEMENT) {
550            depth++;
551            currAttr = getNameProperty(r);
552            if (currAttr == null)
553               currAttr = getElementName(r);
554            K key = convertAttrToType(m, currAttr, keyType);
555            V value = readAnything(valueType, currAttr, r, m, false, pMeta);
556            setName(valueType, value, currAttr);
557            if (valueType.isObject() && m.containsKey(key)) {
558               var o = m.get(key);
559               if (o instanceof List o2)
560                  o2.add(value);
561               else
562                  m.put(key, (V)new JsonList(o, value).setBeanSession(this));
563            } else {
564               m.put(key, value);
565            }
566         } else if (event == END_ELEMENT) {
567            return m;
568         }
569      } while (depth > 0);
570      return m;
571   }
572
573   /**
574    * Decodes and trims the specified string.
575    *
576    * <p>
577    * Any <js>'_x####_'</js> sequences in the string will be decoded.
578    *
579    * @param s The string to be decoded.
580    *    <br>Can be <jk>null</jk> (returns <jk>null</jk>).
581    * @return The decoded string, or <jk>null</jk> if the input was <jk>null</jk>.
582    */
583   protected final String decodeString(String s) {
584      if (s == null)
585         return null;
586      rsb.setLength(0);
587      s = XmlUtils.decode(s, rsb);
588      if (isTrimStrings())
589         s = s.trim();
590      return s;
591   }
592
593   @Override /* Overridden from ParserSession */
594   protected <T> T doRead(ParserPipe pipe, ClassMeta<T> type) throws IOException, ParseException, ExecutableException {
595      try {
596         return readAnything(type, null, getXmlReader(pipe), getOuter(), true, null);
597      } catch (XMLStreamException e) {
598         throw new ParseException(e);
599      }
600   }
601
602   @Override /* Overridden from ReaderParserSession */
603   protected <E> Collection<E> doReadIntoCollection(ParserPipe pipe, Collection<E> c, Type elementType) throws Exception {
604      var cm = getClassMeta(c.getClass(), elementType);
605      return readIntoCollection(getXmlReader(pipe), c, cm.getElementType(), null);
606   }
607
608   @Override /* Overridden from ReaderParserSession */
609   protected <K,V> Map<K,V> doReadIntoMap(ParserPipe pipe, Map<K,V> m, Type keyType, Type valueType) throws Exception {
610      return readIntoMap(getXmlReader(pipe), m, (ClassMeta<K>)getClassMeta(keyType), (ClassMeta<V>)getClassMeta(valueType), null);
611   }
612
613   /**
614    * Opens a whole-value pull-parser cursor over an XML document, bound to this live session.
615    *
616    * <p>
617    * Because XML's wire format (attributes, namespaces, mixed content) is non-trivial to expose
618    * as a fine-grained token cursor, this implementation supports only the cursor-level POJO
619    * bridge &mdash; {@link RecordReader#read(Class) read(...)} delegates to the polymorphic
620    * {@link ParserSession#read(Object, Class)} entry point.
621    *
622    * @param input The input.
623    * @return A new {@link RecordReader} cursor.
624    * @throws IOException If a problem occurred opening the underlying input.
625    */
626   @Override /* RecordReadable */
627   public RecordReader readRecords(Object input) throws IOException {
628      return RecordAdapter.reader(this, input);
629   }
630
631   /**
632    * Buffered array-element {@link RecordReader} for XML, bound to this live session.  Calls
633    * {@code parse(input, List.class, Object.class)} once and iterates the result.
634    *
635    * <p>
636    * For caller-specified-root semantics use {@link #readArrayRecords(Object, String)}.
637    *
638    * @param input The input.
639    * @return A buffered {@link RecordReader}.
640    * @throws IOException If a problem occurred reading the input.
641    */
642   @Override /* ArrayRecordReadable */
643   public RecordReader readArrayRecords(Object input) throws IOException {
644      return RecordAdapter.arrayReader(this, input);
645   }
646
647   /**
648    * Buffered array-element {@link RecordReader} that honors the caller-specified root element
649    * name.
650    *
651    * <p>
652    * The document is parsed once into a generic structure and the records to yield are selected by
653    * the supplied {@code rootElementName}:
654    * <ul>
655    *    <li>If the document root is a <b>wrapper element</b> (parses to a {@link Map}) that contains a
656    *       child named {@code rootElementName}, that child's value supplies the records (a repeated
657    *       child element parses to a {@link List}; a single occurrence yields one record).  Sibling
658    *       elements with other names are ignored.
659    *    <li>If the document root is an <b>array wrapper</b> (parses to a {@link List}, e.g. Juneau's
660    *       {@code <array>} envelope), its already-unwrapped elements are the records and the hint is a
661    *       no-op.
662    *    <li>A scalar root yields a single record.
663    * </ul>
664    *
665    * <p>
666    * A <jk>null</jk> {@code rootElementName} is equivalent to {@link #readArrayRecords(Object)}.
667    * The cursor is buffered ({@link RecordReader#isStreaming()} == <jk>false</jk>); see
668    * {@code 175ab} Item 3 for why true element-at-a-time XML streaming is left for a
669    * demand-driven StAX cursor.
670    *
671    * @param input The input.
672    * @param rootElementName The name of the wrapping/child element whose occurrences are the
673    *    records.  <jk>null</jk> defers to {@link #readArrayRecords(Object)}.
674    * @return A buffered {@link RecordReader}.
675    * @throws IOException If a problem occurred reading the input.
676    */
677   @Override /* ArrayRecordReadable */
678   public RecordReader readArrayRecords(Object input, String rootElementName) throws IOException {
679      if (rootElementName == null)
680         return readArrayRecords(input);
681      Object parsed;
682      try {
683         parsed = read(input, Object.class);
684      } catch (ParseException e) {
685         throw new IOException(e);
686      }
687      var records = selectRootElements(parsed, rootElementName);
688      var iter = records.iterator();
689      return new RecordReader() {
690         @Override public boolean canRead() { return iter.hasNext(); }
691         @Override public <T> T read(Class<T> type) { return convertToType(nextElement(), type); }
692         @Override public <T> T read(ClassMeta<T> type) { return convertToType(nextElement(), type); }
693         @Override public <T> T read(Type type, Type... args) { return XmlParserSession.this.<T>convertToType(nextElement(), type, args); }
694         @Override public boolean isStreaming() { return false; }
695         @Override public void close() throws IOException {
696            if (input instanceof Closeable input2)
697               input2.close();
698         }
699         private Object nextElement() {
700            if (! iter.hasNext())
701               throw new IllegalStateException("Array stream is exhausted.");
702            return iter.next();
703         }
704      };
705   }
706
707   /** Selects the records to yield from a generically-parsed XML document, honoring the root-element hint. */
708   private static List<Object> selectRootElements(Object parsed, String rootElementName) {
709      if (parsed == null)
710         return Collections.emptyList();
711      if (parsed instanceof Map<?,?> parsed2) {
712         var v = parsed2.get(rootElementName);
713         if (v == null)
714            return Collections.emptyList();
715         if (v instanceof List<?> v2)
716            return new ArrayList<>(v2);
717         return new ArrayList<>(Collections.singletonList(v));
718      }
719      if (parsed instanceof List<?> parsed2)
720         return new ArrayList<>(parsed2);
721      return new ArrayList<>(Collections.singletonList(parsed));
722   }
723
724   /**
725    * The XML record cursor is buffered/{@link RecordAdapter}-backed, not O(1) streaming.
726    *
727    * @return Always <jk>false</jk>.
728    */
729   @Override /* RecordReadable */
730   public boolean isRecordStreaming() { return false; }
731
732   /**
733    * The XML array-record cursor is buffered/{@link RecordAdapter}-backed, not O(1) streaming.
734    *
735    * @return Always <jk>false</jk>.
736    */
737   @Override /* ArrayRecordReadable */
738   public boolean isArrayRecordStreaming() { return false; }
739
740   /**
741    * Returns the text content of the current XML element.
742    *
743    * <p>
744    * Any <js>'_x####_'</js> sequences in the string will be decoded.
745    *
746    * <p>
747    * Leading and trailing whitespace (unencoded) will be trimmed from the result.
748    *
749    * @param r The reader to read the element text from.
750    *    <br>Must not be <jk>null</jk>.
751    * @return The decoded text.  <jk>null</jk> if the text consists of the sequence <js>'_x0000_'</js>.
752    * @throws XMLStreamException Thrown by underlying reader.
753    * @throws IOException Thrown by underlying stream.
754    * @throws ParseException Malformed input encountered.
755    */
756   protected String getElementText(XmlReader r) throws XMLStreamException, IOException, ParseException {
757      return decodeString(r.getElementText().trim());
758   }
759
760   /**
761    * XML event allocator.
762    *
763    * @see XmlParser.Builder#eventAllocator(Class)
764    * @return
765    *    The {@link XMLEventAllocator} associated with this parser, or <jk>null</jk> if there isn't one.
766    */
767   protected final XMLEventAllocator getEventAllocator() { return ctx.getEventAllocator(); }
768
769   /**
770    * XML reporter.
771    *
772    * @see XmlParser.Builder#reporter(Class)
773    * @return
774    *    The {@link XMLReporter} associated with this parser, or <jk>null</jk> if there isn't one.
775    */
776   protected final XMLReporter getReporter() { return ctx.getReporter(); }
777
778   /**
779    * XML resolver.
780    *
781    * @see XmlParser.Builder#resolver(Class)
782    * @return
783    *    The {@link XMLResolver} associated with this parser, or <jk>null</jk> if there isn't one.
784    */
785   protected final XMLResolver getResolver() { return ctx.getResolver(); }
786
787   /**
788    * Returns the language-specific metadata on the specified bean.
789    *
790    * @param bm The bean to return the metadata on.
791    * @return The metadata.
792    */
793   protected XmlBeanMeta getXmlBeanMeta(BeanMeta<?> bm) {
794      return ctx.getXmlBeanMeta(bm);
795   }
796
797   /**
798    * Returns the language-specific metadata on the specified bean property.
799    *
800    * @param bpm The bean property to return the metadata on.
801    * @return The metadata.
802    */
803   protected XmlBeanPropertyMeta getXmlBeanPropertyMeta(BeanPropertyMeta bpm) {
804      return ctx.getXmlBeanPropertyMeta(bpm);
805   }
806
807   /**
808    * Returns the language-specific metadata on the specified class.
809    *
810    * @param cm The class to return the metadata on.
811    * @return The metadata.
812    */
813   protected XmlClassMeta getXmlClassMeta(ClassMeta<?> cm) {
814      return ctx.getXmlClassMeta(cm);
815   }
816
817   /**
818    * Wrap the specified reader in a STAX reader based on settings in this context.
819    *
820    * @param pipe The parser input.
821    *    <br>Must not be <jk>null</jk>.
822    * @return The new STAX reader.
823    * @throws IOException Thrown by underlying stream.
824    * @throws XMLStreamException Unexpected XML processing error.
825    */
826   protected final XmlReader getXmlReader(ParserPipe pipe) throws IOException, XMLStreamException {
827      return new XmlReader(pipe, () -> ctx.getInputFactory(validating));
828   }
829
830   /**
831    * Preserve root element during generalized parsing.
832    *
833    * @see XmlParser.Builder#preserveRootElement()
834    * @return
835    *    <jk>true</jk> if when parsing into a generic {@link JsonMap}, the map will contain a single entry whose key
836    *    is the root element name.
837    */
838   protected final boolean isPreserveRootElement() { return preserveRootElement; }
839
840   /**
841    * Enable validation.
842    *
843    * @see XmlParser.Builder#validating()
844    * @return
845    *    <jk>true</jk> if XML document will be validated.
846    */
847   protected final boolean isValidating() { return validating; }
848
849   /**
850    * Returns <jk>true</jk> if the current element is a whitespace element.
851    *
852    * <p>
853    * For the XML parser, this always returns <jk>false</jk>.
854    * However, the HTML parser defines various whitespace elements such as <js>"br"</js> and <js>"sp"</js>.
855    *
856    * @param r The XML stream reader to read the current event from.
857    * @return <jk>true</jk> if the current element is a whitespace element.
858    */
859   protected boolean isWhitespaceElement(XmlReader r) {
860      return false;
861   }
862
863   /**
864    * Workhorse method.
865    *
866    * @param <T> The expected type of object.
867    * @param eType The expected type of object.
868    *    <br>Can be <jk>null</jk>, in which case the generic {@link Object} type is used and the actual type is auto-detected from the XML.
869    * @param currAttr The current bean property name.
870    *    <br>Can be <jk>null</jk> if not being parsed as a named bean property (e.g. the root value).
871    * @param r The reader.
872    *    <br>Must not be <jk>null</jk>.
873    * @param outer The outer object.
874    *    <br>Can be <jk>null</jk> if there is no enclosing (outer) object; the parsed object is then not linked to a parent.
875    * @param isRoot If <jk>true</jk>, then we're serializing a root element in the document.
876    * @param pMeta The bean property metadata.
877    *    <br>Can be <jk>null</jk> if no bean property metadata applies (e.g. the root value or an unknown property).
878    * @return The parsed object, or <jk>null</jk> if the element represents a <jk>null</jk> value.
879    * @throws IOException Thrown by underlying stream.
880    * @throws ParseException Malformed input encountered.
881    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
882    * @throws XMLStreamException Malformed XML encountered.
883    */
884   protected <T> T readAnything(ClassMeta<T> eType, String currAttr, XmlReader r, Object outer, boolean isRoot, BeanPropertyMeta pMeta)
885      throws IOException, ParseException, ExecutableException, XMLStreamException {
886
887      if (eType == null)
888         eType = (ClassMeta<T>)object();
889      var swap = (ObjectSwap<T,Object>)eType.getSwap(this);
890      var builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this);
891      ClassMeta<?> sType = null;
892      if (nn(builder))
893         sType = builder.getBuilderClassMeta(this);
894      else if (nn(swap))
895         sType = swap.getSwapClassMeta(this);
896      else
897         sType = eType;
898
899      if (sType.isOptional())
900         return (T)o(readAnything(eType.getElementType(), currAttr, r, outer, isRoot, pMeta));
901
902      setCurrentClass(sType);
903
904      var wrapperAttr = (isRoot && isPreserveRootElement()) ? r.getName().getLocalPart() : null;
905      var typeAttr = r.getAttributeValue(null, getBeanTypePropertyName(eType));
906      var isNil = eq(r.getAttributeValue(null, "nil"), "true");
907      var jsonType = getJsonType(typeAttr);
908      var elementName = getElementName(r);
909      if (jsonType == 0) {
910         if (elementName == null || elementName.equals(currAttr))
911            jsonType = UNKNOWN;
912         else {
913            typeAttr = elementName;
914            jsonType = getJsonType(elementName);
915         }
916      }
917
918      ClassMeta tcm = getClassMeta(typeAttr, pMeta, eType);
919      if (tcm == null && nn(elementName) && ! elementName.equals(currAttr))
920         tcm = getClassMeta(elementName, pMeta, eType);
921      if (nn(tcm))
922         sType = eType = tcm;
923
924      Object o = null;
925
926      if (jsonType == NULL) {
927         r.nextTag();   // Discard end tag
928         return null;
929      }
930
931      if (sType.isObject()) {
932         if (jsonType == OBJECT) {
933            var m = newGenericMap();
934            readIntoMap(r, m, string(), object(), pMeta);
935            if (nn(wrapperAttr))
936               m = newGenericMap().append(wrapperAttr, m);
937            o = cast(m, pMeta, eType);
938         } else if (jsonType == ARRAY)
939            o = readIntoCollection(r, newGenericList(), null, pMeta);
940         else if (jsonType == STRING) {
941            o = getElementText(r);
942            if (sType.isChar())
943               o = parseCharacter(o);
944         } else if (jsonType == NUMBER)
945            o = parseNumber(getElementText(r), null);
946         else if (jsonType == BOOLEAN)
947            o = Boolean.parseBoolean(getElementText(r));
948         else if (jsonType == UNKNOWN)
949            o = getUnknown(r);
950      } else if (nn(builder) || sType.canCreateNewBean(outer)) {
951         if (getXmlClassMeta(sType).getFormat() == COLLAPSED) {
952            var fieldName = r.getLocalName();
953            var m = nn(builder) ? toBeanMap(builder.create(this, eType)) : newBeanMap(outer, sType.inner());
954            var bpm = getXmlBeanMeta(m.getMeta()).getPropertyMeta(fieldName);
955            var cm = (ClassMeta<?>) m.getMeta().getBeanInfo();
956            var pb = swapParentBean(m.getBean(false));
957            try {
958               Object value = readAnything(cm, currAttr, r, m.getBean(false), false, null);
959               setName(cm, value, currAttr);
960               bpm.set(m, currAttr, value);
961            } finally {
962               swapParentBean(pb);
963            }
964            o = nn(builder) ? builder.build(this, checkRequired(m).getBean(), eType) : checkRequired(m).getBean();
965         } else {
966            var m = nn(builder) ? toBeanMap(builder.create(this, eType)) : newBeanMap(outer, sType.inner());
967            var pb = swapParentBean(m.getBean(false));
968            try {
969               m = readIntoBean(r, m, isNil);
970            } finally {
971               swapParentBean(pb);
972            }
973            o = nn(builder) ? builder.build(this, checkRequired(m).getBean(), eType) : checkRequired(m).getBean();
974         }
975      } else if (sType.isMap()) {
976         var m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : newGenericMap(sType));
977         o = readIntoMap(r, m, sType.getKeyType(), sType.getValueType(), pMeta);
978         if (nn(wrapperAttr)) {
979            var wrapper = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : newGenericMap());
980            wrapper.put(wrapperAttr, m);
981            o = wrapper;
982         }
983      } else if (sType.isCollection()) {
984         var l = (sType.canCreateNewInstance(outer) ? (Collection)sType.newInstance(outer) : newGenericList());
985         o = readIntoCollection(r, l, sType, pMeta);
986      } else if (sType.isArray() || sType.isArgs()) {
987         var l = (ArrayList)readIntoCollection(r, list(), sType, pMeta);
988         o = toArray(sType, l);
989      } else if (sType.isCharSequence()) {
990         o = getElementText(r);
991      } else if (sType.isChar()) {
992         o = parseCharacter(getElementText(r));
993      } else if (sType.isNumber()) {
994         o = parseNumber(getElementText(r), (Class<? extends Number>)sType.inner());
995      } else if (sType.isBoolean()) {
996         o = Boolean.parseBoolean(getElementText(r));
997      } else if (sType.isDate()) {
998         o = readDate(getElementText(r), sType);
999      } else if (sType.isCalendar()) {
1000         o = readCalendar(getElementText(r), sType);
1001      } else if (sType.isTemporal()) {
1002         o = readTemporal(getElementText(r), sType);
1003      } else if (sType.isDuration()) {
1004         o = readDuration(getElementText(r));
1005      } else if (sType.isPeriod()) {
1006         o = readPeriod(getElementText(r));
1007      } else if (sType.canCreateNewInstanceFromString(outer)) {
1008         o = sType.newInstanceFromString(outer, getElementText(r));
1009      } else if (nn(sType.getProxyInvocationHandler())) {
1010         var m = newGenericMap();
1011         readIntoMap(r, m, string(), object(), pMeta);
1012         if (nn(wrapperAttr))
1013            m = newGenericMap().append(wrapperAttr, m);
1014         o = checkRequired(newBeanMap(outer, sType.inner()).load(m)).getBean();
1015      } else {
1016         throw new ParseException(this, "Class '%s' could not be instantiated.  Reason: '%s', property: '%s'", cn(sType), sType.getNotABeanReason(),
1017            pMeta == null ? null : pMeta.getName());
1018      }
1019
1020      if (nn(swap) && nn(o))
1021         o = unswap(swap, o, eType);
1022
1023      if (nn(parentBean()))
1024         setParent(eType, o, parentBean());
1025
1026      return (T)o;
1027   }
1028
1029   /**
1030    * Parses the current element as text.
1031    *
1032    * @param r The input reader.
1033    *    <br>Must not be <jk>null</jk>.
1034    * @return The parsed text.
1035    * @throws XMLStreamException Thrown by underlying reader.
1036    * @throws IOException Thrown by underlying stream.
1037    * @throws ParseException Malformed input encountered.
1038    */
1039   protected String readText(XmlReader r) throws IOException, XMLStreamException, ParseException {
1040      // Note that this is different than {@link #getText(XmlReader)} since it assumes that we're pointing to a
1041      // whitespace element.
1042
1043      var sb2 = getStringBuilder();
1044
1045      int depth = 0;
1046      while (true) {
1047         var et = r.getEventType();
1048         if (et == START_ELEMENT) {
1049            sb2.append(getElementAsString(r));
1050            depth++;
1051         } else if (et == CHARACTERS) {
1052            sb2.append(getText(r));
1053         } else if (et == END_ELEMENT) {
1054            sb2.append(getElementAsString(r));
1055            depth--;
1056            if (depth <= 0)
1057               break;
1058         }
1059         r.next();
1060      }
1061      var s = sb2.toString();
1062      returnStringBuilder(sb2);
1063      return s;
1064   }
1065
1066   /**
1067    * Parses the current whitespace element.
1068    *
1069    * <p>
1070    * For the XML parser, this always returns <jk>null</jk> since there is no concept of a whitespace element.
1071    * However, the HTML parser defines various whitespace elements such as <js>"br"</js> and <js>"sp"</js>.
1072    *
1073    * @param r The XML stream reader to read the current event from.
1074    * @return The whitespace character or characters.
1075    * @throws XMLStreamException Thrown by underlying reader.
1076    * @throws IOException Thrown by underlying stream.
1077    * @throws ParseException Malformed input encountered.
1078    */
1079   protected String readWhitespaceElement(XmlReader r) throws IOException, XMLStreamException, ParseException {
1080      return null;
1081   }
1082}