http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/uon/UonParserSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/uon/UonParserSession.java 
b/juneau-core/src/main/java/org/apache/juneau/uon/UonParserSession.java
index 6fe5ef6..7d50309 100644
--- a/juneau-core/src/main/java/org/apache/juneau/uon/UonParserSession.java
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonParserSession.java
@@ -12,6 +12,7 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.uon;
 
+import static org.apache.juneau.internal.StringUtils.*;
 import static org.apache.juneau.uon.UonParserContext.*;
 
 import java.io.*;
@@ -19,19 +20,27 @@ import java.lang.reflect.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.http.*;
+import org.apache.juneau.internal.*;
 import org.apache.juneau.parser.*;
+import org.apache.juneau.transform.*;
 
 /**
  * Session object that lives for the duration of a single use of {@link 
UonParser}.
  *
  * <p>
- * This class is NOT thread safe.  It is meant to be discarded after one-time 
use.
+ * This class is NOT thread safe.
+ * It is typically discarded after one-time use although it can be reused 
against multiple inputs.
  */
-public class UonParserSession extends ParserSession {
+@SuppressWarnings({ "unchecked", "rawtypes" })
+public class UonParserSession extends ReaderParserSession {
+
+       // Characters that need to be preceded with an escape character.
+       private static final AsciiSet escapedChars = new 
AsciiSet("~'\u0001\u0002");
+
+       private static final char AMP='\u0001', EQ='\u0002';  // Flags set in 
reader to denote & and = characters.
+
 
        private final boolean decodeChars;
-       private UonReader reader;
 
        /**
         * Create a new session using properties specified in the context.
@@ -39,36 +48,16 @@ public class UonParserSession extends ParserSession {
         * @param ctx
         *      The context creating this session object.
         *      The context contains all the configuration settings for this 
object.
-        * @param input
-        *      The input.
-        *      Can be any of the following types:
-        *      <ul>
-        *              <li><jk>null</jk>
-        *              <li>{@link Reader}
-        *              <li>{@link CharSequence}
-        *              <li>{@link InputStream} containing UTF-8 encoded text.
-        *              <li>{@link File} containing system encoded text.
-        *      </ul>
-        * @param op
-        *      The override properties.
-        *      These override any context properties defined in the context.
-        * @param javaMethod The java method that called this parser, usually 
the method in a REST servlet.
-        * @param outer The outer object for instantiating top-level non-static 
inner classes.
-        * @param locale
-        *      The session locale.
-        *      If <jk>null</jk>, then the locale defined on the context is 
used.
-        * @param timeZone
-        *      The session timezone.
-        *      If <jk>null</jk>, then the timezone defined on the context is 
used.
-        * @param mediaType The session media type (e.g. 
<js>"application/json"</js>).
+        * @param args
+        *      Runtime session arguments.
         */
-       public UonParserSession(UonParserContext ctx, ObjectMap op, Object 
input, Method javaMethod, Object outer,
-                       Locale locale, TimeZone timeZone, MediaType mediaType) {
-               super(ctx, op, input, javaMethod, outer, locale, timeZone, 
mediaType);
-               if (op == null || op.isEmpty()) {
+       protected UonParserSession(UonParserContext ctx, ParserSessionArgs 
args) {
+               super(ctx, args);
+               ObjectMap p = getProperties();
+               if (p.isEmpty()) {
                        decodeChars = ctx.decodeChars;
                } else {
-                       decodeChars = op.getBoolean(UON_decodeChars, 
ctx.decodeChars);
+                       decodeChars = p.getBoolean(UON_decodeChars, 
ctx.decodeChars);
                }
        }
 
@@ -80,52 +69,695 @@ public class UonParserSession extends ParserSession {
         * property is always ignored.
         *
         * @param ctx The context to copy setting from.
-        * @param input
-        *      The input.
-        *      Can be any of the following types:
-        *      <ul>
-        *              <li><jk>null</jk>
-        *              <li>{@link Reader}
-        *              <li>{@link CharSequence} (e.g. {@link String})
-        *              <li>{@link InputStream} - Read as UTF-8 encoded 
character stream.
-        *              <li>{@link File} - Read as system-default encoded 
stream.
-        *      </ul>
         */
-       public UonParserSession(UonParserContext ctx, Object input) {
-               super(ctx, null, input, null, null, null, null, null);
+       protected UonParserSession(UonParserContext ctx) {
+               super(ctx, null);
                decodeChars = false;
        }
 
+       @Override /* ParserSession */
+       protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws 
Exception {
+               UonReader r = getUonReader(pipe, decodeChars);
+               T o = parseAnything(type, r, getOuter(), true, null);
+               validateEnd(r);
+               return o;
+       }
+
+       @Override /* ReaderParserSession */
+       protected <K,V> Map<K,V> doParseIntoMap(ParserPipe pipe, Map<K,V> m, 
Type keyType, Type valueType) throws Exception {
+               UonReader r = getUonReader(pipe, decodeChars);
+               m = parseIntoMap(r, m, (ClassMeta<K>)getClassMeta(keyType), 
(ClassMeta<V>)getClassMeta(valueType), null);
+               validateEnd(r);
+               return m;
+       }
+
+       @Override /* ReaderParserSession */
+       protected <E> Collection<E> doParseIntoCollection(ParserPipe pipe, 
Collection<E> c, Type elementType) throws Exception {
+               UonReader r = getUonReader(pipe, decodeChars);
+               c = parseIntoCollection(r, c, 
(ClassMeta<E>)getClassMeta(elementType), false, null);
+               validateEnd(r);
+               return c;
+       }
+
        /**
-        * Returns the {@link UonParserContext#UON_decodeChars} setting value 
for this session.
+        * Workhorse method.
         *
-        * @return The {@link UonParserContext#UON_decodeChars} setting value 
for this session.
+        * @param eType The class type being parsed, or <jk>null</jk> if 
unknown.
+        * @param r The reader being parsed.
+        * @param outer The outer object (for constructing nested inner 
classes).
+        * @param isUrlParamValue
+        *      If <jk>true</jk>, then we're parsing a top-level URL-encoded 
value which is treated a bit different than the
+        *      default case.
+        * @param pMeta The current bean property being parsed.
+        * @return The parsed object.
+        * @throws Exception
         */
-       public final boolean isDecodeChars() {
-               return decodeChars;
+       public <T> T parseAnything(ClassMeta<T> eType, UonReader r, Object 
outer, boolean isUrlParamValue, BeanPropertyMeta pMeta) throws Exception {
+
+               if (eType == null)
+                       eType = (ClassMeta<T>)object();
+               PojoSwap<T,Object> transform = 
(PojoSwap<T,Object>)eType.getPojoSwap();
+               ClassMeta<?> sType = eType.getSerializedClassMeta();
+
+               Object o = null;
+
+               int c = r.peekSkipWs();
+
+               if (c == -1 || c == AMP) {
+                       // If parameter is blank and it's an array or 
collection, return an empty list.
+                       if (sType.isCollectionOrArray())
+                               o = sType.newInstance();
+                       else if (sType.isString() || sType.isObject())
+                               o = "";
+                       else if (sType.isPrimitive())
+                               o = sType.getPrimitiveDefault();
+                       // Otherwise, leave null.
+               } else if (sType.isVoid()) {
+                       String s = parseString(r, isUrlParamValue);
+                       if (s != null)
+                               throw new ParseException(loc(r), "Expected 
''null'' for void value, but was ''{0}''.", s);
+               } else if (sType.isObject()) {
+                       if (c == '(') {
+                               ObjectMap m = new ObjectMap(this);
+                               parseIntoMap(r, m, string(), object(), pMeta);
+                               o = cast(m, pMeta, eType);
+                       } else if (c == '@') {
+                               Collection l = new ObjectList(this);
+                               o = parseIntoCollection(r, l, sType, 
isUrlParamValue, pMeta);
+                       } else {
+                               String s = parseString(r, isUrlParamValue);
+                               if (c != '\'') {
+                                       if ("true".equals(s) || 
"false".equals(s))
+                                               o = Boolean.valueOf(s);
+                                       else if (! "null".equals(s)) {
+                                               if (isNumeric(s))
+                                                       o = 
StringUtils.parseNumber(s, Number.class);
+                                               else
+                                                       o = s;
+                                       }
+                               } else {
+                                       o = s;
+                               }
+                       }
+               } else if (sType.isBoolean()) {
+                       o = parseBoolean(r);
+               } else if (sType.isCharSequence()) {
+                       o = parseString(r, isUrlParamValue);
+               } else if (sType.isChar()) {
+                       String s = parseString(r, isUrlParamValue);
+                       o = s == null ? null : s.charAt(0);
+               } else if (sType.isNumber()) {
+                       o = parseNumber(r, (Class<? extends 
Number>)sType.getInnerClass());
+               } else if (sType.isMap()) {
+                       Map m = (sType.canCreateNewInstance(outer) ? 
(Map)sType.newInstance(outer) : new ObjectMap(this));
+                       o = parseIntoMap(r, m, sType.getKeyType(), 
sType.getValueType(), pMeta);
+               } else if (sType.isCollection()) {
+                       if (c == '(') {
+                               ObjectMap m = new ObjectMap(this);
+                               parseIntoMap(r, m, string(), object(), pMeta);
+                               // Handle case where it's a collection, but 
serialized as a map with a _type or _value key.
+                               if 
(m.containsKey(getBeanTypePropertyName(sType)))
+                                       o = cast(m, pMeta, eType);
+                               // Handle case where it's a collection, but 
only a single value was specified.
+                               else {
+                                       Collection l = (
+                                               
sType.canCreateNewInstance(outer)
+                                               ? 
(Collection)sType.newInstance(outer)
+                                               : new ObjectList(this)
+                                       );
+                                       l.add(m.cast(sType.getElementType()));
+                                       o = l;
+                               }
+                       } else {
+                               Collection l = (
+                                       sType.canCreateNewInstance(outer)
+                                       ? (Collection)sType.newInstance(outer)
+                                       : new ObjectList(this)
+                               );
+                               o = parseIntoCollection(r, l, sType, 
isUrlParamValue, pMeta);
+                       }
+               } else if (sType.canCreateNewBean(outer)) {
+                       BeanMap m = newBeanMap(outer, sType.getInnerClass());
+                       m = parseIntoBeanMap(r, m);
+                       o = m == null ? null : m.getBean();
+               } else if (sType.canCreateNewInstanceFromString(outer)) {
+                       String s = parseString(r, isUrlParamValue);
+                       if (s != null)
+                               o = sType.newInstanceFromString(outer, s);
+               } else if (sType.canCreateNewInstanceFromNumber(outer)) {
+                       o = sType.newInstanceFromNumber(this, outer, 
parseNumber(r, sType.getNewInstanceFromNumberClass()));
+               } else if (sType.isArray() || sType.isArgs()) {
+                       if (c == '(') {
+                               ObjectMap m = new ObjectMap(this);
+                               parseIntoMap(r, m, string(), object(), pMeta);
+                               // Handle case where it's an array, but 
serialized as a map with a _type or _value key.
+                               if 
(m.containsKey(getBeanTypePropertyName(sType)))
+                                       o = cast(m, pMeta, eType);
+                               // Handle case where it's an array, but only a 
single value was specified.
+                               else {
+                                       ArrayList l = new ArrayList(1);
+                                       l.add(m.cast(sType.getElementType()));
+                                       o = toArray(sType, l);
+                               }
+                       } else {
+                               ArrayList l = (ArrayList)parseIntoCollection(r, 
new ArrayList(), sType, isUrlParamValue, pMeta);
+                               o = toArray(sType, l);
+                       }
+               } else if (c == '(') {
+                       // It could be a non-bean with _type attribute.
+                       ObjectMap m = new ObjectMap(this);
+                       parseIntoMap(r, m, string(), object(), pMeta);
+                       if (m.containsKey(getBeanTypePropertyName(sType)))
+                               o = cast(m, pMeta, eType);
+                       else
+                               throw new ParseException(loc(r), "Class ''{0}'' 
could not be instantiated.  Reason: ''{1}''",
+                                       sType.getInnerClass().getName(), 
sType.getNotABeanReason());
+               } else if (c == 'n') {
+                       r.read();
+                       parseNull(r);
+               } else {
+                       throw new ParseException(loc(r), "Class ''{0}'' could 
not be instantiated.  Reason: ''{1}''",
+                               sType.getInnerClass().getName(), 
sType.getNotABeanReason());
+               }
+
+               if (o == null && sType.isPrimitive())
+                       o = sType.getPrimitiveDefault();
+               if (transform != null && o != null)
+                       o = transform.unswap(this, o, eType);
+
+               if (outer != null)
+                       setParent(eType, o, outer);
+
+               return (T)o;
        }
 
-       @Override /* ParserSession */
-       public UonReader getReader() throws Exception {
-               if (reader == null) {
-                       Object input = getInput();
-                       if (input instanceof UonReader)
-                               reader = (UonReader)input;
-                       else if (input instanceof CharSequence)
-                               reader = new UonReader((CharSequence)input, 
decodeChars);
+       private <K,V> Map<K,V> parseIntoMap(UonReader r, Map<K,V> m, 
ClassMeta<K> keyType, ClassMeta<V> valueType,
+                       BeanPropertyMeta pMeta) throws Exception {
+
+               if (keyType == null)
+                       keyType = (ClassMeta<K>)string();
+
+               int c = r.read();
+               if (c == -1 || c == AMP)
+                       return null;
+               if (c == 'n')
+                       return (Map<K,V>)parseNull(r);
+               if (c != '(')
+                       throw new ParseException(loc(r), "Expected '(' at 
beginning of object.");
+
+               final int S1=1; // Looking for attrName start.
+               final int S2=2; // Found attrName end, looking for =.
+               final int S3=3; // Found =, looking for valStart.
+               final int S4=4; // Looking for , or )
+               boolean isInEscape = false;
+
+               int state = S1;
+               K currAttr = null;
+               while (c != -1 && c != AMP) {
+                       c = r.read();
+                       if (! isInEscape) {
+                               if (state == S1) {
+                                       if (c == ')')
+                                               return m;
+                                       if (Character.isWhitespace(c))
+                                               skipSpace(r);
+                                       else {
+                                               r.unread();
+                                               Object attr = parseAttr(r, 
decodeChars);
+                                               currAttr = attr == null ? null 
: convertAttrToType(m, trim(attr.toString()), keyType);
+                                               state = S2;
+                                               c = 0; // Avoid isInEscape if c 
was '\'
+                                       }
+                               } else if (state == S2) {
+                                       if (c == EQ || c == '=')
+                                               state = S3;
+                                       else if (c == -1 || c == ',' || c == 
')' || c == AMP) {
+                                               if (currAttr == null) {
+                                                       // Value was '%00'
+                                                       r.unread();
+                                                       return null;
+                                               }
+                                               m.put(currAttr, null);
+                                               if (c == ')' || c == -1 || c == 
AMP)
+                                                       return m;
+                                               state = S1;
+                                       }
+                               } else if (state == S3) {
+                                       if (c == -1 || c == ',' || c == ')' || 
c == AMP) {
+                                               V value = convertAttrToType(m, 
"", valueType);
+                                               m.put(currAttr, value);
+                                               if (c == -1 || c == ')' || c == 
AMP)
+                                                       return m;
+                                               state = S1;
+                                       } else  {
+                                               V value = 
parseAnything(valueType, r.unread(), m, false, pMeta);
+                                               setName(valueType, value, 
currAttr);
+                                               m.put(currAttr, value);
+                                               state = S4;
+                                               c = 0; // Avoid isInEscape if c 
was '\'
+                                       }
+                               } else if (state == S4) {
+                                       if (c == ',')
+                                               state = S1;
+                                       else if (c == ')' || c == -1 || c == 
AMP) {
+                                               return m;
+                                       }
+                               }
+                       }
+                       isInEscape = isInEscape(c, r, isInEscape);
+               }
+               if (state == S1)
+                       throw new ParseException(loc(r), "Could not find 
attribute name on object.");
+               if (state == S2)
+                       throw new ParseException(loc(r), "Could not find '=' 
following attribute name on object.");
+               if (state == S3)
+                       throw new ParseException(loc(r), "Dangling '=' found in 
object entry");
+               if (state == S4)
+                       throw new ParseException(loc(r), "Could not find ')' 
marking end of object.");
+
+               return null; // Unreachable.
+       }
+
+       private <E> Collection<E> parseIntoCollection(UonReader r, 
Collection<E> l, ClassMeta<E> type, boolean isUrlParamValue, BeanPropertyMeta 
pMeta) throws Exception {
+
+               int c = r.readSkipWs();
+               if (c == -1 || c == AMP)
+                       return null;
+               if (c == 'n')
+                       return (Collection<E>)parseNull(r);
+
+               int argIndex = 0;
+
+               // If we're parsing a top-level parameter, we're allowed to 
have comma-delimited lists outside parenthesis (e.g. "&foo=1,2,3&bar=a,b,c")
+               // This is not allowed at lower levels since we use comma's as 
end delimiters.
+               boolean isInParens = (c == '@');
+               if (! isInParens) {
+                       if (isUrlParamValue)
+                               r.unread();
                        else
-                               reader = new UonReader(super.getReader(), 
decodeChars);
+                               throw new ParseException(loc(r), "Could not 
find '(' marking beginning of collection.");
+               } else {
+                       r.read();
+               }
+
+               if (isInParens) {
+                       final int S1=1; // Looking for starting of first entry.
+                       final int S2=2; // Looking for starting of subsequent 
entries.
+                       final int S3=3; // Looking for , or ) after first entry.
+
+                       int state = S1;
+                       while (c != -1 && c != AMP) {
+                               c = r.read();
+                               if (state == S1 || state == S2) {
+                                       if (c == ')') {
+                                               if (state == S2) {
+                                                       
l.add((E)parseAnything(type.isArgs() ? type.getArg(argIndex++) : 
type.getElementType(),
+                                                                       
r.unread(), l, false, pMeta));
+                                                       r.read();
+                                               }
+                                               return l;
+                                       } else if (Character.isWhitespace(c)) {
+                                               skipSpace(r);
+                                       } else {
+                                               
l.add((E)parseAnything(type.isArgs() ? type.getArg(argIndex++) : 
type.getElementType(),
+                                                               r.unread(), l, 
false, pMeta));
+                                               state = S3;
+                                       }
+                               } else if (state == S3) {
+                                       if (c == ',') {
+                                               state = S2;
+                                       } else if (c == ')') {
+                                               return l;
+                                       }
+                               }
+                       }
+                       if (state == S1 || state == S2)
+                               throw new ParseException(loc(r), "Could not 
find start of entry in array.");
+                       if (state == S3)
+                               throw new ParseException(loc(r), "Could not 
find end of entry in array.");
+
+               } else {
+                       final int S1=1; // Looking for starting of entry.
+                       final int S2=2; // Looking for , or & or END after 
first entry.
+
+                       int state = S1;
+                       while (c != -1 && c != AMP) {
+                               c = r.read();
+                               if (state == S1) {
+                                       if (Character.isWhitespace(c)) {
+                                               skipSpace(r);
+                                       } else {
+                                               
l.add((E)parseAnything(type.isArgs() ? type.getArg(argIndex++) : 
type.getElementType(),
+                                                               r.unread(), l, 
false, pMeta));
+                                               state = S2;
+                                       }
+                               } else if (state == S2) {
+                                       if (c == ',') {
+                                               state = S1;
+                                       } else if (Character.isWhitespace(c)) {
+                                               skipSpace(r);
+                                       } else if (c == AMP || c == -1) {
+                                               r.unread();
+                                               return l;
+                                       }
+                               }
+                       }
                }
-               return reader;
+
+               return null;  // Unreachable.
        }
 
-       @Override /* ParserSession */
-       public Map<String,Object> getLastLocation() {
-               Map<String,Object> m = super.getLastLocation();
-               if (reader != null) {
-                       m.put("line", reader.getLine());
-                       m.put("column", reader.getColumn());
+       private <T> BeanMap<T> parseIntoBeanMap(UonReader r, BeanMap<T> m) 
throws Exception {
+
+               int c = r.readSkipWs();
+               if (c == -1 || c == AMP)
+                       return null;
+               if (c == 'n')
+                       return (BeanMap<T>)parseNull(r);
+               if (c != '(')
+                       throw new ParseException(loc(r), "Expected '(' at 
beginning of object.");
+
+               final int S1=1; // Looking for attrName start.
+               final int S2=2; // Found attrName end, looking for =.
+               final int S3=3; // Found =, looking for valStart.
+               final int S4=4; // Looking for , or }
+               boolean isInEscape = false;
+
+               int state = S1;
+               String currAttr = "";
+               int currAttrLine = -1, currAttrCol = -1;
+               while (c != -1 && c != AMP) {
+                       c = r.read();
+                       if (! isInEscape) {
+                               if (state == S1) {
+                                       if (c == ')' || c == -1 || c == AMP) {
+                                               return m;
+                                       }
+                                       if (Character.isWhitespace(c))
+                                               skipSpace(r);
+                                       else {
+                                               r.unread();
+                                               currAttrLine= r.getLine();
+                                               currAttrCol = r.getColumn();
+                                               currAttr = parseAttrName(r, 
decodeChars);
+                                               if (currAttr == null)  // Value 
was '%00'
+                                                       return null;
+                                               state = S2;
+                                       }
+                               } else if (state == S2) {
+                                       if (c == EQ || c == '=')
+                                               state = S3;
+                                       else if (c == -1 || c == ',' || c == 
')' || c == AMP) {
+                                               m.put(currAttr, null);
+                                               if (c == ')' || c == -1 || c == 
AMP)
+                                                       return m;
+                                               state = S1;
+                                       }
+                               } else if (state == S3) {
+                                       if (c == -1 || c == ',' || c == ')' || 
c == AMP) {
+                                               if (! 
currAttr.equals(getBeanTypePropertyName(m.getClassMeta()))) {
+                                                       BeanPropertyMeta pMeta 
= m.getPropertyMeta(currAttr);
+                                                       if (pMeta == null) {
+                                                               
onUnknownProperty(r.getPipe(), currAttr, m, currAttrLine, currAttrCol);
+                                                       } else {
+                                                               Object value = 
convertToType("", pMeta.getClassMeta());
+                                                               pMeta.set(m, 
currAttr, value);
+                                                       }
+                                               }
+                                               if (c == -1 || c == ')' || c == 
AMP)
+                                                       return m;
+                                               state = S1;
+                                       } else {
+                                               if (! 
currAttr.equals(getBeanTypePropertyName(m.getClassMeta()))) {
+                                                       BeanPropertyMeta pMeta 
= m.getPropertyMeta(currAttr);
+                                                       if (pMeta == null) {
+                                                               
onUnknownProperty(r.getPipe(), currAttr, m, currAttrLine, currAttrCol);
+                                                               
parseAnything(object(), r.unread(), m.getBean(false), false, null); // Read 
content anyway to ignore it
+                                                       } else {
+                                                               
setCurrentProperty(pMeta);
+                                                               ClassMeta<?> cm 
= pMeta.getClassMeta();
+                                                               Object value = 
parseAnything(cm, r.unread(), m.getBean(false), false, pMeta);
+                                                               setName(cm, 
value, currAttr);
+                                                               pMeta.set(m, 
currAttr, value);
+                                                               
setCurrentProperty(null);
+                                                       }
+                                               }
+                                               state = S4;
+                                       }
+                               } else if (state == S4) {
+                                       if (c == ',')
+                                               state = S1;
+                                       else if (c == ')' || c == -1 || c == 
AMP) {
+                                               return m;
+                                       }
+                               }
+                       }
+                       isInEscape = isInEscape(c, r, isInEscape);
                }
-               return m;
+               if (state == S1)
+                       throw new ParseException(loc(r), "Could not find 
attribute name on object.");
+               if (state == S2)
+                       throw new ParseException(loc(r), "Could not find '=' 
following attribute name on object.");
+               if (state == S3)
+                       throw new ParseException(loc(r), "Could not find value 
following '=' on object.");
+               if (state == S4)
+                       throw new ParseException(loc(r), "Could not find ')' 
marking end of object.");
+
+               return null; // Unreachable.
+       }
+
+       private Object parseNull(UonReader r) throws Exception {
+               String s = parseString(r, false);
+               if ("ull".equals(s))
+                       return null;
+               throw new ParseException(loc(r), "Unexpected character 
sequence: ''{0}''", s);
+       }
+
+       /**
+        * Convenience method for parsing an attribute from the specified 
parser.
+        *
+        * @param r
+        * @param encoded
+        * @return The parsed object
+        * @throws Exception
+        */
+       protected final Object parseAttr(UonReader r, boolean encoded) throws 
Exception {
+               Object attr;
+               attr = parseAttrName(r, encoded);
+               return attr;
+       }
+
+       /**
+        * Parses an attribute name from the specified reader.
+        *
+        * @param r
+        * @param encoded
+        * @return The parsed attribute name.
+        * @throws Exception
+        */
+       protected final String parseAttrName(UonReader r, boolean encoded) 
throws Exception {
+
+               // If string is of form 'xxx', we're looking for ' at the end.
+               // Otherwise, we're looking for '&' or '=' or WS or -1 denoting 
the end of this string.
+
+               int c = r.peekSkipWs();
+               if (c == '\'')
+                       return parsePString(r);
+
+               r.mark();
+               boolean isInEscape = false;
+               if (encoded) {
+                       while (c != -1) {
+                               c = r.read();
+                               if (! isInEscape) {
+                                       if (c == AMP || c == EQ || c == -1 || 
Character.isWhitespace(c)) {
+                                               if (c != -1)
+                                                       r.unread();
+                                               String s = r.getMarked();
+                                               return ("null".equals(s) ? null 
: s);
+                                       }
+                               }
+                               else if (c == AMP)
+                                       r.replace('&');
+                               else if (c == EQ)
+                                       r.replace('=');
+                               isInEscape = isInEscape(c, r, isInEscape);
+                       }
+               } else {
+                       while (c != -1) {
+                               c = r.read();
+                               if (! isInEscape) {
+                                       if (c == '=' || c == -1 || 
Character.isWhitespace(c)) {
+                                               if (c != -1)
+                                                       r.unread();
+                                               String s = r.getMarked();
+                                               return ("null".equals(s) ? null 
: trim(s));
+                                       }
+                               }
+                               isInEscape = isInEscape(c, r, isInEscape);
+                       }
+               }
+
+               // We should never get here.
+               throw new ParseException(loc(r), "Unexpected condition.");
+       }
+
+
+       /*
+        * Returns true if the next character in the stream is preceded by an 
escape '~' character.
+        */
+       private static final boolean isInEscape(int c, ParserReader r, boolean 
prevIsInEscape) throws Exception {
+               if (c == '~' && ! prevIsInEscape) {
+                       c = r.peek();
+                       if (escapedChars.contains(c)) {
+                               r.delete();
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Parses a string value from the specified reader.
+        *
+        * @param r
+        * @param isUrlParamValue
+        * @return The parsed string.
+        * @throws Exception
+        */
+       protected final String parseString(UonReader r, boolean 
isUrlParamValue) throws Exception {
+
+               // If string is of form 'xxx', we're looking for ' at the end.
+               // Otherwise, we're looking for ',' or ')' or -1 denoting the 
end of this string.
+
+               int c = r.peekSkipWs();
+               if (c == '\'')
+                       return parsePString(r);
+
+               r.mark();
+               boolean isInEscape = false;
+               String s = null;
+               AsciiSet endChars = (isUrlParamValue ? endCharsParam : 
endCharsNormal);
+               while (c != -1) {
+                       c = r.read();
+                       if (! isInEscape) {
+                               // If this is a URL parameter value, we're 
looking for:  &
+                               // If not, we're looking for:  &,)
+                               if (endChars.contains(c)) {
+                                       r.unread();
+                                       c = -1;
+                               }
+                       }
+                       if (c == -1)
+                               s = r.getMarked();
+                       else if (c == EQ)
+                               r.replace('=');
+                       else if (Character.isWhitespace(c) && ! 
isUrlParamValue) {
+                               s = r.getMarked(0, -1);
+                               skipSpace(r);
+                               c = -1;
+                       }
+                       isInEscape = isInEscape(c, r, isInEscape);
+               }
+
+               if (isUrlParamValue)
+                       s = StringUtils.trim(s);
+
+               return ("null".equals(s) ? null : trim(s));
+       }
+
+       private static final AsciiSet endCharsParam = new AsciiSet(""+AMP), 
endCharsNormal = new AsciiSet(",)"+AMP);
+
+
+       /*
+        * Parses a string of the form "'foo'"
+        * All whitespace within parenthesis are preserved.
+        */
+       private String parsePString(UonReader r) throws Exception {
+
+               r.read(); // Skip first quote.
+               r.mark();
+               int c = 0;
+
+               boolean isInEscape = false;
+               while (c != -1) {
+                       c = r.read();
+                       if (! isInEscape) {
+                               if (c == '\'')
+                                       return trim(r.getMarked(0, -1));
+                       }
+                       if (c == EQ)
+                               r.replace('=');
+                       isInEscape = isInEscape(c, r, isInEscape);
+               }
+               throw new ParseException(loc(r), "Unmatched parenthesis");
+       }
+
+       private Boolean parseBoolean(UonReader r) throws Exception {
+               String s = parseString(r, false);
+               if (s == null || s.equals("null"))
+                       return null;
+               if (s.equals("true"))
+                       return true;
+               if (s.equals("false"))
+                       return false;
+               throw new ParseException(loc(r), "Unrecognized syntax for 
boolean.  ''{0}''.", s);
+       }
+
+       private Number parseNumber(UonReader r, Class<? extends Number> c) 
throws Exception {
+               String s = parseString(r, false);
+               if (s == null)
+                       return null;
+               return StringUtils.parseNumber(s, c);
+       }
+
+       /*
+        * 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 void validateEnd(UonReader r) throws Exception {
+               while (true) {
+                       int c = r.read();
+                       if (c == -1)
+                               return;
+                       if (! Character.isWhitespace(c))
+                               throw new ParseException(loc(r), "Remainder 
after parse: ''{0}''.", (char)c);
+               }
+       }
+
+       private static void skipSpace(ParserReader r) throws Exception {
+               int c = 0;
+               while ((c = r.read()) != -1) {
+                       if (c <= 2 || ! Character.isWhitespace(c)) {
+                               r.unread();
+                               return;
+                       }
+               }
+       }
+
+       /**
+        * Returns a map identifying the current parse location.
+        *
+        * @param r The reader being read from.
+        * @return A map identifying the current parse location.
+        */
+       protected final ObjectMap loc(UonReader r) {
+               return getLastLocation().append("line", 
r.getLine()).append("column", r.getColumn());
+       }
+
+       /**
+        * Creates a {@link UonReader} from the specified parser pipe.
+        *
+        * @param pipe The parser input.
+        * @param decodeChars Whether the reader should automatically decode 
URL-encoded characters.
+        * @return A new {@link UonReader} object.
+        * @throws Exception
+        */
+       @SuppressWarnings({ "static-method", "hiding" })
+       public final UonReader getUonReader(ParserPipe pipe, boolean 
decodeChars) throws Exception {
+               Reader r = pipe.getReader();
+               if (r instanceof UonReader)
+                       return (UonReader)r;
+               return new UonReader(pipe, decodeChars);
        }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/uon/UonReader.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonReader.java 
b/juneau-core/src/main/java/org/apache/juneau/uon/UonReader.java
index 2f932bb..3037ad3 100644
--- a/juneau-core/src/main/java/org/apache/juneau/uon/UonReader.java
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonReader.java
@@ -33,33 +33,27 @@ public final class UonReader extends ParserReader {
 
        private final boolean decodeChars;
        private final char[] buff;
+       
+       // Writable properties.
        private int iCurrent, iEnd;
 
-       /**
-        * Constructor for input from a {@link CharSequence}.
-        *
-        * @param in The character sequence being read from.
-        * @param decodeChars If <jk>true</jk>, decode <code>%xx</code> escape 
sequences.
-        */
-       public UonReader(CharSequence in, boolean decodeChars) {
-               super(in);
-               this.decodeChars = decodeChars;
-               if (in == null || ! decodeChars)
-                       this.buff = new char[0];
-               else
-                       this.buff = new char[in.length() < 1024 ? in.length() : 
1024];
-       }
 
        /**
-        * Constructor for input from a {@link Reader}).
+        * Constructor.
         *
-        * @param r The Reader being wrapped.
-        * @param decodeChars If <jk>true</jk>, decode <code>%xx</code> escape 
sequences.
+        * @param pipe The parser input.
+        * @param decodeChars Whether the input is URL-encoded.
+        * @throws Exception
         */
-       public UonReader(Reader r, boolean decodeChars) {
-               super(r);
+       public UonReader(ParserPipe pipe, boolean decodeChars) throws Exception 
{
+               super(pipe);
                this.decodeChars = decodeChars;
-               this.buff = new char[1024];
+               if (pipe.isString()) {
+                       String in = pipe.getInputAsString();
+                       this.buff = new char[in.length() < 1024 ? in.length() : 
1024];
+               } else {
+                       this.buff = new char[1024];
+               }
        }
 
        @Override /* Reader */
@@ -158,7 +152,7 @@ public final class UonReader extends ParserReader {
                return i;
        }
 
-       private final int readUTF8(int n, final int numBytes) throws 
IOException {
+       private int readUTF8(int n, final int numBytes) throws IOException {
                if (iCurrent + numBytes*3 > iEnd)
                        return -1;
                for (int i = 0; i < numBytes; i++) {
@@ -168,14 +162,14 @@ public final class UonReader extends ParserReader {
                return n;
        }
 
-       private final int readHex() throws IOException {
+       private int readHex() throws IOException {
                int c = buff[iCurrent++];
                if (c != '%')
                        throw new IOException("Did not find expected '%' 
character in UTF-8 sequence.");
                return readEncodedByte();
        }
 
-       private final int readEncodedByte() throws IOException {
+       private int readEncodedByte() throws IOException {
                if (iEnd <= iCurrent + 1)
                        throw new IOException("Incomplete trailing escape 
pattern");
                int h = buff[iCurrent++];
@@ -185,7 +179,7 @@ public final class UonReader extends ParserReader {
                return (h << 4) + l;
        }
 
-       private static final int fromHexChar(int c) throws IOException {
+       private static int fromHexChar(int c) throws IOException {
                if (c >= '0' && c <= '9')
                        return c - '0';
                if (c >= 'a' && c <= 'f')
@@ -194,4 +188,10 @@ public final class UonReader extends ParserReader {
                        return 10 + c - 'A';
                throw new IOException("Invalid hex character '"+c+"' found in 
escape pattern.");
        }
+
+       @Override /* ParserReader */
+       public final UonReader unread() throws IOException {
+               super.unread();
+               return this;
+       }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializer.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializer.java 
b/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializer.java
index 7d5c100..5f1dc23 100644
--- a/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializer.java
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializer.java
@@ -15,14 +15,9 @@ package org.apache.juneau.uon;
 import static org.apache.juneau.serializer.SerializerContext.*;
 import static org.apache.juneau.uon.UonSerializerContext.*;
 
-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.serializer.*;
-import org.apache.juneau.transform.*;
 
 /**
  * Serializes POJO models to UON (a notation for URL-encoded query parameter 
values).
@@ -153,12 +148,7 @@ public class UonSerializer extends WriterSerializer {
                 * @param propertyStore The property store containing all the 
settings for this object.
                 */
                public Readable(PropertyStore propertyStore) {
-                       super(propertyStore);
-               }
-
-               @Override /* CoreObject */
-               protected ObjectMap getOverrideProperties() {
-                       return 
super.getOverrideProperties().append(SERIALIZER_useWhitespace, true);
+                       
super(propertyStore.copy().append(SERIALIZER_useWhitespace, true));
                }
        }
 
@@ -173,12 +163,7 @@ public class UonSerializer extends WriterSerializer {
                 * @param propertyStore The property store containing all the 
settings for this object.
                 */
                public Encoding(PropertyStore propertyStore) {
-                       super(propertyStore);
-               }
-
-               @Override /* CoreObject */
-               protected ObjectMap getOverrideProperties() {
-                       return 
super.getOverrideProperties().append(UON_encodeChars, true);
+                       super(propertyStore.copy().append(UON_encodeChars, 
true));
                }
        }
 
@@ -200,189 +185,6 @@ public class UonSerializer extends WriterSerializer {
                return new UonSerializerBuilder(propertyStore);
        }
 
-       /**
-        * Workhorse method. Determines the type of object, and then calls the 
appropriate type-specific serialization
-        * method.
-        *
-        * @param session The context that exist for the duration of a 
serialize.
-        * @param out The writer to serialize to.
-        * @param o The object being serialized.
-        * @param eType The expected type of the object if this is a bean 
property.
-        * @param attrName
-        *      The bean property name if this is a bean property.
-        *      <jk>null</jk> if this isn't a bean property being serialized.
-        * @param pMeta The bean property metadata.
-        * @return The same writer passed in.
-        * @throws Exception
-        */
-       @SuppressWarnings({ "rawtypes", "unchecked" })
-       protected SerializerWriter serializeAnything(UonSerializerSession 
session, UonWriter out, Object o, ClassMeta<?> eType,
-                       String attrName, BeanPropertyMeta pMeta) throws 
Exception {
-
-               if (o == null) {
-                       out.appendObject(null, false);
-                       return out;
-               }
-
-               if (eType == null)
-                       eType = object();
-
-               ClassMeta<?> aType;                     // The actual type
-               ClassMeta<?> sType;                     // The serialized type
-
-               aType = session.push(attrName, o, eType);
-               boolean isRecursion = aType == null;
-
-               // Handle recursion
-               if (aType == null) {
-                       o = null;
-                       aType = object();
-               }
-
-               sType = aType.getSerializedClassMeta();
-               String typeName = session.getBeanTypeName(eType, aType, pMeta);
-
-               // Swap if necessary
-               PojoSwap swap = aType.getPojoSwap();
-               if (swap != null) {
-                       o = swap.swap(session, o);
-
-                       // If the getSwapClass() method returns Object, we need 
to figure out
-                       // the actual type now.
-                       if (sType.isObject())
-                               sType = session.getClassMetaForObject(o);
-               }
-
-               // '\0' characters are considered null.
-               if (o == null || (sType.isChar() && ((Character)o).charValue() 
== 0))
-                       out.appendObject(null, false);
-               else if (sType.isBoolean())
-                       out.appendBoolean(o);
-               else if (sType.isNumber())
-                       out.appendNumber(o);
-               else if (sType.isBean())
-                       serializeBeanMap(session, out, session.toBeanMap(o), 
typeName);
-               else if (sType.isUri() || (pMeta != null && pMeta.isUri()))
-                       out.appendUri(o);
-               else if (sType.isMap()) {
-                       if (o instanceof BeanMap)
-                               serializeBeanMap(session, out, (BeanMap)o, 
typeName);
-                       else
-                               serializeMap(session, out, (Map)o, eType);
-               }
-               else if (sType.isCollection()) {
-                       serializeCollection(session, out, (Collection) o, 
eType);
-               }
-               else if (sType.isArray()) {
-                       serializeCollection(session, out, 
toList(sType.getInnerClass(), o), eType);
-               }
-               else {
-                       out.appendObject(o, false);
-               }
-
-               if (! isRecursion)
-                       session.pop();
-               return out;
-       }
-
-       @SuppressWarnings({ "rawtypes", "unchecked" })
-       private SerializerWriter serializeMap(UonSerializerSession session, 
UonWriter out, Map m, ClassMeta<?> type) throws Exception {
-
-               m = session.sort(m);
-
-               ClassMeta<?> keyType = type.getKeyType(), valueType = 
type.getValueType();
-
-               int depth = session.getIndent();
-
-               if (! session.isPlainTextParams())
-                       out.append('(');
-
-               Iterator mapEntries = m.entrySet().iterator();
-
-               while (mapEntries.hasNext()) {
-                       Map.Entry e = (Map.Entry) mapEntries.next();
-                       Object value = e.getValue();
-                       Object key = session.generalize(e.getKey(), keyType);
-                       out.cr(depth).appendObject(key, false).append('=');
-                       serializeAnything(session, out, value, valueType, (key 
== null ? null : session.toString(key)), null);
-                       if (mapEntries.hasNext())
-                               out.append(',');
-               }
-
-               if (m.size() > 0)
-                       out.cre(depth-1);
-
-               if (! session.isPlainTextParams())
-                       out.append(')');
-
-               return out;
-       }
-
-       private SerializerWriter serializeBeanMap(UonSerializerSession session, 
UonWriter out, BeanMap<?> m, String typeName) throws Exception {
-               int depth = session.getIndent();
-
-               if (! session.isPlainTextParams())
-                       out.append('(');
-
-               boolean addComma = false;
-
-               for (BeanPropertyValue p : m.getValues(session.isTrimNulls(), 
typeName != null ? session.createBeanTypeNameProperty(m, typeName) : null)) {
-                       BeanPropertyMeta pMeta = p.getMeta();
-                       ClassMeta<?> cMeta = p.getClassMeta();
-
-                       String key = p.getName();
-                       Object value = p.getValue();
-                       Throwable t = p.getThrown();
-                       if (t != null)
-                               session.onBeanGetterException(pMeta, t);
-
-                       if (session.canIgnoreValue(cMeta, key, value))
-                               continue;
-
-                       if (addComma)
-                               out.append(',');
-
-                       out.cr(depth).appendObject(key, false).append('=');
-
-                       serializeAnything(session, out, value, cMeta, key, 
pMeta);
-
-                       addComma = true;
-               }
-
-               if (m.size() > 0)
-                       out.cre(depth-1);
-               if (! session.isPlainTextParams())
-                       out.append(')');
-
-               return out;
-       }
-
-       @SuppressWarnings({ "rawtypes", "unchecked" })
-       private SerializerWriter serializeCollection(UonSerializerSession 
session, UonWriter out, Collection c, ClassMeta<?> type) throws Exception {
-
-               ClassMeta<?> elementType = type.getElementType();
-
-               c = session.sort(c);
-
-               if (! session.isPlainTextParams())
-                       out.append('@').append('(');
-
-               int depth = session.getIndent();
-
-               for (Iterator i = c.iterator(); i.hasNext();) {
-                       out.cr(depth);
-                       serializeAnything(session, out, i.next(), elementType, 
"<iterator>", null);
-                       if (i.hasNext())
-                               out.append(',');
-               }
-
-               if (c.size() > 0)
-                       out.cre(depth-1);
-               if (! session.isPlainTextParams())
-                       out.append(')');
-
-               return out;
-       }
 
 
        
//--------------------------------------------------------------------------------
@@ -390,14 +192,7 @@ public class UonSerializer extends WriterSerializer {
        
//--------------------------------------------------------------------------------
 
        @Override /* Serializer */
-       public UonSerializerSession createSession(ObjectMap op, Method 
javaMethod, Locale locale,
-                       TimeZone timeZone, MediaType mediaType, UriContext 
uriContext) {
-               return new UonSerializerSession(ctx, null, op, javaMethod, 
locale, timeZone, mediaType, uriContext);
-       }
-
-       @Override /* Serializer */
-       protected void doSerialize(SerializerSession session, SerializerOutput 
out, Object o) throws Exception {
-               UonSerializerSession s = (UonSerializerSession)session;
-               serializeAnything(s, s.getUonWriter(out), o, 
s.getExpectedRootType(o), "root", null);
+       public WriterSerializerSession createSession(SerializerSessionArgs 
args) {
+               return new UonSerializerSession(ctx, null, args);
        }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java 
b/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
index 67c50c5..34039bc 100644
--- a/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
@@ -15,20 +15,20 @@ package org.apache.juneau.uon;
 import static org.apache.juneau.msgpack.MsgPackSerializerContext.*;
 import static org.apache.juneau.uon.UonSerializerContext.*;
 
-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 
UonSerializer}.
  *
  * <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 UonSerializerSession extends SerializerSession {
+public class UonSerializerSession extends WriterSerializerSession {
 
        private final boolean
                encodeChars,
@@ -36,51 +36,32 @@ public class UonSerializerSession extends SerializerSession 
{
                plainTextParams;
 
        /**
-        * 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 encode Override the {@link 
UonSerializerContext#UON_encodeChars} setting.
-        * @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 UonSerializerSession(UonSerializerContext ctx, Boolean 
encode, 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()) {
+       public UonSerializerSession(UonSerializerContext ctx, Boolean encode, 
SerializerSessionArgs args) {
+               super(ctx, args);
+               ObjectMap p = getProperties();
+               if (p.isEmpty()) {
                        encodeChars = encode == null ? ctx.encodeChars : encode;
                        addBeanTypeProperties = ctx.addBeanTypeProperties;
                        plainTextParams = ctx.plainTextParams;
                } else {
-                       encodeChars = encode == null ? 
op.getBoolean(UON_encodeChars, ctx.encodeChars) : encode;
-                       addBeanTypeProperties = 
op.getBoolean(MSGPACK_addBeanTypeProperties, ctx.addBeanTypeProperties);
-                       plainTextParams = 
op.getString(UonSerializerContext.UON_paramFormat, "UON").equals("PLAINTEXT");
+                       encodeChars = encode == null ? 
p.getBoolean(UON_encodeChars, ctx.encodeChars) : encode;
+                       addBeanTypeProperties = 
p.getBoolean(MSGPACK_addBeanTypeProperties, ctx.addBeanTypeProperties);
+                       plainTextParams = 
p.getString(UonSerializerContext.UON_paramFormat, "UON").equals("PLAINTEXT");
                }
        }
 
        /**
-        * Returns the {@link UonSerializerContext#UON_encodeChars} setting 
value for this session.
-        *
-        * @return The {@link UonSerializerContext#UON_encodeChars} setting 
value for this session.
-        */
-       public final boolean isEncodeChars() {
-               return encodeChars;
-       }
-
-       /**
         * Returns the {@link UonSerializerContext#UON_addBeanTypeProperties} 
setting value for this session.
         *
         * @return The {@link UonSerializerContext#UON_addBeanTypeProperties} 
setting value for this session.
@@ -91,25 +72,200 @@ public class UonSerializerSession extends 
SerializerSession {
        }
 
        /**
-        * Returns <jk>true</jk> if the {@link 
UonSerializerContext#UON_paramFormat} is <js>"PLAINTEXT"</js>.
-        *
-        * @return <jk>true</jk> if the {@link 
UonSerializerContext#UON_paramFormat} is <js>"PLAINTEXT"</js>.
-        */
-       public boolean isPlainTextParams() {
-               return plainTextParams;
-       }
-
-       /**
         * Converts the specified output target object to an {@link UonWriter}.
         *
         * @param out The output target object.
         * @return The output target object wrapped in an {@link UonWriter}.
         * @throws Exception
         */
-       public final UonWriter getUonWriter(SerializerOutput out) throws 
Exception {
+       protected final UonWriter getUonWriter(SerializerPipe out) throws 
Exception {
                Object output = out.getRawOutput();
                if (output instanceof UonWriter)
                        return (UonWriter)output;
-               return new UonWriter(this, out.getWriter(), isUseWhitespace(), 
getMaxIndent(), isEncodeChars(), isTrimStrings(), isPlainTextParams(), 
getUriResolver());
+               UonWriter w = new UonWriter(this, out.getWriter(), 
isUseWhitespace(), getMaxIndent(), encodeChars, isTrimStrings(), 
plainTextParams, getUriResolver());
+               out.setWriter(w);
+               return w;
+       }
+
+       @Override /* Serializer */
+       protected void doSerialize(SerializerPipe out, Object o) throws 
Exception {
+               serializeAnything(getUonWriter(out), o, getExpectedRootType(o), 
"root", null);
+       }
+
+       /**
+        * Workhorse method. Determines the type of object, and then calls the 
appropriate type-specific serialization
+        * method.
+        *
+        * @param out The writer to serialize to.
+        * @param o The object being serialized.
+        * @param eType The expected type of the object if this is a bean 
property.
+        * @param attrName
+        *      The bean property name if this is a bean property.
+        *      <jk>null</jk> if this isn't a bean property being serialized.
+        * @param pMeta The bean property metadata.
+        * @return The same writer passed in.
+        * @throws Exception
+        */
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       protected SerializerWriter serializeAnything(UonWriter out, Object o, 
ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws Exception {
+
+               if (o == null) {
+                       out.appendObject(null, false);
+                       return out;
+               }
+
+               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.appendObject(null, false);
+               else if (sType.isBoolean())
+                       out.appendBoolean(o);
+               else if (sType.isNumber())
+                       out.appendNumber(o);
+               else if (sType.isBean())
+                       serializeBeanMap(out, toBeanMap(o), typeName);
+               else if (sType.isUri() || (pMeta != null && pMeta.isUri()))
+                       out.appendUri(o);
+               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.appendObject(o, false);
+               }
+
+               if (! isRecursion)
+                       pop();
+               return out;
+       }
+
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       private SerializerWriter serializeMap(UonWriter out, Map m, 
ClassMeta<?> type) throws Exception {
+
+               m = sort(m);
+
+               ClassMeta<?> keyType = type.getKeyType(), valueType = 
type.getValueType();
+
+               if (! plainTextParams)
+                       out.append('(');
+
+               Iterator mapEntries = m.entrySet().iterator();
+
+               while (mapEntries.hasNext()) {
+                       Map.Entry e = (Map.Entry) mapEntries.next();
+                       Object value = e.getValue();
+                       Object key = generalize(e.getKey(), keyType);
+                       out.cr(indent).appendObject(key, false).append('=');
+                       serializeAnything(out, value, valueType, (key == null ? 
null : toString(key)), null);
+                       if (mapEntries.hasNext())
+                               out.append(',');
+               }
+
+               if (m.size() > 0)
+                       out.cre(indent-1);
+
+               if (! plainTextParams)
+                       out.append(')');
+
+               return out;
+       }
+
+       private SerializerWriter serializeBeanMap(UonWriter out, BeanMap<?> m, 
String typeName) throws Exception {
+
+               if (! plainTextParams)
+                       out.append('(');
+
+               boolean addComma = false;
+
+               for (BeanPropertyValue p : m.getValues(isTrimNulls(), typeName 
!= null ? createBeanTypeNameProperty(m, typeName) : null)) {
+                       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;
+
+                       if (addComma)
+                               out.append(',');
+
+                       out.cr(indent).appendObject(key, false).append('=');
+
+                       serializeAnything(out, value, cMeta, key, pMeta);
+
+                       addComma = true;
+               }
+
+               if (m.size() > 0)
+                       out.cre(indent-1);
+               if (! plainTextParams)
+                       out.append(')');
+
+               return out;
+       }
+
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       private SerializerWriter serializeCollection(UonWriter out, Collection 
c, ClassMeta<?> type) throws Exception {
+
+               ClassMeta<?> elementType = type.getElementType();
+
+               c = sort(c);
+
+               if (! plainTextParams)
+                       out.append('@').append('(');
+
+               for (Iterator i = c.iterator(); i.hasNext();) {
+                       out.cr(indent);
+                       serializeAnything(out, i.next(), elementType, 
"<iterator>", null);
+                       if (i.hasNext())
+                               out.append(',');
+               }
+
+               if (c.size() > 0)
+                       out.cre(indent-1);
+               if (! plainTextParams)
+                       out.append(')');
+
+               return out;
        }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
index d07d778..399c882 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
@@ -16,14 +16,11 @@ import static org.apache.juneau.uon.UonParserContext.*;
 import static org.apache.juneau.internal.ArrayUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
 
-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.parser.*;
-import org.apache.juneau.transform.*;
 import org.apache.juneau.uon.*;
 
 /**
@@ -51,7 +48,7 @@ import org.apache.juneau.uon.*;
  *     <li>{@link BeanContext}
  * </ul>
  */
-@SuppressWarnings({ "rawtypes", "unchecked", "hiding" })
+@SuppressWarnings({ "unchecked", "hiding" })
 @Consumes("application/x-www-form-urlencoded")
 public class UrlEncodingParser extends UonParser implements PartParser {
 
@@ -67,272 +64,15 @@ public class UrlEncodingParser extends UonParser 
implements PartParser {
         * @param propertyStore The property store containing all the settings 
for this object.
         */
        public UrlEncodingParser(PropertyStore propertyStore) {
-               super(propertyStore);
+               super(propertyStore.copy().append(UON_decodeChars, true));
                this.ctx = createContext(UrlEncodingParserContext.class);
        }
 
        @Override /* CoreObject */
-       public ObjectMap getOverrideProperties() {
-               return super.getOverrideProperties().append(UON_decodeChars, 
true);
-       }
-
-       @Override /* CoreObject */
        public UrlEncodingParserBuilder builder() {
                return new UrlEncodingParserBuilder(propertyStore);
        }
 
-       private <T> T parseAnything(UrlEncodingParserSession session, 
ClassMeta<T> eType, ParserReader r, Object outer) throws Exception {
-
-               if (eType == null)
-                       eType = (ClassMeta<T>)object();
-               PojoSwap<T,Object> transform = 
(PojoSwap<T,Object>)eType.getPojoSwap();
-               ClassMeta<?> sType = eType.getSerializedClassMeta();
-
-               int c = r.peekSkipWs();
-               if (c == '?')
-                       r.read();
-
-               Object o;
-
-               if (sType.isObject()) {
-                       ObjectMap m = new ObjectMap(session);
-                       parseIntoMap(session, r, m, 
session.getClassMeta(Map.class, String.class, Object.class), outer);
-                       if (m.containsKey("_value"))
-                               o = m.get("_value");
-                       else
-                               o = session.cast(m, null, eType);
-               } else if (sType.isMap()) {
-                       Map m = (sType.canCreateNewInstance() ? 
(Map)sType.newInstance() : new ObjectMap(session));
-                       o = parseIntoMap(session, r, m, sType, m);
-               } else if (sType.canCreateNewBean(outer)) {
-                       BeanMap m = session.newBeanMap(outer, 
sType.getInnerClass());
-                       m = parseIntoBeanMap(session, r, m);
-                       o = m == null ? null : m.getBean();
-               } else if (sType.isCollection() || sType.isArray() || 
sType.isArgs()) {
-                       // ?1=foo&2=bar...
-                       Collection c2 = ((sType.isArray() || sType.isArgs()) || 
! sType.canCreateNewInstance(outer)) ? new ObjectList(session) : 
(Collection)sType.newInstance();
-                       Map<Integer,Object> m = new TreeMap<Integer,Object>();
-                       parseIntoMap(session, r, m, sType, c2);
-                       c2.addAll(m.values());
-                       if (sType.isArray())
-                               o = toArray(c2, 
sType.getElementType().getInnerClass());
-                       else if (sType.isArgs())
-                               o = c2.toArray(new Object[c2.size()]);
-                       else
-                               o = c2;
-               } else {
-                       // It could be a non-bean with _type attribute.
-                       ObjectMap m = new ObjectMap(session);
-                       parseIntoMap(session, r, m, 
session.getClassMeta(Map.class, String.class, Object.class), outer);
-                       if 
(m.containsKey(session.getBeanTypePropertyName(eType)))
-                               o = session.cast(m, null, eType);
-                       else if (m.containsKey("_value")) {
-                               o = session.convertToType(m.get("_value"), 
sType);
-                       } else {
-                               if (sType.getNotABeanReason() != null)
-                                       throw new ParseException(session, 
"Class ''{0}'' could not be instantiated as application/x-www-form-urlencoded.  
Reason: ''{1}''", sType, sType.getNotABeanReason());
-                               throw new ParseException(session, "Malformed 
application/x-www-form-urlencoded input for class ''{0}''.", sType);
-                       }
-               }
-
-               if (transform != null && o != null)
-                       o = transform.unswap(session, o, eType);
-
-               if (outer != null)
-                       setParent(eType, o, outer);
-
-               return (T)o;
-       }
-
-       private <K,V> Map<K,V> parseIntoMap(UonParserSession session, 
ParserReader r, Map<K,V> m, ClassMeta<?> type, Object outer) throws Exception {
-
-               ClassMeta<K> keyType = (ClassMeta<K>)(type.isArgs() || 
type.isCollectionOrArray() ? session.getClassMeta(Integer.class) : 
type.getKeyType());
-
-               int c = r.peekSkipWs();
-               if (c == -1)
-                       return m;
-
-               final int S1=1; // Looking for attrName start.
-               final int S2=2; // Found attrName end, looking for =.
-               final int S3=3; // Found =, looking for valStart.
-               final int S4=4; // Looking for & or end.
-               boolean isInEscape = false;
-
-               int state = S1;
-               int argIndex = 0;
-               K currAttr = null;
-               while (c != -1) {
-                       c = r.read();
-                       if (! isInEscape) {
-                               if (state == S1) {
-                                       if (c == -1)
-                                               return m;
-                                       r.unread();
-                                       Object attr = parseAttr(session, r, 
true);
-                                       currAttr = attr == null ? null : 
convertAttrToType(session, m, session.trim(attr.toString()), keyType);
-                                       state = S2;
-                                       c = 0; // Avoid isInEscape if c was '\'
-                               } else if (state == S2) {
-                                       if (c == '\u0002')
-                                               state = S3;
-                                       else if (c == -1 || c == '\u0001') {
-                                               m.put(currAttr, null);
-                                               if (c == -1)
-                                                       return m;
-                                               state = S1;
-                                       }
-                               } else if (state == S3) {
-                                       if (c == -1 || c == '\u0001') {
-                                               ClassMeta<V> valueType = 
(ClassMeta<V>)(type.isArgs() ? type.getArg(argIndex++) : 
type.isCollectionOrArray() ? type.getElementType() : type.getValueType());
-                                               V value = 
convertAttrToType(session, m, "", valueType);
-                                               m.put(currAttr, value);
-                                               if (c == -1)
-                                                       return m;
-                                               state = S1;
-                                       } else  {
-                                               // For performance, we bypass 
parseAnything for string values.
-                                               ClassMeta<V> valueType = 
(ClassMeta<V>)(type.isArgs() ? type.getArg(argIndex++) : 
type.isCollectionOrArray() ? type.getElementType() : type.getValueType());
-                                               V value = 
(V)(valueType.isString() ? super.parseString(session, r.unread(), true) : 
super.parseAnything(session, valueType, r.unread(), outer, true, null));
-
-                                               // If we already encountered 
this parameter, turn it into a list.
-                                               if (m.containsKey(currAttr) && 
valueType.isObject()) {
-                                                       Object v2 = 
m.get(currAttr);
-                                                       if (! (v2 instanceof 
ObjectList)) {
-                                                               v2 = new 
ObjectList(v2).setBeanSession(session);
-                                                               m.put(currAttr, 
(V)v2);
-                                                       }
-                                                       
((ObjectList)v2).add(value);
-                                               } else {
-                                                       m.put(currAttr, value);
-                                               }
-                                               state = S4;
-                                               c = 0; // Avoid isInEscape if c 
was '\'
-                                       }
-                               } else if (state == S4) {
-                                       if (c == '\u0001')
-                                               state = S1;
-                                       else if (c == -1) {
-                                               return m;
-                                       }
-                               }
-                       }
-                       isInEscape = (c == '\\' && ! isInEscape);
-               }
-               if (state == S1)
-                       throw new ParseException(session, "Could not find 
attribute name on object.");
-               if (state == S2)
-                       throw new ParseException(session, "Could not find '=' 
following attribute name on object.");
-               if (state == S3)
-                       throw new ParseException(session, "Dangling '=' found 
in object entry");
-               if (state == S4)
-                       throw new ParseException(session, "Could not find end 
of object.");
-
-               return null; // Unreachable.
-       }
-
-       private <T> BeanMap<T> parseIntoBeanMap(UrlEncodingParserSession 
session, ParserReader r, BeanMap<T> m) throws Exception {
-
-               int c = r.peekSkipWs();
-               if (c == -1)
-                       return m;
-
-               final int S1=1; // Looking for attrName start.
-               final int S2=2; // Found attrName end, looking for =.
-               final int S3=3; // Found =, looking for valStart.
-               final int S4=4; // Looking for , or }
-               boolean isInEscape = false;
-
-               int state = S1;
-               String currAttr = "";
-               int currAttrLine = -1, currAttrCol = -1;
-               while (c != -1) {
-                       c = r.read();
-                       if (! isInEscape) {
-                               if (state == S1) {
-                                       if (c == -1) {
-                                               return m;
-                                       }
-                                       r.unread();
-                                       currAttrLine= r.getLine();
-                                       currAttrCol = r.getColumn();
-                                       currAttr = parseAttrName(session, r, 
true);
-                                       if (currAttr == null)  // Value was 
'%00'
-                                               return null;
-                                       state = S2;
-                               } else if (state == S2) {
-                                       if (c == '\u0002')
-                                               state = S3;
-                                       else if (c == -1 || c == '\u0001') {
-                                               m.put(currAttr, null);
-                                               if (c == -1)
-                                                       return m;
-                                               state = S1;
-                                       }
-                               } else if (state == S3) {
-                                       if (c == -1 || c == '\u0001') {
-                                               if (! 
currAttr.equals(session.getBeanTypePropertyName(m.getClassMeta()))) {
-                                                       BeanPropertyMeta pMeta 
= m.getPropertyMeta(currAttr);
-                                                       if (pMeta == null) {
-                                                               
session.onUnknownProperty(currAttr, m, currAttrLine, currAttrCol);
-                                                       } else {
-                                                               
session.setCurrentProperty(pMeta);
-                                                               // In cases of 
"&foo=", create an empty instance of the value if createable.
-                                                               // Otherwise, 
leave it null.
-                                                               ClassMeta<?> cm 
= pMeta.getClassMeta();
-                                                               if 
(cm.canCreateNewInstance())
-                                                                       
pMeta.set(m, currAttr, cm.newInstance());
-                                                               
session.setCurrentProperty(null);
-                                                       }
-                                               }
-                                               if (c == -1)
-                                                       return m;
-                                               state = S1;
-                                       } else {
-                                               if (! 
currAttr.equals(session.getBeanTypePropertyName(m.getClassMeta()))) {
-                                                       BeanPropertyMeta pMeta 
= m.getPropertyMeta(currAttr);
-                                                       if (pMeta == null) {
-                                                               
session.onUnknownProperty(currAttr, m, currAttrLine, currAttrCol);
-                                                               
parseAnything(session, object(), r.unread(), m.getBean(false), true, null); // 
Read content anyway to ignore it
-                                                       } else {
-                                                               
session.setCurrentProperty(pMeta);
-                                                               if 
(session.shouldUseExpandedParams(pMeta)) {
-                                                                       
ClassMeta et = pMeta.getClassMeta().getElementType();
-                                                                       Object 
value = parseAnything(session, et, r.unread(), m.getBean(false), true, pMeta);
-                                                                       
setName(et, value, currAttr);
-                                                                       
pMeta.add(m, currAttr, value);
-                                                               } else {
-                                                                       
ClassMeta<?> cm = pMeta.getClassMeta();
-                                                                       Object 
value = parseAnything(session, cm, r.unread(), m.getBean(false), true, pMeta);
-                                                                       
setName(cm, value, currAttr);
-                                                                       
pMeta.set(m, currAttr, value);
-                                                               }
-                                                               
session.setCurrentProperty(null);
-                                                       }
-                                               }
-                                               state = S4;
-                                       }
-                               } else if (state == S4) {
-                                       if (c == '\u0001')
-                                               state = S1;
-                                       else if (c == -1) {
-                                               return m;
-                                       }
-                               }
-                       }
-                       isInEscape = (c == '\\' && ! isInEscape);
-               }
-               if (state == S1)
-                       throw new ParseException(session, "Could not find 
attribute name on object.");
-               if (state == S2)
-                       throw new ParseException(session, "Could not find '=' 
following attribute name on object.");
-               if (state == S3)
-                       throw new ParseException(session, "Could not find value 
following '=' on object.");
-               if (state == S4)
-                       throw new ParseException(session, "Could not find end 
of object.");
-
-               return null; // Unreachable.
-       }
-
        /**
         * Parse a URL query string into a simple map of key/value pairs.
         *
@@ -348,7 +88,9 @@ public class UrlEncodingParser extends UonParser implements 
PartParser {
                if (isEmpty(qs))
                        return m;
 
-               UonReader r = new UonReader(qs, true);
+               // We're reading from a string, so we don't need to make sure 
close() is called on the pipe.
+               ParserPipe p = new ParserPipe(qs, false, false, null, null);
+               UonReader r = new UonReader(p, true);
 
                final int S1=1; // Looking for attrName start.
                final int S2=2; // Found attrName start, looking for = or & or 
end.
@@ -434,15 +176,17 @@ public class UrlEncodingParser extends UonParser 
implements PartParser {
                        if (x == 'n' && "null".equals(in))
                                return null;
                }
-               UonParserSession session = createParameterSession(in);
+               UonParserSession session = createParameterSession();
+               ParserPipe pipe = session.createPipe(in);
                try {
-                       UonReader r = session.getReader();
-                       return super.parseAnything(session, type, r, null, 
true, null);
+                       UonReader r = session.getUonReader(pipe, false);
+                       return session.parseAnything(type, r, null, true, null);
                } catch (ParseException e) {
                        throw e;
                } catch (Exception e) {
-                       throw new ParseException(session, e);
+                       throw new ParseException(session.getLastLocation(), e);
                } finally {
+                       pipe.close();
                        session.close();
                }
        }
@@ -453,25 +197,7 @@ public class UrlEncodingParser extends UonParser 
implements PartParser {
        
//--------------------------------------------------------------------------------
 
        @Override /* Parser */
-       public UrlEncodingParserSession createSession(Object input, ObjectMap 
op, Method javaMethod, Object outer, Locale locale, TimeZone timeZone, 
MediaType mediaType) {
-               return new UrlEncodingParserSession(ctx, op, input, javaMethod, 
outer, locale, timeZone, mediaType);
-       }
-
-       @Override /* Parser */
-       protected <T> T doParse(ParserSession session, ClassMeta<T> type) 
throws Exception {
-               UrlEncodingParserSession s = (UrlEncodingParserSession)session;
-               UonReader r = s.getReader();
-               T o = parseAnything(s, type, r, s.getOuter());
-               return o;
-       }
-
-       @Override /* ReaderParser */
-       protected <K,V> Map<K,V> doParseIntoMap(ParserSession session, Map<K,V> 
m, Type keyType, Type valueType) throws Exception {
-               UrlEncodingParserSession s = (UrlEncodingParserSession)session;
-               UonReader r = s.getReader();
-               if (r.peekSkipWs() == '?')
-                       r.read();
-               m = parseIntoMap(s, r, m, session.getClassMeta(Map.class, 
keyType, valueType), null);
-               return m;
+       public UrlEncodingParserSession createSession(ParserSessionArgs args) {
+               return new UrlEncodingParserSession(ctx, args);
        }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java
 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java
index 5c1ff7e..f2a0c98 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java
@@ -12,20 +12,23 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.urlencoding;
 
-import java.io.*;
 import java.lang.reflect.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.http.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.transform.*;
 import org.apache.juneau.uon.*;
 
 /**
  * Session object that lives for the duration of a single use of {@link 
UrlEncodingParser}.
  *
  * <p>
- * This class is NOT thread safe.  It is meant to be discarded after one-time 
use.
+ * This class is NOT thread safe.
+ * It is typically discarded after one-time use although it can be reused 
against multiple inputs.
  */
+@SuppressWarnings({ "unchecked", "rawtypes" })
 public class UrlEncodingParserSession extends UonParserSession {
 
        private final boolean expandedParams;
@@ -36,35 +39,16 @@ public class UrlEncodingParserSession extends 
UonParserSession {
         * @param ctx
         *      The context creating this session object.
         *      The context contains all the configuration settings for this 
object.
-        * @param input
-        *      The input.
-        *      Can be any of the following types:
-        *      <ul>
-        *              <li><jk>null</jk>
-        *              <li>{@link Reader}
-        *              <li>{@link CharSequence}
-        *              <li>{@link InputStream} containing UTF-8 encoded text.
-        *              <li>{@link File} containing system encoded text.
-        *      </ul>
-        * @param op
-        *      The override properties.
-        *      These override any context properties defined in the context.
-        * @param javaMethod The java method that called this parser, usually 
the method in a REST servlet.
-        * @param outer The outer object for instantiating top-level non-static 
inner classes.
-        * @param locale
-        *      The session locale.
-        *      If <jk>null</jk>, then the locale defined on the context is 
used.
-        * @param timeZone
-        *      The session timezone.
-        *      If <jk>null</jk>, then the timezone defined on the context is 
used.
-        * @param mediaType The session media type (e.g. 
<js>"application/json"</js>).
+        * @param args
+        *      Runtime session arguments.
         */
-       public UrlEncodingParserSession(UrlEncodingParserContext ctx, ObjectMap 
op, Object input, Method javaMethod, Object outer, Locale locale, TimeZone 
timeZone, MediaType mediaType) {
-               super(ctx, op, input, javaMethod, outer, locale, timeZone, 
mediaType);
-               if (op == null || op.isEmpty()) {
+       protected UrlEncodingParserSession(UrlEncodingParserContext ctx, 
ParserSessionArgs args) {
+               super(ctx, args);
+               ObjectMap p = getProperties();
+               if (p.isEmpty()) {
                        expandedParams = ctx.expandedParams;
                } else {
-                       expandedParams = 
op.getBoolean(UrlEncodingContext.URLENC_expandedParams, false);
+                       expandedParams = 
p.getBoolean(UrlEncodingContext.URLENC_expandedParams, false);
                }
        }
 
@@ -84,4 +68,272 @@ public class UrlEncodingParserSession extends 
UonParserSession {
                }
                return false;
        }
+
+       @Override /* ParserSession */
+       protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws 
Exception {
+               UonReader r = getUonReader(pipe, true);
+               T o = parseAnything(type, r, getOuter());
+               return o;
+       }
+
+       @Override /* ReaderParserSession */
+       protected <K,V> Map<K,V> doParseIntoMap(ParserPipe pipe, Map<K,V> m, 
Type keyType, Type valueType) throws Exception {
+               UonReader r = getUonReader(pipe, true);
+               if (r.peekSkipWs() == '?')
+                       r.read();
+               m = parseIntoMap2(r, m, getClassMeta(Map.class, keyType, 
valueType), null);
+               return m;
+       }
+
+       private <T> T parseAnything(ClassMeta<T> eType, UonReader r, Object 
outer) throws Exception {
+
+               if (eType == null)
+                       eType = (ClassMeta<T>)object();
+               PojoSwap<T,Object> transform = 
(PojoSwap<T,Object>)eType.getPojoSwap();
+               ClassMeta<?> sType = eType.getSerializedClassMeta();
+
+               int c = r.peekSkipWs();
+               if (c == '?')
+                       r.read();
+
+               Object o;
+
+               if (sType.isObject()) {
+                       ObjectMap m = new ObjectMap(this);
+                       parseIntoMap2(r, m, getClassMeta(Map.class, 
String.class, Object.class), outer);
+                       if (m.containsKey("_value"))
+                               o = m.get("_value");
+                       else
+                               o = cast(m, null, eType);
+               } else if (sType.isMap()) {
+                       Map m = (sType.canCreateNewInstance() ? 
(Map)sType.newInstance() : new ObjectMap(this));
+                       o = parseIntoMap2(r, m, sType, m);
+               } else if (sType.canCreateNewBean(outer)) {
+                       BeanMap m = newBeanMap(outer, sType.getInnerClass());
+                       m = parseIntoBeanMap(r, m);
+                       o = m == null ? null : m.getBean();
+               } else if (sType.isCollection() || sType.isArray() || 
sType.isArgs()) {
+                       // ?1=foo&2=bar...
+                       Collection c2 = ((sType.isArray() || sType.isArgs()) || 
! sType.canCreateNewInstance(outer)) ? new ObjectList(this) : 
(Collection)sType.newInstance();
+                       Map<Integer,Object> m = new TreeMap<Integer,Object>();
+                       parseIntoMap2(r, m, sType, c2);
+                       c2.addAll(m.values());
+                       if (sType.isArray())
+                               o = ArrayUtils.toArray(c2, 
sType.getElementType().getInnerClass());
+                       else if (sType.isArgs())
+                               o = c2.toArray(new Object[c2.size()]);
+                       else
+                               o = c2;
+               } else {
+                       // It could be a non-bean with _type attribute.
+                       ObjectMap m = new ObjectMap(this);
+                       parseIntoMap2(r, m, getClassMeta(Map.class, 
String.class, Object.class), outer);
+                       if (m.containsKey(getBeanTypePropertyName(eType)))
+                               o = cast(m, null, eType);
+                       else if (m.containsKey("_value")) {
+                               o = convertToType(m.get("_value"), sType);
+                       } else {
+                               if (sType.getNotABeanReason() != null)
+                                       throw new ParseException(loc(r), "Class 
''{0}'' could not be instantiated as application/x-www-form-urlencoded.  
Reason: ''{1}''", sType, sType.getNotABeanReason());
+                               throw new ParseException(loc(r), "Malformed 
application/x-www-form-urlencoded input for class ''{0}''.", sType);
+                       }
+               }
+
+               if (transform != null && o != null)
+                       o = transform.unswap(this, o, eType);
+
+               if (outer != null)
+                       setParent(eType, o, outer);
+
+               return (T)o;
+       }
+
+       private <K,V> Map<K,V> parseIntoMap2(UonReader r, Map<K,V> m, 
ClassMeta<?> type, Object outer) throws Exception {
+
+               ClassMeta<K> keyType = (ClassMeta<K>)(type.isArgs() || 
type.isCollectionOrArray() ? getClassMeta(Integer.class) : type.getKeyType());
+
+               int c = r.peekSkipWs();
+               if (c == -1)
+                       return m;
+
+               final int S1=1; // Looking for attrName start.
+               final int S2=2; // Found attrName end, looking for =.
+               final int S3=3; // Found =, looking for valStart.
+               final int S4=4; // Looking for & or end.
+               boolean isInEscape = false;
+
+               int state = S1;
+               int argIndex = 0;
+               K currAttr = null;
+               while (c != -1) {
+                       c = r.read();
+                       if (! isInEscape) {
+                               if (state == S1) {
+                                       if (c == -1)
+                                               return m;
+                                       r.unread();
+                                       Object attr = parseAttr(r, true);
+                                       currAttr = attr == null ? null : 
convertAttrToType(m, trim(attr.toString()), keyType);
+                                       state = S2;
+                                       c = 0; // Avoid isInEscape if c was '\'
+                               } else if (state == S2) {
+                                       if (c == '\u0002')
+                                               state = S3;
+                                       else if (c == -1 || c == '\u0001') {
+                                               m.put(currAttr, null);
+                                               if (c == -1)
+                                                       return m;
+                                               state = S1;
+                                       }
+                               } else if (state == S3) {
+                                       if (c == -1 || c == '\u0001') {
+                                               ClassMeta<V> valueType = 
(ClassMeta<V>)(type.isArgs() ? type.getArg(argIndex++) : 
type.isCollectionOrArray() ? type.getElementType() : type.getValueType());
+                                               V value = convertAttrToType(m, 
"", valueType);
+                                               m.put(currAttr, value);
+                                               if (c == -1)
+                                                       return m;
+                                               state = S1;
+                                       } else  {
+                                               // For performance, we bypass 
parseAnything for string values.
+                                               ClassMeta<V> valueType = 
(ClassMeta<V>)(type.isArgs() ? type.getArg(argIndex++) : 
type.isCollectionOrArray() ? type.getElementType() : type.getValueType());
+                                               V value = 
(V)(valueType.isString() ? super.parseString(r.unread(), true) : 
super.parseAnything(valueType, r.unread(), outer, true, null));
+
+                                               // If we already encountered 
this parameter, turn it into a list.
+                                               if (m.containsKey(currAttr) && 
valueType.isObject()) {
+                                                       Object v2 = 
m.get(currAttr);
+                                                       if (! (v2 instanceof 
ObjectList)) {
+                                                               v2 = new 
ObjectList(v2).setBeanSession(this);
+                                                               m.put(currAttr, 
(V)v2);
+                                                       }
+                                                       
((ObjectList)v2).add(value);
+                                               } else {
+                                                       m.put(currAttr, value);
+                                               }
+                                               state = S4;
+                                               c = 0; // Avoid isInEscape if c 
was '\'
+                                       }
+                               } else if (state == S4) {
+                                       if (c == '\u0001')
+                                               state = S1;
+                                       else if (c == -1) {
+                                               return m;
+                                       }
+                               }
+                       }
+                       isInEscape = (c == '\\' && ! isInEscape);
+               }
+               if (state == S1)
+                       throw new ParseException(loc(r), "Could not find 
attribute name on object.");
+               if (state == S2)
+                       throw new ParseException(loc(r), "Could not find '=' 
following attribute name on object.");
+               if (state == S3)
+                       throw new ParseException(loc(r), "Dangling '=' found in 
object entry");
+               if (state == S4)
+                       throw new ParseException(loc(r), "Could not find end of 
object.");
+
+               return null; // Unreachable.
+       }
+
+       private <T> BeanMap<T> parseIntoBeanMap(UonReader r, BeanMap<T> m) 
throws Exception {
+
+               int c = r.peekSkipWs();
+               if (c == -1)
+                       return m;
+
+               final int S1=1; // Looking for attrName start.
+               final int S2=2; // Found attrName end, looking for =.
+               final int S3=3; // Found =, looking for valStart.
+               final int S4=4; // Looking for , or }
+               boolean isInEscape = false;
+
+               int state = S1;
+               String currAttr = "";
+               int currAttrLine = -1, currAttrCol = -1;
+               while (c != -1) {
+                       c = r.read();
+                       if (! isInEscape) {
+                               if (state == S1) {
+                                       if (c == -1) {
+                                               return m;
+                                       }
+                                       r.unread();
+                                       currAttrLine= r.getLine();
+                                       currAttrCol = r.getColumn();
+                                       currAttr = parseAttrName(r, true);
+                                       if (currAttr == null)  // Value was 
'%00'
+                                               return null;
+                                       state = S2;
+                               } else if (state == S2) {
+                                       if (c == '\u0002')
+                                               state = S3;
+                                       else if (c == -1 || c == '\u0001') {
+                                               m.put(currAttr, null);
+                                               if (c == -1)
+                                                       return m;
+                                               state = S1;
+                                       }
+                               } else if (state == S3) {
+                                       if (c == -1 || c == '\u0001') {
+                                               if (! 
currAttr.equals(getBeanTypePropertyName(m.getClassMeta()))) {
+                                                       BeanPropertyMeta pMeta 
= m.getPropertyMeta(currAttr);
+                                                       if (pMeta == null) {
+                                                               
onUnknownProperty(r.getPipe(), currAttr, m, currAttrLine, currAttrCol);
+                                                       } else {
+                                                               
setCurrentProperty(pMeta);
+                                                               // In cases of 
"&foo=", create an empty instance of the value if createable.
+                                                               // Otherwise, 
leave it null.
+                                                               ClassMeta<?> cm 
= pMeta.getClassMeta();
+                                                               if 
(cm.canCreateNewInstance())
+                                                                       
pMeta.set(m, currAttr, cm.newInstance());
+                                                               
setCurrentProperty(null);
+                                                       }
+                                               }
+                                               if (c == -1)
+                                                       return m;
+                                               state = S1;
+                                       } else {
+                                               if (! 
currAttr.equals(getBeanTypePropertyName(m.getClassMeta()))) {
+                                                       BeanPropertyMeta pMeta 
= m.getPropertyMeta(currAttr);
+                                                       if (pMeta == null) {
+                                                               
onUnknownProperty(r.getPipe(), currAttr, m, currAttrLine, currAttrCol);
+                                                               
parseAnything(object(), r.unread(), m.getBean(false), true, null); // Read 
content anyway to ignore it
+                                                       } else {
+                                                               
setCurrentProperty(pMeta);
+                                                               if 
(shouldUseExpandedParams(pMeta)) {
+                                                                       
ClassMeta et = pMeta.getClassMeta().getElementType();
+                                                                       Object 
value = parseAnything(et, r.unread(), m.getBean(false), true, pMeta);
+                                                                       
setName(et, value, currAttr);
+                                                                       
pMeta.add(m, currAttr, value);
+                                                               } else {
+                                                                       
ClassMeta<?> cm = pMeta.getClassMeta();
+                                                                       Object 
value = parseAnything(cm, r.unread(), m.getBean(false), true, pMeta);
+                                                                       
setName(cm, value, currAttr);
+                                                                       
pMeta.set(m, currAttr, value);
+                                                               }
+                                                               
setCurrentProperty(null);
+                                                       }
+                                               }
+                                               state = S4;
+                                       }
+                               } else if (state == S4) {
+                                       if (c == '\u0001')
+                                               state = S1;
+                                       else if (c == -1) {
+                                               return m;
+                                       }
+                               }
+                       }
+                       isInEscape = (c == '\\' && ! isInEscape);
+               }
+               if (state == S1)
+                       throw new ParseException(loc(r), "Could not find 
attribute name on object.");
+               if (state == S2)
+                       throw new ParseException(loc(r), "Could not find '=' 
following attribute name on object.");
+               if (state == S3)
+                       throw new ParseException(loc(r), "Could not find value 
following '=' on object.");
+               if (state == S4)
+                       throw new ParseException(loc(r), "Could not find end of 
object.");
+
+               return null; // Unreachable.
+       }
 }

Reply via email to