org.apache.juneau.parser.ParserSession Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * 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.parser;
import static org.apache.juneau.collections.JsonMap.*;
import static org.apache.juneau.common.internal.StringUtils.*;
import static org.apache.juneau.internal.ClassUtils.*;
import java.io.*;
import java.lang.reflect.*;
import java.nio.charset.*;
import java.util.*;
import java.util.function.*;
import org.apache.juneau.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.cp.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.objecttools.*;
import org.apache.juneau.swap.*;
/**
* Session object that lives for the duration of a single use of {@link Parser}.
*
* Notes:
* - This class is not thread safe and is typically discarded after one use.
*
*
* See Also:
*/
public class ParserSession extends BeanSession {
//-------------------------------------------------------------------------------------------------------------------
// Static
//-------------------------------------------------------------------------------------------------------------------
/**
* Creates a new builder for this object.
*
* @param ctx The context creating this session.
* @return A new builder.
*/
public static Builder create(Parser ctx) {
return new Builder(ctx);
}
//-------------------------------------------------------------------------------------------------------------------
// Builder
//-------------------------------------------------------------------------------------------------------------------
/**
* Builder class.
*/
@FluentSetters
public static class Builder extends BeanSession.Builder {
Parser ctx;
Method javaMethod;
Object outer;
HttpPartSchema schema;
/**
* Constructor
*
* @param ctx The context creating this session.
*/
protected Builder(Parser ctx) {
super(ctx.getBeanContext());
this.ctx = ctx;
mediaTypeDefault(ctx.getPrimaryMediaType());
}
@Override
public ParserSession build() {
return new ParserSession(this);
}
/**
* The java method that called this serializer, usually the method in a REST servlet.
*
* @param value
* The new property value.
*
Can be null .
* @return This object.
*/
@FluentSetter
public Builder javaMethod(Method value) {
this.javaMethod = value;
return this;
}
/**
* The outer object for instantiating top-level non-static inner classes.
*
* @param value
* The new property value.
*
Can be null .
* @return This object.
*/
@FluentSetter
public Builder outer(Object value) {
this.outer = value;
return this;
}
/**
* HTTP-part schema.
*
*
* Used for schema-based serializers and parsers to define additional formatting.
*
* @param value
* The new value for this property.
*
Can be null .
* @return This object.
*/
@FluentSetter
public Builder schema(HttpPartSchema value) {
if (value != null)
this.schema = value;
return this;
}
/**
* Same as {@link #schema(HttpPartSchema)} but doesn't overwrite the value if it is already set.
*
* @param value
* The new value for this property.
*
If null , then the locale defined on the context is used.
* @return This object.
*/
@FluentSetter
public Builder schemaDefault(HttpPartSchema value) {
if (value != null && schema == null)
this.schema = value;
return this;
}
//
@Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
public Builder apply(Class type, Consumer apply) {
super.apply(type, apply);
return this;
}
@Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
public Builder debug(Boolean value) {
super.debug(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
public Builder properties(Map value) {
super.properties(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
public Builder property(String key, Object value) {
super.property(key, value);
return this;
}
@Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
public Builder unmodifiable() {
super.unmodifiable();
return this;
}
@Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
public Builder locale(Locale value) {
super.locale(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
public Builder localeDefault(Locale value) {
super.localeDefault(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
public Builder mediaType(MediaType value) {
super.mediaType(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
public Builder mediaTypeDefault(MediaType value) {
super.mediaTypeDefault(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
public Builder timeZone(TimeZone value) {
super.timeZone(value);
return this;
}
@Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
public Builder timeZoneDefault(TimeZone value) {
super.timeZoneDefault(value);
return this;
}
//
}
//-------------------------------------------------------------------------------------------------------------------
// Instance
//-------------------------------------------------------------------------------------------------------------------
private final Parser ctx;
private final Method javaMethod;
private final Object outer;
private final Stack sbStack;
private final HttpPartSchema schema;
// Writable properties.
private BeanPropertyMeta currentProperty;
private ClassMeta> currentClass;
private final ParserListener listener;
private Position mark = new Position(-1);
private ParserPipe pipe;
/**
* Constructor.
*
* @param builder The builder for this object.
*/
protected ParserSession(Builder builder) {
super(builder);
ctx = builder.ctx;
javaMethod = builder.javaMethod;
outer = builder.outer;
schema = builder.schema;
listener = BeanCreator.of(ParserListener.class).type(ctx.getListener()).orElse(null);
sbStack = new Stack<>();
}
//-----------------------------------------------------------------------------------------------------------------
// Abstract methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Workhorse method.
*
*
* Subclasses are expected to implement this method or {@link Parser#doParse(ParserSession,ParserPipe,ClassMeta)}.
*
*
* The default implementation of this method simply calls {@link Parser#doParse(ParserSession,ParserPipe,ClassMeta)}.
*
* @param pipe Where to get the input from.
* @param type
* The class type of the object to create.
* If null or Object.class
, object type is based on what's being parsed.
* For example, when parsing JSON text, it may return a String , Number ,
* JsonMap , etc...
* @param The class type of the object to create.
* @return The parsed object.
* @throws IOException Thrown by underlying stream.
* @throws ParseException Malformed input encountered.
* @throws ExecutableException Exception occurred on invoked constructor/method/field.
*/
protected T doParse(ParserPipe pipe, ClassMeta type) throws IOException, ParseException, ExecutableException {
return ctx.doParse(this, pipe, type);
}
/**
* Returns true if this parser subclasses from {@link ReaderParser}.
*
* @return true if this parser subclasses from {@link ReaderParser}.
*/
public boolean isReaderParser() {
return false;
}
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Wraps the specified input object into a {@link ParserPipe} object so that it can be easily converted into
* a stream or reader.
*
* @param input
* The input.
*
For character-based parsers, this can be any of the following types:
*
* null
* - {@link Reader}
*
- {@link CharSequence}
*
- {@link InputStream} containing UTF-8 encoded text (or whatever the encoding specified by
* {@link ReaderParser.Builder#streamCharset(Charset)}).
*
byte []
containing UTF-8 encoded text (or whatever the encoding specified by
* {@link ReaderParser.Builder#streamCharset(Charset)}).
* - {@link File} containing system encoded text (or whatever the encoding specified by
* {@link ReaderParser.Builder#fileCharset(Charset)}).
*
*
For byte-based parsers, this can be any of the following types:
*
* null
* - {@link InputStream}
*
byte []
* - {@link File}
*
- {@link CharSequence} containing encoded bytes according to the {@link InputStreamParser.Builder#binaryFormat(BinaryFormat)} setting.
*
* @return
* A new {@link ParserPipe} wrapper around the specified input object.
*/
protected ParserPipe createPipe(Object input) {
return null;
}
/**
* Returns information used to determine at what location in the parse a failure occurred.
*
* @return A map, typically containing something like {line:123,column:456,currentProperty:"foobar"}
*/
public final JsonMap getLastLocation() {
JsonMap m = new JsonMap();
if (currentClass != null)
m.put("currentClass", currentClass.toString(true));
if (currentProperty != null)
m.put("currentProperty", currentProperty);
return m;
}
/**
* Returns the Java method that invoked this parser.
*
*
* When using the REST API, this is the Java method invoked by the REST call.
* Can be used to access annotations defined on the method or class.
*
* @return The Java method that invoked this parser.
*/
protected final Method getJavaMethod() {
return javaMethod;
}
/**
* Returns the outer object used for instantiating top-level non-static member classes.
*
*
* When using the REST API, this is the servlet object.
*
* @return The outer object.
*/
protected final Object getOuter() {
return outer;
}
/**
* Sets the current bean property being parsed for proper error messages.
*
* @param currentProperty The current property being parsed.
*/
protected final void setCurrentProperty(BeanPropertyMeta currentProperty) {
this.currentProperty = currentProperty;
}
/**
* Sets the current class being parsed for proper error messages.
*
* @param currentClass The current class being parsed.
*/
protected final void setCurrentClass(ClassMeta> currentClass) {
this.currentClass = currentClass;
}
/**
* Trims the specified object if it's a String and {@link #isTrimStrings()} returns true .
*
* @param The object type.
* @param o The object to trim.
* @return The trimmed string if it's a string.
*/
@SuppressWarnings("unchecked")
protected final K trim(K o) {
if (isTrimStrings() && o instanceof String)
return (K)o.toString().trim();
return o;
}
/**
* Trims the specified string if {@link ParserSession#isTrimStrings()} returns true .
*
* @param s The input string to trim.
* @return The trimmed string, or null if the input was null .
*/
protected final String trim(String s) {
if (isTrimStrings() && s != null)
return s.trim();
return s;
}
/**
* Converts the specified JsonMap into a bean identified by the "_type" property in the map.
*
* @param m The map to convert to a bean.
* @param pMeta The current bean property being parsed.
* @param eType The current expected type being parsed.
* @return
* The converted bean, or the same map if the "_type" entry wasn't found or didn't resolve to a bean.
*/
protected final Object cast(JsonMap m, BeanPropertyMeta pMeta, ClassMeta> eType) {
String btpn = getBeanTypePropertyName(eType);
Object o = m.get(btpn);
if (o == null)
return m;
String typeName = o.toString();
ClassMeta> cm = getClassMeta(typeName, pMeta, eType);
if (cm != null) {
BeanMap> bm = m.getBeanSession().newBeanMap(cm.getInnerClass());
// Iterate through all the entries in the map and set the individual field values.
m.forEach((k,v) -> {
if (! k.equals(btpn)) {
// Attempt to recursively cast child maps.
if (v instanceof JsonMap)
v = cast((JsonMap)v, pMeta, eType);
bm.put(k, v);
}
});
return bm.getBean();
}
return m;
}
/**
* Give the specified dictionary name, resolve it to a class.
*
* @param typeName The dictionary name to resolve.
* @param pMeta The bean property we're currently parsing.
* @param eType The expected type we're currently parsing.
* @return The resolved class, or null if the type name could not be resolved.
*/
protected final ClassMeta> getClassMeta(String typeName, BeanPropertyMeta pMeta, ClassMeta> eType) {
BeanRegistry br = null;
// Resolve via @Beanp(dictionary={})
if (pMeta != null) {
br = pMeta.getBeanRegistry();
if (br != null && br.hasName(typeName))
return br.getClassMeta(typeName);
}
// Resolve via @Bean(dictionary={}) on the expected type where the
// expected type is an interface with subclasses.
if (eType != null) {
br = eType.getBeanRegistry();
if (br != null && br.hasName(typeName))
return br.getClassMeta(typeName);
}
// Last resort, resolve using the session registry.
return getBeanRegistry().getClassMeta(typeName);
}
/**
* Specialized warning when an exception is thrown while executing a bean setter.
*
* @param p The bean map entry representing the bean property.
* @param t The throwable that the bean setter threw.
*/
protected final void onBeanSetterException(BeanPropertyMeta p, Throwable t) {
if (listener != null)
listener.onBeanSetterException(this, t, p);
String prefix = "";
addWarning("{0}Could not call setValue() on property ''{1}'' of class ''{2}'', exception = {3}", prefix,
p.getName(), p.getBeanMeta().getClassMeta(), t.getLocalizedMessage());
}
/**
* Method that gets called when an unknown bean property name is encountered.
*
* @param propertyName The unknown bean property name.
* @param beanMap The bean that doesn't have the expected property.
* @param value The parsed value.
* @throws ParseException
* Automatically thrown if {@link org.apache.juneau.BeanContext.Builder#ignoreUnknownBeanProperties()} setting on this parser is
* false
* @param The class type of the bean map that doesn't have the expected property.
*/
protected final void onUnknownProperty(String propertyName, BeanMap beanMap, Object value) throws ParseException {
if (propertyName.equals(getBeanTypePropertyName(beanMap.getClassMeta())))
return;
if (! isIgnoreUnknownBeanProperties())
if (value != null || ! isIgnoreUnknownNullBeanProperties())
throw new ParseException(this,
"Unknown property ''{0}'' encountered while trying to parse into class ''{1}''", propertyName,
beanMap.getClassMeta());
if (listener != null)
listener.onUnknownBeanProperty(this, propertyName, beanMap.getClassMeta().getInnerClass(), beanMap.getBean());
}
/**
* Parses input into the specified object type.
*
*
* The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps).
*
*
Examples:
*
* ReaderParser parser = JsonParser.DEFAULT ;
*
* // Parse into a linked-list of strings.
* List list1 = parser .parse(json , LinkedList.class , String.class );
*
* // Parse into a linked-list of beans.
* List list2 = parser .parse(json , LinkedList.class , MyBean.class );
*
* // Parse into a linked-list of linked-lists of strings.
* List list3 = parser .parse(json , LinkedList.class , LinkedList.class , String.class );
*
* // Parse into a map of string keys/values.
* Map map1 = parser .parse(json , TreeMap.class , String.class , String.class );
*
* // Parse into a map containing string keys and values of lists containing beans.
* Map map2 = parser .parse(json , TreeMap.class , String.class , List.class , MyBean.class );
*
*
*
* Collection classes are assumed to be followed by zero or one objects indicating the element type.
*
*
* Map classes are assumed to be followed by zero or two meta objects indicating the key and value types.
*
*
* The array can be arbitrarily long to indicate arbitrarily complex data structures.
*
*
Notes:
* -
* Use the {@link #parse(Object, Class)} method instead if you don't need a parameterized map/collection.
*
*
* @param The class type of the object to create.
* @param input
* The input.
*
Character-based parsers can handle the following input class types:
*
* null
* - {@link Reader}
*
- {@link CharSequence}
*
- {@link InputStream} containing UTF-8 encoded text (or charset defined by
* {@link ReaderParser.Builder#streamCharset(Charset)} property value).
*
byte []
containing UTF-8 encoded text (or charset defined by
* {@link ReaderParser.Builder#streamCharset(Charset)} property value).
* - {@link File} containing system encoded text (or charset defined by
* {@link ReaderParser.Builder#fileCharset(Charset)} property value).
*
*
Stream-based parsers can handle the following input class types:
*
* null
* - {@link InputStream}
*
byte []
* - {@link File}
*
* @param type
* The object type to create.
*
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
* @param args
* The type arguments of the class if it's a collection or map.
*
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
*
Ignored if the main type is not a map or collection.
* @return The parsed object.
* @throws ParseException Malformed input encountered.
* @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections.
* @throws IOException Thrown by the underlying stream.
*/
@SuppressWarnings("unchecked")
public final T parse(Object input, Type type, Type...args) throws ParseException, IOException {
try (ParserPipe pipe = createPipe(input)) {
return (T)parseInner(pipe, getClassMeta(type, args));
}
}
/**
* Same as {@link #parse(Object,Type,Type...)} but parses from a string and doesn't throw an {@link IOException}.
*
* @param The class type of the object to create.
* @param input
* The input.
*
Character-based parsers can handle the following input class types:
*
* null
* - {@link Reader}
*
- {@link CharSequence}
*
- {@link InputStream} containing UTF-8 encoded text (or charset defined by
* {@link ReaderParser.Builder#streamCharset(Charset)} property value).
*
byte []
containing UTF-8 encoded text (or charset defined by
* {@link ReaderParser.Builder#streamCharset(Charset)} property value).
* - {@link File} containing system encoded text (or charset defined by
* {@link ReaderParser.Builder#fileCharset(Charset)} property value).
*
*
Stream-based parsers can handle the following input class types:
*
* null
* - {@link InputStream}
*
byte []
* - {@link File}
*
* @param type
* The object type to create.
*
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
* @param args
* The type arguments of the class if it's a collection or map.
*
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
*
Ignored if the main type is not a map or collection.
* @return The parsed object.
* @throws ParseException Malformed input encountered.
* @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections.
*/
@SuppressWarnings("unchecked")
public final T parse(String input, Type type, Type...args) throws ParseException {
try (ParserPipe pipe = createPipe(input)) {
return (T)parseInner(pipe, getClassMeta(type, args));
} catch (IOException e) {
throw new ParseException(e); // Shouldn't happen.
}
}
/**
* Same as {@link #parse(Object, Type, Type...)} except optimized for a non-parameterized class.
*
*
* This is the preferred parse method for simple types since you don't need to cast the results.
*
*
Examples:
*
* ReaderParser parser = JsonParser.DEFAULT ;
*
* // Parse into a string.
* String string = parser .parse(json , String.class );
*
* // Parse into a bean.
* MyBean bean = parser .parse(json , MyBean.class );
*
* // Parse into a bean array.
* MyBean[] beanArray = parser .parse(json , MyBean[].class );
*
* // Parse into a linked-list of objects.
* List list = parser .parse(json , LinkedList.class );
*
* // Parse into a map of object keys/values.
* Map map = parser .parse(json , TreeMap.class );
*
*
* @param The class type of the object being created.
* @param input
* The input.
* See {@link #parse(Object, Type, Type...)} for details.
* @param type The object type to create.
* @return The parsed object.
* @throws ParseException Malformed input encountered.
* @throws IOException Thrown by the underlying stream.
*/
public final T parse(Object input, Class type) throws ParseException, IOException {
try (ParserPipe pipe = createPipe(input)) {
return parseInner(pipe, getClassMeta(type));
}
}
/**
* Same as {@link #parse(Object, Class)} but parses from a string and doesn't throw an {@link IOException}.
*
*
* This is the preferred parse method for simple types since you don't need to cast the results.
*
*
Examples:
*
* ReaderParser parser = JsonParser.DEFAULT ;
*
* // Parse into a string.
* String string = parser .parse(json , String.class );
*
* // Parse into a bean.
* MyBean bean = parser .parse(json , MyBean.class );
*
* // Parse into a bean array.
* MyBean[] beanArray = parser .parse(json , MyBean[].class );
*
* // Parse into a linked-list of objects.
* List list = parser .parse(json , LinkedList.class );
*
* // Parse into a map of object keys/values.
* Map map = parser .parse(json , TreeMap.class );
*
*
* @param The class type of the object being created.
* @param input
* The input.
* See {@link #parse(Object, Type, Type...)} for details.
* @param type The object type to create.
* @return The parsed object.
* @throws ParseException Malformed input encountered.
*/
public final T parse(String input, Class type) throws ParseException {
try (ParserPipe pipe = createPipe(input)) {
return parseInner(pipe, getClassMeta(type));
} catch (IOException e) {
throw new ParseException(e); // Shouldn't happen.
}
}
/**
* Same as {@link #parse(Object, Type, Type...)} except the type has already been converted into a {@link ClassMeta}
* object.
*
*
* This is mostly an internal method used by the framework.
*
* @param The class type of the object being created.
* @param input
* The input.
* See {@link #parse(Object, Type, Type...)} for details.
* @param type The object type to create.
* @return The parsed object.
* @throws ParseException Malformed input encountered.
* @throws IOException Thrown by the underlying stream.
*/
public final T parse(Object input, ClassMeta type) throws ParseException, IOException {
try (ParserPipe pipe = createPipe(input)) {
return parseInner(pipe, type);
}
}
/**
* Same as {@link #parse(Object, ClassMeta)} except parses from a string and doesn't throw an {@link IOException}.
*
*
* This is mostly an internal method used by the framework.
*
* @param The class type of the object being created.
* @param input
* The input.
* See {@link #parse(Object, Type, Type...)} for details.
* @param type The object type to create.
* @return The parsed object.
* @throws ParseException Malformed input encountered.
*/
public final T parse(String input, ClassMeta type) throws ParseException {
try (ParserPipe pipe = createPipe(input)) {
return parseInner(pipe, type);
} catch (IOException e) {
throw new ParseException(e); // Shouldn't happen.
}
}
/**
* Entry point for all parsing calls.
*
*
* Calls the {@link #doParse(ParserPipe, ClassMeta)} implementation class and catches/re-wraps any exceptions
* thrown.
*
* @param pipe The parser input.
* @param type The class type of the object to create.
* @param The class type of the object to create.
* @return The parsed object.
* @throws ParseException Malformed input encountered.
* @throws IOException Thrown by the underlying stream.
*/
private T parseInner(ParserPipe pipe, ClassMeta type) throws ParseException, IOException {
if (type.isVoid())
return null;
try {
return doParse(pipe, type);
} catch (ParseException | IOException e) {
throw e;
} catch (StackOverflowError e) {
throw new ParseException(this, "Depth too deep. Stack overflow occurred.");
} catch (Exception e) {
throw new ParseException(this, e, "Exception occurred. exception={0}, message={1}.",
e.getClass().getSimpleName(), e.getLocalizedMessage());
} finally {
checkForWarnings();
}
}
/**
* Parses the contents of the specified reader and loads the results into the specified map.
*
*
* Reader must contain something that serializes to a map (such as text containing a JSON object).
*
*
* Used in the following locations:
*
* -
* The various character-based constructors in {@link JsonMap} (e.g.
* {@link JsonMap#JsonMap(CharSequence,Parser)}).
*
*
* @param The key class type.
* @param The value class type.
* @param input The input. See {@link #parse(Object, ClassMeta)} for supported input types.
* @param m The map being loaded.
* @param keyType The class type of the keys, or null to default to String.class
.
* @param valueType The class type of the values, or null to default to whatever is being parsed.
* @return The same map that was passed in to allow this method to be chained.
* @throws ParseException Malformed input encountered.
* @throws UnsupportedOperationException If not implemented.
*/
public final Map parseIntoMap(Object input, Map m, Type keyType, Type valueType) throws ParseException {
try (ParserPipe pipe = createPipe(input)) {
return doParseIntoMap(pipe, m, keyType, valueType);
} catch (ParseException e) {
throw e;
} catch (Exception e) {
throw new ParseException(this, e);
} finally {
checkForWarnings();
}
}
/**
* Implementation method.
*
*
* Default implementation throws an {@link UnsupportedOperationException}.
*
* @param The key type.
* @param The value type.
* @param pipe The parser input.
* @param m The map being loaded.
* @param keyType The class type of the keys, or null to default to String.class
.
* @param valueType The class type of the values, or null to default to whatever is being parsed.
* @return The same map that was passed in to allow this method to be chained.
* @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed.
*/
protected Map doParseIntoMap(ParserPipe pipe, Map m, Type keyType, Type valueType) throws Exception {
throw new UnsupportedOperationException("Parser '"+className(getClass())+"' does not support this method.");
}
/**
* Parses the contents of the specified reader and loads the results into the specified collection.
*
*
* Used in the following locations:
*
* -
* The various character-based constructors in {@link JsonList} (e.g.
* {@link JsonList#JsonList(CharSequence,Parser)}.
*
*
* @param The element class type.
* @param input The input. See {@link #parse(Object, ClassMeta)} for supported input types.
* @param c The collection being loaded.
* @param elementType The class type of the elements, or null to default to whatever is being parsed.
* @return The same collection that was passed in to allow this method to be chained.
* @throws ParseException Malformed input encountered.
* @throws UnsupportedOperationException If not implemented.
*/
public final Collection parseIntoCollection(Object input, Collection c, Type elementType) throws ParseException {
try (ParserPipe pipe = createPipe(input)) {
return doParseIntoCollection(pipe, c, elementType);
} catch (ParseException e) {
throw e;
} catch (StackOverflowError e) {
throw new ParseException(this, "Depth too deep. Stack overflow occurred.");
} catch (IOException e) {
throw new ParseException(this, e, "I/O exception occurred. exception={0}, message={1}.",
e.getClass().getSimpleName(), e.getLocalizedMessage());
} catch (Exception e) {
throw new ParseException(this, e, "Exception occurred. exception={0}, message={1}.",
e.getClass().getSimpleName(), e.getLocalizedMessage());
} finally {
checkForWarnings();
}
}
/**
* Implementation method.
*
*
* Default implementation throws an {@link UnsupportedOperationException}.
*
* @param The element type.
* @param pipe The parser input.
* @param c The collection being loaded.
* @param elementType The class type of the elements, or null to default to whatever is being parsed.
* @return The same collection that was passed in to allow this method to be chained.
* @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed.
*/
protected Collection doParseIntoCollection(ParserPipe pipe, Collection c, Type elementType) throws Exception {
throw new UnsupportedOperationException("Parser '"+className(getClass())+"' does not support this method.");
}
/**
* Parses the specified array input with each entry in the object defined by the {@code argTypes}
* argument.
*
*
* Used for converting arrays (e.g. "[arg1,arg2,...]" ) into an {@code Object[]} that can be passed
* to the {@code Method.invoke(target, args)} method.
*
*
* Used in the following locations:
*
* -
* Used to parse argument strings in the {@link ObjectIntrospector#invokeMethod(Method, Reader)} method.
*
*
* @param input The input. Subclasses can support different input types.
* @param argTypes Specifies the type of objects to create for each entry in the array.
* @return An array of parsed objects.
* @throws ParseException Malformed input encountered.
*/
public final Object[] parseArgs(Object input, Type[] argTypes) throws ParseException {
try (ParserPipe pipe = createPipe(input)) {
return doParse(pipe, getArgsClassMeta(argTypes));
} catch (ParseException e) {
throw e;
} catch (StackOverflowError e) {
throw new ParseException(this, "Depth too deep. Stack overflow occurred.");
} catch (IOException e) {
throw new ParseException(this, e, "I/O exception occurred. exception={0}, message={1}.",
e.getClass().getSimpleName(), e.getLocalizedMessage());
} catch (Exception e) {
throw new ParseException(this, e, "Exception occurred. exception={0}, message={1}.",
e.getClass().getSimpleName(), e.getLocalizedMessage());
} finally {
checkForWarnings();
}
}
/**
* Converts the specified string to the specified type.
*
* @param outer
* The outer object if we're converting to an inner object that needs to be created within the context
* of an outer object.
* @param s The string to convert.
* @param type The class type to convert the string to.
* @return The string converted as an object of the specified type.
* @param The class type to convert the string to.
* @throws ParseException Malformed input encountered.
* @throws ExecutableException Exception occurred on invoked constructor/method/field.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected final T convertAttrToType(Object outer, String s, ClassMeta type) throws ParseException {
if (s == null)
return null;
if (type == null)
type = (ClassMeta)object();
ObjectSwap swap = type.getSwap(this);
ClassMeta> sType = swap == null ? type : swap.getSwapClassMeta(this);
Object o = s;
if (sType.isChar())
o = parseCharacter(s);
else if (sType.isNumber())
o = parseNumber(s, (Class extends Number>)sType.getInnerClass());
else if (sType.isBoolean())
o = Boolean.parseBoolean(s);
else if (! (sType.isCharSequence() || sType.isObject())) {
if (sType.canCreateNewInstanceFromString(outer))
o = sType.newInstanceFromString(outer, s);
else
throw new ParseException(this, "Invalid conversion from string to class ''{0}''", type);
}
if (swap != null)
o = unswap(swap, o, type);
return (T)o;
}
/**
* Convenience method for calling the {@link ParentProperty @ParentProperty} method on the specified object if it
* exists.
*
* @param cm The class type of the object.
* @param o The object.
* @param parent The parent to set.
* @throws ExecutableException Exception occurred on invoked constructor/method/field.
*/
protected static final void setParent(ClassMeta> cm, Object o, Object parent) throws ExecutableException {
Setter m = cm.getParentProperty();
if (m != null)
m.set(o, parent);
}
/**
* Convenience method for calling the {@link NameProperty @NameProperty} method on the specified object if it exists.
*
* @param cm The class type of the object.
* @param o The object.
* @param name The name to set.
* @throws ExecutableException Exception occurred on invoked constructor/method/field.
*/
protected static final void setName(ClassMeta> cm, Object o, Object name) throws ExecutableException {
if (cm != null) {
Setter m = cm.getNameProperty();
if (m != null)
m.set(o, name);
}
}
/**
* Returns the listener associated with this session.
*
* @param The listener type.
* @param c The listener class to cast to.
* @return The listener associated with this session, or null if there is no listener.
*/
@SuppressWarnings("unchecked")
public T getListener(Class c) {
return (T)listener;
}
/**
* The {@link #createPipe(Object)} method should call this method to set the pipe for debugging purposes.
*
* @param pipe The pipe created for this session.
* @return The same pipe.
*/
protected ParserPipe setPipe(ParserPipe pipe) {
this.pipe = pipe;
return pipe;
}
/**
* Returns the current position into the reader or input stream.
*
* @return
* The current position into the reader or input stream.
*
Never null .
*/
public Position getPosition() {
if (mark.line != -1 || mark.column != -1 || mark.position != -1)
return mark;
if (pipe == null)
return Position.UNKNOWN;
return pipe.getPosition();
}
/**
* Marks the current position.
*/
protected void mark() {
if (pipe != null) {
Position p = pipe.getPosition();
mark.line = p.line;
mark.column = p.column;
mark.position = p.position;
}
}
/**
* Unmarks the current position.
*/
protected void unmark() {
mark.line = -1;
mark.column = -1;
mark.position = -1;
}
/**
* Returns the input as a string.
*
*
* This always returns a value for input of type {@link CharSequence}.
*
For other input types, use {@link org.apache.juneau.Context.Builder#debug()} setting to enable caching to a string
* before parsing so that this method returns the input.
*
* @return The input as a string, or null if no pipe has been created or we're reading from an uncached reader or input stream source.
*/
public String getInputAsString() {
return pipe == null ? null : pipe.getInputAsString();
}
/**
* Invokes the specified swap on the specified object.
*
* @param swap The swap to invoke.
* @param o The input object.
* @param eType The expected type.
* @return The swapped object.
* @throws ParseException If swap method threw an exception.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected Object unswap(ObjectSwap swap, Object o, ClassMeta> eType) throws ParseException {
try {
return swap.unswap(this, o, eType);
} catch (Exception e) {
throw new ParseException(e);
}
}
/**
* Creates a reusable {@link StringBuilder} object from an internal pool.
*
*
* String builders are returned to the pool by calling {@link #returnStringBuilder(StringBuilder)}.
*
* @return A new or previously returned string builder.
*/
protected final StringBuilder getStringBuilder() {
if (sbStack.isEmpty())
return new StringBuilder();
return sbStack.pop();
}
/**
* Returns a {@link StringBuilder} object back into the internal reuse pool.
*
* @param sb The string builder to return to the pool. No-op if null .
*/
protected final void returnStringBuilder(StringBuilder sb) {
if (sb == null)
return;
sb.setLength(0);
sbStack.push(sb);
}
//-----------------------------------------------------------------------------------------------------------------
// Properties
//-----------------------------------------------------------------------------------------------------------------
/**
* Auto-close streams.
*
* @see Parser.Builder#autoCloseStreams()
* @return
* true if InputStreams and Readers passed into parsers will be closed
* after parsing is complete.
*/
protected final boolean isAutoCloseStreams() {
return ctx.isAutoCloseStreams();
}
/**
* Debug output lines.
*
* @see Parser.Builder#debugOutputLines(int)
* @return
* The number of lines of input before and after the error location to be printed as part of the exception message.
*/
protected final int getDebugOutputLines() {
return ctx.getDebugOutputLines();
}
/**
* Returns the listener associated with this session.
*
* @return The listener associated with this session, or null if there is no listener.
*/
public ParserListener getListener() {
return listener;
}
/**
* Strict mode.
*
* @see Parser.Builder#strict()
* @return
* true if strict mode for the parser is enabled.
*/
protected final boolean isStrict() {
return ctx.isStrict();
}
/**
* Trim parsed strings.
*
* @see Parser.Builder#trimStrings()
* @return
* true if string values will be trimmed of whitespace using {@link String#trim()} before being added to
* the POJO.
*/
protected final boolean isTrimStrings() {
return ctx.isTrimStrings();
}
/**
* Unbuffered.
*
* @see Parser.Builder#unbuffered()
* @return
* true if parsers don't use internal buffering during parsing.
*/
protected final boolean isUnbuffered() {
return ctx.isUnbuffered();
}
/**
* HTTP part schema of object being parsed.
*
* @return HTTP part schema of object being parsed, or null if not specified.
*/
public final HttpPartSchema getSchema() {
return schema;
}
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Parser listener.
*
* @see Parser.Builder#listener(Class)
* @return
* Class used to listen for errors and warnings that occur during parsing.
*/
protected final Class extends ParserListener> getListenerClass() {
return ctx.getListener();
}
@Override /* ContextSession */
protected JsonMap properties() {
return filteredMap("javaMethod", javaMethod, "listener", listener, "outer", outer);
}
}