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