juneau-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jamesbog...@apache.org
Subject [06/12] incubator-juneau git commit: Allow serializer and parser sessions to be reused.
Date Fri, 28 Jul 2017 18:22:21 GMT
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);
+		}
+	}
+}


Mime
View raw message