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