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}