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.xml;
018
019import static javax.xml.stream.XMLStreamConstants.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.StringUtils.*;
022import static org.apache.juneau.commons.utils.ThrowableUtils.*;
023import static org.apache.juneau.commons.utils.Utils.*;
024import static org.apache.juneau.xml.annotation.XmlFormat.*;
025
026import java.io.*;
027import java.lang.reflect.*;
028import java.nio.charset.*;
029import java.util.*;
030import java.util.function.*;
031
032import javax.xml.stream.*;
033import javax.xml.stream.util.*;
034
035import org.apache.juneau.*;
036import org.apache.juneau.collections.*;
037import org.apache.juneau.commons.reflect.*;
038import org.apache.juneau.httppart.*;
039import org.apache.juneau.parser.*;
040import org.apache.juneau.swap.*;
041
042/**
043 * Session object that lives for the duration of a single use of {@link XmlParser}.
044 *
045 * <h5 class='section'>Notes:</h5><ul>
046 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
047 * </ul>
048 *
049 * <h5 class='section'>See Also:</h5><ul>
050 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/XmlBasics">XML Basics</a>
051
052 * </ul>
053 */
054@SuppressWarnings({ "unchecked", "rawtypes" })
055public class XmlParserSession extends ReaderParserSession {
056   /**
057    * Builder class.
058    */
059   public static class Builder extends ReaderParserSession.Builder {
060
061      private XmlParser ctx;
062
063      /**
064       * Constructor
065       *
066       * @param ctx The context creating this session.
067       */
068      protected Builder(XmlParser ctx) {
069         super(ctx);
070         this.ctx = ctx;
071      }
072
073      @Override /* Overridden from Builder */
074      public <T> Builder apply(Class<T> type, Consumer<T> apply) {
075         super.apply(type, apply);
076         return this;
077      }
078
079      @Override
080      public XmlParserSession build() {
081         return new XmlParserSession(this);
082      }
083
084      @Override /* Overridden from Builder */
085      public Builder debug(Boolean value) {
086         super.debug(value);
087         return this;
088      }
089
090      @Override /* Overridden from Builder */
091      public Builder fileCharset(Charset value) {
092         super.fileCharset(value);
093         return this;
094      }
095
096      @Override /* Overridden from Builder */
097      public Builder javaMethod(Method value) {
098         super.javaMethod(value);
099         return this;
100      }
101
102      @Override /* Overridden from Builder */
103      public Builder locale(Locale value) {
104         super.locale(value);
105         return this;
106      }
107
108      @Override /* Overridden from Builder */
109      public Builder mediaType(MediaType value) {
110         super.mediaType(value);
111         return this;
112      }
113
114      @Override /* Overridden from Builder */
115      public Builder mediaTypeDefault(MediaType value) {
116         super.mediaTypeDefault(value);
117         return this;
118      }
119
120      @Override /* Overridden from Builder */
121      public Builder outer(Object value) {
122         super.outer(value);
123         return this;
124      }
125
126      @Override /* Overridden from Builder */
127      public Builder properties(Map<String,Object> value) {
128         super.properties(value);
129         return this;
130      }
131
132      @Override /* Overridden from Builder */
133      public Builder property(String key, Object value) {
134         super.property(key, value);
135         return this;
136      }
137
138      @Override /* Overridden from Builder */
139      public Builder schema(HttpPartSchema value) {
140         super.schema(value);
141         return this;
142      }
143
144      @Override /* Overridden from Builder */
145      public Builder schemaDefault(HttpPartSchema value) {
146         super.schemaDefault(value);
147         return this;
148      }
149
150      @Override /* Overridden from Builder */
151      public Builder streamCharset(Charset value) {
152         super.streamCharset(value);
153         return this;
154      }
155
156      @Override /* Overridden from Builder */
157      public Builder timeZone(TimeZone value) {
158         super.timeZone(value);
159         return this;
160      }
161
162      @Override /* Overridden from Builder */
163      public Builder timeZoneDefault(TimeZone value) {
164         super.timeZoneDefault(value);
165         return this;
166      }
167
168      @Override /* Overridden from Builder */
169      public Builder unmodifiable() {
170         super.unmodifiable();
171         return this;
172      }
173   }
174
175   private static final int UNKNOWN = 0, OBJECT = 1, ARRAY = 2, STRING = 3, NUMBER = 4, BOOLEAN = 5, NULL = 6;
176
177   /**
178    * Creates a new builder for this object.
179    *
180    * @param ctx The context creating this session.
181    * @return A new builder.
182    */
183   public static Builder create(XmlParser ctx) {
184      return new Builder(ctx);
185   }
186
187   private static int getJsonType(String s) {
188      if (s == null)
189         return UNKNOWN;
190      var c = s.charAt(0);
191      return switch (c) {
192         case 'o' -> (s.equals("object") ? OBJECT : UNKNOWN);
193         case 'a' -> (s.equals("array") ? ARRAY : UNKNOWN);
194         case 's' -> (s.equals("string") ? STRING : UNKNOWN);
195         case 'b' -> (s.equals("boolean") ? BOOLEAN : UNKNOWN);
196         case 'n' -> {
197            c = s.charAt(2);
198            yield switch (c) {
199               case 'm' -> (s.equals("number") ? NUMBER : UNKNOWN);
200               case 'l' -> (s.equals("null") ? NULL : UNKNOWN);
201               default -> NUMBER;
202            };
203         }
204         default -> UNKNOWN;
205      };
206   }
207
208   private final XmlParser ctx;
209
210   private final StringBuilder rsb = new StringBuilder();  // Reusable string builder used in this class.
211
212   /**
213    * Constructor.
214    *
215    * @param builder The builder for this object.
216    */
217   protected XmlParserSession(Builder builder) {
218      super(builder);
219      ctx = builder.ctx;
220   }
221
222   /*
223    * Returns the name of the specified attribute on the current XML element.
224    * Any <js>'_x####_'</js> sequences in the string will be decoded.
225    */
226   private String getAttributeName(XmlReader r, int i) {
227      return decodeString(r.getAttributeLocalName(i));
228   }
229
230   /*
231    * Returns the value of the specified attribute on the current XML element.
232    * Any <js>'_x####_'</js> sequences in the string will be decoded.
233    */
234   private String getAttributeValue(XmlReader r, int i) {
235      return decodeString(r.getAttributeValue(i));
236   }
237
238   /*
239    * Takes the element being read from the XML stream reader and reconstructs it as XML.
240    * Used when reconstructing bean properties of type {@link XmlFormat#XMLTEXT}.
241    */
242   private String getElementAsString(XmlReader r) {
243      int t = r.getEventType();
244      if (t > 2)
245         throw rex("Invalid event type on stream reader for elementToString() method: ''{0}''", XmlUtils.toReadableEvent(r));
246      rsb.setLength(0);
247      rsb.append("<").append(t == 1 ? "" : "/").append(r.getLocalName());
248      if (t == 1)
249         for (var i = 0; i < r.getAttributeCount(); i++)
250            rsb.append(' ').append(r.getAttributeName(i)).append('=').append('\'').append(r.getAttributeValue(i)).append('\'');
251      rsb.append('>');
252      return rsb.toString();
253   }
254
255   /*
256    * Returns the name of the current XML element.
257    * Any <js>'_x####_'</js> sequences in the string will be decoded.
258    */
259   private String getElementName(XmlReader r) {
260      return decodeString(r.getLocalName());
261   }
262
263   /*
264    * Returns the _name attribute value.
265    * Any <js>'_x####_'</js> sequences in the string will be decoded.
266    */
267   private String getNameProperty(XmlReader r) {
268      return decodeString(r.getAttributeValue(null, getNamePropertyName()));
269   }
270
271   /*
272    * Shortcut for calling <code>getText(r, <jk>true</jk>);</code>.
273    */
274   private String getText(XmlReader r) {
275      return getText(r, true);
276   }
277
278   /*
279    * Returns the content of the current CHARACTERS node.
280    * Any <js>'_x####_'</js> sequences in the string will be decoded.
281    * Leading and trailing whitespace (unencoded) will be trimmed from the result.
282    */
283   private String getText(XmlReader r, boolean trim) {
284      var s = r.getText();
285      if (trim)
286         s = s.trim();
287      if (s.isEmpty())
288         return null;
289      return decodeString(s);
290   }
291
292   @SuppressWarnings("null")
293   private Object getUnknown(XmlReader r) throws IOException, ParseException, ExecutableException, XMLStreamException {
294      if (r.getEventType() != START_ELEMENT) {
295         throw new ParseException(this, "Parser must be on START_ELEMENT to read next text.");
296      }
297      var m = (JsonMap)null;
298
299      // If this element has attributes, then it's always a JsonMap.
300      if (r.getAttributeCount() > 0) {
301         m = new JsonMap(this);
302         for (var i = 0; i < r.getAttributeCount(); i++) {
303            var key = getAttributeName(r, i);
304            var val = r.getAttributeValue(i);
305            if (! isSpecialAttr(key))
306               m.put(key, val);
307         }
308      }
309      int eventType = r.next();
310      var sb = getStringBuilder();
311      while (eventType != END_ELEMENT) {
312         if (eventType == CHARACTERS || eventType == CDATA || eventType == XMLStreamConstants.SPACE || eventType == ENTITY_REFERENCE) {
313            sb.append(r.getText());
314         } else if (eventType == PROCESSING_INSTRUCTION || eventType == COMMENT) {
315            // skipping
316         } else if (eventType == END_DOCUMENT) {
317            throw new ParseException(this, "Unexpected end of document when reading element text content");
318         } else if (eventType == START_ELEMENT) {
319            // Oops...this has an element in it.
320            // Parse it as a map.
321            if (m == null)
322               m = new JsonMap(this);
323            int depth = 0;
324            do {
325               int event = (eventType == -1 ? r.nextTag() : eventType);
326               String currAttr;
327               if (event == START_ELEMENT) {
328                  depth++;
329                  currAttr = getNameProperty(r);
330                  if (currAttr == null)
331                     currAttr = getElementName(r);
332                  String key = convertAttrToType(null, currAttr, string());
333                  var value = parseAnything(object(), currAttr, r, null, false, null);
334                  if (m.containsKey(key)) {
335                     var o = m.get(key);
336                     if (o instanceof JsonList o2)
337                        o2.add(value);
338                     else
339                        m.put(key, new JsonList(o, value).setBeanSession(this));
340                  } else {
341                     m.put(key, value);
342                  }
343
344               } else if (event == END_ELEMENT) {
345                  depth--;
346                  break;
347               }
348               eventType = -1;
349            } while (depth > 0);
350            break;
351         } else {
352            throw new ParseException(this, "Unexpected event type ''{0}''", eventType);
353         }
354         eventType = r.next();
355      }
356      var s = sb.toString().trim();
357      returnStringBuilder(sb);
358      s = decodeString(s);
359      if (nn(m)) {
360         if (! s.isEmpty())
361            m.put("contents", s);
362         return m;
363      }
364      return s;
365   }
366
367   private boolean isSpecialAttr(String key) {
368      return key.equals(getBeanTypePropertyName(null)) || key.equals(getNamePropertyName());
369   }
370
371   @SuppressWarnings("null")
372   private <T> BeanMap<T> parseIntoBean(XmlReader r, BeanMap<T> m, boolean isNil) throws IOException, ParseException, ExecutableException, XMLStreamException {
373      var bMeta = m.getMeta();
374      var xmlMeta = getXmlBeanMeta(bMeta);
375
376      for (var i = 0; i < r.getAttributeCount(); i++) {
377         String key = getAttributeName(r, i);
378         if (! ("nil".equals(key) || isSpecialAttr(key))) {
379            var val = r.getAttributeValue(i);
380            var ns = r.getAttributeNamespace(i);
381            var bpm = xmlMeta.getPropertyMeta(key);
382            if (bpm == null) {
383               if (nn(xmlMeta.getAttrsProperty())) {
384                  xmlMeta.getAttrsProperty().add(m, key, key, val);
385               } else if (ns == null) {
386                  onUnknownProperty(key, m, val);
387               }
388            } else {
389               try {
390                  bpm.set(m, key, val);
391               } catch (BeanRuntimeException e) {
392                  onBeanSetterException(bpm, e);
393                  throw e;
394               }
395            }
396         }
397      }
398
399      var cp = xmlMeta.getContentProperty();
400      var cpf = xmlMeta.getContentFormat();
401      var trim = cp == null || ! cpf.isOneOf(MIXED_PWS, TEXT_PWS);
402      var cpcm = (cp == null ? object() : cp.getClassMeta());
403      var sb = (StringBuilder)null;
404      var breg = cp == null ? null : cp.getBeanRegistry();
405      var l = (LinkedList<Object>)null;
406
407      int depth = 0;
408      do {
409         var event = r.next();
410         String currAttr;
411         // We only care about text in MIXED mode.
412         // Ignore if in ELEMENTS mode.
413         if (event == CHARACTERS) {
414            if (nn(cp) && cpf.isOneOf(MIXED, MIXED_PWS)) {
415               if (cpcm.isCollectionOrArray()) {
416                  if (l == null)
417                     l = new LinkedList<>();
418                  l.add(getText(r, false));
419               } else {
420                  cp.set(m, null, getText(r, trim));
421               }
422            } else if (cpf != ELEMENTS) {
423               var s = getText(r, trim);
424               if (nn(s)) {
425                  if (sb == null)
426                     sb = getStringBuilder();
427                  sb.append(s);
428               }
429            } else {
430               // Do nothing...we're in ELEMENTS mode.
431            }
432         } else if (event == START_ELEMENT) {
433            if (nn(cp) && cpf.isOneOf(TEXT, TEXT_PWS)) {
434               var s = parseText(r);
435               if (nn(s)) {
436                  if (sb == null)
437                     sb = getStringBuilder();
438                  sb.append(s);
439               }
440               depth--;
441            } else if (cpf == XMLTEXT) {
442               if (sb == null)
443                  sb = getStringBuilder();
444               sb.append(getElementAsString(r));
445               depth++;
446            } else if (nn(cp) && cpf.isOneOf(MIXED, MIXED_PWS)) {
447               if (isWhitespaceElement(r) && (breg == null || ! breg.hasName(r.getLocalName()))) {
448                  if (cpcm.isCollectionOrArray()) {
449                     if (l == null)
450                        l = new LinkedList<>();
451                     l.add(parseWhitespaceElement(r));
452                  } else {
453                     cp.set(m, null, parseWhitespaceElement(r));
454                  }
455               } else {
456                  if (cpcm.isCollectionOrArray()) {
457                     if (l == null)
458                        l = new LinkedList<>();
459                     l.add(parseAnything(cpcm.getElementType(), cp.getName(), r, m.getBean(false), false, cp));
460                  } else {
461                     cp.set(m, null, parseAnything(cpcm, cp.getName(), r, m.getBean(false), false, cp));
462                  }
463               }
464            } else if (nn(cp) && cpf == ELEMENTS) {
465               cp.add(m, null, parseAnything(cpcm.getElementType(), cp.getName(), r, m.getBean(false), false, cp));
466            } else {
467               currAttr = getNameProperty(r);
468               if (currAttr == null)
469                  currAttr = getElementName(r);
470               var pMeta = xmlMeta.getPropertyMeta(currAttr);
471               if (pMeta == null) {
472                  var value = parseAnything(object(), currAttr, r, m.getBean(false), false, null);
473                  onUnknownProperty(currAttr, m, value);
474               } else {
475                  setCurrentProperty(pMeta);
476                  var xf = getXmlBeanPropertyMeta(pMeta).getXmlFormat();
477                  if (xf == COLLAPSED) {
478                     var et = pMeta.getClassMeta().getElementType();
479                     var value = parseAnything(et, currAttr, r, m.getBean(false), false, pMeta);
480                     setName(et, value, currAttr);
481                     pMeta.add(m, currAttr, value);
482                  } else if (xf == ATTR) {
483                     pMeta.set(m, currAttr, getAttributeValue(r, 0));
484                     r.nextTag();
485                  } else {
486                     var cm = pMeta.getClassMeta();
487                     var value = parseAnything(cm, currAttr, r, m.getBean(false), false, pMeta);
488                     setName(cm, value, currAttr);
489                     pMeta.set(m, currAttr, value);
490                  }
491                  setCurrentProperty(null);
492               }
493            }
494         } else if (event == END_ELEMENT) {
495            if (depth > 0) {
496               if (cpf == XMLTEXT) {
497                  if (sb == null)
498                     sb = getStringBuilder();
499                  sb.append(getElementAsString(r));
500               } else
501                  throw new ParseException("End element found where one was not expected.  {0}", XmlUtils.toReadableEvent(r));
502            }
503            depth--;
504         } else if (event == COMMENT) {
505            // Ignore comments.
506         } else {
507            throw new ParseException("Unexpected event type: {0}", XmlUtils.toReadableEvent(r));
508         }
509      } while (depth >= 0);
510
511      if (nn(cp) && ! isNil) {
512         if (nn(sb))
513            cp.set(m, null, sb.toString());
514         else if (nn(l))
515            cp.set(m, null, XmlUtils.collapseTextNodes(l));
516         else if (cpcm.isCollectionOrArray()) {
517            var o = cp.get(m, null);
518            if (o == null)
519               cp.set(m, cp.getName(), list());
520         }
521      }
522
523      returnStringBuilder(sb);
524      return m;
525   }
526
527   private <E> Collection<E> parseIntoCollection(XmlReader r, Collection<E> l, ClassMeta<?> type, BeanPropertyMeta pMeta) throws IOException, ParseException, ExecutableException, XMLStreamException {
528      int depth = 0;
529      int argIndex = 0;
530      do {
531         var event = r.nextTag();
532         if (event == START_ELEMENT) {
533            depth++;
534            var elementType = type == null ? object() : type.isArgs() ? type.getArg(argIndex++) : type.getElementType();
535            E value = (E)parseAnything(elementType, null, r, l, false, pMeta);
536            l.add(value);
537         } else if (event == END_ELEMENT) {
538            depth--;
539            return l;
540         }
541      } while (depth > 0);
542      return l;
543   }
544
545   private <K,V> Map<K,V> parseIntoMap(XmlReader r, Map<K,V> m, ClassMeta<K> keyType, ClassMeta<V> valueType, BeanPropertyMeta pMeta)
546      throws IOException, ParseException, ExecutableException, XMLStreamException {
547      int depth = 0;
548      for (var i = 0; i < r.getAttributeCount(); i++) {
549         var a = r.getAttributeLocalName(i);
550         // TODO - Need better handling of namespaces here.
551         if (! isSpecialAttr(a)) {
552            K key = trim(convertAttrToType(m, a, keyType));
553            V value = trim(convertAttrToType(m, r.getAttributeValue(i), valueType));
554            setName(valueType, value, key);
555            m.put(key, value);
556         }
557      }
558      do {
559         var event = r.nextTag();
560         String currAttr;
561         if (event == START_ELEMENT) {
562            depth++;
563            currAttr = getNameProperty(r);
564            if (currAttr == null)
565               currAttr = getElementName(r);
566            K key = convertAttrToType(m, currAttr, keyType);
567            V value = parseAnything(valueType, currAttr, r, m, false, pMeta);
568            setName(valueType, value, currAttr);
569            if (valueType.isObject() && m.containsKey(key)) {
570               var o = m.get(key);
571               if (o instanceof List o2)
572                  o2.add(value);
573               else
574                  m.put(key, (V)new JsonList(o, value).setBeanSession(this));
575            } else {
576               m.put(key, value);
577            }
578         } else if (event == END_ELEMENT) {
579            depth--;
580            return m;
581         }
582      } while (depth > 0);
583      return m;
584   }
585
586   /**
587    * Decodes and trims the specified string.
588    *
589    * <p>
590    * Any <js>'_x####_'</js> sequences in the string will be decoded.
591    *
592    * @param s The string to be decoded.
593    * @return The decoded string.
594    */
595   protected final String decodeString(String s) {
596      if (s == null)
597         return null;
598      rsb.setLength(0);
599      s = XmlUtils.decode(s, rsb);
600      if (isTrimStrings())
601         s = s.trim();
602      return s;
603   }
604
605   @Override /* Overridden from ParserSession */
606   protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws IOException, ParseException, ExecutableException {
607      try {
608         return parseAnything(type, null, getXmlReader(pipe), getOuter(), true, null);
609      } catch (XMLStreamException e) {
610         throw new ParseException(e);
611      }
612   }
613
614   @Override /* Overridden from ReaderParserSession */
615   protected <E> Collection<E> doParseIntoCollection(ParserPipe pipe, Collection<E> c, Type elementType) throws Exception {
616      var cm = getClassMeta(c.getClass(), elementType);
617      return parseIntoCollection(pipe, c, cm.getElementType());
618   }
619
620   @Override /* Overridden from ReaderParserSession */
621   protected <K,V> Map<K,V> doParseIntoMap(ParserPipe pipe, Map<K,V> m, Type keyType, Type valueType) throws Exception {
622      var cm = getClassMeta(m.getClass(), keyType, valueType);
623      return parseIntoMap(pipe, m, cm.getKeyType(), cm.getValueType());
624   }
625
626   /**
627    * Returns the text content of the current XML element.
628    *
629    * <p>
630    * Any <js>'_x####_'</js> sequences in the string will be decoded.
631    *
632    * <p>
633    * Leading and trailing whitespace (unencoded) will be trimmed from the result.
634    *
635    * @param r The reader to read the element text from.
636    * @return The decoded text.  <jk>null</jk> if the text consists of the sequence <js>'_x0000_'</js>.
637    * @throws XMLStreamException Thrown by underlying reader.
638    * @throws IOException Thrown by underlying stream.
639    * @throws ParseException Malformed input encountered.
640    */
641   protected String getElementText(XmlReader r) throws XMLStreamException, IOException, ParseException {
642      return decodeString(r.getElementText().trim());
643   }
644
645   /**
646    * XML event allocator.
647    *
648    * @see XmlParser.Builder#eventAllocator(Class)
649    * @return
650    *    The {@link XMLEventAllocator} associated with this parser, or <jk>null</jk> if there isn't one.
651    */
652   protected final XMLEventAllocator getEventAllocator() { return ctx.getEventAllocator(); }
653
654   /**
655    * XML reporter.
656    *
657    * @see XmlParser.Builder#reporter(Class)
658    * @return
659    *    The {@link XMLReporter} associated with this parser, or <jk>null</jk> if there isn't one.
660    */
661   protected final XMLReporter getReporter() { return ctx.getReporter(); }
662
663   /**
664    * XML resolver.
665    *
666    * @see XmlParser.Builder#resolver(Class)
667    * @return
668    *    The {@link XMLResolver} associated with this parser, or <jk>null</jk> if there isn't one.
669    */
670   protected final XMLResolver getResolver() { return ctx.getResolver(); }
671
672   /**
673    * Returns the language-specific metadata on the specified bean.
674    *
675    * @param bm The bean to return the metadata on.
676    * @return The metadata.
677    */
678   protected XmlBeanMeta getXmlBeanMeta(BeanMeta<?> bm) {
679      return ctx.getXmlBeanMeta(bm);
680   }
681
682   /**
683    * Returns the language-specific metadata on the specified bean property.
684    *
685    * @param bpm The bean property to return the metadata on.
686    * @return The metadata.
687    */
688   protected XmlBeanPropertyMeta getXmlBeanPropertyMeta(BeanPropertyMeta bpm) {
689      return ctx.getXmlBeanPropertyMeta(bpm);
690   }
691
692   /**
693    * Returns the language-specific metadata on the specified class.
694    *
695    * @param cm The class to return the metadata on.
696    * @return The metadata.
697    */
698   protected XmlClassMeta getXmlClassMeta(ClassMeta<?> cm) {
699      return ctx.getXmlClassMeta(cm);
700   }
701
702   /**
703    * Wrap the specified reader in a STAX reader based on settings in this context.
704    *
705    * @param pipe The parser input.
706    * @return The new STAX reader.
707    * @throws IOException Thrown by underlying stream.
708    * @throws XMLStreamException Unexpected XML processing error.
709    */
710   protected final XmlReader getXmlReader(ParserPipe pipe) throws IOException, XMLStreamException {
711      return new XmlReader(pipe, isValidating(), getReporter(), getResolver(), getEventAllocator());
712   }
713
714   /**
715    * Preserve root element during generalized parsing.
716    *
717    * @see XmlParser.Builder#preserveRootElement()
718    * @return
719    *    <jk>true</jk> if when parsing into a generic {@link JsonMap}, the map will contain a single entry whose key
720    *    is the root element name.
721    */
722   protected final boolean isPreserveRootElement() { return ctx.isPreserveRootElement(); }
723
724   /**
725    * Enable validation.
726    *
727    * @see XmlParser.Builder#validating()
728    * @return
729    *    <jk>true</jk> if XML document will be validated.
730    */
731   protected final boolean isValidating() { return ctx.isValidating(); }
732
733   /**
734    * Returns <jk>true</jk> if the current element is a whitespace element.
735    *
736    * <p>
737    * For the XML parser, this always returns <jk>false</jk>.
738    * However, the HTML parser defines various whitespace elements such as <js>"br"</js> and <js>"sp"</js>.
739    *
740    * @param r The XML stream reader to read the current event from.
741    * @return <jk>true</jk> if the current element is a whitespace element.
742    */
743   protected boolean isWhitespaceElement(XmlReader r) {
744      return false;
745   }
746
747   /**
748    * Workhorse method.
749    *
750    * @param <T> The expected type of object.
751    * @param eType The expected type of object.
752    * @param currAttr The current bean property name.
753    * @param r The reader.
754    * @param outer The outer object.
755    * @param isRoot If <jk>true</jk>, then we're serializing a root element in the document.
756    * @param pMeta The bean property metadata.
757    * @return The parsed object.
758    * @throws IOException Thrown by underlying stream.
759    * @throws ParseException Malformed input encountered.
760    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
761    * @throws XMLStreamException Malformed XML encountered.
762    */
763   @SuppressWarnings("null")
764   protected <T> T parseAnything(ClassMeta<T> eType, String currAttr, XmlReader r, Object outer, boolean isRoot, BeanPropertyMeta pMeta)
765      throws IOException, ParseException, ExecutableException, XMLStreamException {
766
767      if (eType == null)
768         eType = (ClassMeta<T>)object();
769      var swap = (ObjectSwap<T,Object>)eType.getSwap(this);
770      var builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this);
771      var sType = (ClassMeta<?>)null;
772      if (nn(builder))
773         sType = builder.getBuilderClassMeta(this);
774      else if (nn(swap))
775         sType = swap.getSwapClassMeta(this);
776      else
777         sType = eType;
778
779      if (sType.isOptional())
780         return (T)opt(parseAnything(eType.getElementType(), currAttr, r, outer, isRoot, pMeta));
781
782      setCurrentClass(sType);
783
784      var wrapperAttr = (isRoot && isPreserveRootElement()) ? r.getName().getLocalPart() : null;
785      var typeAttr = r.getAttributeValue(null, getBeanTypePropertyName(eType));
786      var isNil = "true".equals(r.getAttributeValue(null, "nil"));
787      var jsonType = getJsonType(typeAttr);
788      var elementName = getElementName(r);
789      if (jsonType == 0) {
790         if (elementName == null || elementName.equals(currAttr))
791            jsonType = UNKNOWN;
792         else {
793            typeAttr = elementName;
794            jsonType = getJsonType(elementName);
795         }
796      }
797
798      ClassMeta tcm = getClassMeta(typeAttr, pMeta, eType);
799      if (tcm == null && nn(elementName) && ! elementName.equals(currAttr))
800         tcm = getClassMeta(elementName, pMeta, eType);
801      if (nn(tcm))
802         sType = eType = tcm;
803
804      var o = (Object)null;
805
806      if (jsonType == NULL) {
807         r.nextTag();   // Discard end tag
808         return null;
809      }
810
811      if (sType.isObject()) {
812         if (jsonType == OBJECT) {
813            var m = new JsonMap(this);
814            parseIntoMap(r, m, string(), object(), pMeta);
815            if (nn(wrapperAttr))
816               m = new JsonMap(this).append(wrapperAttr, m);
817            o = cast(m, pMeta, eType);
818         } else if (jsonType == ARRAY)
819            o = parseIntoCollection(r, new JsonList(this), null, pMeta);
820         else if (jsonType == STRING) {
821            o = getElementText(r);
822            if (sType.isChar())
823               o = parseCharacter(o);
824         } else if (jsonType == NUMBER)
825            o = parseNumber(getElementText(r), null);
826         else if (jsonType == BOOLEAN)
827            o = Boolean.parseBoolean(getElementText(r));
828         else if (jsonType == UNKNOWN)
829            o = getUnknown(r);
830      } else if (sType.isBoolean()) {
831         o = Boolean.parseBoolean(getElementText(r));
832      } else if (sType.isCharSequence()) {
833         o = getElementText(r);
834      } else if (sType.isChar()) {
835         o = parseCharacter(getElementText(r));
836      } else if (sType.isMap()) {
837         var m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : newGenericMap(sType));
838         o = parseIntoMap(r, m, sType.getKeyType(), sType.getValueType(), pMeta);
839         if (nn(wrapperAttr))
840            o = new JsonMap(this).append(wrapperAttr, m);
841      } else if (sType.isCollection()) {
842         var l = (sType.canCreateNewInstance(outer) ? (Collection)sType.newInstance(outer) : new JsonList(this));
843         o = parseIntoCollection(r, l, sType, pMeta);
844      } else if (sType.isNumber()) {
845         o = parseNumber(getElementText(r), (Class<? extends Number>)sType.inner());
846      } else if (nn(builder) || sType.canCreateNewBean(outer)) {
847         if (getXmlClassMeta(sType).getFormat() == COLLAPSED) {
848            var fieldName = r.getLocalName();
849            var m = nn(builder) ? toBeanMap(builder.create(this, eType)) : newBeanMap(outer, sType.inner());
850            var bpm = getXmlBeanMeta(m.getMeta()).getPropertyMeta(fieldName);
851            var cm = m.getMeta().getClassMeta();
852            Object value = parseAnything(cm, currAttr, r, m.getBean(false), false, null);
853            setName(cm, value, currAttr);
854            bpm.set(m, currAttr, value);
855            o = nn(builder) ? builder.build(this, m.getBean(), eType) : m.getBean();
856         } else {
857            var m = nn(builder) ? toBeanMap(builder.create(this, eType)) : newBeanMap(outer, sType.inner());
858            m = parseIntoBean(r, m, isNil);
859            o = nn(builder) ? builder.build(this, m.getBean(), eType) : m.getBean();
860         }
861      } else if (sType.isArray() || sType.isArgs()) {
862         var l = (ArrayList)parseIntoCollection(r, list(), sType, pMeta);
863         o = toArray(sType, l);
864      } else if (sType.canCreateNewInstanceFromString(outer)) {
865         o = sType.newInstanceFromString(outer, getElementText(r));
866      } else if (nn(sType.getProxyInvocationHandler())) {
867         var m = new JsonMap(this);
868         parseIntoMap(r, m, string(), object(), pMeta);
869         if (nn(wrapperAttr))
870            m = new JsonMap(this).append(wrapperAttr, m);
871         o = newBeanMap(outer, sType.inner()).load(m).getBean();
872      } else {
873         throw new ParseException(this, "Class ''{0}'' could not be instantiated.  Reason: ''{1}'', property: ''{2}''", cn(sType), sType.getNotABeanReason(),
874            pMeta == null ? null : pMeta.getName());
875      }
876
877      if (nn(swap) && nn(o))
878         o = unswap(swap, o, eType);
879
880      if (nn(outer))
881         setParent(eType, o, outer);
882
883      return (T)o;
884   }
885
886   /**
887    * Parses the current element as text.
888    *
889    * @param r The input reader.
890    * @return The parsed text.
891    * @throws XMLStreamException Thrown by underlying reader.
892    * @throws IOException Thrown by underlying stream.
893    * @throws ParseException Malformed input encountered.
894    */
895   protected String parseText(XmlReader r) throws IOException, XMLStreamException, ParseException {
896      // Note that this is different than {@link #getText(XmlReader)} since it assumes that we're pointing to a
897      // whitespace element.
898
899      var sb2 = getStringBuilder();
900
901      int depth = 0;
902      while (true) {
903         var et = r.getEventType();
904         if (et == START_ELEMENT) {
905            sb2.append(getElementAsString(r));
906            depth++;
907         } else if (et == CHARACTERS) {
908            sb2.append(getText(r));
909         } else if (et == END_ELEMENT) {
910            sb2.append(getElementAsString(r));
911            depth--;
912            if (depth <= 0)
913               break;
914         }
915         et = r.next();
916      }
917      var s = sb2.toString();
918      returnStringBuilder(sb2);
919      return s;
920   }
921
922   /**
923    * Parses the current whitespace element.
924    *
925    * <p>
926    * For the XML parser, this always returns <jk>null</jk> since there is no concept of a whitespace element.
927    * However, the HTML parser defines various whitespace elements such as <js>"br"</js> and <js>"sp"</js>.
928    *
929    * @param r The XML stream reader to read the current event from.
930    * @return The whitespace character or characters.
931    * @throws XMLStreamException Thrown by underlying reader.
932    * @throws IOException Thrown by underlying stream.
933    * @throws ParseException Malformed input encountered.
934    */
935   protected String parseWhitespaceElement(XmlReader r) throws IOException, XMLStreamException, ParseException {
936      return null;
937   }
938}