// *************************************************************************************************************************** | |
// * 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.internal.StringUtils.*; | |
import static org.apache.juneau.parser.Parser.*; | |
import java.io.*; | |
import java.lang.reflect.*; | |
import java.util.*; | |
import org.apache.juneau.*; | |
import org.apache.juneau.annotation.*; | |
import org.apache.juneau.transform.*; | |
import org.apache.juneau.utils.*; | |
/** | |
* Session object that lives for the duration of a single use of {@link Parser}. | |
* | |
* <p> | |
* This class is NOT thread safe. | |
* It is typically discarded after one-time use although it can be reused against multiple inputs. | |
*/ | |
public abstract class ParserSession extends BeanSession { | |
private final Parser ctx; | |
private final Method javaMethod; | |
private final Object outer; | |
// Writable properties. | |
private BeanPropertyMeta currentProperty; | |
private ClassMeta<?> currentClass; | |
private final ParserListener listener; | |
private Position mark = new Position(-1); | |
private ParserPipe pipe; | |
/** | |
* 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 ParserSession(Parser ctx, ParserSessionArgs args) { | |
super(ctx, args == null ? ParserSessionArgs.DEFAULT : args); | |
args = args == null ? ParserSessionArgs.DEFAULT : args; | |
this.ctx = ctx; | |
javaMethod = args.javaMethod; | |
outer = args.outer; | |
listener = getInstanceProperty(PARSER_listener, ParserListener.class, ctx.getListener()); | |
} | |
/** | |
* Default constructor. | |
* | |
* @param args | |
* Runtime session arguments. | |
*/ | |
protected ParserSession(ParserSessionArgs args) { | |
this(Parser.DEFAULT, args); | |
} | |
//----------------------------------------------------------------------------------------------------------------- | |
// Abstract methods | |
//----------------------------------------------------------------------------------------------------------------- | |
/** | |
* Workhorse method. | |
* | |
* <p> | |
* Subclasses are expected to implement this method. | |
* | |
* @param pipe Where to get the input from. | |
* @param type | |
* The class type of the object to create. | |
* If <jk>null</jk> or <code>Object.<jk>class</jk></code>, object type is based on what's being parsed. | |
* For example, when parsing JSON text, it may return a <c>String</c>, <c>Number</c>, | |
* <c>ObjectMap</c>, etc... | |
* @param <T> 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 abstract <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws IOException, ParseException, ExecutableException; | |
/** | |
* Returns <jk>true</jk> if this parser subclasses from {@link ReaderParser}. | |
* | |
* @return <jk>true</jk> if this parser subclasses from {@link ReaderParser}. | |
*/ | |
public abstract boolean isReaderParser(); | |
//----------------------------------------------------------------------------------------------------------------- | |
// 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. | |
* <br>For character-based parsers, this can be any of the following types: | |
* <ul> | |
* <li><jk>null</jk> | |
* <li>{@link Reader} | |
* <li>{@link CharSequence} | |
* <li>{@link InputStream} containing UTF-8 encoded text (or whatever the encoding specified by | |
* {@link ReaderParser#RPARSER_streamCharset}). | |
* <li><code><jk>byte</jk>[]</code> containing UTF-8 encoded text (or whatever the encoding specified by | |
* {@link ReaderParser#RPARSER_streamCharset}). | |
* <li>{@link File} containing system encoded text (or whatever the encoding specified by | |
* {@link ReaderParser#RPARSER_fileCharset}). | |
* </ul> | |
* <br>For byte-based parsers, this can be any of the following types: | |
* <ul> | |
* <li><jk>null</jk> | |
* <li>{@link InputStream} | |
* <li><code><jk>byte</jk>[]</code> | |
* <li>{@link File} | |
* <li>{@link CharSequence} containing encoded bytes according to the {@link InputStreamParser#ISPARSER_binaryFormat} setting. | |
* </ul> | |
* @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 <c>{line:123,column:456,currentProperty:"foobar"}</c> | |
*/ | |
public final ObjectMap getLastLocation() { | |
ObjectMap m = new ObjectMap(); | |
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. | |
* | |
* <p> | |
* 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. | |
* | |
* <p> | |
* 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 <c>String</c> and {@link #isTrimStrings()} returns <jk>true</jk>. | |
* | |
* @param o The object to trim. | |
* @return The trimmed string if it's a string. | |
*/ | |
@SuppressWarnings("unchecked") | |
protected final <K> 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 <jk>true</jk>. | |
* | |
* @param s The input string to trim. | |
* @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>. | |
*/ | |
protected final String trim(String s) { | |
if (isTrimStrings() && s != null) | |
return s.trim(); | |
return s; | |
} | |
/** | |
* Converts the specified <c>ObjectMap</c> into a bean identified by the <js>"_type"</js> 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 <js>"_type"</js> entry wasn't found or didn't resolve to a bean. | |
*/ | |
protected final Object cast(ObjectMap 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. | |
for (Map.Entry<String,Object> e : m.entrySet()) { | |
String k = e.getKey(); | |
Object v = e.getValue(); | |
if (! k.equals(btpn)) { | |
// Attempt to recursively cast child maps. | |
if (v instanceof ObjectMap) | |
v = cast((ObjectMap)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 <jk>null</jk> 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); | |
} | |
/** | |
* 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. | |
* @throws ParseException | |
* Automatically thrown if {@link BeanContext#BEAN_ignoreUnknownBeanProperties} setting on this parser is | |
* <jk>false</jk> | |
* @param <T> The class type of the bean map that doesn't have the expected property. | |
*/ | |
protected final <T> void onUnknownProperty(String propertyName, BeanMap<T> beanMap) throws ParseException { | |
if (propertyName.equals(getBeanTypePropertyName(beanMap.getClassMeta()))) | |
return; | |
if (! isIgnoreUnknownBeanProperties()) | |
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. | |
* | |
* <p> | |
* The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). | |
* | |
* <h5 class='section'>Examples:</h5> | |
* <p class='bcode w800'> | |
* ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; | |
* | |
* <jc>// Parse into a linked-list of strings.</jc> | |
* List l = p.parse(json, LinkedList.<jk>class</jk>, String.<jk>class</jk>); | |
* | |
* <jc>// Parse into a linked-list of beans.</jc> | |
* List l = p.parse(json, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); | |
* | |
* <jc>// Parse into a linked-list of linked-lists of strings.</jc> | |
* List l = p.parse(json, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); | |
* | |
* <jc>// Parse into a map of string keys/values.</jc> | |
* Map m = p.parse(json, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); | |
* | |
* <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> | |
* Map m = p.parse(json, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); | |
* </p> | |
* | |
* <p> | |
* <c>Collection</c> classes are assumed to be followed by zero or one objects indicating the element type. | |
* | |
* <p> | |
* <c>Map</c> classes are assumed to be followed by zero or two meta objects indicating the key and value types. | |
* | |
* <p> | |
* The array can be arbitrarily long to indicate arbitrarily complex data structures. | |
* | |
* <ul class='notes'> | |
* <li> | |
* Use the {@link #parse(Object, Class)} method instead if you don't need a parameterized map/collection. | |
* </ul> | |
* | |
* @param <T> The class type of the object to create. | |
* @param input | |
* The input. | |
* <br>Character-based parsers can handle the following input class types: | |
* <ul> | |
* <li><jk>null</jk> | |
* <li>{@link Reader} | |
* <li>{@link CharSequence} | |
* <li>{@link InputStream} containing UTF-8 encoded text (or charset defined by | |
* {@link ReaderParser#RPARSER_streamCharset} property value). | |
* <li><code><jk>byte</jk>[]</code> containing UTF-8 encoded text (or charset defined by | |
* {@link ReaderParser#RPARSER_streamCharset} property value). | |
* <li>{@link File} containing system encoded text (or charset defined by | |
* {@link ReaderParser#RPARSER_fileCharset} property value). | |
* </ul> | |
* <br>Stream-based parsers can handle the following input class types: | |
* <ul> | |
* <li><jk>null</jk> | |
* <li>{@link InputStream} | |
* <li><code><jk>byte</jk>[]</code> | |
* <li>{@link File} | |
* </ul> | |
* @param type | |
* The object type to create. | |
* <br>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. | |
* <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} | |
* <br>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> 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 <T> The class type of the object to create. | |
* @param input | |
* The input. | |
* <br>Character-based parsers can handle the following input class types: | |
* <ul> | |
* <li><jk>null</jk> | |
* <li>{@link Reader} | |
* <li>{@link CharSequence} | |
* <li>{@link InputStream} containing UTF-8 encoded text (or charset defined by | |
* {@link ReaderParser#RPARSER_streamCharset} property value). | |
* <li><code><jk>byte</jk>[]</code> containing UTF-8 encoded text (or charset defined by | |
* {@link ReaderParser#RPARSER_streamCharset} property value). | |
* <li>{@link File} containing system encoded text (or charset defined by | |
* {@link ReaderParser#RPARSER_fileCharset} property value). | |
* </ul> | |
* <br>Stream-based parsers can handle the following input class types: | |
* <ul> | |
* <li><jk>null</jk> | |
* <li>{@link InputStream} | |
* <li><code><jk>byte</jk>[]</code> | |
* <li>{@link File} | |
* </ul> | |
* @param type | |
* The object type to create. | |
* <br>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. | |
* <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} | |
* <br>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> 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. | |
* | |
* <p> | |
* This is the preferred parse method for simple types since you don't need to cast the results. | |
* | |
* <h5 class='section'>Examples:</h5> | |
* <p class='bcode w800'> | |
* ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; | |
* | |
* <jc>// Parse into a string.</jc> | |
* String s = p.parse(json, String.<jk>class</jk>); | |
* | |
* <jc>// Parse into a bean.</jc> | |
* MyBean b = p.parse(json, MyBean.<jk>class</jk>); | |
* | |
* <jc>// Parse into a bean array.</jc> | |
* MyBean[] ba = p.parse(json, MyBean[].<jk>class</jk>); | |
* | |
* <jc>// Parse into a linked-list of objects.</jc> | |
* List l = p.parse(json, LinkedList.<jk>class</jk>); | |
* | |
* <jc>// Parse into a map of object keys/values.</jc> | |
* Map m = p.parse(json, TreeMap.<jk>class</jk>); | |
* </p> | |
* | |
* @param <T> 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> T parse(Object input, Class<T> 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}. | |
* | |
* <p> | |
* This is the preferred parse method for simple types since you don't need to cast the results. | |
* | |
* <h5 class='section'>Examples:</h5> | |
* <p class='bcode w800'> | |
* ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; | |
* | |
* <jc>// Parse into a string.</jc> | |
* String s = p.parse(json, String.<jk>class</jk>); | |
* | |
* <jc>// Parse into a bean.</jc> | |
* MyBean b = p.parse(json, MyBean.<jk>class</jk>); | |
* | |
* <jc>// Parse into a bean array.</jc> | |
* MyBean[] ba = p.parse(json, MyBean[].<jk>class</jk>); | |
* | |
* <jc>// Parse into a linked-list of objects.</jc> | |
* List l = p.parse(json, LinkedList.<jk>class</jk>); | |
* | |
* <jc>// Parse into a map of object keys/values.</jc> | |
* Map m = p.parse(json, TreeMap.<jk>class</jk>); | |
* </p> | |
* | |
* @param <T> 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> T parse(String input, Class<T> 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. | |
* | |
* <p> | |
* This is mostly an internal method used by the framework. | |
* | |
* @param <T> 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> T parse(Object input, ClassMeta<T> 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}. | |
* | |
* <p> | |
* This is mostly an internal method used by the framework. | |
* | |
* @param <T> 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> T parse(String input, ClassMeta<T> 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. | |
* | |
* <p> | |
* 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 <T> 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> T parseInner(ParserPipe pipe, ClassMeta<T> 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. | |
* | |
* <p> | |
* Reader must contain something that serializes to a map (such as text containing a JSON object). | |
* | |
* <p> | |
* Used in the following locations: | |
* <ul class='spaced-list'> | |
* <li> | |
* The various character-based constructors in {@link ObjectMap} (e.g. | |
* {@link ObjectMap#ObjectMap(CharSequence,Parser)}). | |
* </ul> | |
* | |
* @param <K> The key class type. | |
* @param <V> 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 <jk>null</jk> to default to <code>String.<jk>class</jk></code>. | |
* @param valueType The class type of the values, or <jk>null</jk> 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 <K,V> Map<K,V> parseIntoMap(Object input, Map<K,V> 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. | |
* | |
* <p> | |
* Default implementation throws an {@link UnsupportedOperationException}. | |
* | |
* @param pipe The parser input. | |
* @param m The map being loaded. | |
* @param keyType The class type of the keys, or <jk>null</jk> to default to <code>String.<jk>class</jk></code>. | |
* @param valueType The class type of the values, or <jk>null</jk> 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 <K,V> Map<K,V> doParseIntoMap(ParserPipe pipe, Map<K,V> m, Type keyType, Type valueType) throws Exception { | |
throw new UnsupportedOperationException("Parser '"+getClass().getName()+"' does not support this method."); | |
} | |
/** | |
* Parses the contents of the specified reader and loads the results into the specified collection. | |
* | |
* <p> | |
* Used in the following locations: | |
* <ul class='spaced-list'> | |
* <li> | |
* The various character-based constructors in {@link ObjectList} (e.g. | |
* {@link ObjectList#ObjectList(CharSequence,Parser)}. | |
* </ul> | |
* | |
* @param <E> 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 <jk>null</jk> 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 <E> Collection<E> parseIntoCollection(Object input, Collection<E> 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. | |
* | |
* <p> | |
* Default implementation throws an {@link UnsupportedOperationException}. | |
* | |
* @param pipe The parser input. | |
* @param c The collection being loaded. | |
* @param elementType The class type of the elements, or <jk>null</jk> 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 <E> Collection<E> doParseIntoCollection(ParserPipe pipe, Collection<E> c, Type elementType) throws Exception { | |
throw new UnsupportedOperationException("Parser '"+getClass().getName()+"' does not support this method."); | |
} | |
/** | |
* Parses the specified array input with each entry in the object defined by the {@code argTypes} | |
* argument. | |
* | |
* <p> | |
* Used for converting arrays (e.g. <js>"[arg1,arg2,...]"</js>) into an {@code Object[]} that can be passed | |
* to the {@code Method.invoke(target, args)} method. | |
* | |
* <p> | |
* Used in the following locations: | |
* <ul class='spaced-list'> | |
* <li> | |
* Used to parse argument strings in the {@link PojoIntrospector#invokeMethod(Method, Reader)} method. | |
* </ul> | |
* | |
* @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 <T> The class type to convert the string to. | |
* @throws IOException Thrown by underlying stream. | |
* @throws ParseException Malformed input encountered. | |
* @throws ExecutableException Exception occurred on invoked constructor/method/field. | |
*/ | |
@SuppressWarnings({ "unchecked", "rawtypes" }) | |
protected final <T> T convertAttrToType(Object outer, String s, ClassMeta<T> type) throws IOException, ParseException, ExecutableException { | |
if (s == null) | |
return null; | |
if (type == null) | |
type = (ClassMeta<T>)object(); | |
PojoSwap swap = type.getPojoSwap(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 c The listener class to cast to. | |
* @return The listener associated with this session, or <jk>null</jk> if there is no listener. | |
*/ | |
@SuppressWarnings("unchecked") | |
public <T extends ParserListener> T getListener(Class<T> 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. | |
* <br>Never <jk>null</jk>. | |
*/ | |
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. | |
* | |
* <p> | |
* This always returns a value for input of type {@link CharSequence}. | |
* <br>For other input types, use {@link BeanContext#BEAN_debug} setting to enable caching to a string | |
* before parsing so that this method returns the input. | |
* | |
* @return The input as a string, or <jk>null</jk> 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(PojoSwap swap, Object o, ClassMeta<?> eType) throws ParseException { | |
try { | |
return swap.unswap(this, o, eType); | |
} catch (Exception e) { | |
throw new ParseException(e); | |
} | |
} | |
//----------------------------------------------------------------------------------------------------------------- | |
// Properties | |
//----------------------------------------------------------------------------------------------------------------- | |
/** | |
* Configuration property: Auto-close streams. | |
* | |
* @see Parser#PARSER_autoCloseStreams | |
* @return | |
* <jk>true</jk> if <l>InputStreams</l> and <l>Readers</l> passed into parsers will be closed | |
* after parsing is complete. | |
*/ | |
protected final boolean isAutoCloseStreams() { | |
return ctx.isAutoCloseStreams(); | |
} | |
/** | |
* Configuration property: Debug output lines. | |
* | |
* @see Parser#PARSER_debugOutputLines | |
* @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 <jk>null</jk> if there is no listener. | |
*/ | |
public ParserListener getListener() { | |
return listener; | |
} | |
/** | |
* Configuration property: Strict mode. | |
* | |
* @see Parser#PARSER_strict | |
* @return | |
* <jk>true</jk> if strict mode for the parser is enabled. | |
*/ | |
protected final boolean isStrict() { | |
return ctx.isStrict(); | |
} | |
/** | |
* Configuration property: Trim parsed strings. | |
* | |
* @see Parser#PARSER_trimStrings | |
* @return | |
* <jk>true</jk> 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(); | |
} | |
/** | |
* Configuration property: Unbuffered. | |
* | |
* @see Parser#PARSER_unbuffered | |
* @return | |
* <jk>true</jk> if parsers don't use internal buffering during parsing. | |
*/ | |
protected final boolean isUnbuffered() { | |
return ctx.isUnbuffered(); | |
} | |
//----------------------------------------------------------------------------------------------------------------- | |
// Other methods | |
//----------------------------------------------------------------------------------------------------------------- | |
/** | |
* Configuration property: Parser listener. | |
* | |
* @see Parser#PARSER_listener | |
* @return | |
* Class used to listen for errors and warnings that occur during parsing. | |
*/ | |
protected final Class<? extends ParserListener> getListenerClass() { | |
return ctx.getListener(); | |
} | |
@Override /* Session */ | |
public ObjectMap toMap() { | |
return super.toMap() | |
.append("ParserSession", new DefaultFilteringObjectMap() | |
.append("javaMethod", javaMethod) | |
.append("listener", listener) | |
.append("outer", outer) | |
); | |
} | |
} |