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