org.apache.juneau.parser.Parser 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 java.io.*;
import java.lang.reflect.*;
import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.http.*;
import org.apache.juneau.json.*;
import org.apache.juneau.transform.*;
import org.apache.juneau.transforms.*;
import org.apache.juneau.utils.*;
/**
* Parent class for all Juneau parsers.
*
* Valid data conversions
*
* Parsers can parse any parsable POJO types, as specified in the POJO Categories.
*
*
* Some examples of conversions are shown below...
*
*
*
* Data type
* Class type
* JSON example
* XML example
* Class examples
*
*
* object
* Maps, Java beans
* {name:'John Smith' ,age:21}
* <object>
* <name type ='string' > John Smith</name>
* <age type ='number' > 21</age>
* </object>
* HashMap, TreeMap<String,Integer>
*
*
* array
* Collections, Java arrays
* [1,2,3]
* <array>
* <number> 1</number>
* <number> 2</number>
* <number> 3</number>
* </array>
* List<Integer>, int [], Float[], Set<Person>
*
*
* number
* Numbers
* 123
* <number> 123</number>
* Integer, Long, Float, int
*
*
* boolean
* Booleans
* true
* <boolean> true</boolean>
* Boolean
*
*
* string
* CharSequences
* 'foobar'
* <string> foobar</string>
* String, StringBuilder
*
*
*
*
* In addition, any class types with {@link PojoSwap PojoSwaps} associated with them on the registered
* {@link #getBeanContext() beanContext} can also be passed in.
*
*
* For example, if the {@link CalendarSwap} transform is used to generalize {@code Calendar} objects to {@code String}
* objects.
* When registered with this parser, you can construct {@code Calendar} objects from {@code Strings} using the
* following syntax...
*
* Calendar c = parser.parse("'Sun Mar 03 04:05:06 EST 2001'" , GregorianCalendar.class );
*
*
* If Object.class
is specified as the target type, then the parser automatically determines the
* data types and generates the following object types...
*
* JSON type Class type
* object {@link ObjectMap}
* array {@link ObjectList}
* number {@link Number}
(depending on length and format, could be {@link Integer},
* {@link Double}, {@link Float}, etc...)
* boolean {@link Boolean}
* string {@link String}
*
*
*
* Supported types
*
* Several of the methods below take {@link Type} parameters to identify the type of object to create.
* Any of the following types can be passed in to these methods...
*
* - {@link ClassMeta}
*
- {@link Class}
*
- {@link ParameterizedType}
*
- {@link GenericArrayType}
*
*
*
* However, {@code ParameterizedTypes} and {@code GenericArrayTypes} should not contain
* {@link WildcardType WildcardTypes} or {@link TypeVariable TypeVariables}.
*
*
* Passing in null or Object.class
typically signifies that it's up to the parser
* to determine what object type is being parsed parsed based on the rules above.
*/
public abstract class Parser extends CoreObject {
//-------------------------------------------------------------------------------------------------------------------
// Configurable properties
//-------------------------------------------------------------------------------------------------------------------
private static final String PREFIX = "Parser.";
/**
* Configuration property: Trim parsed strings.
*
*
* - Name:
"Parser.trimStrings"
* - Data type:
Boolean
* - Default:
false
* - Session-overridable:
true
*
*
*
* If true , string values will be trimmed of whitespace using {@link String#trim()} before being added to
* the POJO.
*/
public static final String PARSER_trimStrings = PREFIX + "trimStrings";
/**
* Configuration property: Strict mode.
*
*
* - Name:
"Parser.strict"
* - Data type:
Boolean
* - Default:
false
* - Session-overridable:
true
*
*
* If true , strict mode for the parser is enabled.
*
*
* Strict mode can mean different things for different parsers.
*
*
* Parser class Strict behavior
*
* All reader-based parsers
*
* When enabled, throws {@link ParseException ParseExceptions} on malformed charset input.
* Otherwise, malformed input is ignored.
*
*
*
* {@link JsonParser}
*
* When enabled, throws exceptions on the following invalid JSON syntax:
*
* - Unquoted attributes.
*
- Missing attribute values.
*
- Concatenated strings.
*
- Javascript comments.
*
- Numbers and booleans when Strings are expected.
*
- Numbers valid in Java but not JSON (e.g. octal notation, etc...)
*
*
*
*
*/
public static final String PARSER_strict = PREFIX + "strict";
/**
* Configuration property: Input stream charset.
*
*
* - Name:
"Parser.inputStreamCharset"
* - Data type:
String
* - Default:
"UTF-8"
* - Session-overridable:
true
*
*
*
* The character set to use for converting InputStreams
and byte arrays to readers.
*
*
* Used when passing in input streams and byte arrays to {@link Parser#parse(Object, Class)}.
*/
public static final String PARSER_inputStreamCharset = PREFIX + "inputStreamCharset";
/**
* Configuration property: File charset.
*
*
* - Name:
"Parser.fileCharset"
* - Data type:
String
* - Default:
"default"
* - Session-overridable:
true
*
*
*
* The character set to use for reading Files
from the file system.
*
*
* Used when passing in files to {@link Parser#parse(Object, Class)}.
*
*
* "default" can be used to indicate the JVM default file system charset.
*/
public static final String PARSER_fileCharset = PREFIX + "fileCharset";
/**
* Configuration property: Parser listener.
*
*
* - Name:
"Parser.listener"
* - Data type:
Class<? extends ParserListener>
* - Default:
null
* - Session-overridable:
true
*
*
*
* Class used to listen for errors and warnings that occur during parsing.
*/
public static final String PARSER_listener = PREFIX + "listener";
//-------------------------------------------------------------------------------------------------------------------
// Instance
//-------------------------------------------------------------------------------------------------------------------
/** General parser properties currently set on this parser. */
private final MediaType[] consumes;
// Hidden constructor to force subclass from InputStreamParser or ReaderParser.
Parser(PropertyStore propertyStore, String...consumes) {
super(propertyStore);
this.consumes = new MediaType[consumes.length];
for (int i = 0; i < consumes.length; i++) {
this.consumes[i] = MediaType.forString(consumes[i]);
}
}
@Override /* CoreObject */
public ParserBuilder builder() {
return new ParserBuilder(propertyStore);
}
//--------------------------------------------------------------------------------
// Abstract methods
//--------------------------------------------------------------------------------
/**
* Returns true if this parser subclasses from {@link ReaderParser}.
*
* @return true if this parser subclasses from {@link ReaderParser}.
*/
public abstract boolean isReaderParser();
/**
* Create the session object that will be passed in to the parse method.
*
*
* It's up to implementers to decide what the session object looks like, although typically it's going to be a
* subclass of {@link ParserSession}.
*
* @param args
* Runtime arguments.
* @return The new session.
*/
public abstract ParserSession createSession(ParserSessionArgs args);
//--------------------------------------------------------------------------------
// Other methods
//--------------------------------------------------------------------------------
/**
* 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 p = JsonParser.DEFAULT ;
*
* // Parse into a linked-list of strings.
* List l = p.parse(json, LinkedList.class , String.class );
*
* // Parse into a linked-list of beans.
* List l = p.parse(json, LinkedList.class , MyBean.class );
*
* // Parse into a linked-list of linked-lists of strings.
* List l = p.parse(json, LinkedList.class , LinkedList.class , String.class );
*
* // Parse into a map of string keys/values.
* Map m = p.parse(json, TreeMap.class , String.class , String.class );
*
* // Parse into a map containing string keys and values of lists containing beans.
* Map m = p.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 #PARSER_inputStreamCharset} property value).
*
byte []
containing UTF-8 encoded text (or charset defined by
* {@link #PARSER_inputStreamCharset} property value).
* - {@link File} containing system encoded text (or charset defined by
* {@link #PARSER_fileCharset} 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
* If the input contains a syntax error or is malformed, or is not valid for the specified type.
* @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections.
*/
public final T parse(Object input, Type type, Type...args) throws ParseException {
ParserSession session = createSession();
try {
return session.parse(input, type, args);
} finally {
session.close();
}
}
/**
* 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 p = JsonParser.DEFAULT ;
*
* // Parse into a string.
* String s = p.parse(json, String.class );
*
* // Parse into a bean.
* MyBean b = p.parse(json, MyBean.class );
*
* // Parse into a bean array.
* MyBean[] ba = p.parse(json, MyBean[].class );
*
* // Parse into a linked-list of objects.
* List l = p.parse(json, LinkedList.class );
*
* // Parse into a map of object keys/values.
* Map m = p.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
* If the input contains a syntax error or is malformed, or is not valid for the specified type.
*/
public final T parse(Object input, Class type) throws ParseException {
ParserSession session = createSession();
try {
return session.parse(input, type);
} finally {
session.close();
}
}
/**
* 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
* If the input contains a syntax error or is malformed, or is not valid for the specified type.
*/
public final T parse(Object input, ClassMeta type) throws ParseException {
ParserSession session = createSession();
try {
return session.parse(input, type);
} finally {
session.close();
}
}
/**
* Create a basic session object without overriding properties or specifying javaMethod
.
*
*
* Equivalent to calling createSession(null , null )
.
*
* @return The new context.
*/
public final ParserSession createSession() {
return createSession(createDefaultSessionArgs());
}
/**
* Creates the session arguments object that gets passed to the {@link #createSession(ParserSessionArgs)} method.
*
* @return
* A new default session arguments object.
*
The arguments can be modified before passing to the {@link #createSession(ParserSessionArgs)}.
*/
protected final ParserSessionArgs createDefaultSessionArgs() {
return new ParserSessionArgs(ObjectMap.EMPTY_MAP, null, null, null, getPrimaryMediaType(), null);
}
//--------------------------------------------------------------------------------
// Optional methods
//--------------------------------------------------------------------------------
/**
* 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 ObjectMap} (e.g.
* {@link ObjectMap#ObjectMap(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 If the input contains a syntax error or is malformed, or is not valid for the specified type.
* @throws UnsupportedOperationException If not implemented.
*/
public final Map parseIntoMap(Object input, Map m, Type keyType, Type valueType) throws ParseException {
ParserSession session = createSession();
try {
return session.parseIntoMap(input, m, keyType, valueType);
} finally {
session.close();
}
}
/**
* 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 ObjectList} (e.g.
* {@link ObjectList#ObjectList(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
* If the input contains a syntax error or is malformed, or is not valid for the specified type.
* @throws UnsupportedOperationException If not implemented.
*/
public final Collection parseIntoCollection(Object input, Collection c, Type elementType) throws ParseException {
ParserSession session = createSession();
try {
return session.parseIntoCollection(input, c, elementType);
} finally {
session.close();
}
}
/**
* 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 PojoIntrospector#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
* If the input contains a syntax error or is malformed, or is not valid for the specified type.
*/
public final Object[] parseArgs(Object input, Type[] argTypes) throws ParseException {
if (argTypes == null || argTypes.length == 0)
return new Object[0];
ParserSession session = createSession();
try {
return session.parseArgs(input, argTypes);
} finally {
session.close();
}
}
//--------------------------------------------------------------------------------
// Other methods
//--------------------------------------------------------------------------------
/**
* Returns the media types handled based on the values passed to the consumes
constructor parameter.
*
* @return The list of media types. Never null .
*/
public final MediaType[] getMediaTypes() {
return consumes;
}
/**
* Returns the first media type handled based on the values passed to the consumes
constructor parameter.
*
* @return The media type.
*/
public final MediaType getPrimaryMediaType() {
return consumes == null || consumes.length == 0 ? null : consumes[0];
}
}