blob: 07232607421060c957a08b68ccec6158a6454e11 [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.xml;
import static javax.xml.stream.XMLStreamConstants.*;
import static org.apache.juneau.internal.StringUtils.*;
import static org.apache.juneau.xml.annotation.XmlFormat.*;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.*;
import javax.xml.stream.*;
import javax.xml.stream.util.*;
import org.apache.juneau.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.transform.*;
import org.apache.juneau.xml.annotation.*;
/**
* Session object that lives for the duration of a single use of {@link XmlParser}.
*
* <p>
* This class is NOT thread safe.
* It is typically discarded after one-time use although it can be reused against multiple inputs.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class XmlParserSession extends ReaderParserSession {
private static final int UNKNOWN=0, OBJECT=1, ARRAY=2, STRING=3, NUMBER=4, BOOLEAN=5, NULL=6;
private final XmlParser ctx;
private final StringBuilder rsb = new StringBuilder(); // Reusable string builder used in this class.
/**
* Create a new session using properties specified in the context.
*
* @param ctx
* The context creating this session object.
* The context contains all the configuration settings for this object.
* @param args
* Runtime session arguments.
*/
protected XmlParserSession(XmlParser ctx, ParserSessionArgs args) {
super(ctx, args);
this.ctx = ctx;
}
/**
* Wrap the specified reader in a STAX reader based on settings in this context.
*
* @param pipe The parser input.
* @return The new STAX reader.
* @throws IOException Thrown by underlying stream.
* @throws XMLStreamException Unexpected XML processing error.
*/
protected final XmlReader getXmlReader(ParserPipe pipe) throws IOException, XMLStreamException {
return new XmlReader(pipe, isValidating(), getReporter(), getResolver(), getEventAllocator());
}
/**
* Decodes and trims the specified string.
*
* <p>
* Any <js>'_x####_'</js> sequences in the string will be decoded.
*
* @param s The string to be decoded.
* @return The decoded string.
*/
protected final String decodeString(String s) {
if (s == null)
return null;
rsb.setLength(0);
s = XmlUtils.decode(s, rsb);
if (isTrimStrings())
s = s.trim();
return s;
}
/*
* Returns the name of the current XML element.
* Any <js>'_x####_'</js> sequences in the string will be decoded.
*/
private String getElementName(XmlReader r) {
return decodeString(r.getLocalName());
}
/*
* Returns the name of the specified attribute on the current XML element.
* Any <js>'_x####_'</js> sequences in the string will be decoded.
*/
private String getAttributeName(XmlReader r, int i) {
return decodeString(r.getAttributeLocalName(i));
}
/*
* Returns the value of the specified attribute on the current XML element.
* Any <js>'_x####_'</js> sequences in the string will be decoded.
*/
private String getAttributeValue(XmlReader r, int i) {
return decodeString(r.getAttributeValue(i));
}
/**
* Returns the text content of the current XML element.
*
* <p>
* Any <js>'_x####_'</js> sequences in the string will be decoded.
*
* <p>
* Leading and trailing whitespace (unencoded) will be trimmed from the result.
*
* @param r The reader to read the element text from.
* @return The decoded text. <jk>null</jk> if the text consists of the sequence <js>'_x0000_'</js>.
* @throws XMLStreamException Thrown by underlying reader.
* @throws IOException Thrown by underlying stream.
* @throws ParseException Malformed input encountered.
*/
protected String getElementText(XmlReader r) throws XMLStreamException, IOException, ParseException {
return decodeString(r.getElementText().trim());
}
/*
* Returns the content of the current CHARACTERS node.
* Any <js>'_x####_'</js> sequences in the string will be decoded.
* Leading and trailing whitespace (unencoded) will be trimmed from the result.
*/
private String getText(XmlReader r, boolean trim) {
String s = r.getText();
if (trim)
s = s.trim();
if (s.isEmpty())
return null;
return decodeString(s);
}
/*
* Shortcut for calling <code>getText(r, <jk>true</jk>);</code>.
*/
private String getText(XmlReader r) {
return getText(r, true);
}
/*
* Takes the element being read from the XML stream reader and reconstructs it as XML.
* Used when reconstructing bean properties of type {@link XmlFormat#XMLTEXT}.
*/
private String getElementAsString(XmlReader r) {
int t = r.getEventType();
if (t > 2)
throw new FormattedRuntimeException("Invalid event type on stream reader for elementToString() method: ''{0}''", XmlUtils.toReadableEvent(r));
rsb.setLength(0);
rsb.append("<").append(t == 1 ? "" : "/").append(r.getLocalName());
if (t == 1)
for (int i = 0; i < r.getAttributeCount(); i++)
rsb.append(' ').append(r.getAttributeName(i)).append('=').append('\'').append(r.getAttributeValue(i)).append('\'');
rsb.append('>');
return rsb.toString();
}
/**
* Parses the current element as text.
*
* @param r The input reader.
* @return The parsed text.
* @throws XMLStreamException Thrown by underlying reader.
* @throws IOException Thrown by underlying stream.
* @throws ParseException Malformed input encountered.
*/
protected String parseText(XmlReader r) throws IOException, XMLStreamException, ParseException {
// Note that this is different than {@link #getText(XmlReader)} since it assumes that we're pointing to a
// whitespace element.
StringBuilder sb2 = getStringBuilder();
int depth = 0;
while (true) {
int et = r.getEventType();
if (et == START_ELEMENT) {
sb2.append(getElementAsString(r));
depth++;
} else if (et == CHARACTERS) {
sb2.append(getText(r));
} else if (et == END_ELEMENT) {
sb2.append(getElementAsString(r));
depth--;
if (depth <= 0)
break;
}
et = r.next();
}
String s = sb2.toString();
returnStringBuilder(sb2);
return s;
}
/**
* Returns <jk>true</jk> if the current element is a whitespace element.
*
* <p>
* For the XML parser, this always returns <jk>false</jk>.
* However, the HTML parser defines various whitespace elements such as <js>"br"</js> and <js>"sp"</js>.
*
* @param r The XML stream reader to read the current event from.
* @return <jk>true</jk> if the current element is a whitespace element.
*/
protected boolean isWhitespaceElement(XmlReader r) {
return false;
}
/**
* Parses the current whitespace element.
*
* <p>
* For the XML parser, this always returns <jk>null</jk> since there is no concept of a whitespace element.
* However, the HTML parser defines various whitespace elements such as <js>"br"</js> and <js>"sp"</js>.
*
* @param r The XML stream reader to read the current event from.
* @return The whitespace character or characters.
* @throws XMLStreamException Thrown by underlying reader.
* @throws IOException Thrown by underlying stream.
* @throws ParseException Malformed input encountered.
*/
protected String parseWhitespaceElement(XmlReader r) throws IOException, XMLStreamException, ParseException {
return null;
}
@Override /* ParserSession */
protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws IOException, ParseException, ExecutableException {
try {
return parseAnything(type, null, getXmlReader(pipe), getOuter(), true, null);
} catch (XMLStreamException e) {
throw new ParseException(e);
}
}
@Override /* ReaderParserSession */
protected <K,V> Map<K,V> doParseIntoMap(ParserPipe pipe, Map<K,V> m, Type keyType, Type valueType) throws Exception {
ClassMeta cm = getClassMeta(m.getClass(), keyType, valueType);
return parseIntoMap(pipe, m, cm.getKeyType(), cm.getValueType());
}
@Override /* ReaderParserSession */
protected <E> Collection<E> doParseIntoCollection(ParserPipe pipe, Collection<E> c, Type elementType) throws Exception {
ClassMeta cm = getClassMeta(c.getClass(), elementType);
return parseIntoCollection(pipe, c, cm.getElementType());
}
/**
* Workhorse method.
*
* @param <T> The expected type of object.
* @param eType The expected type of object.
* @param currAttr The current bean property name.
* @param r The reader.
* @param outer The outer object.
* @param isRoot If <jk>true</jk>, then we're serializing a root element in the document.
* @param pMeta The bean property metadata.
* @return The parsed object.
* @throws IOException Thrown by underlying stream.
* @throws ParseException Malformed input encountered.
* @throws ExecutableException Exception occurred on invoked constructor/method/field.
* @throws XMLStreamException Malformed XML encountered.
*/
protected <T> T parseAnything(ClassMeta<T> eType, String currAttr, XmlReader r,
Object outer, boolean isRoot, BeanPropertyMeta pMeta) throws IOException, ParseException, ExecutableException, XMLStreamException {
if (eType == null)
eType = (ClassMeta<T>)object();
PojoSwap<T,Object> swap = (PojoSwap<T,Object>)eType.getPojoSwap(this);
BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this);
ClassMeta<?> sType = null;
if (builder != null)
sType = builder.getBuilderClassMeta(this);
else if (swap != null)
sType = swap.getSwapClassMeta(this);
else
sType = eType;
if (sType.isOptional())
return (T)Optional.ofNullable(parseAnything(eType.getElementType(), currAttr, r, outer, isRoot, pMeta));
setCurrentClass(sType);
String wrapperAttr = (isRoot && isPreserveRootElement()) ? r.getName().getLocalPart() : null;
String typeAttr = r.getAttributeValue(null, getBeanTypePropertyName(eType));
boolean isNil = "true".equals(r.getAttributeValue(null, "nil"));
int jsonType = getJsonType(typeAttr);
String elementName = getElementName(r);
if (jsonType == 0) {
if (elementName == null || elementName.equals(currAttr))
jsonType = UNKNOWN;
else {
typeAttr = elementName;
jsonType = getJsonType(elementName);
}
}
ClassMeta tcm = getClassMeta(typeAttr, pMeta, eType);
if (tcm == null && elementName != null && ! elementName.equals(currAttr))
tcm = getClassMeta(elementName, pMeta, eType);
if (tcm != null)
sType = eType = tcm;
Object o = null;
if (jsonType == NULL) {
r.nextTag(); // Discard end tag
return null;
}
if (sType.isObject()) {
if (jsonType == OBJECT) {
ObjectMap m = new ObjectMap(this);
parseIntoMap(r, m, string(), object(), pMeta);
if (wrapperAttr != null)
m = new ObjectMap(this).append(wrapperAttr, m);
o = cast(m, pMeta, eType);
} else if (jsonType == ARRAY)
o = parseIntoCollection(r, new ObjectList(this), null, pMeta);
else if (jsonType == STRING) {
o = getElementText(r);
if (sType.isChar())
o = parseCharacter(o);
}
else if (jsonType == NUMBER)
o = parseNumber(getElementText(r), null);
else if (jsonType == BOOLEAN)
o = Boolean.parseBoolean(getElementText(r));
else if (jsonType == UNKNOWN)
o = getUnknown(r);
} else if (sType.isBoolean()) {
o = Boolean.parseBoolean(getElementText(r));
} else if (sType.isCharSequence()) {
o = getElementText(r);
} else if (sType.isChar()) {
o = parseCharacter(getElementText(r));
} else if (sType.isMap()) {
Map m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : new ObjectMap(this));
o = parseIntoMap(r, m, sType.getKeyType(), sType.getValueType(), pMeta);
if (wrapperAttr != null)
o = new ObjectMap(this).append(wrapperAttr, m);
} else if (sType.isCollection()) {
Collection l = (sType.canCreateNewInstance(outer) ? (Collection)sType.newInstance(outer) : new ObjectList(this));
o = parseIntoCollection(r, l, sType, pMeta);
} else if (sType.isNumber()) {
o = parseNumber(getElementText(r), (Class<? extends Number>)sType.getInnerClass());
} else if (builder != null || sType.canCreateNewBean(outer)) {
if (getXmlClassMeta(sType).getFormat() == COLLAPSED) {
String fieldName = r.getLocalName();
BeanMap<?> m = builder != null ? toBeanMap(builder.create(this, eType)) : newBeanMap(outer, sType.getInnerClass());
BeanPropertyMeta bpm = getXmlBeanMeta(m.getMeta()).getPropertyMeta(fieldName);
ClassMeta<?> cm = m.getMeta().getClassMeta();
Object value = parseAnything(cm, currAttr, r, m.getBean(false), false, null);
setName(cm, value, currAttr);
bpm.set(m, currAttr, value);
o = builder != null ? builder.build(this, m.getBean(), eType) : m.getBean();
} else {
BeanMap m = builder != null ? toBeanMap(builder.create(this, eType)) : newBeanMap(outer, sType.getInnerClass());
m = parseIntoBean(r, m, isNil);
o = builder != null ? builder.build(this, m.getBean(), eType) : m.getBean();
}
} else if (sType.isArray() || sType.isArgs()) {
ArrayList l = (ArrayList)parseIntoCollection(r, new ArrayList(), sType, pMeta);
o = toArray(sType, l);
} else if (sType.canCreateNewInstanceFromString(outer)) {
o = sType.newInstanceFromString(outer, getElementText(r));
} else {
throw new ParseException(this,
"Class ''{0}'' could not be instantiated. Reason: ''{1}'', property: ''{2}''",
sType.getInnerClass().getName(), sType.getNotABeanReason(), pMeta == null ? null : pMeta.getName());
}
if (swap != null && o != null)
o = unswap(swap, o, eType);
if (outer != null)
setParent(eType, o, outer);
return (T)o;
}
private <K,V> Map<K,V> parseIntoMap(XmlReader r, Map<K,V> m, ClassMeta<K> keyType,
ClassMeta<V> valueType, BeanPropertyMeta pMeta) throws IOException, ParseException, ExecutableException, XMLStreamException {
int depth = 0;
for (int i = 0; i < r.getAttributeCount(); i++) {
String a = r.getAttributeLocalName(i);
// TODO - Need better handling of namespaces here.
if (! (a.equals(getBeanTypePropertyName(null)))) {
K key = trim(convertAttrToType(m, a, keyType));
V value = trim(convertAttrToType(m, r.getAttributeValue(i), valueType));
setName(valueType, value, key);
m.put(key, value);
}
}
do {
int event = r.nextTag();
String currAttr;
if (event == START_ELEMENT) {
depth++;
currAttr = getElementName(r);
K key = convertAttrToType(m, currAttr, keyType);
V value = parseAnything(valueType, currAttr, r, m, false, pMeta);
setName(valueType, value, currAttr);
if (valueType.isObject() && m.containsKey(key)) {
Object o = m.get(key);
if (o instanceof List)
((List)o).add(value);
else
m.put(key, (V)new ObjectList(o, value).setBeanSession(this));
} else {
m.put(key, value);
}
} else if (event == END_ELEMENT) {
depth--;
return m;
}
} while (depth > 0);
return m;
}
private <E> Collection<E> parseIntoCollection(XmlReader r, Collection<E> l,
ClassMeta<?> type, BeanPropertyMeta pMeta) throws IOException, ParseException, ExecutableException, XMLStreamException {
int depth = 0;
int argIndex = 0;
do {
int event = r.nextTag();
if (event == START_ELEMENT) {
depth++;
ClassMeta<?> elementType = type == null ? object() : type.isArgs() ? type.getArg(argIndex++) : type.getElementType();
E value = (E)parseAnything(elementType, null, r, l, false, pMeta);
l.add(value);
} else if (event == END_ELEMENT) {
depth--;
return l;
}
} while (depth > 0);
return l;
}
private static int getJsonType(String s) {
if (s == null)
return UNKNOWN;
char c = s.charAt(0);
switch(c) {
case 'o': return (s.equals("object") ? OBJECT : UNKNOWN);
case 'a': return (s.equals("array") ? ARRAY : UNKNOWN);
case 's': return (s.equals("string") ? STRING : UNKNOWN);
case 'b': return (s.equals("boolean") ? BOOLEAN : UNKNOWN);
case 'n': {
c = s.charAt(2);
switch(c) {
case 'm': return (s.equals("number") ? NUMBER : UNKNOWN);
case 'l': return (s.equals("null") ? NULL : UNKNOWN);
}
//return NUMBER;
}
}
return UNKNOWN;
}
private <T> BeanMap<T> parseIntoBean(XmlReader r, BeanMap<T> m, boolean isNil) throws IOException, ParseException, ExecutableException, XMLStreamException {
BeanMeta<?> bMeta = m.getMeta();
XmlBeanMeta xmlMeta = getXmlBeanMeta(bMeta);
for (int i = 0; i < r.getAttributeCount(); i++) {
String key = getAttributeName(r, i);
if (! "nil".equals(key)) {
String val = r.getAttributeValue(i);
String ns = r.getAttributeNamespace(i);
BeanPropertyMeta bpm = xmlMeta.getPropertyMeta(key);
if (bpm == null) {
if (xmlMeta.getAttrsProperty() != null) {
xmlMeta.getAttrsProperty().add(m, key, key, val);
} else if (ns == null) {
onUnknownProperty(key, m);
}
} else {
bpm.set(m, key, val);
}
}
}
BeanPropertyMeta cp = xmlMeta.getContentProperty();
XmlFormat cpf = xmlMeta.getContentFormat();
boolean trim = cp == null || ! cpf.isOneOf(MIXED_PWS, TEXT_PWS);
ClassMeta<?> cpcm = (cp == null ? object() : cp.getClassMeta());
StringBuilder sb = null;
BeanRegistry breg = cp == null ? null : cp.getBeanRegistry();
LinkedList<Object> l = null;
int depth = 0;
do {
int event = r.next();
String currAttr;
// We only care about text in MIXED mode.
// Ignore if in ELEMENTS mode.
if (event == CHARACTERS) {
if (cp != null && cpf.isOneOf(MIXED, MIXED_PWS)) {
if (cpcm.isCollectionOrArray()) {
if (l == null)
l = new LinkedList<>();
l.add(getText(r, false));
} else {
cp.set(m, null, getText(r, trim));
}
} else if (cpf != ELEMENTS) {
String s = getText(r, trim);
if (s != null) {
if (sb == null)
sb = getStringBuilder();
sb.append(s);
}
} else {
// Do nothing...we're in ELEMENTS mode.
}
} else if (event == START_ELEMENT) {
if (cp != null && cpf.isOneOf(TEXT, TEXT_PWS)) {
String s = parseText(r);
if (s != null) {
if (sb == null)
sb = getStringBuilder();
sb.append(s);
}
depth--;
} else if (cpf == XMLTEXT) {
if (sb == null)
sb = getStringBuilder();
sb.append(getElementAsString(r));
depth++;
} else if (cp != null && cpf.isOneOf(MIXED, MIXED_PWS)) {
if (isWhitespaceElement(r) && (breg == null || ! breg.hasName(r.getLocalName()))) {
if (cpcm.isCollectionOrArray()) {
if (l == null)
l = new LinkedList<>();
l.add(parseWhitespaceElement(r));
} else {
cp.set(m, null, parseWhitespaceElement(r));
}
} else {
if (cpcm.isCollectionOrArray()) {
if (l == null)
l = new LinkedList<>();
l.add(parseAnything(cpcm.getElementType(), cp.getName(), r, m.getBean(false), false, cp));
} else {
cp.set(m, null, parseAnything(cpcm, cp.getName(), r, m.getBean(false), false, cp));
}
}
} else if (cp != null && cpf == ELEMENTS) {
cp.add(m, null, parseAnything(cpcm.getElementType(), cp.getName(), r, m.getBean(false), false, cp));
} else {
currAttr = getElementName(r);
BeanPropertyMeta pMeta = xmlMeta.getPropertyMeta(currAttr);
if (pMeta == null) {
onUnknownProperty(currAttr, m);
skipCurrentTag(r);
} else {
setCurrentProperty(pMeta);
XmlFormat xf = getXmlBeanPropertyMeta(pMeta).getXmlFormat();
if (xf == COLLAPSED) {
ClassMeta<?> et = pMeta.getClassMeta().getElementType();
Object value = parseAnything(et, currAttr, r, m.getBean(false), false, pMeta);
setName(et, value, currAttr);
pMeta.add(m, currAttr, value);
} else if (xf == ATTR) {
pMeta.set(m, currAttr, getAttributeValue(r, 0));
r.nextTag();
} else {
ClassMeta<?> cm = pMeta.getClassMeta();
Object value = parseAnything(cm, currAttr, r, m.getBean(false), false, pMeta);
setName(cm, value, currAttr);
pMeta.set(m, currAttr, value);
}
setCurrentProperty(null);
}
}
} else if (event == END_ELEMENT) {
if (depth > 0) {
if (cpf == XMLTEXT) {
if (sb == null)
sb = getStringBuilder();
sb.append(getElementAsString(r));
}
else
throw new ParseException("End element found where one was not expected. {0}", XmlUtils.toReadableEvent(r));
}
depth--;
} else if (event == COMMENT) {
// Ignore comments.
} else {
throw new ParseException("Unexpected event type: {0}", XmlUtils.toReadableEvent(r));
}
} while (depth >= 0);
if (cp != null && ! isNil) {
if (sb != null)
cp.set(m, null, sb.toString());
else if (l != null)
cp.set(m, null, XmlUtils.collapseTextNodes(l));
else if (cpcm.isCollectionOrArray()) {
Object o = cp.get(m, null);
if (o == null)
cp.set(m, cp.getName(), new ArrayList<>());
}
}
returnStringBuilder(sb);
return m;
}
private static void skipCurrentTag(XmlReader r) throws XMLStreamException {
int depth = 1;
do {
int event = r.next();
if (event == START_ELEMENT)
depth++;
else if (event == END_ELEMENT)
depth--;
} while (depth > 0);
}
private Object getUnknown(XmlReader r) throws IOException, ParseException, ExecutableException, XMLStreamException {
if (r.getEventType() != START_ELEMENT) {
throw new ParseException(this, "Parser must be on START_ELEMENT to read next text.");
}
ObjectMap m = null;
// If this element has attributes, then it's always an ObjectMap.
if (r.getAttributeCount() > 0) {
m = new ObjectMap(this);
for (int i = 0; i < r.getAttributeCount(); i++) {
String key = getAttributeName(r, i);
String val = r.getAttributeValue(i);
if (! key.equals(getBeanTypePropertyName(null)))
m.put(key, val);
}
}
int eventType = r.next();
StringBuilder sb = getStringBuilder();
while (eventType != END_ELEMENT) {
if (eventType == CHARACTERS || eventType == CDATA || eventType == SPACE || eventType == ENTITY_REFERENCE) {
sb.append(r.getText());
} else if (eventType == PROCESSING_INSTRUCTION || eventType == COMMENT) {
// skipping
} else if (eventType == END_DOCUMENT) {
throw new ParseException(this, "Unexpected end of document when reading element text content");
} else if (eventType == START_ELEMENT) {
// Oops...this has an element in it.
// Parse it as a map.
if (m == null)
m = new ObjectMap(this);
int depth = 0;
do {
int event = (eventType == -1 ? r.nextTag() : eventType);
String currAttr;
if (event == START_ELEMENT) {
depth++;
currAttr = getElementName(r);
String key = convertAttrToType(null, currAttr, string());
Object value = parseAnything(object(), currAttr, r, null, false, null);
if (m.containsKey(key)) {
Object o = m.get(key);
if (o instanceof ObjectList)
((ObjectList)o).add(value);
else
m.put(key, new ObjectList(o, value).setBeanSession(this));
} else {
m.put(key, value);
}
} else if (event == END_ELEMENT) {
depth--;
break;
}
eventType = -1;
} while (depth > 0);
break;
} else {
throw new ParseException(this, "Unexpected event type ''{0}''", eventType);
}
eventType = r.next();
}
String s = sb.toString().trim();
returnStringBuilder(sb);
s = decodeString(s);
if (m != null) {
if (! s.isEmpty())
m.put("contents", s);
return m;
}
return s;
}
//-----------------------------------------------------------------------------------------------------------------
// Properties
//-----------------------------------------------------------------------------------------------------------------
/**
* Configuration property: XML event allocator.
*
* @see XmlParser#XML_eventAllocator
* @return
* The {@link XMLEventAllocator} associated with this parser, or <jk>null</jk> if there isn't one.
*/
protected final XMLEventAllocator getEventAllocator() {
return ctx.getEventAllocator();
}
/**
* Configuration property: Preserve root element during generalized parsing.
*
* @see XmlParser#XML_preserveRootElement
* @return
* <jk>true</jk> if when parsing into a generic {@link ObjectMap}, the map will contain a single entry whose key
* is the root element name.
*/
protected final boolean isPreserveRootElement() {
return ctx.isPreserveRootElement();
}
/**
* Configuration property: XML reporter.
*
* @see XmlParser#XML_reporter
* @return
* The {@link XMLReporter} associated with this parser, or <jk>null</jk> if there isn't one.
*/
protected final XMLReporter getReporter() {
return ctx.getReporter();
}
/**
* Configuration property: XML resolver.
*
* @see XmlParser#XML_resolver
* @return
* The {@link XMLResolver} associated with this parser, or <jk>null</jk> if there isn't one.
*/
protected final XMLResolver getResolver() {
return ctx.getResolver();
}
/**
* Configuration property: Enable validation.
*
* @see XmlParser#XML_validating
* @return
* <jk>true</jk> if XML document will be validated.
*/
protected final boolean isValidating() {
return ctx.isValidating();
}
//-----------------------------------------------------------------------------------------------------------------
// Extended metadata
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the language-specific metadata on the specified class.
*
* @param cm The class to return the metadata on.
* @return The metadata.
*/
protected XmlClassMeta getXmlClassMeta(ClassMeta<?> cm) {
return ctx.getXmlClassMeta(cm);
}
/**
* Returns the language-specific metadata on the specified bean.
*
* @param bm The bean to return the metadata on.
* @return The metadata.
*/
protected XmlBeanMeta getXmlBeanMeta(BeanMeta<?> bm) {
return ctx.getXmlBeanMeta(bm);
}
/**
* Returns the language-specific metadata on the specified bean property.
*
* @param bpm The bean property to return the metadata on.
* @return The metadata.
*/
protected XmlBeanPropertyMeta getXmlBeanPropertyMeta(BeanPropertyMeta bpm) {
return ctx.getXmlBeanPropertyMeta(bpm);
}
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
@Override /* Session */
public ObjectMap toMap() {
return super.toMap()
.append("XmlParserSession", new DefaultFilteringObjectMap()
);
}
}