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