You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2017/07/28 18:22:22 UTC

[07/12] incubator-juneau git commit: Allow serializer and parser sessions to be reused.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerSession.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerSession.java
index f632c84..4a8177d 100644
--- a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerSession.java
+++ b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerSession.java
@@ -14,20 +14,20 @@ package org.apache.juneau.msgpack;
 
 import static org.apache.juneau.msgpack.MsgPackSerializerContext.*;
 
-import java.lang.reflect.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.http.*;
 import org.apache.juneau.serializer.*;
+import org.apache.juneau.transform.*;
 
 /**
  * Session object that lives for the duration of a single use of {@link MsgPackSerializer}.
  *
  * <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 within the same thread.
  */
-public final class MsgPackSerializerSession extends SerializerSession {
+public final class MsgPackSerializerSession extends OutputStreamSerializerSession {
 
 	private final boolean
 		addBeanTypeProperties;
@@ -38,28 +38,20 @@ public final class MsgPackSerializerSession extends SerializerSession {
 	 * @param ctx
 	 * 	The context creating this session object.
 	 * 	The context contains all the configuration settings for this object.
-	 * @param op
-	 * 	The override properties.
-	 * 	These override any context properties defined in the context.
-	 * @param javaMethod The java method that called this serializer, usually the method in a REST servlet.
-	 * @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.
+	 * @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 MsgPackSerializerSession(MsgPackSerializerContext ctx, ObjectMap op, Method javaMethod,
-			Locale locale, TimeZone timeZone, MediaType mediaType, UriContext uriContext) {
-		super(ctx, op, javaMethod, locale, timeZone, mediaType, uriContext);
-		if (op == null || op.isEmpty()) {
+	protected MsgPackSerializerSession(MsgPackSerializerContext ctx, SerializerSessionArgs args) {
+		super(ctx, args);
+		ObjectMap p = getProperties();
+		if (p.isEmpty()) {
 			addBeanTypeProperties = ctx.addBeanTypeProperties;
 		} else {
-			addBeanTypeProperties = op.getBoolean(MSGPACK_addBeanTypeProperties, ctx.addBeanTypeProperties);
+			addBeanTypeProperties = p.getBoolean(MSGPACK_addBeanTypeProperties, ctx.addBeanTypeProperties);
 		}
 	}
 
@@ -69,22 +61,167 @@ public final class MsgPackSerializerSession extends SerializerSession {
 	 * @return The {@link MsgPackSerializerContext#MSGPACK_addBeanTypeProperties} setting value for this session.
 	 */
 	@Override /* SerializerSession */
-	public final boolean isAddBeanTypeProperties() {
+	protected final boolean isAddBeanTypeProperties() {
 		return addBeanTypeProperties;
 	}
 
-	/**
+	@Override /* SerializerSession */
+	protected void doSerialize(SerializerPipe out, Object o) throws Exception {
+		serializeAnything(getMsgPackOutputStream(out), o, getExpectedRootType(o), "root", null);
+	}
+
+	/*
 	 * Converts the specified output target object to an {@link MsgPackOutputStream}.
-	 *
-	 * @param out The output target object.
-	 * @return The output target object wrapped in an {@link MsgPackOutputStream}.
-	 * @throws Exception
 	 */
-	@SuppressWarnings("static-method")
-	public MsgPackOutputStream getMsgPackOutputStream(SerializerOutput out) throws Exception {
+	private static final MsgPackOutputStream getMsgPackOutputStream(SerializerPipe out) throws Exception {
 		Object output = out.getRawOutput();
 		if (output instanceof MsgPackOutputStream)
 			return (MsgPackOutputStream)output;
-		return new MsgPackOutputStream(out.getOutputStream());
+		MsgPackOutputStream os = new MsgPackOutputStream(out.getOutputStream());
+		out.setOutputStream(os);
+		return os;
+	}
+
+	/*
+	 * Workhorse method.
+	 * Determines the type of object, and then calls the appropriate type-specific serialization method.
+	 */
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	private MsgPackOutputStream serializeAnything(MsgPackOutputStream out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws Exception {
+
+		if (o == null)
+			return out.appendNull();
+
+		if (eType == null)
+			eType = object();
+
+		ClassMeta<?> aType;			// The actual type
+		ClassMeta<?> sType;			// The serialized type
+
+		aType = push(attrName, o, eType);
+		boolean isRecursion = aType == null;
+
+		// Handle recursion
+		if (aType == null) {
+			o = null;
+			aType = object();
+		}
+
+		sType = aType.getSerializedClassMeta();
+		String typeName = getBeanTypeName(eType, aType, pMeta);
+
+		// Swap if necessary
+		PojoSwap swap = aType.getPojoSwap();
+		if (swap != null) {
+			o = swap.swap(this, o);
+
+			// If the getSwapClass() method returns Object, we need to figure out
+			// the actual type now.
+			if (sType.isObject())
+				sType = getClassMetaForObject(o);
+		}
+
+		// '\0' characters are considered null.
+		if (o == null || (sType.isChar() && ((Character)o).charValue() == 0))
+			out.appendNull();
+		else if (sType.isBoolean())
+			out.appendBoolean((Boolean)o);
+		else if (sType.isNumber())
+			out.appendNumber((Number)o);
+		else if (sType.isBean())
+			serializeBeanMap(out, toBeanMap(o), typeName);
+		else if (sType.isUri() || (pMeta != null && pMeta.isUri()))
+			out.appendString(resolveUri(o.toString()));
+		else if (sType.isMap()) {
+			if (o instanceof BeanMap)
+				serializeBeanMap(out, (BeanMap)o, typeName);
+			else
+				serializeMap(out, (Map)o, eType);
+		}
+		else if (sType.isCollection()) {
+			serializeCollection(out, (Collection) o, eType);
+		}
+		else if (sType.isArray()) {
+			serializeCollection(out, toList(sType.getInnerClass(), o), eType);
+		} else
+			out.appendString(toString(o));
+
+		if (! isRecursion)
+			pop();
+		return out;
+	}
+
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	private void serializeMap(MsgPackOutputStream out, Map m, ClassMeta<?> type) throws Exception {
+
+		ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType();
+
+		m = sort(m);
+
+		// The map size may change as we're iterating over it, so
+		// grab a snapshot of the entries in a separate list.
+		List<SimpleMapEntry> entries = new ArrayList<SimpleMapEntry>(m.size());
+		for (Map.Entry e : (Set<Map.Entry>)m.entrySet())
+			entries.add(new SimpleMapEntry(e.getKey(), e.getValue()));
+
+		out.startMap(entries.size());
+
+		for (SimpleMapEntry e : entries) {
+			Object value = e.value;
+			Object key = generalize(e.key, keyType);
+
+			serializeAnything(out, key, keyType, null, null);
+			serializeAnything(out, value, valueType, null, null);
+		}
+	}
+
+	private void serializeBeanMap(MsgPackOutputStream out, final BeanMap<?> m, String typeName) throws Exception {
+
+		List<BeanPropertyValue> values = m.getValues(isTrimNulls(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null);
+
+		int size = values.size();
+		for (BeanPropertyValue p : values)
+			if (p.getThrown() != null)
+				size--;
+		out.startMap(size);
+
+		for (BeanPropertyValue p : values) {
+			BeanPropertyMeta pMeta = p.getMeta();
+			ClassMeta<?> cMeta = p.getClassMeta();
+			String key = p.getName();
+			Object value = p.getValue();
+			Throwable t = p.getThrown();
+			if (t != null)
+				onBeanGetterException(pMeta, t);
+			else {
+				serializeAnything(out, key, null, null, null);
+				serializeAnything(out, value, cMeta, key, pMeta);
+			}
+		}
+	}
+
+	private static class SimpleMapEntry {
+		final Object key;
+		final Object value;
+
+		private SimpleMapEntry(Object key, Object value) {
+			this.key = key;
+			this.value = value;
+		}
+	}
+
+	@SuppressWarnings({"rawtypes", "unchecked"})
+	private void serializeCollection(MsgPackOutputStream out, Collection c, ClassMeta<?> type) throws Exception {
+
+		ClassMeta<?> elementType = type.getElementType();
+		List<Object> l = new ArrayList<Object>(c.size());
+
+		c = sort(c);
+		l.addAll(c);
+
+		out.startArray(l.size());
+
+		for (Object o : l)
+			serializeAnything(out, o, elementType, "<iterator>", null);
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/parser/InputStreamParser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/InputStreamParser.java b/juneau-core/src/main/java/org/apache/juneau/parser/InputStreamParser.java
index 46b1678..9fb15d6 100644
--- a/juneau-core/src/main/java/org/apache/juneau/parser/InputStreamParser.java
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/InputStreamParser.java
@@ -45,7 +45,7 @@ public abstract class InputStreamParser extends Parser {
 	}
 
 	@Override /* Parser */
-	public boolean isReaderParser() {
+	public final boolean isReaderParser() {
 		return false;
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/parser/InputStreamParserSession.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/InputStreamParserSession.java b/juneau-core/src/main/java/org/apache/juneau/parser/InputStreamParserSession.java
new file mode 100644
index 0000000..3d061a5
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/InputStreamParserSession.java
@@ -0,0 +1,50 @@
+// ***************************************************************************************************************************
+// * 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 byte-based parsers.
+ *
+ * <p>
+ * This class is NOT thread safe.  It is typically discarded after one-time use.
+ */
+public abstract class InputStreamParserSession 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 InputStreamParserSession(ParserContext ctx, ParserSessionArgs args) {
+		super(ctx, args);
+	}
+
+	/**
+	 * Constructor for sessions that don't require context.
+	 *
+	 * @param args
+	 * 	Runtime session arguments.
+	 */
+	protected InputStreamParserSession(ParserSessionArgs args) {
+		this(null, args);
+	}
+
+	@Override /* ParserSession */
+	public final boolean isReaderParser() {
+		return false;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/parser/ParseException.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParseException.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParseException.java
index f675377..f90d497 100644
--- a/juneau-core/src/main/java/org/apache/juneau/parser/ParseException.java
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParseException.java
@@ -15,10 +15,8 @@ package org.apache.juneau.parser;
 import static org.apache.juneau.internal.StringUtils.*;
 
 import java.text.*;
-import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.json.*;
 
 /**
  * Exception that indicates invalid syntax encountered during parsing.
@@ -30,12 +28,12 @@ public class ParseException extends FormattedException {
 	/**
 	 * Constructor.
 	 *
-	 * @param session The parser session to extract information from.
+	 * @param location The location of the parse exception.
 	 * @param message The exception message containing {@link MessageFormat}-style arguments.
 	 * @param args Optional {@link MessageFormat}-style arguments.
 	 */
-	public ParseException(ParserSession session, String message, Object...args) {
-		super(getMessage(session, message, args));
+	public ParseException(ObjectMap location, String message, Object...args) {
+		super(getMessage(location, message, args));
 	}
 
 	/**
@@ -51,11 +49,11 @@ public class ParseException extends FormattedException {
 	/**
 	 * Constructor.
 	 *
-	 * @param session The parser session to extract information from.
+	 * @param location The location of the parse exception.
 	 * @param causedBy The inner exception.
 	 */
-	public ParseException(ParserSession session, Exception causedBy) {
-		super(causedBy, getMessage(session, causedBy.getMessage()));
+	public ParseException(ObjectMap location, Exception causedBy) {
+		super(causedBy, getMessage(location, causedBy.getMessage()));
 	}
 
 	/**
@@ -67,13 +65,11 @@ public class ParseException extends FormattedException {
 		super(causedBy, getMessage(null, causedBy.getMessage()));
 	}
 
-	private static String getMessage(ParserSession session, String msg, Object... args) {
+	private static String getMessage(ObjectMap location, String msg, Object... args) {
 		if (args.length != 0)
 			msg = format(msg, args);
-		if (session != null) {
-			Map<String,Object> m = session.getLastLocation();
-			if (m != null && ! m.isEmpty())
-				msg = "Parse exception occurred at " + JsonSerializer.DEFAULT_LAX.toString(m) + ".  " + msg;
+		if (location != null && ! location.isEmpty()) {
+			msg = "Parse exception occurred at " + location.toString() + ".  " + msg;
 		}
 		return msg;
 	}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java b/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java
index c28dbf9..b077654 100644
--- a/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java
@@ -145,12 +145,10 @@ public abstract class Parser extends CoreObject {
 
 	/** General parser properties currently set on this parser. */
 	private final MediaType[] mediaTypes;
-	private final ParserContext ctx;
 
 	// Hidden constructor to force subclass from InputStreamParser or ReaderParser.
 	Parser(PropertyStore propertyStore) {
 		super(propertyStore);
-		this.ctx = createContext(ParserContext.class);
 
 		Consumes c = getAnnotation(Consumes.class, getClass());
 		if (c == null)
@@ -168,74 +166,35 @@ public abstract class Parser extends CoreObject {
 		return new ParserBuilder(propertyStore);
 	}
 
+
 	//--------------------------------------------------------------------------------
 	// Abstract methods
 	//--------------------------------------------------------------------------------
 
 	/**
-	 * Workhorse method.  Subclasses are expected to implement this method.
-	 *
-	 * @param session
-	 * 	The runtime session object returned by {@link #createSession(Object, ObjectMap, Method, Object,
-	 * 	Locale, TimeZone, MediaType)}.
-	 * 	If <jk>null</jk>, one will be created using {@link #createSession(Object)}.
-	 * @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(ParserSession session, 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
-	//--------------------------------------------------------------------------------
-
 	/**
-	 * Entry point for all parsing calls.
+	 * Create the session object that will be passed in to the parse method.
 	 *
 	 * <p>
-	 * Calls the {@link #doParse(ParserSession, ClassMeta)} implementation class and catches/re-wraps any exceptions
-	 * thrown.
+	 * It's up to implementers to decide what the session object looks like, although typically it's going to be a
+	 * subclass of {@link ParserSession}.
 	 *
-	 * @param session
-	 * 	The runtime session returned by {@link #createSession(Object, ObjectMap, Method, Object, Locale,
-	 * 	TimeZone, MediaType)}.
-	 * @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.
+	 * @param args
+	 * 	Runtime arguments.
+	 * @return The new session.
 	 */
-	public final <T> T parseSession(ParserSession session, ClassMeta<T> type) throws ParseException {
-		try {
-			if (type.isVoid())
-				return null;
-			return doParse(session, type);
-		} catch (ParseException e) {
-			throw e;
-		} catch (StackOverflowError e) {
-			throw new ParseException(session, "Depth too deep.  Stack overflow occurred.");
-		} catch (IOException e) {
-			throw new ParseException(session, "I/O exception occurred.  exception={0}, message={1}.",
-				e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e);
-		} catch (Exception e) {
-			throw new ParseException(session, "Exception occurred.  exception={0}, message={1}.",
-				e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e);
-		} finally {
-			session.close();
-		}
-	}
+	public abstract ParserSession createSession(ParserSessionArgs args);
+
+
+	//--------------------------------------------------------------------------------
+	// Other methods
+	//--------------------------------------------------------------------------------
 
 	/**
 	 * Parses input into the specified object type.
@@ -313,10 +272,13 @@ public abstract class Parser extends CoreObject {
 	 * 	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.
 	 */
-	@SuppressWarnings("unchecked")
 	public final <T> T parse(Object input, Type type, Type...args) throws ParseException {
-		ParserSession session = createSession(input);
-		return (T)parseSession(session, session.getClassMeta(type, args));
+		ParserSession session = createSession();
+		try {
+			return session.parse(input, type, args);
+		} finally {
+			session.close();
+		}
 	}
 
 	/**
@@ -355,8 +317,12 @@ public abstract class Parser extends CoreObject {
 	 * 	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 {
-		ParserSession session = createSession(input);
-		return parseSession(session, session.getClassMeta(type));
+		ParserSession session = createSession();
+		try {
+			return session.parse(input, type);
+		} finally {
+			session.close();
+		}
 	}
 
 	/**
@@ -376,37 +342,12 @@ public abstract class Parser extends CoreObject {
 	 * 	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 {
-		return parseSession(createSession(input), type);
-	}
-
-	/**
-	 * Create the session object that will be passed in to the parse 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 ParserSession}.
-	 *
-	 * @param input
-	 * 	The input.
-	 * 	See {@link #parse(Object, ClassMeta)} for supported input types.
-	 * @param op Optional additional properties.
-	 * @param javaMethod
-	 * 	Java method that invoked this parser.
-	 * 	When using the REST API, this is the Java method invoked by the REST call.
-	 * 	Can be used to access annotations defined on the method or class.
-	 * @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>).
-	 * @return The new session.
-	 */
-	public ParserSession createSession(Object input, ObjectMap op, Method javaMethod, Object outer, Locale locale,
-			TimeZone timeZone, MediaType mediaType) {
-		return new ParserSession(ctx, op, input, javaMethod, outer, locale, timeZone, mediaType);
+		ParserSession session = createSession();
+		try {
+			return session.parse(input, type);
+		} finally {
+			session.close();
+		}
 	}
 
 	/**
@@ -415,11 +356,10 @@ public abstract class Parser extends CoreObject {
 	 * <p>
 	 * Equivalent to calling <code>createSession(<jk>null</jk>, <jk>null</jk>)</code>.
 	 *
-	 * @param input The input.  See {@link #parse(Object, ClassMeta)} for supported input types.
 	 * @return The new context.
 	 */
-	protected final ParserSession createSession(Object input) {
-		return createSession(input, null, null, null, null, null, getPrimaryMediaType());
+	public final ParserSession createSession() {
+		return createSession(null);
 	}
 
 
@@ -452,39 +392,15 @@ public abstract class Parser extends CoreObject {
 	 * @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 {
-		ParserSession session = createSession(input);
+		ParserSession session = createSession();
 		try {
-			return doParseIntoMap(session, m, keyType, valueType);
-		} catch (ParseException e) {
-			throw e;
-		} catch (Exception e) {
-			throw new ParseException(session, e);
+			return session.parseIntoMap(input, m, keyType, valueType);
 		} finally {
 			session.close();
 		}
 	}
 
 	/**
-	 * Implementation method.
-	 *
-	 * <p>
-	 * Default implementation throws an {@link UnsupportedOperationException}.
-	 *
-	 * @param session
-	 * 	The runtime session object returned by
-	 * 	{@link #createSession(Object, ObjectMap, Method, Object, Locale, TimeZone, MediaType)}.
-	 * 	If <jk>null</jk>, one will be created using {@link #createSession(Object)}.
-	 * @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(ParserSession session, 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>
@@ -504,40 +420,16 @@ public abstract class Parser extends CoreObject {
 	 * 	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 {
-		ParserSession session = createSession(input);
+	public final <E> Collection<E> parseIntoCollection(Object input, Collection<E> c, Type elementType) throws ParseException {
+		ParserSession session = createSession();
 		try {
-			return doParseIntoCollection(session, c, elementType);
-		} catch (ParseException e) {
-			throw e;
-		} catch (Exception e) {
-			throw new ParseException(session, e);
+			return session.parseIntoCollection(input, c, elementType);
 		} finally {
 			session.close();
 		}
 	}
 
 	/**
-	 * Implementation method.
-	 *
-	 * <p>
-	 * Default implementation throws an {@link UnsupportedOperationException}.
-	 *
-	 * @param session
-	 * 	The runtime session object returned by {@link #createSession(Object, ObjectMap, Method, Object,
-	 * 	Locale, TimeZone, MediaType)}.
-	 * 	If <jk>null</jk>, one will be created using {@link #createSession(Object)}.
-	 * @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(ParserSession session, 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.
 	 *
@@ -561,13 +453,9 @@ public abstract class Parser extends CoreObject {
 	public final Object[] parseArgs(Object input, Type[] argTypes) throws ParseException {
 		if (argTypes == null || argTypes.length == 0)
 			return new Object[0];
-		ParserSession session = createSession(input);
+		ParserSession session = createSession();
 		try {
-			return doParse(session, session.getArgsClassMeta(argTypes));
-		} catch (ParseException e) {
-			throw e;
-		} catch (Exception e) {
-			throw new ParseException(session, e);
+			return session.parseArgs(input, argTypes);
 		} finally {
 			session.close();
 		}
@@ -579,83 +467,6 @@ public abstract class Parser extends CoreObject {
 	//--------------------------------------------------------------------------------
 
 	/**
-	 * Converts the specified string to the specified type.
-	 *
-	 * @param session The session object.
-	 * @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" })
-	protected <T> T convertAttrToType(ParserSession session, 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(session, 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(session, "Invalid conversion from string to class ''{0}''", type);
-		}
-
-		if (transform != null)
-			o = transform.unswap(session, 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 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 void setName(ClassMeta<?> cm, Object o, Object name) throws Exception {
-		if (cm != null) {
-			Setter m = cm.getNameProperty();
-			if (m != null)
-				m.set(o, name);
-		}
-	}
-
-	/**
 	 * Returns the media types handled based on the value of the {@link Consumes} annotation on the parser class.
 	 *
 	 * <p>

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/parser/ParserContext.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserContext.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserContext.java
index e8fd4b7..583649f 100644
--- a/juneau-core/src/main/java/org/apache/juneau/parser/ParserContext.java
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserContext.java
@@ -21,6 +21,12 @@ import org.apache.juneau.json.*;
 public class ParserContext extends BeanContext {
 
 	/**
+	 * Default context with all default values.
+	 */
+	@SuppressWarnings("hiding")
+	protected static final ParserContext DEFAULT = new ParserContext(PropertyStore.create());
+
+	/**
 	 * <b>Configuration property:</b>  Trim parsed strings.
 	 *
 	 * <ul>

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroup.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroup.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroup.java
index 846ebcb..d9ee830 100644
--- a/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroup.java
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroup.java
@@ -92,7 +92,7 @@ public final class ParserGroup {
 	 * 	tried to match against media types.
 	 */
 	public ParserGroup(PropertyStore propertyStore, Parser[] parsers) {
-		this.propertyStore = PropertyStore.create(propertyStore);
+		this.propertyStore = propertyStore.copy();
 		this.parsers = Collections.unmodifiableList(new ArrayList<Parser>(Arrays.asList(parsers)));
 
 		List<MediaType> lmt = new ArrayList<MediaType>();
@@ -187,7 +187,7 @@ public final class ParserGroup {
 	 * @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/parser/ParserInput.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserInput.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserInput.java
deleted file mode 100644
index 773606e..0000000
--- a/juneau-core/src/main/java/org/apache/juneau/parser/ParserInput.java
+++ /dev/null
@@ -1,230 +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.parser;
-
-import static org.apache.juneau.internal.IOUtils.*;
-import static org.apache.juneau.internal.StringUtils.*;
-
-import java.io.*;
-import java.nio.charset.*;
-
-import org.apache.juneau.*;
-
-/**
- * A wrapper around an object that a parser reads its input from.
- *
- * <p>
- * For character-based parsers, the input object can be any of the following:
- * <ul>
- * 	<li>{@link Reader}
- * 	<li>{@link CharSequence}
- * 	<li>{@link InputStream}
- * 	<li><code><jk>byte</jk>[]</code>
- * 	<li>{@link File}
- * 	<li><code><jk>null</jk></code>
- * </ul>
- *
- * <p>
- * For stream-based parsers, the input object can be any of the following:
- * <ul>
- * 	<li>{@link InputStream}
- * 	<li><code><jk>byte</jk>[]</code>
- * 	<li>{@link File}
- * 	<li>{@link String} - Hex-encoded bytes.  (not BASE-64!)
- * 	<li><code><jk>null</jk></code>
- * </ul>
- */
-public class ParserInput {
-
-	private Object input;
-	private String inputString;
-	private InputStream inputStream;
-	private Reader reader, noCloseReader;
-	private boolean debug, strict;
-	private String fileCharset, inputStreamCharset;
-
-	/**
-	 * Constructor.
-	 *
-	 * @param input
-	 * @param debug
-	 * @param strict
-	 * @param fileCharset
-	 * @param inputStreamCharset
-	 */
-	public ParserInput(Object input, boolean debug, boolean strict, String fileCharset, String inputStreamCharset) {
-		this.input = input;
-		this.debug = debug;
-		this.strict = strict;
-		this.fileCharset = fileCharset;
-		this.inputStreamCharset = inputStreamCharset;
-	}
-
-	/**
-	 * 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 {
-		try {
-			if (input == null)
-				return null;
-			if (input instanceof InputStream) {
-				if (debug) {
-					byte[] b = readBytes((InputStream)input, 1024);
-					inputString = toHex(b);
-					return new ByteArrayInputStream(b);
-				}
-				return (InputStream)input;
-			}
-			if (input instanceof byte[]) {
-				if (debug)
-					inputString = toHex((byte[])input);
-				return new ByteArrayInputStream((byte[])input);
-			}
-			if (input instanceof String) {
-				inputString = (String)input;
-				return new ByteArrayInputStream(fromHex((String)input));
-			}
-			if (input instanceof File) {
-				if (debug) {
-					byte[] b = readBytes((File)input);
-					inputString = toHex(b);
-					return new ByteArrayInputStream(b);
-				}
-				inputStream = new FileInputStream((File)input);
-				return inputStream;
-			}
-		} catch (IOException e) {
-			throw new ParseException(e);
-		}
-		throw new ParseException("Cannot convert object of type {0} to an InputStream.", input.getClass().getName());
-	}
-
-
-	/**
-	 * 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.
-	 */
-	public Reader getReader() throws Exception {
-		if (input == null)
-			return null;
-		if (input instanceof Reader) {
-			if (debug) {
-				inputString = read((Reader)input);
-				return new StringReader(inputString);
-			}
-			return (Reader)input;
-		}
-		if (input instanceof CharSequence) {
-			inputString = input.toString();
-			if (reader == null)
-				reader = new ParserReader((CharSequence)input);
-			return reader;
-		}
-		if (input instanceof InputStream || input instanceof byte[]) {
-			InputStream is = (
-				input instanceof InputStream
-				? (InputStream)input
-				: new ByteArrayInputStream((byte[])input)
-			);
-			if (noCloseReader == null) {
-				CharsetDecoder cd = (
-					"default".equalsIgnoreCase(inputStreamCharset)
-					? Charset.defaultCharset()
-					: Charset.forName(inputStreamCharset)
-				).newDecoder();
-				if (strict) {
-					cd.onMalformedInput(CodingErrorAction.REPORT);
-					cd.onUnmappableCharacter(CodingErrorAction.REPORT);
-				} else {
-					cd.onMalformedInput(CodingErrorAction.REPLACE);
-					cd.onUnmappableCharacter(CodingErrorAction.REPLACE);
-				}
-				noCloseReader = new InputStreamReader(is, cd);
-			}
-			if (debug) {
-				inputString = read(noCloseReader);
-				return new StringReader(inputString);
-			}
-			return noCloseReader;
-		}
-		if (input instanceof File) {
-			if (reader == null) {
-				CharsetDecoder cd = (
-					"default".equalsIgnoreCase(fileCharset)
-					? Charset.defaultCharset()
-					: Charset.forName(fileCharset)
-				).newDecoder();
-				if (strict) {
-					cd.onMalformedInput(CodingErrorAction.REPORT);
-					cd.onUnmappableCharacter(CodingErrorAction.REPORT);
-				} else {
-					cd.onMalformedInput(CodingErrorAction.REPLACE);
-					cd.onUnmappableCharacter(CodingErrorAction.REPLACE);
-				}
-				reader = new InputStreamReader(new FileInputStream((File)input), cd);
-			}
-			if (debug) {
-				inputString = read(reader);
-				return new StringReader(inputString);
-			}
-			return reader;
-		}
-		throw new ParseException("Cannot convert object of type {0} to a Reader.", input.getClass().getName());
-	}
-
-	/**
-	 * Returns the raw input object passed into this session.
-	 *
-	 * @return The raw input object passed into this session.
-	 */
-	protected Object getRawInput() {
-		return input;
-	}
-
-	/**
-	 * Returns the input to this parser as a plain string.
-	 *
-	 * <p>
-	 * This method only returns a value if {@link BeanContext#BEAN_debug} is enabled.
-	 *
-	 * @return The input as a string, or <jk>null</jk> if debug mode not enabled.
-	 */
-	public String getInputAsString() {
-		return inputString;
-	}
-
-	/**
-	 * Perform cleanup on this context object if necessary.
-	 */
-	public void close() {
-		try {
-			if (inputStream != null)
-				inputStream.close();
-			if (reader != null)
-				reader.close();
-		} catch (IOException e) {
-			throw new BeanRuntimeException(e);
-		}
-	}
-}
\ 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/ParserListener.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserListener.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserListener.java
index 75d39bb..83ac9a7 100644
--- a/juneau-core/src/main/java/org/apache/juneau/parser/ParserListener.java
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserListener.java
@@ -29,10 +29,11 @@ public class ParserListener {
 	 * Otherwise, the parser will throw a {@link ParseException}.
 	 *
 	 * @param <T> The class type of the bean.
-	 * @param session
-	 * 	The parser session.
+	 * @param session The parser session.
+	 * @param pipe
+	 * 	The parser input.
 	 * 	Note that if {@link BeanContext#BEAN_debug} is enabled on the parser, you can get the input as a string through
-	 * 	{@link ParserSession#getInputAsString()}.
+	 * 	{@link ParserPipe#getInputAsString()}.
 	 * @param propertyName The property name encountered in the document.
 	 * @param beanClass The bean class.
 	 * @param bean The bean.
@@ -41,8 +42,8 @@ public class ParserListener {
 	 * @param col
 	 * 	The column number where the unknown property was found (-1 if parser doesn't support line/column indicators).
 	 */
-	public <T> void onUnknownBeanProperty(ParserSession session, String propertyName, Class<T> beanClass, T bean, int line, int col) {
-		onError(session, null,
+	public <T> void onUnknownBeanProperty(ParserSession session, ParserPipe pipe, String propertyName, Class<T> beanClass, T bean, int line, int col) {
+		onError(session, pipe, null,
 			format("Unknown property ''{0}'' encountered while trying to parse into class ''{1}'' at line {2} column {3}",
 				propertyName, beanClass, line, col)
 		);
@@ -51,13 +52,15 @@ public class ParserListener {
 	/**
 	 * Called when an error occurs during parsing but is ignored.
 	 *
-	 * @param session The parsers session.
+	 * @param session The parser session.
+	 * @param pipe
+	 * 	The parser input.
 	 * 	Note that if {@link BeanContext#BEAN_debug} is enabled on the parser, you can get the input as a string through
-	 * 	{@link ParserSession#getInputAsString()}.
+	 * 	{@link ParserPipe#getInputAsString()}.
 	 * @param t The throwable that was thrown by the getter method.
 	 * @param msg The error message.
 	 */
-	public void onError(ParserSession session, Throwable t, String msg) {
+	public void onError(ParserSession session, ParserPipe pipe, Throwable t, String msg) {
 		// Do something with this information.
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/parser/ParserPipe.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserPipe.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserPipe.java
new file mode 100644
index 0000000..4faf988
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserPipe.java
@@ -0,0 +1,278 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.parser;
+
+import static org.apache.juneau.internal.IOUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.io.*;
+import java.nio.charset.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * A wrapper around an object that a parser reads its input from.
+ *
+ * <p>
+ * For character-based parsers, the input object can be any of the following:
+ * <ul>
+ * 	<li>{@link Reader}
+ * 	<li>{@link CharSequence}
+ * 	<li>{@link InputStream}
+ * 	<li><code><jk>byte</jk>[]</code>
+ * 	<li>{@link File}
+ * 	<li><code><jk>null</jk></code>
+ * </ul>
+ *
+ * <p>
+ * For stream-based parsers, the input object can be any of the following:
+ * <ul>
+ * 	<li>{@link InputStream}
+ * 	<li><code><jk>byte</jk>[]</code>
+ * 	<li>{@link File}
+ * 	<li>{@link String} - Hex-encoded bytes.  (not BASE-64!)
+ * 	<li><code><jk>null</jk></code>
+ * </ul>
+ *
+ * <p>
+ * Note that Readers and InputStreams will NOT be automatically closed when {@link #close()} is called, but
+ * streams and readers created from other types (e.g. Files) WILL be automatically closed.
+ */
+public final class ParserPipe {
+
+	private final Object input;
+	private final boolean debug, strict;
+	private final String fileCharset, inputStreamCharset;
+
+	private String inputString;
+	private InputStream inputStream;
+	private Reader reader;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param input The parser input object.
+	 * @param debug
+	 * 	If <jk>true</jk>, the input contents will be copied locally and accessible via the {@link #getInputAsString()}
+	 * 	method.
+	 * 	This allows the contents of the pipe to be accessed when a problem occurs.
+	 * @param strict
+	 * 	If <jk>true</jk>, sets {@link CodingErrorAction#REPORT} on {@link CharsetDecoder#onMalformedInput(CodingErrorAction)}
+	 * 	and {@link CharsetDecoder#onUnmappableCharacter(CodingErrorAction)}.
+	 * 	Otherwise, sets them to {@link CodingErrorAction#REPLACE}.
+	 * @param fileCharset
+	 * 	The charset to expect when reading from {@link File Files}.
+	 * 	Use <js>"default"</js> to specify {@link Charset#defaultCharset()}.
+	 * @param inputStreamCharset
+	 * 	The charset to expect when reading from {@link InputStream InputStreams}.
+	 * 	Use <js>"default"</js> to specify {@link Charset#defaultCharset()}.
+	 */
+	public ParserPipe(Object input, boolean debug, boolean strict, String fileCharset, String inputStreamCharset) {
+		this.input = input;
+		this.debug = debug;
+		this.strict = strict;
+		this.fileCharset = fileCharset;
+		this.inputStreamCharset = inputStreamCharset;
+		if (input instanceof CharSequence)
+			this.inputString = input.toString();
+	}
+
+	/**
+	 * Shortcut constructor, typically for straight string input.
+	 *
+	 * <p>
+	 * Equivalent to calling <code><jk>new</jk> ParserPipe(input, <jk>false</jk>, <jk>false</jk>, <jk>null</jk>, <jk>null</jk>);</code>
+	 *
+	 * @param input The input object.
+	 */
+	public ParserPipe(Object input) {
+		this(input, false, false, null, null);
+	}
+
+	/**
+	 * 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 IOException If object could not be converted to an input stream.
+	 */
+	public InputStream getInputStream() throws IOException {
+		if (input == null)
+			return null;
+
+		if (input instanceof InputStream) {
+			if (debug) {
+				byte[] b = readBytes((InputStream)input, 1024);
+				inputString = toHex(b);
+				inputStream = new ByteArrayInputStream(b);
+			} else {
+				inputStream = (InputStream)input;
+			}
+		} else if (input instanceof byte[]) {
+			if (debug)
+				inputString = toHex((byte[])input);
+			inputStream = new ByteArrayInputStream((byte[])input);
+		} else if (input instanceof String) {
+			inputString = (String)input;
+			inputStream = new ByteArrayInputStream(fromHex((String)input));
+		} else if (input instanceof File) {
+			if (debug) {
+				byte[] b = readBytes((File)input);
+				inputString = toHex(b);
+				inputStream = new ByteArrayInputStream(b);
+			} else {
+				inputStream = new FileInputStream((File)input);
+			}
+		} else {
+			throw new IOException("Cannot convert object of type "+input.getClass().getName()+" to an InputStream.");
+		}
+
+		return inputStream;
+	}
+
+
+	/**
+	 * 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 IOException If object could not be converted to a reader.
+	 */
+	public Reader getReader() throws IOException {
+		if (input == null)
+			return null;
+
+		if (input instanceof Reader) {
+			if (debug) {
+				inputString = read((Reader)input);
+				reader = new StringReader(inputString);
+			} else {
+				reader = (Reader)input;
+			}
+		} else if (input instanceof CharSequence) {
+			inputString = input.toString();
+			reader = new ParserReader(this);
+		} else if (input instanceof InputStream || input instanceof byte[]) {
+			InputStream is = (
+				input instanceof InputStream
+				? (InputStream)input
+				: new ByteArrayInputStream((byte[])input)
+			);
+			CharsetDecoder cd = (
+				"default".equalsIgnoreCase(inputStreamCharset)
+				? Charset.defaultCharset()
+				: Charset.forName(inputStreamCharset)
+			).newDecoder();
+			if (strict) {
+				cd.onMalformedInput(CodingErrorAction.REPORT);
+				cd.onUnmappableCharacter(CodingErrorAction.REPORT);
+			} else {
+				cd.onMalformedInput(CodingErrorAction.REPLACE);
+				cd.onUnmappableCharacter(CodingErrorAction.REPLACE);
+			}
+			reader = new InputStreamReader(is, cd);
+			if (debug) {
+				inputString = read(reader);
+				reader = new StringReader(inputString);
+			}
+		} else if (input instanceof File) {
+			CharsetDecoder cd = (
+				"default".equalsIgnoreCase(fileCharset)
+				? Charset.defaultCharset()
+				: Charset.forName(fileCharset)
+			).newDecoder();
+			if (strict) {
+				cd.onMalformedInput(CodingErrorAction.REPORT);
+				cd.onUnmappableCharacter(CodingErrorAction.REPORT);
+			} else {
+				cd.onMalformedInput(CodingErrorAction.REPLACE);
+				cd.onUnmappableCharacter(CodingErrorAction.REPLACE);
+			}
+			reader = new InputStreamReader(new FileInputStream((File)input), cd);
+			if (debug) {
+				inputString = read(reader);
+				reader = new StringReader(inputString);
+			}
+		} else {
+			throw new IOException("Cannot convert object of type "+input.getClass().getName()+" to a Reader.");
+		}
+
+		return reader;
+	}
+
+	/**
+	 * Returns the contents of this pipe as a buffered reader.
+	 *
+	 * <p>
+	 * If the reader passed into this pipe is already a buffered reader, that reader will be returned.
+	 *
+	 * @return The contents of this pipe as a buffered reader.
+	 * @throws Exception
+	 */
+	public Reader getBufferedReader() throws Exception {
+		return IOUtils.getBufferedReader(getReader());
+	}
+
+	/**
+	 * Returns the input to this parser as a plain string.
+	 *
+	 * <p>
+	 * This method only returns a value if {@link BeanContext#BEAN_debug} is enabled.
+	 *
+	 * @return The input as a string, or <jk>null</jk> if debug mode not enabled.
+	 */
+	public String getInputAsString() {
+		return inputString;
+	}
+
+	/**
+	 * Converts this pipe into a {@link ParserReader}.
+	 *
+	 * @return The converted pipe.
+	 * @throws Exception
+	 */
+	public ParserReader getParserReader() throws Exception {
+		if (input == null)
+			return null;
+		if (input instanceof ParserReader)
+			reader = (ParserReader)input;
+		else
+			reader = new ParserReader(this);
+		return (ParserReader)reader;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if the contents passed into this pipe was a {@link CharSequence}.
+	 *
+	 * @return <jk>true</jk> if the contents passed into this pipe was a {@link CharSequence}.
+	 */
+	public boolean isString() {
+		return inputString != null;
+	}
+
+	/**
+	 * Perform cleanup on this context object if necessary.
+	 */
+	public void close() {
+		try {
+			IOUtils.close(reader, inputStream);
+		} catch (IOException e) {
+			throw new BeanRuntimeException(e);
+		}
+	}
+}
\ 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/ParserReader.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserReader.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserReader.java
index daa1593..3c75e36 100644
--- a/juneau-core/src/main/java/org/apache/juneau/parser/ParserReader.java
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserReader.java
@@ -14,6 +14,7 @@ package org.apache.juneau.parser;
 
 import java.io.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.internal.*;
 
 /**
@@ -32,7 +33,8 @@ import org.apache.juneau.internal.*;
 public class ParserReader extends Reader {
 
 	/** Wrapped reader */
-	protected Reader r;
+	protected final Reader r;
+	private final ParserPipe pipe;
 
 	private char[] buff;       // Internal character buffer
 	private int line = 1;      // Current line number
@@ -42,33 +44,28 @@ public class ParserReader extends Reader {
 	private int iEnd = 0;      // The last good character position in the buffer
 	private boolean endReached, holesExist;
 
-	ParserReader() {}
-
 	/**
-	 * Constructor for input from a {@link CharSequence}.
+	 * Constructor.
 	 *
-	 * @param in The character sequence being read from.
+	 * @param pipe The parser input.
+	 * @throws IOException
 	 */
-	public ParserReader(CharSequence in) {
-		this.r = new CharSequenceReader(in);
-		if (in == null)
-			this.buff = new char[0];
-		else
+	public ParserReader(ParserPipe pipe) throws IOException {
+		this.pipe = pipe;
+		if (pipe.isString()) {
+			String in = pipe.getInputAsString();
+			this.r = new CharSequenceReader(in);
 			this.buff = new char[in.length() < 1024 ? in.length() : 1024];
+		} else {
+			Reader _r = pipe.getReader();
+			if (_r instanceof ParserReader)
+				this.r = ((ParserReader)_r).r;
+			else
+				this.r = _r;
+			this.buff = new char[1024];
+		}
 	}
 
-	/**
-	 * Constructor for input from a {@link Reader}).
-	 *
-	 * @param r The Reader being wrapped.
-	 */
-	public ParserReader(Reader r) {
-		if (r instanceof ParserReader)
-			this.r = ((ParserReader)r).r;
-		else
-			this.r = r;
-		this.buff = new char[1024];
-	}
 
 	/**
 	 * Returns the current line number position in this reader.
@@ -278,7 +275,7 @@ public class ParserReader extends Reader {
 	 * @return This object (for method chaining).
 	 * @throws IOException If a problem occurred trying to read from the reader.
 	 */
-	public final ParserReader unread() throws IOException {
+	public ParserReader unread() throws IOException {
 		if (iCurrent <= 0)
 			throw new IOException("Buffer underflow.");
 		iCurrent--;
@@ -293,7 +290,8 @@ public class ParserReader extends Reader {
 	 */
 	@Override /* Reader */
 	public void close() throws IOException {
-		r.close();
+		if (r != null)
+			r.close();
 	}
 
 	/**
@@ -417,4 +415,23 @@ public class ParserReader extends Reader {
 	public int read(char[] cbuf, int off, int len) throws IOException {
 		return r.read(cbuf, off, len);
 	}
+
+	/**
+	 * Returns the combined location information on both this reader and the session.
+	 *
+	 * @param session The session object to read the last location on.
+	 * @return A new map describing the current parse location.
+	 */
+	public ObjectMap getLocation(ParserSession session) {
+		return session.getLastLocation().append("line", getLine()).append("column", getColumn());
+	}
+
+	/**
+	 * Returns the pipe that was passed into the constructor.
+	 *
+	 * @return The pipe that was passed into the constructor.
+	 */
+	public final ParserPipe getPipe() {
+		return pipe;
+	}
 }