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