http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/parser/ParserSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserSession.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserSession.java index e5b085e..994eec3 100644 --- a/juneau-core/src/main/java/org/apache/juneau/parser/ParserSession.java +++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserSession.java @@ -14,28 +14,32 @@ package org.apache.juneau.parser; import static org.apache.juneau.parser.ParserContext.*; import static org.apache.juneau.internal.ClassUtils.*; +import static org.apache.juneau.internal.StringUtils.*; import java.io.*; import java.lang.reflect.*; import java.util.*; import org.apache.juneau.*; -import org.apache.juneau.http.*; +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 meant to be discarded after one-time use. + * This class is NOT thread safe. + * It is typically discarded after one-time use although it can be reused against multiple inputs. */ -public class ParserSession extends BeanSession { +public abstract class ParserSession extends BeanSession { private final boolean trimStrings, strict; private final String inputStreamCharset, fileCharset; - private final Method javaMethod; private final Object outer; - private final ParserInput input; + + // Writable properties. private BeanPropertyMeta currentProperty; private ClassMeta<?> currentClass; private final ParserListener listener; @@ -46,6 +50,71 @@ public class ParserSession extends BeanSession { * @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(ParserContext ctx, ParserSessionArgs args) { + super(ctx != null ? ctx : ParserContext.DEFAULT, args != null ? args : ParserSessionArgs.DEFAULT); + if (ctx == null) + ctx = ParserContext.DEFAULT; + if (args == null) + args = ParserSessionArgs.DEFAULT; + Class<?> listenerClass; + ObjectMap p = getProperties(); + if (p.isEmpty()) { + trimStrings = ctx.trimStrings; + strict = ctx.strict; + inputStreamCharset = ctx.inputStreamCharset; + fileCharset = ctx.fileCharset; + listenerClass = ctx.listener; + } else { + trimStrings = p.getBoolean(PARSER_trimStrings, ctx.trimStrings); + strict = p.getBoolean(PARSER_strict, ctx.strict); + inputStreamCharset = p.getString(PARSER_inputStreamCharset, ctx.inputStreamCharset); + fileCharset = p.getString(PARSER_fileCharset, ctx.fileCharset); + listenerClass = p.get(Class.class, PARSER_listener, ctx.listener); + } + this.javaMethod = args.javaMethod; + this.outer = args.outer; + this.listener = newInstance(ParserListener.class, listenerClass); + } + + + //-------------------------------------------------------------------------------- + // Abstract methods + //-------------------------------------------------------------------------------- + + /** + * Workhorse method. 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 <code>String</code>, <code>Number</code>, + * <code>ObjectMap</code>, etc... + * @param <T> The class type of the object to create. + * @return The parsed object. + * @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed. + */ + protected abstract <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception; + + /** + * 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: @@ -67,67 +136,11 @@ public class ParserSession extends BeanSession { * <li><code><jk>byte</jk>[]</code> * <li>{@link File} * </ul> - * @param op - * The override properties. - * These override any context properties defined in the context. - * @param javaMethod The java method that called this parser, usually the method in a REST servlet. - * @param outer The outer object for instantiating top-level non-static inner classes. - * @param locale - * The session locale. - * If <jk>null</jk>, then the locale defined on the context is used. - * @param timeZone - * The session timezone. - * If <jk>null</jk>, then the timezone defined on the context is used. - * @param mediaType The session media type (e.g. <js>"application/json"</js>). - */ - public ParserSession(ParserContext ctx, ObjectMap op, Object input, Method javaMethod, Object outer, Locale locale, - TimeZone timeZone, MediaType mediaType) { - super(ctx, op, locale, timeZone, mediaType); - Class<?> listenerClass; - if (op == null || op.isEmpty()) { - trimStrings = ctx.trimStrings; - strict = ctx.strict; - inputStreamCharset = ctx.inputStreamCharset; - fileCharset = ctx.fileCharset; - listenerClass = ctx.listener; - } else { - trimStrings = op.getBoolean(PARSER_trimStrings, ctx.trimStrings); - strict = op.getBoolean(PARSER_strict, ctx.strict); - inputStreamCharset = op.getString(PARSER_inputStreamCharset, ctx.inputStreamCharset); - fileCharset = op.getString(PARSER_fileCharset, ctx.fileCharset); - listenerClass = op.get(Class.class, PARSER_listener, ctx.listener); - } - this.input = new ParserInput(input, isDebug(), strict, fileCharset, inputStreamCharset); - this.javaMethod = javaMethod; - this.outer = outer; - this.listener = newInstance(ParserListener.class, listenerClass); - } - - /** - * Wraps the specified input object inside an input stream. - * - * <p> - * Subclasses can override this method to implement their own input streams. - * - * @return The input object wrapped in an input stream, or <jk>null</jk> if the object is null. - * @throws ParseException If object could not be converted to an input stream. - */ - public InputStream getInputStream() throws ParseException { - return input.getInputStream(); - } - - - /** - * Wraps the specified input object inside a reader. - * - * <p> - * Subclasses can override this method to implement their own readers. - * - * @return The input object wrapped in a Reader, or <jk>null</jk> if the object is null. - * @throws Exception If object could not be converted to a reader. + * @return + * A new {@link ParserPipe} wrapper around the specified input object. */ - public Reader getReader() throws Exception { - return input.getReader(); + public final ParserPipe createPipe(Object input) { + return new ParserPipe(input, isDebug(), strict, fileCharset, inputStreamCharset); } /** @@ -135,8 +148,8 @@ public class ParserSession extends BeanSession { * * @return A map, typically containing something like <code>{line:123,column:456,currentProperty:"foobar"}</code> */ - public Map<String,Object> getLastLocation() { - Map<String,Object> m = new LinkedHashMap<String,Object>(); + public final ObjectMap getLastLocation() { + ObjectMap m = new ObjectMap(); if (currentClass != null) m.put("currentClass", currentClass.toString(true)); if (currentProperty != null) @@ -145,15 +158,6 @@ public class ParserSession extends BeanSession { } /** - * Returns the raw input object passed into this session. - * - * @return The raw input object passed into this session. - */ - protected Object getInput() { - return input.getRawInput(); - } - - /** * Returns the Java method that invoked this parser. * * <p> @@ -162,7 +166,7 @@ public class ParserSession extends BeanSession { * * @return The Java method that invoked this parser. */ - public final Method getJavaMethod() { + protected final Method getJavaMethod() { return javaMethod; } @@ -174,7 +178,7 @@ public class ParserSession extends BeanSession { * * @return The outer object. */ - public final Object getOuter() { + protected final Object getOuter() { return outer; } @@ -183,7 +187,7 @@ public class ParserSession extends BeanSession { * * @param currentProperty The current property being parsed. */ - public void setCurrentProperty(BeanPropertyMeta currentProperty) { + protected final void setCurrentProperty(BeanPropertyMeta currentProperty) { this.currentProperty = currentProperty; } @@ -192,7 +196,7 @@ public class ParserSession extends BeanSession { * * @param currentClass The current class being parsed. */ - public void setCurrentClass(ClassMeta<?> currentClass) { + protected final void setCurrentClass(ClassMeta<?> currentClass) { this.currentClass = currentClass; } @@ -201,7 +205,7 @@ public class ParserSession extends BeanSession { * * @return The {@link ParserContext#PARSER_trimStrings} setting value for this session. */ - public final boolean isTrimStrings() { + protected final boolean isTrimStrings() { return trimStrings; } @@ -210,7 +214,7 @@ public class ParserSession extends BeanSession { * * @return The {@link ParserContext#PARSER_strict} setting value for this session. */ - public final boolean isStrict() { + protected final boolean isStrict() { return strict; } @@ -221,7 +225,7 @@ public class ParserSession extends BeanSession { * @return The trimmed string if it's a string. */ @SuppressWarnings("unchecked") - public final <K> K trim(K o) { + protected final <K> K trim(K o) { if (trimStrings && o instanceof String) return (K)o.toString().trim(); return o; @@ -234,7 +238,7 @@ public class ParserSession extends BeanSession { * @param s The input string to trim. * @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>. */ - public final String trim(String s) { + protected final String trim(String s) { if (trimStrings && s != null) return s.trim(); return s; @@ -249,7 +253,7 @@ public class ParserSession extends BeanSession { * @return * The converted bean, or the same map if the <js>"_type"</js> entry wasn't found or didn't resolve to a bean. */ - public final Object cast(ObjectMap m, BeanPropertyMeta pMeta, ClassMeta<?> eType) { + protected final Object cast(ObjectMap m, BeanPropertyMeta pMeta, ClassMeta<?> eType) { String btpn = getBeanTypePropertyName(eType); @@ -288,7 +292,7 @@ public class ParserSession extends BeanSession { * @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. */ - public final ClassMeta<?> getClassMeta(String typeName, BeanPropertyMeta pMeta, ClassMeta<?> eType) { + protected final ClassMeta<?> getClassMeta(String typeName, BeanPropertyMeta pMeta, ClassMeta<?> eType) { BeanRegistry br = null; // Resolve via @BeanProperty(beanDictionary={}) @@ -313,6 +317,7 @@ public class ParserSession extends BeanSession { /** * Method that gets called when an unknown bean property name is encountered. * + * @param pipe The parser input. * @param propertyName The unknown bean property name. * @param beanMap The bean that doesn't have the expected property. * @param line The line number where the property was found. <code>-1</code> if line numbers are not available. @@ -322,39 +327,427 @@ public class ParserSession extends BeanSession { * <jk>false</jk> * @param <T> The class type of the bean map that doesn't have the expected property. */ - public <T> void onUnknownProperty(String propertyName, BeanMap<T> beanMap, int line, int col) throws ParseException { + protected final <T> void onUnknownProperty(ParserPipe pipe, String propertyName, BeanMap<T> beanMap, int line, int col) throws ParseException { if (propertyName.equals(getBeanTypePropertyName(beanMap.getClassMeta()))) return; if (! isIgnoreUnknownBeanProperties()) - throw new ParseException(this, + throw new ParseException(getLastLocation(), "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(), + listener.onUnknownBeanProperty(this, pipe, propertyName, beanMap.getClassMeta().getInnerClass(), beanMap.getBean(), line, col); } /** - * Returns the input to this parser as a plain string. + * 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'> + * 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> + * <code>Collection</code> classes are assumed to be followed by zero or one objects indicating the element type. + * + * <p> + * <code>Map</code> classes are assumed to be followed by zero or two meta objects indicating the key and value types. * * <p> - * This method only returns a value if {@link BeanContext#BEAN_debug} is enabled. + * The array can be arbitrarily long to indicate arbitrarily complex data structures. + * + * <h5 class='section'>Notes:</h5> + * <ul> + * <li>Use the {@link #parse(Object, Class)} method instead if you don't need a parameterized map/collection. + * </ul> * - * @return The input as a string, or <jk>null</jk> if debug mode not enabled. + * @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 ParserContext#PARSER_inputStreamCharset} property value). + * <li><code><jk>byte</jk>[]</code> containing UTF-8 encoded text (or charset defined by + * {@link ParserContext#PARSER_inputStreamCharset} property value). + * <li>{@link File} containing system encoded text (or charset defined by + * {@link ParserContext#PARSER_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 + * 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 String getInputAsString() { - return input.getInputAsString(); + @SuppressWarnings("unchecked") + public final <T> T parse(Object input, Type type, Type...args) throws ParseException { + ParserPipe pipe = createPipe(input); + try { + return (T)parseInner(pipe, getClassMeta(type, args)); + } finally { + pipe.close(); + } } /** - * Perform cleanup on this context object if necessary. + * 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'> + * 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 + * If the input contains a syntax error or is malformed, or is not valid for the specified type. + */ + public final <T> T parse(Object input, Class<T> type) throws ParseException { + ParserPipe pipe = createPipe(input); + try { + return parseInner(pipe, getClassMeta(type)); + } finally { + pipe.close(); + } + } + + /** + * 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 + * If the input contains a syntax error or is malformed, or is not valid for the specified type. + */ + public final <T> T parse(Object input, ClassMeta<T> type) throws ParseException { + ParserPipe pipe = createPipe(input); + try { + return parseInner(pipe, type); + } finally { + pipe.close(); + } + } + + /** + * 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 + * If the input contains a syntax error or is malformed, or is not valid for the specified type. */ - @Override - public boolean close() { - if (super.close()) { - input.close(); - return true; + private <T> T parseInner(ParserPipe pipe, ClassMeta<T> type) throws ParseException { + try { + if (type.isVoid()) + return null; + return doParse(pipe, type); + } catch (ParseException e) { + throw e; + } catch (StackOverflowError e) { + throw new ParseException(getLastLocation(), "Depth too deep. Stack overflow occurred."); + } catch (IOException e) { + throw new ParseException(getLastLocation(), "I/O exception occurred. exception={0}, message={1}.", + e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e); + } catch (Exception e) { + throw new ParseException(getLastLocation(), "Exception occurred. exception={0}, message={1}.", + e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e); + } + } + + /** + * 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 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 <K,V> Map<K,V> parseIntoMap(Object input, Map<K,V> m, Type keyType, Type valueType) throws ParseException { + ParserPipe pipe = createPipe(input); + try { + return doParseIntoMap(pipe, m, keyType, valueType); + } catch (ParseException e) { + throw e; + } catch (Exception e) { + throw new ParseException(getLastLocation(), e); + } finally { + pipe.close(); + } + } + + /** + * 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 + * 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 <E> Collection<E> parseIntoCollection(Object input, Collection<E> c, Type elementType) throws ParseException { + ParserPipe pipe = createPipe(input); + try { + return doParseIntoCollection(pipe, c, elementType); + } catch (ParseException e) { + throw e; + } catch (StackOverflowError e) { + throw new ParseException(getLastLocation(), "Depth too deep. Stack overflow occurred."); + } catch (IOException e) { + throw new ParseException(getLastLocation(), "I/O exception occurred. exception={0}, message={1}.", + e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e); + } catch (Exception e) { + throw new ParseException(getLastLocation(), "Exception occurred. exception={0}, message={1}.", + e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e); + } finally { + pipe.close(); + } + } + + /** + * 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 + * 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 { + ParserPipe pipe = createPipe(input); + try { + return doParse(pipe, getArgsClassMeta(argTypes)); + } catch (ParseException e) { + throw e; + } catch (StackOverflowError e) { + throw new ParseException(getLastLocation(), "Depth too deep. Stack overflow occurred."); + } catch (IOException e) { + throw new ParseException(getLastLocation(), "I/O exception occurred. exception={0}, message={1}.", + e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e); + } catch (Exception e) { + throw new ParseException(getLastLocation(), "Exception occurred. exception={0}, message={1}.", + e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e); + } finally { + pipe.close(); + } + } + + /** + * 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. + * @throws Exception If the input contains a syntax error or is malformed, or is not valid for the specified type. + * @param <T> The class type to convert the string to. + */ + @SuppressWarnings({ "unchecked", "rawtypes", "hiding" }) + protected final <T> T convertAttrToType(Object outer, String s, ClassMeta<T> type) throws Exception { + if (s == null) + return null; + + if (type == null) + type = (ClassMeta<T>)object(); + PojoSwap transform = type.getPojoSwap(); + ClassMeta<?> sType = type.getSerializedClassMeta(); + + Object o = s; + if (sType.isChar()) + o = s.charAt(0); + else if (sType.isNumber()) + if (type.canCreateNewInstanceFromNumber(outer)) + o = type.newInstanceFromNumber(this, outer, parseNumber(s, type.getNewInstanceFromNumberClass())); + else + 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(getLastLocation(), "Invalid conversion from string to class ''{0}''", type); + } + + if (transform != null) + o = transform.unswap(this, 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 Exception + */ + protected final static void setParent(ClassMeta<?> cm, Object o, Object parent) throws Exception { + 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 Exception + */ + protected final static void setName(ClassMeta<?> cm, Object o, Object name) throws Exception { + if (cm != null) { + Setter m = cm.getNameProperty(); + if (m != null) + m.set(o, name); } - return false; } }
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/parser/ParserSessionArgs.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserSessionArgs.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserSessionArgs.java new file mode 100644 index 0000000..65ce23e --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserSessionArgs.java @@ -0,0 +1,61 @@ +// *************************************************************************************************************************** +// * 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.lang.reflect.*; +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.http.*; + +/** + * Runtime arguments common to all parser sessions. + */ +public final class ParserSessionArgs extends BeanSessionArgs { + + /** + * Default session arguments. + */ + protected static final ParserSessionArgs DEFAULT = new ParserSessionArgs(ObjectMap.EMPTY_MAP, null, null, null, null, null); + + final Method javaMethod; + final Object outer; + + /** + * Constructor. + * + * @param properties + * Session-level properties. + * These override context-level properties. + * Can be <jk>null</jk>. + * @param javaMethod + * The java method that called this serializer, usually the method in a REST servlet. + * Can be <jk>null</jk>. + * @param locale + * The session locale. + * If <jk>null</jk>, then the locale defined on the context is used. + * @param timeZone + * The session timezone. + * If <jk>null</jk>, then the timezone defined on the context is used. + * @param mediaType + * The session media type (e.g. <js>"application/json"</js>). + * Can be <jk>null</jk>. + * @param outer + * The outer object for instantiating top-level non-static inner classes. + */ + public ParserSessionArgs(ObjectMap properties, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType, Object outer) { + super(properties, locale, timeZone, mediaType); + this.javaMethod = javaMethod; + this.outer = outer; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParser.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParser.java b/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParser.java index 9d9c710..a987a5e 100644 --- a/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParser.java +++ b/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParser.java @@ -45,7 +45,7 @@ public abstract class ReaderParser extends Parser { } @Override /* Parser */ - public boolean isReaderParser() { + public final boolean isReaderParser() { return true; } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParserSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParserSession.java b/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParserSession.java new file mode 100644 index 0000000..1c7da89 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParserSession.java @@ -0,0 +1,51 @@ +// *************************************************************************************************************************** +// * 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; + +/** + * Subclass of parser session objects for character-based parsers. + * + * <p> + * This class is NOT thread safe. It is typically discarded after one-time use. + */ +public abstract class ReaderParserSession extends ParserSession { + + /** + * 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 ReaderParserSession(ParserContext ctx, ParserSessionArgs args) { + super(ctx, args); + } + + /** + * Constructor for sessions that don't require context. + * + * @param args + * Runtime session arguments. + */ + protected ReaderParserSession(ParserSessionArgs args) { + this(null, args); + } + + + @Override /* ParserSession */ + public final boolean isReaderParser() { + return true; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParser.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParser.java b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParser.java index c25c2a0..c5a2695 100644 --- a/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParser.java +++ b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParser.java @@ -12,8 +12,6 @@ // *************************************************************************************************************************** package org.apache.juneau.plaintext; -import static org.apache.juneau.internal.IOUtils.*; - import org.apache.juneau.*; import org.apache.juneau.annotation.*; import org.apache.juneau.parser.*; @@ -67,13 +65,8 @@ public class PlainTextParser extends ReaderParser { return new PlainTextParserBuilder(propertyStore); } - - //-------------------------------------------------------------------------------- - // Overridden methods - //-------------------------------------------------------------------------------- - @Override /* Parser */ - protected <T> T doParse(ParserSession session, ClassMeta<T> type) throws Exception { - return session.convertToType(read(session.getReader()), type); + public ReaderParserSession createSession(ParserSessionArgs args) { + return new PlainTextParserSession(args); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParserSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParserSession.java b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParserSession.java new file mode 100644 index 0000000..c4731fa --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParserSession.java @@ -0,0 +1,43 @@ +// *************************************************************************************************************************** +// * 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.plaintext; + +import static org.apache.juneau.internal.IOUtils.*; + +import org.apache.juneau.*; +import org.apache.juneau.parser.*; + +/** + * Session object that lives for the duration of a single use of {@link PlainTextParser}. + * + * <p> + * This class is NOT thread safe. + * It is typically discarded after one-time use although it can be reused against multiple inputs. + */ +public class PlainTextParserSession extends ReaderParserSession { + + /** + * Create a new session using properties specified in the context. + * + * @param args + * Runtime session arguments. + */ + protected PlainTextParserSession(ParserSessionArgs args) { + super(null, args); + } + + @Override /* ParserSession */ + protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception { + return convertToType(read(pipe.getReader()), type); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializer.java b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializer.java index cf0975c..910e2d5 100644 --- a/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializer.java @@ -49,6 +49,7 @@ public class PlainTextSerializer extends WriterSerializer { /** Default serializer, all default settings.*/ public static final PlainTextSerializer DEFAULT = new PlainTextSerializer(PropertyStore.create()); + private final SerializerContext ctx; /** * Constructor. @@ -57,6 +58,7 @@ public class PlainTextSerializer extends WriterSerializer { */ public PlainTextSerializer(PropertyStore propertyStore) { super(propertyStore); + this.ctx = createContext(SerializerContext.class); } @Override /* CoreObject */ @@ -64,13 +66,8 @@ public class PlainTextSerializer extends WriterSerializer { return new PlainTextSerializerBuilder(propertyStore); } - - //-------------------------------------------------------------------------------- - // Overridden methods - //-------------------------------------------------------------------------------- - @Override /* Serializer */ - protected void doSerialize(SerializerSession session, SerializerOutput out, Object o) throws Exception { - out.getWriter().write(o == null ? "null" : session.convertToType(o, String.class)); + public WriterSerializerSession createSession(SerializerSessionArgs args) { + return new PlainTextSerializerSession(ctx, args); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializerSession.java new file mode 100644 index 0000000..0e69e09 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializerSession.java @@ -0,0 +1,47 @@ +// *************************************************************************************************************************** +// * 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.plaintext; + +import org.apache.juneau.serializer.*; + +/** + * Session object that lives for the duration of a single use of {@link PlainTextSerializer}. + * + * <p> + * This class is NOT thread safe. + * It is typically discarded after one-time use although it can be reused within the same thread. + */ +public class PlainTextSerializerSession extends WriterSerializerSession { + + /** + * 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 arguments. + * These specify session-level information such as locale and URI context. + * It also include session-level properties that override the properties defined on the bean and + * serializer contexts. + * <br>If <jk>null</jk>, defaults to {@link SerializerSessionArgs#DEFAULT}. + */ + protected PlainTextSerializerSession(SerializerContext ctx, SerializerSessionArgs args) { + super(ctx, args); + } + + @Override /* SerializerSession */ + protected void doSerialize(SerializerPipe out, Object o) throws Exception { + out.getWriter().write(o == null ? "null" : convertToType(o, String.class)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/serializer/OutputStreamSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/OutputStreamSerializer.java b/juneau-core/src/main/java/org/apache/juneau/serializer/OutputStreamSerializer.java index 8c743b6..b481f2e 100644 --- a/juneau-core/src/main/java/org/apache/juneau/serializer/OutputStreamSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/serializer/OutputStreamSerializer.java @@ -14,22 +14,12 @@ package org.apache.juneau.serializer; import static org.apache.juneau.internal.StringUtils.*; -import java.io.*; - import org.apache.juneau.*; import org.apache.juneau.annotation.*; /** * Subclass of {@link Serializer} for byte-based serializers. * - * <h5 class='section'>Description:</h5> - * - * This class is typically the parent class of all byte-based serializers. - * It has 1 abstract method to implement... - * <ul> - * <li>{@link #doSerialize(SerializerSession, SerializerOutput, Object)} - * </ul> - * * <h6 class='topic'>@Produces annotation</h6> * * The media types that this serializer can produce is specified through the {@link Produces @Produces} annotation. @@ -49,16 +39,24 @@ public abstract class OutputStreamSerializer extends Serializer { super(propertyStore); } - @Override /* Serializer */ - public boolean isWriterSerializer() { - return false; - } + + //-------------------------------------------------------------------------------- + // Abstract methods + //-------------------------------------------------------------------------------- + + @Override /* SerializerSession */ + public abstract OutputStreamSerializerSession createSession(SerializerSessionArgs args); //-------------------------------------------------------------------------------- // Other methods //-------------------------------------------------------------------------------- + @Override /* Serializer */ + public final boolean isWriterSerializer() { + return false; + } + /** * Convenience method for serializing an object to a <code><jk>byte</jk></code>. * @@ -68,10 +66,7 @@ public abstract class OutputStreamSerializer extends Serializer { */ @Override public final byte[] serialize(Object o) throws SerializeException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - SerializerOutput out = new SerializerOutput(baos); - serialize(createSession(), out, o); - return baos.toByteArray(); + return createSession(null).serialize(o); } /** @@ -82,9 +77,6 @@ public abstract class OutputStreamSerializer extends Serializer { * @throws SerializeException If a problem occurred trying to convert the output. */ public final String serializeToHex(Object o) throws SerializeException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - SerializerOutput out = new SerializerOutput(baos); - serialize(createSession(), out, o); - return toHex(baos.toByteArray()); + return toHex(serialize(o)); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/serializer/OutputStreamSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/OutputStreamSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/serializer/OutputStreamSerializerSession.java new file mode 100644 index 0000000..8098566 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/serializer/OutputStreamSerializerSession.java @@ -0,0 +1,75 @@ +// *************************************************************************************************************************** +// * 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.serializer; + +import java.io.*; + +/** + * Subclass of {@link SerializerSession} for stream-based serializers. + * + * <h5 class='section'>Description:</h5> + * + * This class is the parent class of all byte-based serializers. + * <br>It has 1 abstract method to implement... + * <ul> + * <li>{@link #doSerialize(SerializerPipe, Object)} + * </ul> + */ +public abstract class OutputStreamSerializerSession extends SerializerSession { + + /** + * 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 arguments. + * These specify session-level information such as locale and URI context. + * It also include session-level properties that override the properties defined on the bean and + * serializer contexts. + * <br>If <jk>null</jk>, defaults to {@link SerializerSessionArgs#DEFAULT}. + */ + protected OutputStreamSerializerSession(SerializerContext ctx, SerializerSessionArgs args) { + super(ctx, args); + } + + /** + * Constructor for sessions that don't require context. + * + * @param args + * Runtime session arguments. + */ + protected OutputStreamSerializerSession(SerializerSessionArgs args) { + this(null, args); + } + + @Override /* SerializerSession */ + public final boolean isWriterSerializer() { + return false; + } + + /** + * Convenience method for serializing an object to a <code><jk>byte</jk></code>. + * + * @param o The object to serialize. + * @return The output serialized to a byte array. + * @throws SerializeException If a problem occurred trying to convert the output. + */ + @Override /* SerializerSession */ + public final byte[] serialize(Object o) throws SerializeException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + serialize(baos, o); + return baos.toByteArray(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/serializer/Serializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/Serializer.java b/juneau-core/src/main/java/org/apache/juneau/serializer/Serializer.java index bb0e623..d2640c3 100644 --- a/juneau-core/src/main/java/org/apache/juneau/serializer/Serializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/serializer/Serializer.java @@ -16,13 +16,10 @@ import static org.apache.juneau.internal.StringUtils.*; import static org.apache.juneau.internal.ReflectionUtils.*; import java.io.*; -import java.lang.reflect.*; -import java.util.*; import org.apache.juneau.*; import org.apache.juneau.annotation.*; import org.apache.juneau.http.*; -import org.apache.juneau.soap.*; /** * Parent class for all Juneau serializers. @@ -32,13 +29,21 @@ import org.apache.juneau.soap.*; * Base serializer class that serves as the parent class for all serializers. * * <p> - * Subclasses should extend directly from {@link OutputStreamSerializer} or {@link WriterSerializer}. + * The purpose of this class is: + * <ul> + * <li>Maintain a read-only configuration state of a serializer (i.e. {@link SerializerContext}). + * <li>Create session objects used for serializing POJOs (i.e. {@link SerializerSession}). + * <li>Provide convenience methods for serializing POJOs without having to construct session objects. + * </ul> + * + * <p> + * Subclasses should extend directly from {@link OutputStreamSerializer} or {@link WriterSerializer} depending on + * whether it's a stream or character based serializer. * * <h6 class='topic'>@Produces annotation</h6> * * The media types that this serializer can produce is specified through the {@link Produces @Produces} annotation. - * - * <p> + * <br> * However, the media types can also be specified programmatically by overriding the {@link #getMediaTypes()} * and {@link #getResponseContentType()} methods. */ @@ -46,12 +51,10 @@ public abstract class Serializer extends CoreObject { private final MediaType[] mediaTypes; private final MediaType contentType; - private final SerializerContext ctx; // Hidden constructors to force subclass from OuputStreamSerializer or WriterSerializer. Serializer(PropertyStore propertyStore) { super(propertyStore); - this.ctx = createContext(SerializerContext.class); Produces p = getAnnotation(Produces.class, getClass()); if (p == null) @@ -72,83 +75,55 @@ public abstract class Serializer extends CoreObject { return new SerializerBuilder(propertyStore); } - /** - * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. - * - * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. - */ - public abstract boolean isWriterSerializer(); - //-------------------------------------------------------------------------------- // Abstract methods //-------------------------------------------------------------------------------- /** - * Serializes a POJO to the specified output stream or writer. - * - * <p> - * This method should NOT close the context object. + * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. * - * @param session - * The serializer session object return by {@link #createSession(ObjectMap, Method, Locale, TimeZone, - * MediaType, UriContext)}. - * If <jk>null</jk>, session is created using {@link #createSession()}. - * @param out - * Where to send the output from the serializer. - * @param o The object to serialize. - * @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed. + * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. */ - protected abstract void doSerialize(SerializerSession session, SerializerOutput out, Object o) throws Exception; + public abstract boolean isWriterSerializer(); /** - * Shortcut method for serializing objects directly to either a <code>String</code> or <code><jk>byte</jk>[]</code> - * depending on the serializer type. - * - * @param o The object to serialize. + * Create the session object used for actual serialization of objects. + * + * @param args + * Runtime arguments. + * These specify session-level information such as locale and URI context. + * It also include session-level properties that override the properties defined on the bean and serializer + * contexts. + * <br>If <jk>null</jk>, defaults to {@link SerializerSessionArgs#DEFAULT}. * @return - * The serialized object. - * <br>Character-based serializers will return a <code>String</code> - * <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code> - * @throws SerializeException If a problem occurred trying to convert the output. + * The new session object. + * <br>Note that you must call {@link SerializerSession#close()} on this object to perform any necessary + * cleanup. */ - public abstract Object serialize(Object o) throws SerializeException; + public abstract SerializerSession createSession(SerializerSessionArgs args); + //-------------------------------------------------------------------------------- - // Other methods + // Convenience methods //-------------------------------------------------------------------------------- /** - * Serialize the specified object using the specified session. + * Shortcut for calling <code>createSession(<jk>null</jk>)</code>. * - * @param session - * The serializer session object return by {@link #createSession(ObjectMap, Method, Locale, TimeZone, - * MediaType, UriContext)}. - * If <jk>null</jk>, session is created using {@link #createSession()}. - * @param out - * Where to send the output from the serializer. - * @param o The object to serialize. - * @throws SerializeException If a problem occurred trying to convert the output. + * @return + * The new session object. + * <br>Note that you must call {@link SerializerSession#close()} on this object to perform any necessary + * cleanup. */ - public final void serialize(SerializerSession session, SerializerOutput out, Object o) throws SerializeException { - try { - doSerialize(session, out, o); - } catch (SerializeException e) { - throw e; - } catch (StackOverflowError e) { - throw new SerializeException(session, - "Stack overflow occurred. This can occur when trying to serialize models containing loops. It's recommended you use the SerializerContext.SERIALIZER_detectRecursions setting to help locate the loop.").initCause(e); - } catch (Exception e) { - throw new SerializeException(session, e); - } finally { - session.close(); - } + public final SerializerSession createSession() { + return createSession(null); } /** * Serializes a POJO to the specified output stream or writer. * * <p> - * Equivalent to calling <code>serializer.serialize(o, out, <jk>null</jk>);</code> + * Equivalent to calling <code>serializer.createSession().serialize(o, output);</code> * * @param o The object to serialize. * @param output @@ -168,79 +143,38 @@ public abstract class Serializer extends CoreObject { * @throws SerializeException If a problem occurred trying to convert the output. */ public final void serialize(Object o, Object output) throws SerializeException { - SerializerSession session = createSession(); - SerializerOutput out = new SerializerOutput(output); - serialize(session, out, o); - } - - /** - * Create the session object that will be passed in to the serialize method. - * - * <p> - * It's up to implementers to decide what the session object looks like, although typically it's going to be a - * subclass of {@link SerializerSession}. - * - * @param op Optional additional properties. - * @param javaMethod - * Java method that invoked this serializer. - * 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. - * @param locale - * The session locale. - * If <jk>null</jk>, then the locale defined on the context is used. - * @param timeZone - * The session timezone. - * If <jk>null</jk>, then the timezone defined on the context is used. - * @param mediaType The session media type (e.g. <js>"application/json"</js>). - * @param uriContext - * The URI context. - * Identifies the current request URI used for resolution of URIs to absolute or root-relative form. - * @return The new session. - */ - public SerializerSession createSession(ObjectMap op, Method javaMethod, Locale locale, - TimeZone timeZone, MediaType mediaType, UriContext uriContext) { - return new SerializerSession(ctx, op, javaMethod, locale, timeZone, mediaType, uriContext); - } - - /** - * Create a basic session object without overriding properties or specifying <code>javaMethod</code>. - * - * <p> - * Equivalent to calling <code>createSession(<jk>null</jk>, <jk>null</jk>)</code>. - * - * @return The new session. - */ - protected SerializerSession createSession() { - return createSession(null, null, null, null, getPrimaryMediaType(), null); + SerializerSession s = createSession(); + try { + s.serialize(o, output); + } finally { + s.close(); + } } /** - * Converts the contents of the specified object array to a list. - * - * <p> - * Works on both object and primitive arrays. - * - * <p> - * In the case of multi-dimensional arrays, the outgoing list will contain elements of type n-1 dimension. - * i.e. if {@code type} is <code><jk>int</jk>[][]</code> then {@code list} will have entries of type - * <code><jk>int</jk>[]</code>. + * Shortcut method for serializing objects directly to either a <code>String</code> or <code><jk>byte</jk>[]</code> + * depending on the serializer type. * - * @param type The type of array. - * @param array The array being converted. - * @return The array as a list. + * @param o The object to serialize. + * @return + * The serialized object. + * <br>Character-based serializers will return a <code>String</code> + * <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code> + * @throws SerializeException If a problem occurred trying to convert the output. */ - protected static final List<Object> toList(Class<?> type, Object array) { - Class<?> componentType = type.getComponentType(); - if (componentType.isPrimitive()) { - int l = Array.getLength(array); - List<Object> list = new ArrayList<Object>(l); - for (int i = 0; i < l; i++) - list.add(Array.get(array, i)); - return list; + public Object serialize(Object o) throws SerializeException { + SerializerSession s = createSession(); + try { + return s.serialize(o); + } finally { + s.close(); } - return Arrays.asList((Object[])array); } + //-------------------------------------------------------------------------------- + // Other methods + //-------------------------------------------------------------------------------- + /** * Returns the media types handled based on the value of the {@link Produces} annotation on the serializer class. * @@ -249,7 +183,7 @@ public abstract class Serializer extends CoreObject { * * @return The list of media types. Never <jk>null</jk>. */ - public MediaType[] getMediaTypes() { + public final MediaType[] getMediaTypes() { return mediaTypes; } @@ -258,32 +192,11 @@ public abstract class Serializer extends CoreObject { * * @return The media type. */ - public MediaType getPrimaryMediaType() { + public final MediaType getPrimaryMediaType() { return mediaTypes == null || mediaTypes.length == 0 ? null : mediaTypes[0]; } /** - * Optional method that specifies HTTP request headers for this serializer. - * - * <p> - * For example, {@link SoapXmlSerializer} needs to set a <code>SOAPAction</code> header. - * - * <p> - * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server - * or client). - * - * @param properties - * Optional run-time properties (the same that are passed to {@link WriterSerializer#doSerialize(SerializerSession, SerializerOutput, Object)}. - * Can be <jk>null</jk>. - * @return - * The HTTP headers to set on HTTP requests. - * Can be <jk>null</jk>. - */ - public ObjectMap getResponseHeaders(ObjectMap properties) { - return ObjectMap.EMPTY_MAP; - } - - /** * Optional method that returns the response <code>Content-Type</code> for this serializer if it is different from * the matched media type. * http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java index 4ab30cf..8d50aa8 100644 --- a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java +++ b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java @@ -21,6 +21,12 @@ import org.apache.juneau.annotation.*; public class SerializerContext extends BeanContext { /** + * Default context with all default values. + */ + @SuppressWarnings("hiding") + protected static final SerializerContext DEFAULT = new SerializerContext(PropertyStore.create()); + + /** * <b>Configuration property:</b> Max serialization depth. * * <ul> http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java index f8e192b..1fd700c 100644 --- a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java +++ b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java @@ -88,7 +88,7 @@ public final class SerializerGroup { * to match against media types. */ public SerializerGroup(PropertyStore propertyStore, Serializer[] serializers) { - this.propertyStore = PropertyStore.create(propertyStore); + this.propertyStore = propertyStore.copy(); this.beanContext = propertyStore.getBeanContext(); this.serializers = Collections.unmodifiableList(new ArrayList<Serializer>(Arrays.asList(serializers))); @@ -198,7 +198,7 @@ public final class SerializerGroup { * @return A new copy of the property store passed in to the constructor. */ public PropertyStore createPropertyStore() { - return PropertyStore.create(propertyStore); + return propertyStore.copy(); } /** http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerOutput.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerOutput.java b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerOutput.java deleted file mode 100644 index 8cc9cc0..0000000 --- a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerOutput.java +++ /dev/null @@ -1,174 +0,0 @@ -// *************************************************************************************************************************** -// * 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.serializer; - -import static org.apache.juneau.internal.IOUtils.*; - -import java.io.*; - -import org.apache.juneau.*; -import org.apache.juneau.internal.*; - -/** - * A wrapper around an object that a serializer sends its output to. - * - * <p> - * For character-based serializers, the output object can be any of the following: - * <ul> - * <li>{@link Writer} - * <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream. - * <li>{@link File} - Output will be written as system-default encoded stream. - * <li>{@link StringBuilder} - * </ul> - * - * <p> - * For stream-based serializers, the output object can be any of the following: - * <ul> - * <li>{@link OutputStream} - * <li>{@link File} - * </ul> - */ -public class SerializerOutput { - - private final Object output; - private final boolean autoClose; - private OutputStream outputStream; - private Writer writer, flushOnlyWriter; - - /** - * Constructor. - * - * <p> - * Equivalent to calling <code>SerializerOutput(output, <jk>true</jk>);</code>. - * - * @param output The object to pipe the serializer output to. - */ - public SerializerOutput(Object output) { - this(output, true); - } - - /** - * Constructor. - * - * @param output The object to pipe the serializer output to. - * @param autoClose Close the stream or writer at the end of the session. - */ - public SerializerOutput(Object output, boolean autoClose) { - this.output = output; - this.autoClose = autoClose; - } - - /** - * Wraps the specified output object inside an output stream. - * - * <p> - * Subclasses can override this method to implement their own specialized output streams. - * - * <p> - * This method can be used if the output object is any of the following class types: - * <ul> - * <li>{@link OutputStream} - * <li>{@link File} - * </ul> - * - * @return The output object wrapped in an output stream. - * @throws Exception If object could not be converted to an output stream. - */ - public OutputStream getOutputStream() throws Exception { - if (output == null) - throw new SerializeException("Output cannot be null."); - if (output instanceof OutputStream) - return (OutputStream)output; - if (output instanceof File) { - if (outputStream == null) - outputStream = new BufferedOutputStream(new FileOutputStream((File)output)); - return outputStream; - } - throw new SerializeException("Cannot convert object of type {0} to an OutputStream.", output.getClass().getName()); - } - - - /** - * Wraps the specified output object inside a writer. - * - * <p> - * Subclasses can override this method to implement their own specialized writers. - * - * <p> - * This method can be used if the output object is any of the following class types: - * <ul> - * <li>{@link Writer} - * <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream. - * <li>{@link File} - Output will be written as system-default encoded stream. - * </ul> - * - * @return The output object wrapped in a Writer. - * @throws Exception If object could not be converted to a writer. - */ - public Writer getWriter() throws Exception { - if (output == null) - throw new SerializeException("Output cannot be null."); - if (output instanceof Writer) - return (Writer)output; - if (output instanceof OutputStream) { - if (flushOnlyWriter == null) - flushOnlyWriter = new OutputStreamWriter((OutputStream)output, UTF8); - return flushOnlyWriter; - } - if (output instanceof File) { - if (writer == null) - writer = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream((File)output))); - return writer; - } - if (output instanceof StringBuilder) { - if (writer == null) - writer = new StringBuilderWriter((StringBuilder)output); - return writer; - } - throw new SerializeException("Cannot convert object of type {0} to a Writer.", output.getClass().getName()); - } - - /** - * Returns the raw output object passed into this session. - * - * @return The raw output object passed into this session. - */ - public Object getRawOutput() { - return output; - } - - /** - * Closes the output pipe. - */ - public void close() { - try { - if (! autoClose) { - if (outputStream != null) - outputStream.flush(); - if (flushOnlyWriter != null) - flushOnlyWriter.flush(); - if (writer != null) - writer.flush(); - } else { - if (outputStream != null) - outputStream.close(); - if (flushOnlyWriter != null) - flushOnlyWriter.flush(); - if (writer != null) - writer.close(); - } - } catch (IOException e) { - throw new BeanRuntimeException(e); - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerPipe.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerPipe.java b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerPipe.java new file mode 100644 index 0000000..4a39264 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerPipe.java @@ -0,0 +1,172 @@ +// *************************************************************************************************************************** +// * 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.serializer; + +import static org.apache.juneau.internal.IOUtils.*; + +import java.io.*; + +import org.apache.juneau.*; +import org.apache.juneau.internal.*; + +/** + * A wrapper around an object that a serializer sends its output to. + * + * <p> + * For character-based serializers, the output object can be any of the following: + * <ul> + * <li>{@link Writer} + * <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream. + * <li>{@link File} - Output will be written as system-default encoded stream. + * <li>{@link StringBuilder} + * </ul> + * + * <p> + * For stream-based serializers, the output object can be any of the following: + * <ul> + * <li>{@link OutputStream} + * <li>{@link File} + * </ul> + */ +public final class SerializerPipe { + + private final Object output; + private final boolean autoClose; + + private OutputStream outputStream; + private Writer writer; + + /** + * Constructor. + * + * @param output The object to pipe the serializer output to. + */ + SerializerPipe(Object output) { + this.output = output; + this.autoClose = (output instanceof File); + } + + /** + * Wraps the specified output object inside an output stream. + * + * <p> + * Subclasses can override this method to implement their own specialized output streams. + * + * <p> + * This method can be used if the output object is any of the following class types: + * <ul> + * <li>{@link OutputStream} + * <li>{@link File} + * </ul> + * + * @return The output object wrapped in an output stream. + * @throws IOException If object could not be converted to an output stream. + */ + public OutputStream getOutputStream() throws IOException { + if (output == null) + throw new IOException("Output cannot be null."); + + if (output instanceof OutputStream) + outputStream = (OutputStream)output; + else if (output instanceof File) + outputStream = new BufferedOutputStream(new FileOutputStream((File)output)); + else + throw new IOException("Cannot convert object of type "+output.getClass().getName()+" to an OutputStream."); + + return outputStream; + } + + + /** + * Wraps the specified output object inside a writer. + * + * <p> + * Subclasses can override this method to implement their own specialized writers. + * + * <p> + * This method can be used if the output object is any of the following class types: + * <ul> + * <li>{@link Writer} + * <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream. + * <li>{@link File} - Output will be written as system-default encoded stream. + * </ul> + * + * @return The output object wrapped in a Writer. + * @throws IOException If object could not be converted to a writer. + */ + public Writer getWriter() throws IOException { + if (output == null) + throw new IOException("Output cannot be null."); + + if (output instanceof Writer) + writer = (Writer)output; + else if (output instanceof OutputStream) + writer = new OutputStreamWriter((OutputStream)output, UTF8); + else if (output instanceof File) + writer = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream((File)output))); + else if (output instanceof StringBuilder) + writer = new StringBuilderWriter((StringBuilder)output); + else + throw new IOException("Cannot convert object of type "+output.getClass().getName()+" to a Writer."); + + return writer; + } + + /** + * Overwrites the writer in this pipe. + * + * <p> + * Used when wrapping the writer returned by {@link #getWriter()} so that the wrapped writer will be flushed + * and closed when {@link #close()} is called. + * + * @param writer The wrapped writer. + */ + public void setWriter(Writer writer) { + this.writer = writer; + } + + /** + * Overwrites the output stream in this pipe. + * + * <p> + * Used when wrapping the stream returned by {@link #getOutputStream()} so that the wrapped stream will be flushed + * and closed when {@link #close()} is called. + * + * @param outputStream The wrapped stream. + */ + public void setOutputStream(OutputStream outputStream) { + this.outputStream = outputStream; + } + + /** + * Returns the raw output object passed into this session. + * + * @return The raw output object passed into this session. + */ + public Object getRawOutput() { + return output; + } + + /** + * Closes the output pipe. + */ + public void close() { + try { + IOUtils.flush(writer, outputStream); + if (autoClose) + IOUtils.close(writer, outputStream); + } catch (IOException e) { + throw new BeanRuntimeException(e); + } + } +}
