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:24 UTC

[09/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/html/HtmlSerializerSession.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
index 6de1afc..7ec11d6 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
@@ -12,24 +12,28 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.html;
 
+import static org.apache.juneau.html.HtmlSerializerSession.ContentResult.*;
 import static org.apache.juneau.html.HtmlSerializerContext.*;
 import static org.apache.juneau.msgpack.MsgPackSerializerContext.*;
 import static org.apache.juneau.xml.XmlUtils.*;
 
-import java.lang.reflect.*;
+import java.io.*;
 import java.util.*;
 import java.util.regex.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.http.*;
+import org.apache.juneau.internal.*;
 import org.apache.juneau.serializer.*;
+import org.apache.juneau.transform.*;
 import org.apache.juneau.xml.*;
+import org.apache.juneau.xml.annotation.*;
 
 /**
  * Session object that lives for the duration of a single use of {@link HtmlSerializer}.
  *
  * <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 class HtmlSerializerSession extends XmlSerializerSession {
 
@@ -54,26 +58,18 @@ public class HtmlSerializerSession extends XmlSerializerSession {
 	 * @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 HtmlSerializerSession(HtmlSerializerContext ctx, ObjectMap op, Method javaMethod,
-			Locale locale, TimeZone timeZone, MediaType mediaType, UriContext uriContext) {
-		super(ctx, op, javaMethod, locale, timeZone, mediaType, uriContext);
+	protected HtmlSerializerSession(HtmlSerializerContext ctx, SerializerSessionArgs args) {
+		super(ctx, args);
 		String labelParameter;
-		if (op == null || op.isEmpty()) {
+		ObjectMap p = getProperties();
+		if (p.isEmpty()) {
 			anchorText = Enum.valueOf(AnchorText.class, ctx.uriAnchorText);
 			detectLinksInStrings = ctx.detectLinksInStrings;
 			lookForLabelParameters = ctx.lookForLabelParameters;
@@ -81,12 +77,12 @@ public class HtmlSerializerSession extends XmlSerializerSession {
 			addKeyValueTableHeaders = ctx.addKeyValueTableHeaders;
 			addBeanTypeProperties = ctx.addBeanTypeProperties;
 		} else {
-			anchorText = Enum.valueOf(AnchorText.class, op.getString(HTML_uriAnchorText, ctx.uriAnchorText));
-			detectLinksInStrings = op.getBoolean(HTML_detectLinksInStrings, ctx.detectLinksInStrings);
-			lookForLabelParameters = op.getBoolean(HTML_lookForLabelParameters, ctx.lookForLabelParameters);
-			labelParameter = op.getString(HTML_labelParameter, ctx.labelParameter);
-			addKeyValueTableHeaders = op.getBoolean(HTML_addKeyValueTableHeaders, ctx.addKeyValueTableHeaders);
-			addBeanTypeProperties = op.getBoolean(MSGPACK_addBeanTypeProperties, ctx.addBeanTypeProperties);
+			anchorText = Enum.valueOf(AnchorText.class, p.getString(HTML_uriAnchorText, ctx.uriAnchorText));
+			detectLinksInStrings = p.getBoolean(HTML_detectLinksInStrings, ctx.detectLinksInStrings);
+			lookForLabelParameters = p.getBoolean(HTML_lookForLabelParameters, ctx.lookForLabelParameters);
+			labelParameter = p.getString(HTML_labelParameter, ctx.labelParameter);
+			addKeyValueTableHeaders = p.getBoolean(HTML_addKeyValueTableHeaders, ctx.addKeyValueTableHeaders);
+			addBeanTypeProperties = p.getBoolean(MSGPACK_addBeanTypeProperties, ctx.addBeanTypeProperties);
 		}
 		labelPattern = Pattern.compile("[\\?\\&]" + Pattern.quote(labelParameter) + "=([^\\&]*)");
 	}
@@ -98,12 +94,14 @@ public class HtmlSerializerSession extends XmlSerializerSession {
 	 * @return The output target object wrapped in an {@link HtmlWriter}.
 	 * @throws Exception
 	 */
-	public HtmlWriter getHtmlWriter(SerializerOutput out) throws Exception {
+	protected final HtmlWriter getHtmlWriter(SerializerPipe out) throws Exception {
 		Object output = out.getRawOutput();
 		if (output instanceof HtmlWriter)
 			return (HtmlWriter)output;
-		return new HtmlWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isTrimStrings(), getQuoteChar(),
+		HtmlWriter w = new HtmlWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isTrimStrings(), getQuoteChar(),
 			getUriResolver());
+		out.setWriter(w);
+		return w;
 	}
 
 	/**
@@ -196,4 +194,581 @@ public class HtmlSerializerSession extends XmlSerializerSession {
 	public boolean isHtmlMode() {
 		return true;
 	}
+
+	@Override /* Serializer */
+	protected void doSerialize(SerializerPipe out, Object o) throws Exception {
+		doSerialize(o, getHtmlWriter(out));
+	}
+
+	/**
+	 * Main serialization routine.
+	 *
+	 * @param session The serialization context object.
+	 * @param o The object being serialized.
+	 * @param w The writer to serialize to.
+	 * @return The same writer passed in.
+	 * @throws IOException If a problem occurred trying to send output to the writer.
+	 */
+	private HtmlWriter doSerialize(Object o, HtmlWriter w) throws Exception {
+		serializeAnything(w, o, getExpectedRootType(o), null, getInitialDepth()-1, null, true);
+		return w;
+	}
+
+	/**
+	 * Serialize the specified object to the specified writer.
+	 *
+	 * @param out The writer.
+	 * @param o The object to serialize.
+	 * @param eType The expected type of the object if this is a bean property.
+	 * @param name
+	 * 	The attribute name of this object if this object was a field in a JSON object (i.e. key of a
+	 * 	{@link java.util.Map.Entry} or property name of a bean).
+	 * @param xIndent The current indentation value.
+	 * @param pMeta The bean property being serialized, or <jk>null</jk> if we're not serializing a bean property.
+	 * @param isRoot <jk>true</jk> if this is the root element of the document.
+	 * @return The type of content encountered.  Either simple (no whitespace) or normal (elements with whitespace).
+	 * @throws Exception If a problem occurred trying to convert the output.
+	 */
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	protected ContentResult serializeAnything(HtmlWriter out, Object o,
+			ClassMeta<?> eType, String name, int xIndent, BeanPropertyMeta pMeta, boolean isRoot) throws Exception {
+
+		ClassMeta<?> aType = null;       // The actual type
+		ClassMeta<?> wType = null;     // The wrapped type (delegate)
+		ClassMeta<?> sType = object();   // The serialized type
+
+		if (eType == null)
+			eType = object();
+
+		aType = push(name, o, eType);
+
+		// Handle recursion
+		if (aType == null) {
+			o = null;
+			aType = object();
+		}
+
+		indent += xIndent;
+
+		ContentResult cr = CR_NORMAL;
+
+		// Determine the type.
+		if (o == null || (aType.isChar() && ((Character)o).charValue() == 0)) {
+			out.tag("null");
+			cr = ContentResult.CR_SIMPLE;
+
+		} else {
+
+			if (aType.isDelegate()) {
+				wType = aType;
+				aType = ((Delegate)o).getClassMeta();
+			}
+
+			sType = aType.getSerializedClassMeta();
+			String typeName = null;
+			if (isAddBeanTypeProperties() && ! eType.equals(aType))
+				typeName = aType.getDictionaryName();
+
+			// 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);
+			}
+
+			HtmlClassMeta html = sType.getExtendedMeta(HtmlClassMeta.class);
+			HtmlRender render = (pMeta == null ? null : pMeta.getExtendedMeta(HtmlBeanPropertyMeta.class).getRender());
+			if (render == null)
+				render = html.getRender();
+
+			if (render != null) {
+				Object o2 = render.getContent(this, o);
+				if (o2 != o) {
+					indent -= xIndent;
+					pop();
+					out.nl(indent);
+					return serializeAnything(out, o2, null, typeName, xIndent, null, false);
+				}
+			}
+
+			if (html.isAsXml() || (pMeta != null && pMeta.getExtendedMeta(HtmlBeanPropertyMeta.class).isAsXml())) {
+				pop();
+				indent++;
+				super.serializeAnything(out, o, null, null, null, false, XmlFormat.MIXED, false, false, null);
+				indent -= xIndent+1;
+				return cr;
+
+			} else if (html.isAsPlainText() || (pMeta != null && pMeta.getExtendedMeta(HtmlBeanPropertyMeta.class).isAsPlainText())) {
+				out.write(o == null ? "null" : o.toString());
+				cr = CR_SIMPLE;
+
+			} else if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) {
+				out.tag("null");
+				cr = CR_SIMPLE;
+
+			} else if (sType.isNumber()) {
+				if (eType.isNumber() && ! isRoot)
+					out.append(o);
+				else
+					out.sTag("number").append(o).eTag("number");
+				cr = CR_SIMPLE;
+
+			} else if (sType.isBoolean()) {
+				if (eType.isBoolean() && ! isRoot)
+					out.append(o);
+				else
+					out.sTag("boolean").append(o).eTag("boolean");
+				cr = CR_SIMPLE;
+
+			} else if (sType.isMap() || (wType != null && wType.isMap())) {
+				out.nlIf(! isRoot, xIndent+1);
+				if (o instanceof BeanMap)
+					serializeBeanMap(out, (BeanMap)o, eType, pMeta);
+				else
+					serializeMap(out, (Map)o, sType, eType.getKeyType(), eType.getValueType(), typeName, pMeta);
+
+			} else if (sType.isBean()) {
+				BeanMap m = toBeanMap(o);
+				Class<?> c = o.getClass();
+				if (c.isAnnotationPresent(HtmlLink.class)) {
+					HtmlLink h = o.getClass().getAnnotation(HtmlLink.class);
+					Object urlProp = m.get(h.hrefProperty());
+					Object nameProp = m.get(h.nameProperty());
+					out.oTag("a").attrUri("href", urlProp).append('>').text(nameProp).eTag("a");
+					cr = CR_SIMPLE;
+				} else {
+					out.nlIf(! isRoot, xIndent+2);
+					serializeBeanMap(out, m, eType, pMeta);
+				}
+
+			} else if (sType.isCollection() || sType.isArray() || (wType != null && wType.isCollection())) {
+				out.nlIf(! isRoot, xIndent+1);
+				serializeCollection(out, o, sType, eType, name, pMeta);
+
+			} else if (isUri(sType, pMeta, o)) {
+				String label = getAnchorText(pMeta, o);
+				out.oTag("a").attrUri("href", o).append('>');
+				out.text(label);
+				out.eTag("a");
+				cr = CR_SIMPLE;
+
+			} else {
+				if (isRoot)
+					out.sTag("string").text(toString(o)).eTag("string");
+				else
+					out.text(toString(o));
+				cr = CR_SIMPLE;
+			}
+		}
+		pop();
+		indent -= xIndent;
+		return cr;
+	}
+
+	/**
+	 * Identifies what the contents were of a serialized bean.
+	 */
+	static enum ContentResult {
+		CR_SIMPLE,    // Simple content.  Shouldn't use whitespace.
+		CR_NORMAL     // Normal content.  Use whitespace.
+	}
+
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	private void serializeMap(HtmlWriter out, Map m, ClassMeta<?> sType,
+			ClassMeta<?> eKeyType, ClassMeta<?> eValueType, String typeName, BeanPropertyMeta ppMeta) throws Exception {
+
+		ClassMeta<?> keyType = eKeyType == null ? string() : eKeyType;
+		ClassMeta<?> valueType = eValueType == null ? object() : eValueType;
+		ClassMeta<?> aType = getClassMetaForObject(m);       // The actual type
+
+		int i = indent;
+
+		out.oTag(i, "table");
+
+		if (typeName != null && ppMeta != null && ppMeta.getClassMeta() != aType)
+			out.attr(getBeanTypePropertyName(sType), typeName);
+
+		out.append(">").nl(i+1);
+		if (isAddKeyValueTableHeaders() && ! (aType.getExtendedMeta(HtmlClassMeta.class).isNoTableHeaders()
+				|| (ppMeta != null && ppMeta.getExtendedMeta(HtmlBeanPropertyMeta.class).isNoTableHeaders()))) {
+			out.sTag(i+1, "tr").nl(i+2);
+			out.sTag(i+2, "th").append("key").eTag("th").nl(i+3);
+			out.sTag(i+2, "th").append("value").eTag("th").nl(i+3);
+			out.ie(i+1).eTag("tr").nl(i+2);
+		}
+		for (Map.Entry e : (Set<Map.Entry>)m.entrySet()) {
+
+			Object key = generalize(e.getKey(), keyType);
+			Object value = null;
+			try {
+				value = e.getValue();
+			} catch (StackOverflowError t) {
+				throw t;
+			} catch (Throwable t) {
+				onError(t, "Could not call getValue() on property ''{0}'', {1}", e.getKey(), t.getLocalizedMessage());
+			}
+
+			String link = getLink(ppMeta);
+			String style = getStyle(this, ppMeta, value);
+
+			out.sTag(i+1, "tr").nl(i+2);
+			out.oTag(i+2, "td");
+			if (style != null)
+				out.attr("style", style);
+			out.cTag();
+			if (link != null)
+				out.oTag(i+3, "a").attrUri("href", link.replace("{#}", StringUtils.toString(value))).cTag();
+			ContentResult cr = serializeAnything(out, key, keyType, null, 2, null, false);
+			if (link != null)
+				out.eTag("a");
+			if (cr == CR_NORMAL)
+				out.i(i+2);
+			out.eTag("td").nl(i+2);
+			out.sTag(i+2, "td");
+			cr = serializeAnything(out, value, valueType, (key == null ? "_x0000_" : toString(key)), 2, null, false);
+			if (cr == CR_NORMAL)
+				out.ie(i+2);
+			out.eTag("td").nl(i+2);
+			out.ie(i+1).eTag("tr").nl(i+1);
+		}
+		out.ie(i).eTag("table").nl(i);
+	}
+
+	private void serializeBeanMap(HtmlWriter out, BeanMap<?> m, ClassMeta<?> eType,
+			BeanPropertyMeta ppMeta) throws Exception {
+		int i = indent;
+
+		out.oTag(i, "table");
+
+		String typeName = m.getMeta().getDictionaryName();
+		if (typeName != null && eType != m.getClassMeta())
+			out.attr(getBeanTypePropertyName(m.getClassMeta()), typeName);
+
+		out.append('>').nl(i);
+		if (isAddKeyValueTableHeaders() && ! (m.getClassMeta().getExtendedMeta(HtmlClassMeta.class).isNoTableHeaders()
+				|| (ppMeta != null && ppMeta.getExtendedMeta(HtmlBeanPropertyMeta.class).isNoTableHeaders()))) {
+			out.sTag(i+1, "tr").nl(i+1);
+			out.sTag(i+2, "th").append("key").eTag("th").nl(i+2);
+			out.sTag(i+2, "th").append("value").eTag("th").nl(i+2);
+			out.ie(i+1).eTag("tr").nl(i+1);
+		}
+
+		for (BeanPropertyValue p : m.getValues(isTrimNulls())) {
+			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);
+
+			if (canIgnoreValue(cMeta, key, value))
+				continue;
+
+			String link = cMeta.isCollectionOrArray() ? null : getLink(pMeta);
+
+			out.sTag(i+1, "tr").nl(i+1);
+			out.sTag(i+2, "td").text(key).eTag("td").nl(i+2);
+			out.oTag(i+2, "td");
+			String style = getStyle(this, pMeta, value);
+			if (style != null)
+				out.attr("style", style);
+			out.cTag();
+
+			try {
+				if (link != null)
+					out.oTag(i+3, "a").attrUri("href", m.resolveVars(link)).cTag();
+				ContentResult cr = serializeAnything(out, value, cMeta, key, 2, pMeta, false);
+				if (cr == CR_NORMAL)
+					out.i(i+2);
+				if (link != null)
+					out.eTag("a");
+			} catch (SerializeException e) {
+				throw e;
+			} catch (Error e) {
+				throw e;
+			} catch (Throwable e) {
+				e.printStackTrace();
+				onBeanGetterException(pMeta, e);
+			}
+			out.eTag("td").nl(i+2);
+			out.ie(i+1).eTag("tr").nl(i+1);
+		}
+		out.ie(i).eTag("table").nl(i);
+	}
+
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	private void serializeCollection(HtmlWriter out, Object in, ClassMeta<?> sType,
+			ClassMeta<?> eType, String name, BeanPropertyMeta ppMeta) throws Exception {
+
+		ClassMeta<?> seType = sType.getElementType();
+		if (seType == null)
+			seType = object();
+
+		Collection c = (sType.isCollection() ? (Collection)in : toList(sType.getInnerClass(), in));
+
+		int i = indent;
+		if (c.isEmpty()) {
+			out.appendln(i, "<ul></ul>");
+			return;
+		}
+
+		String type2 = null;
+		if (sType != eType)
+			type2 = sType.getDictionaryName();
+		if (type2 == null)
+			type2 = "array";
+
+		c = sort(c);
+
+		HtmlBeanPropertyMeta hbpMeta = (ppMeta == null ? null : ppMeta.getExtendedMeta(HtmlBeanPropertyMeta.class));
+		String btpn = getBeanTypePropertyName(eType);
+
+		// Look at the objects to see how we're going to handle them.  Check the first object to see how we're going to
+		// handle this.
+		// If it's a map or bean, then we'll create a table.
+		// Otherwise, we'll create a list.
+		Object[] th = getTableHeaders(c, hbpMeta);
+
+		if (th != null) {
+
+			out.oTag(i, "table").attr(btpn, type2).append('>').nl(i+1);
+			out.sTag(i+1, "tr").nl(i+2);
+			for (Object key : th) {
+				out.sTag(i+2, "th");
+				out.text(convertToType(key, String.class));
+				out.eTag("th").nl(i+2);
+			}
+			out.ie(i+1).eTag("tr").nl(i+1);
+
+			for (Object o : c) {
+				ClassMeta<?> cm = getClassMetaForObject(o);
+
+				if (cm != null && cm.getPojoSwap() != null) {
+					PojoSwap f = cm.getPojoSwap();
+					o = f.swap(this, o);
+					cm = cm.getSerializedClassMeta();
+				}
+
+				out.oTag(i+1, "tr");
+				String typeName = (cm == null ? null : cm.getDictionaryName());
+				String typeProperty = getBeanTypePropertyName(cm);
+
+				if (typeName != null && eType.getElementType() != cm)
+					out.attr(typeProperty, typeName);
+				out.cTag().nl(i+2);
+
+				if (cm == null) {
+					serializeAnything(out, o, null, null, 1, null, false);
+
+				} else if (cm.isMap() && ! (cm.isBeanMap())) {
+					Map m2 = sort((Map)o);
+
+					for (Object k : th) {
+						out.sTag(i+2, "td");
+						ContentResult cr = serializeAnything(out, m2.get(k), eType.getElementType(), toString(k), 2, null, false);
+						if (cr == CR_NORMAL)
+							out.i(i+2);
+						out.eTag("td").nl(i+2);
+					}
+				} else {
+					BeanMap m2 = null;
+					if (o instanceof BeanMap)
+						m2 = (BeanMap)o;
+					else
+						m2 = toBeanMap(o);
+
+					for (Object k : th) {
+						BeanMapEntry p = m2.getProperty(toString(k));
+						BeanPropertyMeta pMeta = p.getMeta();
+						String link = pMeta.getClassMeta().isCollectionOrArray() ? null : getLink(pMeta);
+						Object value = p.getValue();
+						String style = getStyle(this, pMeta, value);
+						out.oTag(i+2, "td");
+						if (style != null)
+							out.attr("style", style);
+						out.cTag();
+						if (link != null)
+							out.oTag("a").attrUri("href", m2.resolveVars(link)).cTag();
+						ContentResult cr = serializeAnything(out, value, pMeta.getClassMeta(), p.getKey().toString(), 2, pMeta, false);
+						if (cr == CR_NORMAL)
+							out.i(i+2);
+						if (link != null)
+							out.eTag("a");
+						out.eTag("td").nl(i+2);
+					}
+				}
+				out.ie(i+1).eTag("tr").nl(i+1);
+			}
+			out.ie(i).eTag("table").nl(i);
+
+		} else {
+			out.oTag(i, "ul");
+			if (! type2.equals("array"))
+				out.attr(btpn, type2);
+			out.append('>').nl(i+1);
+			for (Object o : c) {
+				out.oTag(i+1, "li");
+				String style = getStyle(this, ppMeta, o);
+				String link = getLink(ppMeta);
+				if (style != null)
+					out.attr("style", style);
+				out.cTag();
+				if (link != null)
+					out.oTag(i+2, "a").attrUri("href", link.replace("{#}", StringUtils.toString(o))).cTag();
+				ContentResult cr = serializeAnything(out, o, eType.getElementType(), name, 1, null, false);
+				if (link != null)
+					out.eTag("a");
+				if (cr == CR_NORMAL)
+					out.ie(i+1);
+				out.eTag("li").nl(i+1);
+			}
+			out.ie(i).eTag("ul").nl(i);
+		}
+	}
+
+	private static HtmlRender<?> getRender(HtmlSerializerSession session, BeanPropertyMeta pMeta, Object value) {
+		if (pMeta == null)
+			return null;
+		HtmlBeanPropertyMeta hpMeta = pMeta.getExtendedMeta(HtmlBeanPropertyMeta.class);
+		HtmlRender<?> render = hpMeta.getRender();
+		if (render != null)
+			return render;
+		ClassMeta<?> cMeta = session.getClassMetaForObject(value);
+		render = cMeta == null ? null : cMeta.getExtendedMeta(HtmlClassMeta.class).getRender();
+		return render;
+	}
+
+	@SuppressWarnings({"rawtypes","unchecked"})
+	private static String getStyle(HtmlSerializerSession session, BeanPropertyMeta pMeta, Object value) {
+		HtmlRender render = getRender(session, pMeta, value);
+		return render == null ? null : render.getStyle(session, value);
+	}
+
+	private static String getLink(BeanPropertyMeta pMeta) {
+		return pMeta == null ? null : pMeta.getExtendedMeta(HtmlBeanPropertyMeta.class).getLink();
+	}
+
+	/*
+	 * Returns the table column headers for the specified collection of objects.
+	 * Returns null if collection should not be serialized as a 2-dimensional table.
+	 * 2-dimensional tables are used for collections of objects that all have the same set of property names.
+	 */
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	private Object[] getTableHeaders(Collection c, HtmlBeanPropertyMeta hbpMeta) throws Exception {
+		if (c.size() == 0)
+			return null;
+		c = sort(c);
+		Object[] th;
+		Set<ClassMeta> prevC = new HashSet<ClassMeta>();
+		Object o1 = null;
+		for (Object o : c)
+			if (o != null) {
+				o1 = o;
+				break;
+			}
+		if (o1 == null)
+			return null;
+		ClassMeta<?> cm = getClassMetaForObject(o1);
+		if (cm.getPojoSwap() != null) {
+			PojoSwap f = cm.getPojoSwap();
+			o1 = f.swap(this, o1);
+			cm = cm.getSerializedClassMeta();
+		}
+		if (cm == null || ! cm.isMapOrBean())
+			return null;
+		if (cm.getInnerClass().isAnnotationPresent(HtmlLink.class))
+			return null;
+		HtmlClassMeta h = cm.getExtendedMeta(HtmlClassMeta.class);
+		if (h.isNoTables() || (hbpMeta != null && hbpMeta.isNoTables()))
+			return null;
+		if (h.isNoTableHeaders() || (hbpMeta != null && hbpMeta.isNoTableHeaders()))
+			return new Object[0];
+		if (canIgnoreValue(cm, null, o1))
+			return null;
+		if (cm.isMap() && ! cm.isBeanMap()) {
+			Set<Object> set = new LinkedHashSet<Object>();
+			for (Object o : c) {
+				if (! canIgnoreValue(cm, null, o)) {
+					if (! cm.isInstance(o))
+						return null;
+					Map m = sort((Map)o);
+					for (Map.Entry e : (Set<Map.Entry>)m.entrySet()) {
+						if (e.getValue() != null)
+							set.add(e.getKey() == null ? null : e.getKey());
+					}
+				}
+			}
+			th = set.toArray(new Object[set.size()]);
+		} else {
+			Map<String,Boolean> m = new LinkedHashMap<String,Boolean>();
+			for (Object o : c) {
+				if (! canIgnoreValue(cm, null, o)) {
+					if (! cm.isInstance(o))
+						return null;
+					BeanMap<?> bm = (o instanceof BeanMap ? (BeanMap)o : toBeanMap(o));
+					for (Map.Entry<String,Object> e : bm.entrySet()) {
+						String key = e.getKey();
+						if (e.getValue() != null)
+							m.put(key, true);
+						else if (! m.containsKey(key))
+							m.put(key, false);
+					}
+				}
+			}
+			for (Iterator<Boolean> i = m.values().iterator(); i.hasNext();)
+				if (! i.next())
+					i.remove();
+			th = m.keySet().toArray(new Object[m.size()]);
+		}
+		prevC.add(cm);
+		boolean isSortable = true;
+		for (Object o : th)
+			isSortable &= (o instanceof Comparable);
+		Set<Object> s = (isSortable ? new TreeSet<Object>() : new LinkedHashSet<Object>());
+		s.addAll(Arrays.asList(th));
+
+		for (Object o : c) {
+			if (o == null)
+				continue;
+			cm = getClassMetaForObject(o);
+			if (cm != null && cm.getPojoSwap() != null) {
+				PojoSwap f = cm.getPojoSwap();
+				o = f.swap(this, o);
+				cm = cm.getSerializedClassMeta();
+			}
+			if (prevC.contains(cm))
+				continue;
+			if (cm == null || ! (cm.isMap() || cm.isBean()))
+				return null;
+			if (cm.getInnerClass().isAnnotationPresent(HtmlLink.class))
+				return null;
+			if (canIgnoreValue(cm, null, o))
+				return null;
+			if (cm.isMap() && ! cm.isBeanMap()) {
+				Map m = (Map)o;
+				if (th.length != m.keySet().size())
+					return null;
+				for (Object k : m.keySet())
+					if (! s.contains(k.toString()))
+						return null;
+			} else {
+				BeanMap<?> bm = (o instanceof BeanMap ? (BeanMap)o : toBeanMap(o));
+				int l = 0;
+				for (String k : bm.keySet()) {
+					if (! s.contains(k))
+						return null;
+					l++;
+				}
+				if (s.size() != l)
+					return null;
+			}
+		}
+		return th;
+	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializer.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializer.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializer.java
index 938fecf..2d61aab 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializer.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializer.java
@@ -12,9 +12,6 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.html;
 
-import java.lang.reflect.*;
-import java.util.*;
-
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
 import org.apache.juneau.serializer.*;
@@ -47,19 +44,8 @@ public class HtmlStrippedDocSerializer extends HtmlSerializer {
 		super(propertyStore);
 	}
 
-	//---------------------------------------------------------------------------
-	// Overridden methods
-	//---------------------------------------------------------------------------
-
 	@Override /* Serializer */
-	protected void doSerialize(SerializerSession session, SerializerOutput out, Object o) throws Exception {
-		HtmlSerializerSession s = (HtmlSerializerSession)session;
-		HtmlWriter w = s.getHtmlWriter(out);
-		if (o == null
-			|| (o instanceof Collection && ((Collection<?>)o).size() == 0)
-			|| (o.getClass().isArray() && Array.getLength(o) == 0))
-			w.sTag(1, "p").append("No Results").eTag("p").nl(1);
-		else
-			super.doSerialize(s, out, o);
+	public WriterSerializerSession createSession(SerializerSessionArgs args) {
+		return new HtmlStrippedDocSerializerSession(ctx, args);
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializerSession.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializerSession.java
new file mode 100644
index 0000000..7589781
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializerSession.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.html;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.serializer.*;
+
+/**
+ * Session object that lives for the duration of a single use of {@link HtmlStrippedDocSerializer}.
+ *
+ * <p>
+ * This class is NOT thread safe.  It is meant to be discarded after one-time use.
+ */
+public class HtmlStrippedDocSerializerSession extends HtmlSerializerSession {
+
+	/**
+	 * 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.
+	 */
+	protected HtmlStrippedDocSerializerSession(HtmlSerializerContext ctx, SerializerSessionArgs args) {
+		super(ctx, args);
+	}
+
+	@Override /* SerializerSession */
+	protected void doSerialize(SerializerPipe out, Object o) throws Exception {
+		HtmlWriter w = getHtmlWriter(out);
+		if (o == null
+			|| (o instanceof Collection && ((Collection<?>)o).size() == 0)
+			|| (o.getClass().isArray() && Array.getLength(o) == 0))
+			w.sTag(1, "p").append("No Results").eTag("p").nl(1);
+		else
+			super.doSerialize(out, o);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java
index c93aa5d..e841931 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java
@@ -129,8 +129,6 @@ enum HtmlTag {
 			t = (end ? xP : P);
 		else if (c == 'h')
 			t = (end ? xHTML : HTML);
-		if (t == null)
-			throw new XmlParseException(null, "Unknown tag ''{0}'' encountered", tag);
 		return t;
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java b/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java
index b2da86a..1c3626e 100644
--- a/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java
@@ -261,7 +261,7 @@ public final class IOUtils {
 	 * 	reader.
 	 */
 	public static Reader getBufferedReader(Reader r) {
-		if (r instanceof BufferedReader || r instanceof StringReader)
+		if (r == null || r instanceof BufferedReader || r instanceof StringReader)
 			return r;
 		return new BufferedReader(r);
 	}
@@ -410,6 +410,59 @@ public final class IOUtils {
 	}
 
 	/**
+	 * Flushes multiple output streams and writers in a single call.
+	 *
+	 * @param o
+	 * 	The objects to flush.
+	 * 	<jk>null</jk> entries are ignored.
+	 * @throws IOException
+	 */
+	public static void flush(Object...o) throws IOException {
+		IOException ex = null;
+		for (Object o2 : o) {
+			try {
+				if (o2 instanceof OutputStream)
+					((OutputStream)o2).flush();
+				if (o2 instanceof Writer)
+					((Writer)o2).flush();
+			} catch (IOException e) {
+				ex = e;
+			}
+		}
+		if (ex != null)
+			throw ex;
+	}
+
+	/**
+	 * Close all specified input streams, output streams, readers, and writers.
+	 *
+	 * @param o
+	 * 	The list of all objects to close.
+	 * 	<jk>null</jk> entries are ignored.
+	 * @throws IOException
+	 */
+	public static void close(Object...o) throws IOException {
+		IOException ex = null;
+		for (Object o2 : o) {
+			try {
+				if (o2 instanceof InputStream)
+					((InputStream)o2).close();
+				if (o2 instanceof OutputStream)
+					((OutputStream)o2).close();
+				if (o2 instanceof Reader)
+					((Reader)o2).close();
+				if (o2 instanceof Writer)
+					((Writer)o2).close();
+			} catch (IOException e) {
+				ex = e;
+			}
+		}
+		if (ex != null)
+			throw ex;
+	}
+
+
+	/**
 	 * Converts an object to an <code>InputStream</code>.
 	 *
 	 * @param o

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/jso/JsoParser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/jso/JsoParser.java b/juneau-core/src/main/java/org/apache/juneau/jso/JsoParser.java
index 67a302f..599fa17 100644
--- a/juneau-core/src/main/java/org/apache/juneau/jso/JsoParser.java
+++ b/juneau-core/src/main/java/org/apache/juneau/jso/JsoParser.java
@@ -46,15 +46,8 @@ public final class JsoParser extends InputStreamParser {
 		return new JsoParserBuilder(propertyStore);
 	}
 
-
-	//--------------------------------------------------------------------------------
-	// Overridden methods
-	//--------------------------------------------------------------------------------
-
-	@SuppressWarnings("unchecked")
-	@Override /* InputStreamParser */
-	protected <T> T doParse(ParserSession session, ClassMeta<T> type) throws Exception {
-		ObjectInputStream ois = new ObjectInputStream(session.getInputStream());
-		return (T)ois.readObject();
+	@Override /* Parser */
+	public InputStreamParserSession createSession(ParserSessionArgs args) {
+		return new JsoParserSession(args);
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/jso/JsoParserSession.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/jso/JsoParserSession.java b/juneau-core/src/main/java/org/apache/juneau/jso/JsoParserSession.java
new file mode 100644
index 0000000..12f9cf2
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/jso/JsoParserSession.java
@@ -0,0 +1,45 @@
+// ***************************************************************************************************************************
+// * 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.jso;
+
+import java.io.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.parser.*;
+
+/**
+ * Session object that lives for the duration of a single use of {@link JsoParser}.
+ *
+ * <p>
+ * This class is NOT thread safe.
+ * It is typically discarded after one-time use although it can be reused against multiple inputs.
+ */
+@SuppressWarnings("unchecked")
+public class JsoParserSession extends InputStreamParserSession {
+
+	/**
+	 * Create a new session using properties specified in the context.
+	 *
+	 * @param args
+	 * 	Runtime session arguments.
+	 */
+	protected JsoParserSession(ParserSessionArgs args) {
+		super(args);
+	}
+
+	@Override /* ParserSession */
+	protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception {
+		ObjectInputStream ois = new ObjectInputStream(pipe.getInputStream());
+		return (T)ois.readObject();
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializer.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializer.java b/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializer.java
index 6ea9e43..6e3ceb8 100644
--- a/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializer.java
+++ b/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializer.java
@@ -34,6 +34,7 @@ public class JsoSerializer extends OutputStreamSerializer {
 	/** Default serializer, all default settings.*/
 	public static final JsoSerializer DEFAULT = new JsoSerializer(PropertyStore.create());
 
+	private final SerializerContext ctx;
 
 	/**
 	 * Constructor.
@@ -42,6 +43,7 @@ public class JsoSerializer extends OutputStreamSerializer {
 	 */
 	public JsoSerializer(PropertyStore propertyStore) {
 		super(propertyStore);
+		this.ctx = createContext(SerializerContext.class);
 	}
 
 	@Override /* CoreObject */
@@ -49,15 +51,8 @@ public class JsoSerializer extends OutputStreamSerializer {
 		return new JsoSerializerBuilder(propertyStore);
 	}
 
-	//--------------------------------------------------------------------------------
-	// Overridden methods
-	//--------------------------------------------------------------------------------
-
-	@Override /* OutputStreamSerializer */
-	protected void doSerialize(SerializerSession session, SerializerOutput out, Object o) throws Exception {
-		ObjectOutputStream oos = new ObjectOutputStream(out.getOutputStream());
-		oos.writeObject(o);
-		oos.flush();
-		oos.close();
+	@Override /* Serializer */
+	public OutputStreamSerializerSession createSession(SerializerSessionArgs args) {
+		return new JsoSerializerSession(ctx, args);
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializerSession.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializerSession.java
new file mode 100644
index 0000000..a445ac4
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializerSession.java
@@ -0,0 +1,52 @@
+// ***************************************************************************************************************************
+// * 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.jso;
+
+import java.io.*;
+
+import org.apache.juneau.serializer.*;
+
+/**
+ * Session object that lives for the duration of a single use of {@link JsoSerializer}.
+ *
+ * <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 JsoSerializerSession extends OutputStreamSerializerSession {
+
+	/**
+	 * 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 JsoSerializerSession(SerializerContext ctx, SerializerSessionArgs args) {
+		super(ctx, args);
+	}
+
+	@Override /* OutputStreamSerializerSession */
+	protected void doSerialize(SerializerPipe out, Object o) throws Exception {
+		ObjectOutputStream oos = new ObjectOutputStream(out.getOutputStream());
+		oos.writeObject(o);
+		oos.flush();
+		oos.close();
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java b/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java
index 3afd115..460ae5d 100644
--- a/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java
+++ b/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java
@@ -13,18 +13,10 @@
 package org.apache.juneau.json;
 
 import static org.apache.juneau.parser.ParserContext.*;
-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.annotation.*;
-import org.apache.juneau.http.*;
-import org.apache.juneau.internal.*;
 import org.apache.juneau.parser.*;
-import org.apache.juneau.transform.*;
 
 /**
  * Parses any valid JSON text into a POJO model.
@@ -116,7 +108,6 @@ import org.apache.juneau.transform.*;
  * 	<li>{@link JsonParserContext}
  * </ul>
  */
-@SuppressWarnings({ "rawtypes", "unchecked" })
 @Consumes("application/json,text/json")
 public class JsonParser extends ReaderParser {
 
@@ -126,9 +117,6 @@ public class JsonParser extends ReaderParser {
 	/** Default parser, all default settings.*/
 	public static final JsonParser DEFAULT_STRICT = new JsonParser.Strict(PropertyStore.create());
 
-	private static final AsciiSet decChars = new AsciiSet("0123456789");
-
-
 	/** Default parser, strict mode. */
 	public static class Strict extends JsonParser {
 
@@ -138,12 +126,7 @@ public class JsonParser extends ReaderParser {
 		 * @param propertyStore The property store containing all the settings for this object.
 		 */
 		public Strict(PropertyStore propertyStore) {
-			super(propertyStore);
-		}
-
-		@Override /* CoreObject */
-		protected ObjectMap getOverrideProperties() {
-			return super.getOverrideProperties().append(PARSER_strict, true);
+			super(propertyStore.copy().append(PARSER_strict, true));
 		}
 	}
 
@@ -165,680 +148,8 @@ public class JsonParser extends ReaderParser {
 		return new JsonParserBuilder(propertyStore);
 	}
 
-	private <T> T parseAnything(JsonParserSession session, ClassMeta<T> eType, ParserReader r, Object outer,
-			BeanPropertyMeta pMeta) throws Exception {
-
-		if (eType == null)
-			eType = (ClassMeta<T>)object();
-		PojoSwap<T,Object> transform = (PojoSwap<T,Object>)eType.getPojoSwap();
-		ClassMeta<?> sType = eType.getSerializedClassMeta();
-		session.setCurrentClass(sType);
-		String wrapperAttr = sType.getExtendedMeta(JsonClassMeta.class).getWrapperAttr();
-
-		Object o = null;
-
-		skipCommentsAndSpace(session, r);
-		if (wrapperAttr != null)
-			skipWrapperAttrStart(session, r, wrapperAttr);
-		int c = r.peek();
-		if (c == -1) {
-			if (session.isStrict())
-				throw new ParseException(session, "Empty input.");
-			// Let o be null.
-		} else if ((c == ',' || c == '}' || c == ']')) {
-			if (session.isStrict())
-				throw new ParseException(session, "Missing value detected.");
-			// Handle bug in Cognos 10.2.1 that can product non-existent values.
-			// Let o be null;
-		} else if (c == 'n') {
-			parseKeyword(session, "null", r);
-		} else if (sType.isObject()) {
-			if (c == '{') {
-				ObjectMap m2 = new ObjectMap(session);
-				parseIntoMap2(session, r, m2, string(), object(), pMeta);
-				o = session.cast(m2, pMeta, eType);
-			} else if (c == '[') {
-				o = parseIntoCollection2(session, r, new ObjectList(session), object(), pMeta);
-			} else if (c == '\'' || c == '"') {
-				o = parseString(session, r);
-				if (sType.isChar())
-					o = o.toString().charAt(0);
-			} else if (c >= '0' && c <= '9' || c == '-' || c == '.') {
-				o = parseNumber(session, r, null);
-			} else if (c == 't') {
-				parseKeyword(session, "true", r);
-				o = Boolean.TRUE;
-			} else {
-				parseKeyword(session, "false", r);
-				o = Boolean.FALSE;
-			}
-		} else if (sType.isBoolean()) {
-			o = parseBoolean(session, r);
-		} else if (sType.isCharSequence()) {
-			o = parseString(session, r);
-		} else if (sType.isChar()) {
-			o = parseString(session, r).charAt(0);
-		} else if (sType.isNumber()) {
-			o = parseNumber(session, r, (Class<? extends Number>)sType.getInnerClass());
-		} else if (sType.isMap()) {
-			Map m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : new ObjectMap(session));
-			o = parseIntoMap2(session, r, m, sType.getKeyType(), sType.getValueType(), pMeta);
-		} else if (sType.isCollection()) {
-			if (c == '{') {
-				ObjectMap m = new ObjectMap(session);
-				parseIntoMap2(session, r, m, string(), object(), pMeta);
-				o = session.cast(m, pMeta, eType);
-			} else {
-				Collection l = (sType.canCreateNewInstance(outer) ? (Collection)sType.newInstance() : new ObjectList(session));
-				o = parseIntoCollection2(session, r, l, sType, pMeta);
-			}
-		} else if (sType.canCreateNewBean(outer)) {
-			BeanMap m = session.newBeanMap(outer, sType.getInnerClass());
-			o = parseIntoBeanMap2(session, r, m).getBean();
-		} else if (sType.canCreateNewInstanceFromString(outer) && (c == '\'' || c == '"')) {
-			o = sType.newInstanceFromString(outer, parseString(session, r));
-		} else if (sType.canCreateNewInstanceFromNumber(outer) && isFirstNumberChar((char)c)) {
-			o = sType.newInstanceFromNumber(session, outer, parseNumber(session, r, sType.getNewInstanceFromNumberClass()));
-		} else if (sType.isArray() || sType.isArgs()) {
-			if (c == '{') {
-				ObjectMap m = new ObjectMap(session);
-				parseIntoMap2(session, r, m, string(), object(), pMeta);
-				o = session.cast(m, pMeta, eType);
-			} else {
-				ArrayList l = (ArrayList)parseIntoCollection2(session, r, new ArrayList(), sType, pMeta);
-				o = session.toArray(sType, l);
-			}
-		} else if (c == '{') {
-			Map m = new ObjectMap(session);
-			parseIntoMap2(session, r, m, sType.getKeyType(), sType.getValueType(), pMeta);
-			if (m.containsKey(session.getBeanTypePropertyName(eType)))
-				o = session.cast((ObjectMap)m, pMeta, eType);
-			else
-				throw new ParseException(session, "Class ''{0}'' could not be instantiated.  Reason: ''{1}''",
-						sType.getInnerClass().getName(), sType.getNotABeanReason());
-		} else if (sType.canCreateNewInstanceFromString(outer) && ! session.isStrict()) {
-			o = sType.newInstanceFromString(outer, parseString(session, r));
-		} else {
-			throw new ParseException(session, "Unrecognized syntax for class type ''{0}'', starting character ''{1}''",
-				sType, (char)c);
-		}
-
-		if (wrapperAttr != null)
-			skipWrapperAttrEnd(session, r);
-
-		if (transform != null && o != null)
-			o = transform.unswap(session, o, eType);
-
-		if (outer != null)
-			setParent(eType, o, outer);
-
-		return (T)o;
-	}
-
-	private Number parseNumber(JsonParserSession session, ParserReader r, Class<? extends Number> type) throws Exception {
-		int c = r.peek();
-		if (c == '\'' || c == '"')
-			return parseNumber(session, parseString(session, r), type);
-		return parseNumber(session, parseNumberString(r), type);
-	}
-
-	private static Number parseNumber(JsonParserSession session, String s, Class<? extends Number> type) throws Exception {
-
-		// JSON has slightly different number rules from Java.
-		// Strict mode enforces these different rules, lax does not.
-		if (session.isStrict()) {
-
-			// Lax allows blank strings to represent 0.
-			// Strict does not allow blank strings.
-			if (s.length() == 0)
-				throw new ParseException(session, "Invalid JSON number: ''{0}''", s);
-
-			// Need to weed out octal and hexadecimal formats:  0123,-0123,0x123,-0x123.
-			// Don't weed out 0 or -0.
-			boolean isNegative = false;
-			char c = s.charAt(0);
-			if (c == '-') {
-				isNegative = true;
-				c = (s.length() == 1 ? 'x' : s.charAt(1));
-			}
-
-			// JSON doesn't allow '.123' and '-.123'.
-			if (c == '.')
-				throw new ParseException(session, "Invalid JSON number: ''{0}''", s);
-
-			// '01' is not a valid number, but '0.1', '0e1', '0e+1' are valid.
-			if (c == '0' && s.length() > (isNegative ? 2 : 1)) {
-				char c2 = s.charAt((isNegative ? 2 : 1));
-				if (c2 != '.' && c2 != 'e' && c2 != 'E')
-					throw new ParseException(session, "Invalid JSON number: ''{0}''", s);
-			}
-
-			// JSON doesn't allow '1.' or '0.e1'.
-			int i = s.indexOf('.');
-			if (i != -1 && (s.length() == (i+1) || ! decChars.contains(s.charAt(i+1))))
-				throw new ParseException(session, "Invalid JSON number: ''{0}''", s);
-
-		}
-		return StringUtils.parseNumber(s, type);
-	}
-
-	private Boolean parseBoolean(JsonParserSession session, ParserReader r) throws Exception {
-		int c = r.peek();
-		if (c == '\'' || c == '"')
-			return Boolean.valueOf(parseString(session, r));
-		if (c == 't') {
-			parseKeyword(session, "true", r);
-			return Boolean.TRUE;
-		}
-		parseKeyword(session, "false", r);
-		return Boolean.FALSE;
-	}
-
-
-	private <K,V> Map<K,V> parseIntoMap2(JsonParserSession session, ParserReader r, Map<K,V> m, ClassMeta<K> keyType,
-			ClassMeta<V> valueType, BeanPropertyMeta pMeta) throws Exception {
-
-		if (keyType == null)
-			keyType = (ClassMeta<K>)string();
-
-		int S0=0; // Looking for outer {
-		int S1=1; // Looking for attrName start.
-		int S3=3; // Found attrName end, looking for :.
-		int S4=4; // Found :, looking for valStart: { [ " ' LITERAL.
-		int S5=5; // Looking for , or }
-		int S6=6; // Found , looking for attr start.
-
-		int state = S0;
-		String currAttr = null;
-		int c = 0;
-		while (c != -1) {
-			c = r.read();
-			if (state == S0) {
-				if (c == '{')
-					state = S1;
-			} else if (state == S1) {
-				if (c == '}') {
-					return m;
-				} else if (session.isCommentOrWhitespace(c)) {
-					skipCommentsAndSpace(session, r.unread());
-				} else {
-					currAttr = parseFieldName(session, r.unread());
-					state = S3;
-				}
-			} else if (state == S3) {
-				if (c == ':')
-					state = S4;
-			} else if (state == S4) {
-				if (session.isCommentOrWhitespace(c)) {
-					skipCommentsAndSpace(session, r.unread());
-				} else {
-					K key = convertAttrToType(session, m, currAttr, keyType);
-					V value = parseAnything(session, valueType, r.unread(), m, pMeta);
-					setName(valueType, value, key);
-					m.put(key, value);
-					state = S5;
-				}
-			} else if (state == S5) {
-				if (c == ',')
-					state = S6;
-				else if (session.isCommentOrWhitespace(c))
-					skipCommentsAndSpace(session, r.unread());
-				else if (c == '}') {
-					return m;
-				} else {
-					break;
-				}
-			} else if (state == S6) {
-				if (c == '}') {
-					break;
-				} else if (session.isCommentOrWhitespace(c)) {
-					skipCommentsAndSpace(session, r.unread());
-				} else {
-					currAttr = parseFieldName(session, r.unread());
-					state = S3;
-				}
-			}
-		}
-		if (state == S0)
-			throw new ParseException(session, "Expected '{' at beginning of JSON object.");
-		if (state == S1)
-			throw new ParseException(session, "Could not find attribute name on JSON object.");
-		if (state == S3)
-			throw new ParseException(session, "Could not find ':' following attribute name on JSON object.");
-		if (state == S4)
-			throw new ParseException(session, "Expected one of the following characters: {,[,',\",LITERAL.");
-		if (state == S5)
-			throw new ParseException(session, "Could not find '}' marking end of JSON object.");
-		if (state == S6)
-			throw new ParseException(session, "Unexpected '}' found in JSON object.");
-
-		return null; // Unreachable.
-	}
-
-	/*
-	 * Parse a JSON attribute from the character array at the specified position, then
-	 * set the position marker to the last character in the field name.
-	 */
-	private String parseFieldName(JsonParserSession session, ParserReader r) throws Exception {
-		int c = r.peek();
-		if (c == '\'' || c == '"')
-			return parseString(session, r);
-		if (session.isStrict())
-			throw new ParseException(session, "Unquoted attribute detected.");
-		r.mark();
-		// Look for whitespace.
-		while (c != -1) {
-			c = r.read();
-			if (c == ':' || session.isWhitespace(c) || c == '/') {
-				r.unread();
-				String s = r.getMarked().intern();
-				return s.equals("null") ? null : s;
-			}
-		}
-		throw new ParseException(session, "Could not find the end of the field name.");
-	}
-
-	private <E> Collection<E> parseIntoCollection2(JsonParserSession session, ParserReader r, Collection<E> l,
-			ClassMeta<?> type, BeanPropertyMeta pMeta) throws Exception {
-
-		int S0=0; // Looking for outermost [
-		int S1=1; // Looking for starting [ or { or " or ' or LITERAL or ]
-		int S2=2; // Looking for , or ]
-		int S3=3; // Looking for starting [ or { or " or ' or LITERAL
-
-		int argIndex = 0;
-
-		int state = S0;
-		int c = 0;
-		while (c != -1) {
-			c = r.read();
-			if (state == S0) {
-				if (c == '[')
-					state = S1;
-			} else if (state == S1) {
-				if (c == ']') {
-					return l;
-				} else if (session.isCommentOrWhitespace(c)) {
-					skipCommentsAndSpace(session, r.unread());
-				} else if (c != -1) {
-					l.add((E)parseAnything(session, type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), r.unread(), l, pMeta));
-					state = S2;
-				}
-			} else if (state == S2) {
-				if (c == ',') {
-					state = S3;
-				} else if (session.isCommentOrWhitespace(c)) {
-					skipCommentsAndSpace(session, r.unread());
-				} else if (c == ']') {
-					return l;
-				} else {
-					break;  // Invalid character found.
-				}
-			} else if (state == S3) {
-				if (session.isCommentOrWhitespace(c)) {
-					skipCommentsAndSpace(session, r.unread());
-				} else if (c == ']') {
-					break;
-				} else if (c != -1) {
-					l.add((E)parseAnything(session, type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), r.unread(), l, pMeta));
-					state = S2;
-				}
-			}
-		}
-		if (state == S0)
-			throw new ParseException(session, "Expected '[' at beginning of JSON array.");
-		if (state == S1)
-			throw new ParseException(session, "Expected one of the following characters: {,[,',\",LITERAL.");
-		if (state == S2)
-			throw new ParseException(session, "Expected ',' or ']'.");
-		if (state == S3)
-			throw new ParseException(session, "Unexpected trailing comma in array.");
-
-		return null;  // Unreachable.
-	}
-
-	private <T> BeanMap<T> parseIntoBeanMap2(JsonParserSession session, ParserReader r, BeanMap<T> m) throws Exception {
-
-		int S0=0; // Looking for outer {
-		int S1=1; // Looking for attrName start.
-		int S3=3; // Found attrName end, looking for :.
-		int S4=4; // Found :, looking for valStart: { [ " ' LITERAL.
-		int S5=5; // Looking for , or }
-
-		int state = S0;
-		String currAttr = "";
-		int c = 0;
-		int currAttrLine = -1, currAttrCol = -1;
-		while (c != -1) {
-			c = r.read();
-			if (state == S0) {
-				if (c == '{')
-					state = S1;
-			} else if (state == S1) {
-				if (c == '}') {
-					return m;
-				} else if (session.isCommentOrWhitespace(c)) {
-					skipCommentsAndSpace(session, r.unread());
-				} else {
-					r.unread();
-					currAttrLine= r.getLine();
-					currAttrCol = r.getColumn();
-					currAttr = parseFieldName(session, r);
-					state = S3;
-				}
-			} else if (state == S3) {
-				if (c == ':')
-					state = S4;
-			} else if (state == S4) {
-				if (session.isCommentOrWhitespace(c)) {
-					skipCommentsAndSpace(session, r.unread());
-				} else {
-					if (! currAttr.equals(session.getBeanTypePropertyName(m.getClassMeta()))) {
-						BeanPropertyMeta pMeta = m.getPropertyMeta(currAttr);
-						session.setCurrentProperty(pMeta);
-						if (pMeta == null) {
-							session.onUnknownProperty(currAttr, m, currAttrLine, currAttrCol);
-							parseAnything(session, object(), r.unread(), m.getBean(false), null); // Read content anyway to ignore it
-						} else {
-							ClassMeta<?> cm = pMeta.getClassMeta();
-							Object value = parseAnything(session, cm, r.unread(), m.getBean(false), pMeta);
-							setName(cm, value, currAttr);
-							pMeta.set(m, currAttr, value);
-						}
-						session.setCurrentProperty(null);
-					}
-					state = S5;
-				}
-			} else if (state == S5) {
-				if (c == ',')
-					state = S1;
-				else if (session.isCommentOrWhitespace(c))
-					skipCommentsAndSpace(session, r.unread());
-				else if (c == '}') {
-					return m;
-				}
-			}
-		}
-		if (state == S0)
-			throw new ParseException(session, "Expected '{' at beginning of JSON object.");
-		if (state == S1)
-			throw new ParseException(session, "Could not find attribute name on JSON object.");
-		if (state == S3)
-			throw new ParseException(session, "Could not find ':' following attribute name on JSON object.");
-		if (state == S4)
-			throw new ParseException(session, "Expected one of the following characters: {,[,',\",LITERAL.");
-		if (state == S5)
-			throw new ParseException(session, "Could not find '}' marking end of JSON object.");
-
-		return null; // Unreachable.
-	}
-
-	/*
-	 * Starting from the specified position in the character array, returns the
-	 * position of the character " or '.
-	 * If the string consists of a concatenation of strings (e.g. 'AAA' + "BBB"), this method
-	 * will automatically concatenate the strings and return the result.
-	 */
-	private String parseString(JsonParserSession session, ParserReader r) throws Exception  {
-		r.mark();
-		int qc = r.read();		// The quote character being used (" or ')
-		if (qc != '"' && session.isStrict()) {
-			String msg = (
-				qc == '\''
-				? "Invalid quote character \"{0}\" being used."
-				: "Did not find quote character marking beginning of string.  Character=\"{0}\""
-			);
-			throw new ParseException(session, msg, (char)qc);
-		}
-		final boolean isQuoted = (qc == '\'' || qc == '"');
-		String s = null;
-		boolean isInEscape = false;
-		int c = 0;
-		while (c != -1) {
-			c = r.read();
-			// Strict syntax requires that all control characters be escaped.
-			if (session.isStrict() && c <= 0x1F)
-				throw new ParseException("Unescaped control character encountered: ''0x{0}''", String.format("%04X", c));
-			if (isInEscape) {
-				switch (c) {
-					case 'n': r.replace('\n'); break;
-					case 'r': r.replace('\r'); break;
-					case 't': r.replace('\t'); break;
-					case 'f': r.replace('\f'); break;
-					case 'b': r.replace('\b'); break;
-					case '\\': r.replace('\\'); break;
-					case '/': r.replace('/'); break;
-					case '\'': r.replace('\''); break;
-					case '"': r.replace('"'); break;
-					case 'u': {
-						String n = r.read(4);
-						try {
-							r.replace(Integer.parseInt(n, 16), 6);
-						} catch (NumberFormatException e) {
-							throw new ParseException(session, "Invalid Unicode escape sequence in string.");
-						}
-						break;
-					}
-					default:
-						throw new ParseException(session, "Invalid escape sequence in string.");
-				}
-				isInEscape = false;
-			} else {
-				if (c == '\\') {
-					isInEscape = true;
-					r.delete();
-				} else if (isQuoted) {
-					if (c == qc) {
-						s = r.getMarked(1, -1);
-						break;
-					}
-				} else {
-					if (c == ',' || c == '}' || c == ']' || session.isWhitespace(c)) {
-						s = r.getMarked(0, -1);
-						r.unread();
-						break;
-					} else if (c == -1) {
-						s = r.getMarked(0, 0);
-						break;
-					}
-				}
-			}
-		}
-		if (s == null)
-			throw new ParseException(session, "Could not find expected end character ''{0}''.", (char)qc);
-
-		// Look for concatenated string (i.e. whitespace followed by +).
-		skipCommentsAndSpace(session, r);
-		if (r.peek() == '+') {
-			if (session.isStrict())
-				throw new ParseException(session, "String concatenation detected.");
-			r.read();	// Skip past '+'
-			skipCommentsAndSpace(session, r);
-			s += parseString(session, r);
-		}
-		return session.trim(s); // End of input reached.
-	}
-
-	/*
-	 * Looks for the keywords true, false, or null.
-	 * Throws an exception if any of these keywords are not found at the specified position.
-	 */
-	private static void parseKeyword(JsonParserSession session, String keyword, ParserReader r) throws Exception {
-		try {
-			String s = r.read(keyword.length());
-			if (s.equals(keyword))
-				return;
-			throw new ParseException(session, "Unrecognized syntax.");
-		} catch (IndexOutOfBoundsException e) {
-			throw new ParseException(session, "Unrecognized syntax.");
-		}
-	}
-
-	/*
-	 * Doesn't actually parse anything, but moves the position beyond any whitespace or comments.
-	 * If positionOnNext is 'true', then the cursor will be set to the point immediately after
-	 * the comments and whitespace.  Otherwise, the cursor will be set to the last position of
-	 * the comments and whitespace.
-	 */
-	private static void skipCommentsAndSpace(JsonParserSession session, ParserReader r) throws Exception {
-		int c = 0;
-		while ((c = r.read()) != -1) {
-			if (! session.isWhitespace(c)) {
-				if (c == '/') {
-					if (session.isStrict())
-						throw new ParseException(session, "Javascript comment detected.");
-					skipComments(session, r);
-				} else {
-					r.unread();
-					return;
-				}
-			}
-		}
-	}
-
-	/*
-	 * Doesn't actually parse anything, but moves the position beyond the construct "{wrapperAttr:" when
-	 * the @Json.wrapperAttr() annotation is used on a class.
-	 */
-	private void skipWrapperAttrStart(JsonParserSession session, ParserReader r, String wrapperAttr) throws Exception {
-
-		int S0=0; // Looking for outer {
-		int S1=1; // Looking for attrName start.
-		int S3=3; // Found attrName end, looking for :.
-		int S4=4; // Found :, looking for valStart: { [ " ' LITERAL.
-
-		int state = S0;
-		String currAttr = null;
-		int c = 0;
-		while (c != -1) {
-			c = r.read();
-			if (state == S0) {
-				if (c == '{')
-					state = S1;
-			} else if (state == S1) {
-				if (session.isCommentOrWhitespace(c)) {
-					skipCommentsAndSpace(session, r.unread());
-				} else {
-					currAttr = parseFieldName(session, r.unread());
-					if (! currAttr.equals(wrapperAttr))
-						throw new ParseException(session,
-							"Expected to find wrapper attribute ''{0}'' but found attribute ''{1}''", wrapperAttr, currAttr);
-					state = S3;
-				}
-			} else if (state == S3) {
-				if (c == ':')
-					state = S4;
-			} else if (state == S4) {
-				if (session.isCommentOrWhitespace(c)) {
-					skipCommentsAndSpace(session, r.unread());
-				} else {
-					r.unread();
-					return;
-				}
-			}
-		}
-		if (state == S0)
-			throw new ParseException(session, "Expected '{' at beginning of JSON object.");
-		if (state == S1)
-			throw new ParseException(session, "Could not find attribute name on JSON object.");
-		if (state == S3)
-			throw new ParseException(session, "Could not find ':' following attribute name on JSON object.");
-		if (state == S4)
-			throw new ParseException(session, "Expected one of the following characters: {,[,',\",LITERAL.");
-	}
-
-	/*
-	 * Doesn't actually parse anything, but moves the position beyond the construct "}" when
-	 * the @Json.wrapperAttr() annotation is used on a class.
-	 */
-	private static void skipWrapperAttrEnd(JsonParserSession session, ParserReader r) throws ParseException, IOException {
-		int c = 0;
-		while ((c = r.read()) != -1) {
-			if (! session.isWhitespace(c)) {
-				if (c == '/') {
-					if (session.isStrict())
-						throw new ParseException(session, "Javascript comment detected.");
-					skipComments(session, r);
-				} else if (c == '}') {
-					return;
-				} else {
-					throw new ParseException(session, "Could not find '}' at the end of JSON wrapper object.");
-				}
-			}
-		}
-	}
-
-	/*
-	 * Doesn't actually parse anything, but when positioned at the beginning of comment,
-	 * it will move the pointer to the last character in the comment.
-	 */
-	private static void skipComments(JsonParserSession session, ParserReader r) throws ParseException, IOException {
-		int c = r.read();
-		//  "/* */" style comments
-		if (c == '*') {
-			while (c != -1)
-				if ((c = r.read()) == '*')
-					if ((c = r.read()) == '/')
-						return;
-		//  "//" style comments
-		} else if (c == '/') {
-			while (c != -1) {
-				c = r.read();
-				if (c == -1 || c == '\n')
-					return;
-			}
-		}
-		throw new ParseException(session, "Open ended comment.");
-	}
-
-	/*
-	 * Call this method after you've finished a parsing a string to make sure that if there's any
-	 * remainder in the input, that it consists only of whitespace and comments.
-	 */
-	private static void validateEnd(JsonParserSession session, ParserReader r) throws Exception {
-		skipCommentsAndSpace(session, r);
-		int c = r.read();
-		if (c != -1 && c != ';')  // var x = {...}; expressions can end with a semicolon.
-			throw new ParseException(session, "Remainder after parse: ''{0}''.", (char)c);
-	}
-
-
-	//--------------------------------------------------------------------------------
-	// Entry point methods
-	//--------------------------------------------------------------------------------
-
-	@Override /* Parser */
-	public JsonParserSession createSession(Object input, ObjectMap op, Method javaMethod, Object outer, Locale locale,
-			TimeZone timeZone, MediaType mediaType) {
-		return new JsonParserSession(ctx, op, input, javaMethod, outer, locale, timeZone, mediaType);
-	}
-
 	@Override /* Parser */
-	protected <T> T doParse(ParserSession session, ClassMeta<T> type) throws Exception {
-		JsonParserSession s = (JsonParserSession)session;
-		ParserReader r = s.getReader();
-		if (r == null)
-			return null;
-		T o = parseAnything(s, type, r, s.getOuter(), null);
-		validateEnd(s, r);
-		return o;
-	}
-
-	@Override /* ReaderParser */
-	protected <K,V> Map<K,V> doParseIntoMap(ParserSession session, Map<K,V> m, Type keyType, Type valueType) throws Exception {
-		JsonParserSession s = (JsonParserSession)session;
-		ParserReader r = s.getReader();
-		m = parseIntoMap2(s, r, m, (ClassMeta<K>)s.getClassMeta(keyType), (ClassMeta<V>)s.getClassMeta(valueType), null);
-		validateEnd(s, r);
-		return m;
-	}
-
-	@Override /* ReaderParser */
-	protected <E> Collection<E> doParseIntoCollection(ParserSession session, Collection<E> c, Type elementType) throws Exception {
-		JsonParserSession s = (JsonParserSession)session;
-		ParserReader r = s.getReader();
-		c = parseIntoCollection2(s, r, c, s.getClassMeta(elementType), null);
-		validateEnd(s, r);
-		return c;
+	public ReaderParserSession createSession(ParserSessionArgs args) {
+		return new JsonParserSession(ctx, args);
 	}
 }