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