juneau-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jamesbog...@apache.org
Subject [13/34] incubator-juneau git commit: Add builder classes for all serializers and parsers.
Date Fri, 10 Mar 2017 16:51:09 GMT
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java b/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java
new file mode 100644
index 0000000..ef4c95c
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java
@@ -0,0 +1,800 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.uon;
+
+import static org.apache.juneau.uon.UonParserContext.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.transform.*;
+
+/**
+ * Parses UON (a notation for URL-encoded query parameter values) text into POJO models.
+ *
+ * <h5 class='section'>Media types:</h5>
+ * <p>
+ * Handles <code>Content-Type</code> types: <code>text/uon</code>
+ *
+ * <h5 class='section'>Description:</h5>
+ * <p>
+ * This parser uses a state machine, which makes it very fast and efficient.
+ *
+ * <h5 class='section'>Configurable properties:</h5>
+ * <p>
+ * This class has the following properties associated with it:
+ * <ul>
+ * 	<li>{@link UonParserContext}
+ * 	<li>{@link ParserContext}
+ * 	<li>{@link BeanContext}
+ * </ul>
+ */
+@SuppressWarnings({ "rawtypes", "unchecked" })
+@Consumes("text/uon")
+public class UonParser extends ReaderParser {
+
+	/** Reusable instance of {@link UonParser}, all default settings. */
+	public static final UonParser DEFAULT = new UonParser(PropertyStore.create());
+
+	/** Reusable instance of {@link UonParser} with decodeChars set to true. */
+	public static final UonParser DEFAULT_DECODING = new UonParser.Decoding(PropertyStore.create());
+
+	// Characters that need to be preceeded 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.
+
+
+	/** Default parser, decoding. */
+	public static class Decoding extends UonParser {
+
+		/**
+		 * Constructor.
+		 * @param propertyStore The property store containing all the settings for this object.
+		 */
+		public Decoding(PropertyStore propertyStore) {
+			super(propertyStore);
+		}
+
+		@Override /* CoreObject */
+		protected ObjectMap getOverrideProperties() {
+			return super.getOverrideProperties().append(UON_decodeChars, true);
+		}
+	}
+
+
+	private final UonParserContext ctx;
+
+	/**
+	 * Constructor.
+	 * @param propertyStore The property store containing all the settings for this object.
+	 */
+	public UonParser(PropertyStore propertyStore) {
+		super(propertyStore);
+		this.ctx = createContext(UonParserContext.class);
+	}
+
+	@Override /* CoreObject */
+	public UonParserBuilder builder() {
+		return new UonParserBuilder(propertyStore);
+	}
+
+	/**
+	 * Workhorse method.
+	 *
+	 * @param session The parser context for this parse.
+	 * @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
+	 */
+	protected <T> T parseAnything(UonParserSession session, ClassMeta<T> eType, ParserReader 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.isObject()) {
+			if (c == '(') {
+				ObjectMap m = new ObjectMap(session);
+				parseIntoMap(session, r, m, string(), object(), pMeta);
+				o = session.cast(m, pMeta, eType);
+			} else if (c == '@') {
+				Collection l = new ObjectList(session);
+				o = parseIntoCollection(session, r, l, sType.getElementType(), isUrlParamValue, pMeta);
+			} else {
+				String s = parseString(session, r, isUrlParamValue);
+				if (c != '\'') {
+					if ("true".equals(s) || "false".equals(s))
+						o = Boolean.valueOf(s);
+					else if (StringUtils.isNumeric(s))
+						o = StringUtils.parseNumber(s, Number.class);
+					else
+						o = s;
+				} else {
+					o = s;
+				}
+			}
+		} else if (sType.isBoolean()) {
+			o = parseBoolean(session, r);
+		} else if (sType.isCharSequence()) {
+			o = parseString(session, r, isUrlParamValue);
+		} else if (sType.isChar()) {
+			String s = parseString(session, r, isUrlParamValue);
+			o = s == null ? null : s.charAt(0);
+		} else if (sType.isNumber()) {
+			o = parseNumber(session, r, (Class<? extends Number>)sType.getInnerClass());
+		} else if (sType.isMap()) {
+			Map m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : new ObjectMap(session));
+			o = parseIntoMap(session, r, m, sType.getKeyType(), sType.getValueType(), pMeta);
+		} else if (sType.isCollection()) {
+			if (c == '(') {
+				ObjectMap m = new ObjectMap(session);
+				parseIntoMap(session, 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(session.getBeanTypePropertyName()))
+					o = session.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(session));
+					l.add(m.cast(sType.getElementType()));
+					o = l;
+				}
+			} else {
+				Collection l = (sType.canCreateNewInstance(outer) ? (Collection)sType.newInstance(outer) : new ObjectList(session));
+				o = parseIntoCollection(session, r, l, sType.getElementType(), isUrlParamValue, pMeta);
+			}
+		} 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.canCreateNewInstanceFromString(outer)) {
+			String s = parseString(session, r, isUrlParamValue);
+			if (s != null)
+				o = sType.newInstanceFromString(outer, s);
+		} else if (sType.canCreateNewInstanceFromNumber(outer)) {
+			o = sType.newInstanceFromNumber(session, outer, parseNumber(session, r, sType.getNewInstanceFromNumberClass()));
+		} else if (sType.isArray()) {
+			if (c == '(') {
+				ObjectMap m = new ObjectMap(session);
+				parseIntoMap(session, 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(session.getBeanTypePropertyName()))
+					o = session.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 = session.toArray(sType, l);
+				}
+			} else {
+				ArrayList l = (ArrayList)parseIntoCollection(session, r, new ArrayList(), sType.getElementType(), isUrlParamValue, pMeta);
+				o = session.toArray(sType, l);
+			}
+		} else if (c == '(') {
+			// It could be a non-bean with _type attribute.
+			ObjectMap m = new ObjectMap(session);
+			parseIntoMap(session, r, m, string(), object(), pMeta);
+			if (m.containsKey(session.getBeanTypePropertyName()))
+				o = session.cast(m, pMeta, eType);
+			else
+				throw new ParseException(session, "Class ''{0}'' could not be instantiated.  Reason: ''{1}''", sType.getInnerClass().getName(), sType.getNotABeanReason());
+		} else {
+			throw new ParseException(session, "Class ''{0}'' could not be instantiated.  Reason: ''{1}''", sType.getInnerClass().getName(), sType.getNotABeanReason());
+		}
+
+		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<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(session, r);
+		if (c != '(')
+			throw new ParseException(session, "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(session, r, session.isDecodeChars());
+						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 == 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(session, m, "", valueType);
+						m.put(currAttr, value);
+						if (c == -1 || c == ')' || c == AMP)
+							return m;
+						state = S1;
+					} else  {
+						V value = parseAnything(session, 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(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 ')' marking end of object.");
+
+		return null; // Unreachable.
+	}
+
+	private <E> Collection<E> parseIntoCollection(UonParserSession session, ParserReader r, Collection<E> l, ClassMeta<E> elementType, boolean isUrlParamValue, BeanPropertyMeta pMeta) throws Exception {
+
+		int c = r.readSkipWs();
+		if (c == -1 || c == AMP)
+			return null;
+		if (c == 'n')
+			return (Collection<E>)parseNull(session, r);
+
+		// 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
+				throw new ParseException(session, "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(parseAnything(session, elementType, r.unread(), l, false, pMeta));
+							r.read();
+						}
+						return l;
+					} else if (Character.isWhitespace(c)) {
+						skipSpace(r);
+					} else {
+						l.add(parseAnything(session, elementType, 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(session, "Could not find start of entry in array.");
+			if (state == S3)
+				throw new ParseException(session, "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(parseAnything(session, elementType, 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 null;  // Unreachable.
+	}
+
+	private <T> BeanMap<T> parseIntoBeanMap(UonParserSession session, ParserReader r, BeanMap<T> m) throws Exception {
+
+		int c = r.readSkipWs();
+		if (c == -1 || c == AMP)
+			return null;
+		if (c == 'n')
+			return (BeanMap<T>)parseNull(session, r);
+		if (c != '(')
+			throw new ParseException(session, "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(session, r, session.isDecodeChars());
+						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(session.getBeanTypePropertyName())) {
+							BeanPropertyMeta pMeta = m.getPropertyMeta(currAttr);
+							if (pMeta == null) {
+								onUnknownProperty(session, currAttr, m, currAttrLine, currAttrCol);
+							} else {
+								Object value = session.convertToType("", pMeta.getClassMeta());
+								pMeta.set(m, value);
+							}
+						}
+						if (c == -1 || c == ')' || c == AMP)
+							return m;
+						state = S1;
+					} else {
+						if (! currAttr.equals(session.getBeanTypePropertyName())) {
+							BeanPropertyMeta pMeta = m.getPropertyMeta(currAttr);
+							if (pMeta == null) {
+								onUnknownProperty(session, currAttr, m, currAttrLine, currAttrCol);
+								parseAnything(session, object(), r.unread(), m.getBean(false), false, null); // Read content anyway to ignore it
+							} else {
+								session.setCurrentProperty(pMeta);
+								ClassMeta<?> cm = pMeta.getClassMeta();
+								Object value = parseAnything(session, cm, r.unread(), m.getBean(false), false, pMeta);
+								setName(cm, value, currAttr);
+								pMeta.set(m, value);
+								session.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);
+		}
+		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 ')' marking end of object.");
+
+		return null; // Unreachable.
+	}
+
+	private Object parseNull(UonParserSession session, ParserReader r) throws Exception {
+		String s = parseString(session, r, false);
+		if ("ull".equals(s))
+			return null;
+		throw new ParseException(session, "Unexpected character sequence: ''{0}''", s);
+	}
+
+	/**
+	 * Convenience method for parsing an attribute from the specified parser.
+	 *
+	 * @param session
+	 * @param r
+	 * @param encoded
+	 * @return The parsed object
+	 * @throws Exception
+	 */
+	protected final Object parseAttr(UonParserSession session, ParserReader r, boolean encoded) throws Exception {
+		Object attr;
+		attr = parseAttrName(session, r, encoded);
+		return attr;
+	}
+
+	/**
+	 * Parses an attribute name from the specified reader.
+	 *
+	 * @param session
+	 * @param r
+	 * @param encoded
+	 * @return The parsed attribute name.
+	 * @throws Exception
+	 */
+	protected final String parseAttrName(UonParserSession session, ParserReader 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(session, 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 : session.trim(s));
+					}
+				}
+				isInEscape = isInEscape(c, r, isInEscape);
+			}
+		}
+
+		// We should never get here.
+		throw new ParseException(session, "Unexpected condition.");
+	}
+
+
+	/**
+	 * Returns true if the next character in the stream is preceeded by an escape '~' character.
+	 * @param c The current character.
+	 * @param r The reader.
+	 * @param prevIsInEscape What the flag was last time.
+	 */
+	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 session
+	 * @param r
+	 * @param isUrlParamValue
+	 * @return The parsed string.
+	 * @throws Exception
+	 */
+	protected final String parseString(UonParserSession session, ParserReader 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(session, 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 : session.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.
+	 */
+	static String parsePString(UonParserSession session, ParserReader 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 session.trim(r.getMarked(0, -1));
+			}
+			if (c == EQ)
+				r.replace('=');
+			isInEscape = isInEscape(c, r, isInEscape);
+		}
+		throw new ParseException(session, "Unmatched parenthesis");
+	}
+
+	private Boolean parseBoolean(UonParserSession session, ParserReader r) throws Exception {
+		String s = parseString(session, 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(session, "Unrecognized syntax for boolean.  ''{0}''.", s);
+	}
+
+	private Number parseNumber(UonParserSession session, ParserReader r, Class<? extends Number> c) throws Exception {
+		String s = parseString(session, 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(UonParserSession session, ParserReader r) throws Exception {
+		while (true) {
+			int c = r.read();
+			if (c == -1)
+				return;
+			if (! Character.isWhitespace(c))
+				throw new ParseException(session, "Remainder after parse: ''{0}''.", (char)c);
+		}
+	}
+
+	private Object[] parseArgs(UonParserSession session, ParserReader r, ClassMeta<?>[] argTypes) throws Exception {
+
+		final int S1=1; // Looking for start of entry
+		final int S2=2; // Looking for , or )
+
+		Object[] o = new Object[argTypes.length];
+		int i = 0;
+
+		int c = r.readSkipWs();
+		if (c == -1 || c == AMP)
+			return null;
+		if (c != '@')
+			throw new ParseException(session, "Expected '@' at beginning of args array.");
+		c = r.read();
+
+		int state = S1;
+		while (c != -1 && c != AMP) {
+			c = r.read();
+			if (state == S1) {
+				if (c == ')')
+					return o;
+				o[i] = parseAnything(session, argTypes[i], r.unread(), session.getOuter(), false, null);
+				i++;
+				state = S2;
+			} else if (state == S2) {
+				if (c == ',') {
+					state = S1;
+				} else if (c == ')') {
+					return o;
+				}
+			}
+		}
+
+		throw new ParseException(session, "Did not find ')' at the end of args array.");
+	}
+
+	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;
+			}
+		}
+	}
+
+	/**
+	 * Create a UON parser session for parsing parameter values.
+	 *
+	 * @param input
+	 * @return A new parser session.
+	 */
+	protected final UonParserSession createParameterSession(Object input) {
+		return new UonParserSession(ctx, input);
+	}
+
+
+	//--------------------------------------------------------------------------------
+	// Entry point methods
+	//--------------------------------------------------------------------------------
+
+	@Override /* Parser */
+	public UonParserSession createSession(Object input, ObjectMap op, Method javaMethod, Object outer, Locale locale, TimeZone timeZone, MediaType mediaType) {
+		return new UonParserSession(ctx, op, input, javaMethod, outer, locale, timeZone, mediaType);
+	}
+
+	@Override /* Parser */
+	protected <T> T doParse(ParserSession session, ClassMeta<T> type) throws Exception {
+		UonParserSession s = (UonParserSession)session;
+		UonReader r = s.getReader();
+		T o = parseAnything(s, type, r, s.getOuter(), true, null);
+		validateEnd(s, r);
+		return o;
+	}
+
+	@Override /* ReaderParser */
+	protected <K,V> Map<K,V> doParseIntoMap(ParserSession session, Map<K,V> m, Type keyType, Type valueType) throws Exception {
+		UonParserSession s = (UonParserSession)session;
+		UonReader r = s.getReader();
+		m = parseIntoMap(s, r, m, (ClassMeta<K>)session.getClassMeta(keyType), (ClassMeta<V>)session.getClassMeta(valueType), null);
+		validateEnd(s, r);
+		return m;
+	}
+
+	@Override /* ReaderParser */
+	protected <E> Collection<E> doParseIntoCollection(ParserSession session, Collection<E> c, Type elementType) throws Exception {
+		UonParserSession s = (UonParserSession)session;
+		UonReader r = s.getReader();
+		c = parseIntoCollection(s, r, c, (ClassMeta<E>)session.getClassMeta(elementType), false, null);
+		validateEnd(s, r);
+		return c;
+	}
+
+	@Override /* ReaderParser */
+	protected Object[] doParseArgs(ParserSession session, ClassMeta<?>[] argTypes) throws Exception {
+		UonParserSession s = (UonParserSession)session;
+		UonReader r = s.getReader();
+		Object[] a = parseArgs(s, r, argTypes);
+		return a;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/juneau-core/src/main/java/org/apache/juneau/uon/UonParserBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonParserBuilder.java b/juneau-core/src/main/java/org/apache/juneau/uon/UonParserBuilder.java
new file mode 100644
index 0000000..694a97e
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonParserBuilder.java
@@ -0,0 +1,495 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.uon;
+
+import static org.apache.juneau.uon.UonParserContext.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.urlencoding.*;
+
+/**
+ * Builder class for building instances of UON parsers.
+ */
+public class UonParserBuilder extends ParserBuilder {
+
+	/**
+	 * Constructor, default settings.
+	 */
+	public UonParserBuilder() {
+		super();
+	}
+
+	/**
+	 * Constructor.
+	 * @param propertyStore The initial configuration settings for this builder.
+	 */
+	public UonParserBuilder(PropertyStore propertyStore) {
+		super(propertyStore);
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParser build() {
+		return new UonParser(propertyStore);
+	}
+
+
+	//--------------------------------------------------------------------------------
+	// Properties
+	//--------------------------------------------------------------------------------
+
+	/**
+	 * <b>Configuration property:</b> Decode <js>"%xx"</js> sequences.
+	 * <p>
+	 * <ul>
+	 * 	<li><b>Name:</b> <js>"UonParser.decodeChars"</js>
+	 * 	<li><b>Data type:</b> <code>Boolean</code>
+	 * 	<li><b>Default:</b> <jk>false</jk> for {@link UonParser}, <jk>true</jk> for {@link UrlEncodingParser}
+	 * 	<li><b>Session-overridable:</b> <jk>true</jk>
+	 * </ul>
+	 * <p>
+	 * Specify <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk>
+	 * 	if they've already been decoded before being passed to this parser.
+	 * <p>
+	 * <h5 class='section'>Notes:</h5>
+	 * <ul>
+	 * 	<li>This is equivalent to calling <code>property(<jsf>UON_decodeChars</jsf>, value)</code>.
+	 * </ul>
+	 *
+	 * @param value The new value for this property.
+	 * @return This object (for method chaining).
+	 * @see UonParserContext#UON_decodeChars
+	 */
+	public UonParserBuilder decodeChars(boolean value) {
+		return property(UON_decodeChars, value);
+	}
+
+	/**
+	 * Shortcut for calling <code>decodeChars(<jk>true</jk>)</code>.
+	 *
+	 * @return This object (for method chaining).
+	 */
+	public UonParserBuilder decoding() {
+		return decodeChars(true);
+	}
+
+	@Override /* ParserBuilder */
+	public UonParserBuilder trimStrings(boolean value) {
+		super.trimStrings(value);
+		return this;
+	}
+
+	@Override /* ParserBuilder */
+	public UonParserBuilder strict(boolean value) {
+		super.strict(value);
+		return this;
+	}
+
+	@Override /* ParserBuilder */
+	public UonParserBuilder strict() {
+		super.strict();
+		return this;
+	}
+
+	@Override /* ParserBuilder */
+	public UonParserBuilder inputStreamCharset(String value) {
+		super.inputStreamCharset(value);
+		return this;
+	}
+
+	@Override /* ParserBuilder */
+	public UonParserBuilder fileCharset(String value) {
+		super.fileCharset(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder beansRequireDefaultConstructor(boolean value) {
+		super.beansRequireDefaultConstructor(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder beansRequireSerializable(boolean value) {
+		super.beansRequireSerializable(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder beansRequireSettersForGetters(boolean value) {
+		super.beansRequireSettersForGetters(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder beansRequireSomeProperties(boolean value) {
+		super.beansRequireSomeProperties(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder beanMapPutReturnsOldValue(boolean value) {
+		super.beanMapPutReturnsOldValue(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder beanConstructorVisibility(Visibility value) {
+		super.beanConstructorVisibility(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder beanClassVisibility(Visibility value) {
+		super.beanClassVisibility(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder beanFieldVisibility(Visibility value) {
+		super.beanFieldVisibility(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder methodVisibility(Visibility value) {
+		super.methodVisibility(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder useJavaBeanIntrospector(boolean value) {
+		super.useJavaBeanIntrospector(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder useInterfaceProxies(boolean value) {
+		super.useInterfaceProxies(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder ignoreUnknownBeanProperties(boolean value) {
+		super.ignoreUnknownBeanProperties(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder ignoreUnknownNullBeanProperties(boolean value) {
+		super.ignoreUnknownNullBeanProperties(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder ignorePropertiesWithoutSetters(boolean value) {
+		super.ignorePropertiesWithoutSetters(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder ignoreInvocationExceptionsOnGetters(boolean value) {
+		super.ignoreInvocationExceptionsOnGetters(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder ignoreInvocationExceptionsOnSetters(boolean value) {
+		super.ignoreInvocationExceptionsOnSetters(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder sortProperties(boolean value) {
+		super.sortProperties(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder notBeanPackages(String...values) {
+		super.notBeanPackages(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder notBeanPackages(Collection<String> values) {
+		super.notBeanPackages(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder setNotBeanPackages(String...values) {
+		super.setNotBeanPackages(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder setNotBeanPackages(Collection<String> values) {
+		super.setNotBeanPackages(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder removeNotBeanPackages(String...values) {
+		super.removeNotBeanPackages(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder removeNotBeanPackages(Collection<String> values) {
+		super.removeNotBeanPackages(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder notBeanClasses(Class<?>...values) {
+		super.notBeanClasses(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder notBeanClasses(Collection<Class<?>> values) {
+		super.notBeanClasses(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder setNotBeanClasses(Class<?>...values) {
+		super.setNotBeanClasses(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder setNotBeanClasses(Collection<Class<?>> values) {
+		super.setNotBeanClasses(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder removeNotBeanClasses(Class<?>...values) {
+		super.removeNotBeanClasses(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder removeNotBeanClasses(Collection<Class<?>> values) {
+		super.removeNotBeanClasses(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder beanFilters(Class<?>...values) {
+		super.beanFilters(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder beanFilters(Collection<Class<?>> values) {
+		super.beanFilters(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder setBeanFilters(Class<?>...values) {
+		super.setBeanFilters(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder setBeanFilters(Collection<Class<?>> values) {
+		super.setBeanFilters(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder removeBeanFilters(Class<?>...values) {
+		super.removeBeanFilters(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder removeBeanFilters(Collection<Class<?>> values) {
+		super.removeBeanFilters(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder pojoSwaps(Class<?>...values) {
+		super.pojoSwaps(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder pojoSwaps(Collection<Class<?>> values) {
+		super.pojoSwaps(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder setPojoSwaps(Class<?>...values) {
+		super.setPojoSwaps(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder setPojoSwaps(Collection<Class<?>> values) {
+		super.setPojoSwaps(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder removePojoSwaps(Class<?>...values) {
+		super.removePojoSwaps(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder removePojoSwaps(Collection<Class<?>> values) {
+		super.removePojoSwaps(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder implClasses(Map<Class<?>,Class<?>> values) {
+		super.implClasses(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public <T> UonParserBuilder implClass(Class<T> interfaceClass, Class<? extends T> implClass) {
+		super.implClass(interfaceClass, implClass);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder beanDictionary(Class<?>...values) {
+		super.beanDictionary(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder beanDictionary(Collection<Class<?>> values) {
+		super.beanDictionary(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder setBeanDictionary(Class<?>...values) {
+		super.setBeanDictionary(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder setBeanDictionary(Collection<Class<?>> values) {
+		super.setBeanDictionary(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder removeFromBeanDictionary(Class<?>...values) {
+		super.removeFromBeanDictionary(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder removeFromBeanDictionary(Collection<Class<?>> values) {
+		super.removeFromBeanDictionary(values);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder beanTypePropertyName(String value) {
+		super.beanTypePropertyName(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder defaultParser(Class<?> value) {
+		super.defaultParser(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder locale(Locale value) {
+		super.locale(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder timeZone(TimeZone value) {
+		super.timeZone(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder mediaType(MediaType value) {
+		super.mediaType(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder debug(boolean value) {
+		super.debug(value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder property(String name, Object value) {
+		super.property(name, value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder properties(Map<String,Object> properties) {
+		super.properties(properties);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder addToProperty(String name, Object value) {
+		super.addToProperty(name, value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder putToProperty(String name, Object key, Object value) {
+		super.putToProperty(name, key, value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder putToProperty(String name, Object value) {
+		super.putToProperty(name, value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder removeFromProperty(String name, Object value) {
+		super.removeFromProperty(name, value);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder classLoader(ClassLoader classLoader) {
+		super.classLoader(classLoader);
+		return this;
+	}
+
+	@Override /* CoreObjectBuilder */
+	public UonParserBuilder apply(PropertyStore copyFrom) {
+		super.apply(copyFrom);
+		return this;
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/juneau-core/src/main/java/org/apache/juneau/uon/UonParserContext.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonParserContext.java b/juneau-core/src/main/java/org/apache/juneau/uon/UonParserContext.java
new file mode 100644
index 0000000..7217f9d
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonParserContext.java
@@ -0,0 +1,74 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.uon;
+
+import org.apache.juneau.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.urlencoding.*;
+
+/**
+ * Configurable properties on the {@link UonParser} class.
+ * <p>
+ * Context properties are set by calling {@link PropertyStore#setProperty(String, Object)} on the property store
+ * passed into the constructor.
+ * <p>
+ * See {@link PropertyStore} for more information about context properties.
+ *
+ * <h5 class='section'>Inherited configurable properties:</h5>
+ * <ul class='javahierarchy'>
+ * 	<li class='c'><a class="doclink" href="../BeanContext.html#ConfigProperties">BeanContext</a> - Properties associated with handling beans on serializers and parsers.
+ * 	<ul>
+ * 		<li class='c'><a class="doclink" href="../parser/ParserContext.html#ConfigProperties">ParserContext</a> - Configurable properties common to all parsers.
+ * 	</ul>
+ * </ul>
+ */
+public class UonParserContext extends ParserContext {
+
+	/**
+	 * <b>Configuration property:</b> Decode <js>"%xx"</js> sequences.
+	 * <p>
+	 * <ul>
+	 * 	<li><b>Name:</b> <js>"UonParser.decodeChars"</js>
+	 * 	<li><b>Data type:</b> <code>Boolean</code>
+	 * 	<li><b>Default:</b> <jk>false</jk> for {@link UonParser}, <jk>true</jk> for {@link UrlEncodingParser}
+	 * 	<li><b>Session-overridable:</b> <jk>true</jk>
+	 * </ul>
+	 * <p>
+	 * Specify <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk>
+	 * 	if they've already been decoded before being passed to this parser.
+	 */
+	public static final String UON_decodeChars = "UonParser.decodeChars";
+
+	final boolean
+		decodeChars;
+
+	/**
+	 * Constructor.
+	 * <p>
+	 * Typically only called from {@link PropertyStore#getContext(Class)}.
+	 *
+	 * @param ps The property store that created this context.
+	 */
+	public UonParserContext(PropertyStore ps) {
+		super(ps);
+		this.decodeChars = ps.getProperty(UON_decodeChars, boolean.class, false);
+	}
+
+	@Override /* Context */
+	public ObjectMap asMap() {
+		return super.asMap()
+			.append("UonParserContext", new ObjectMap()
+				.append("decodeChars", decodeChars)
+			);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/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
new file mode 100644
index 0000000..bf2334b
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonParserSession.java
@@ -0,0 +1,118 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.uon;
+
+import static org.apache.juneau.uon.UonParserContext.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.parser.*;
+
+/**
+ * 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.
+ */
+public class UonParserSession extends ParserSession {
+
+	private final boolean decodeChars;
+	private UonReader reader;
+
+	/**
+	 * Create a new session using properties specified in the context.
+	 *
+	 * @param ctx The context creating this session object.
+	 * 	he 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>).
+	 */
+	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()) {
+			decodeChars = ctx.decodeChars;
+		} else {
+			decodeChars = op.getBoolean(UON_decodeChars, ctx.decodeChars);
+		}
+	}
+
+	/**
+	 * Create a specialized parser session for parsing URL parameters.
+	 * <p>
+	 * The main difference is that characters are never decoded, and the {@link UonParserContext#UON_decodeChars} 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);
+		decodeChars = false;
+	}
+
+	/**
+	 * Returns the {@link UonParserContext#UON_decodeChars} setting value for this session.
+	 *
+	 * @return The {@link UonParserContext#UON_decodeChars} setting value for this session.
+	 */
+	public final boolean isDecodeChars() {
+		return decodeChars;
+	}
+
+	@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);
+			else
+				reader = new UonReader(super.getReader(), decodeChars);
+		}
+		return reader;
+	}
+
+	@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());
+		}
+		return m;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/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
new file mode 100644
index 0000000..eba2df4
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonReader.java
@@ -0,0 +1,195 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.uon;
+
+import java.io.*;
+
+import org.apache.juneau.parser.*;
+
+/**
+ * Same functionality as {@link ParserReader} except automatically decoded <code>%xx</code> escape sequences.
+ * <p>
+ * Escape sequences are assumed to be encoded UTF-8.  Extended Unicode (&gt;\u10000) is supported.
+ * <p>
+ * If decoding is enabled, the following character replacements occur so that boundaries are not lost:
+ * <ul>
+ * 	<li><js>'&amp;'</js> -&gt; <js>'\u0001'</js>
+ * 	<li><js>'='</js> -&gt; <js>'\u0002'</js>
+ * </ul>
+ */
+public final class UonReader extends ParserReader {
+
+	private final boolean decodeChars;
+	private final char[] buff;
+	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}).
+	 *
+	 * @param r The Reader being wrapped.
+	 * @param decodeChars If <jk>true</jk>, decode <code>%xx</code> escape sequences.
+	 */
+	public UonReader(Reader r, boolean decodeChars) {
+		super(r);
+		this.decodeChars = decodeChars;
+		this.buff = new char[1024];
+	}
+
+	@Override /* Reader */
+	public final int read(char[] cbuf, int off, int len) throws IOException {
+
+		if (! decodeChars)
+			return super.read(cbuf, off, len);
+
+		// Copy any remainder to the beginning of the buffer.
+		int remainder = iEnd - iCurrent;
+		if (remainder > 0)
+			System.arraycopy(buff, iCurrent, buff, 0, remainder);
+		iCurrent = 0;
+
+		int expected = buff.length - remainder;
+
+		int x = super.read(buff, remainder, expected);
+		if (x == -1 && remainder == 0)
+			return -1;
+
+		iEnd = remainder + (x == -1 ? 0 : x);
+
+		int i = 0;
+		while (i < len) {
+			if (iCurrent >= iEnd)
+				return i;
+			char c = buff[iCurrent++];
+			if (c == '+') {
+				cbuf[off + i++] = ' ';
+			} else if (c == '&') {
+				cbuf[off + i++] = '\u0001';
+			} else if (c == '=') {
+				cbuf[off + i++] = '\u0002';
+			} else if (c != '%') {
+				cbuf[off + i++] = c;
+			} else {
+				int iMark = iCurrent-1;  // Keep track of current position.
+
+				// Stop if there aren't at least two more characters following '%' in the buffer,
+				// or there aren't at least two more positions open in cbuf to handle double-char chars.
+				if (iMark+2 >= iEnd || i+2 > len) {
+					iCurrent--;
+					return i;
+				}
+
+				int b0 = readEncodedByte();
+				int cx;
+
+				// 0xxxxxxx
+				if (b0 < 128) {
+					cx = b0;
+
+				// 10xxxxxx
+				} else if (b0 < 192) {
+					throw new IOException("Invalid hex value for first escape pattern in UTF-8 sequence:  " + b0);
+
+				// 110xxxxx	10xxxxxx
+				// 11000000(192) - 11011111(223)
+				} else if (b0 < 224) {
+					cx = readUTF8(b0-192, 1);
+					if (cx == -1) {
+						iCurrent = iMark;
+						return i;
+					}
+
+				// 1110xxxx	10xxxxxx	10xxxxxx
+				// 11100000(224) - 11101111(239)
+				} else if (b0 < 240) {
+					cx = readUTF8(b0-224, 2);
+					if (cx == -1) {
+						iCurrent = iMark;
+						return i;
+					}
+
+				// 11110xxx	10xxxxxx	10xxxxxx	10xxxxxx
+				// 11110000(240) - 11110111(247)
+				} else if (b0 < 248) {
+					cx = readUTF8(b0-240, 3);
+					if (cx == -1) {
+						iCurrent = iMark;
+						return i;
+					}
+
+				} else
+					throw new IOException("Invalid hex value for first escape pattern in UTF-8 sequence:  " + b0);
+
+				if (cx < 0x10000)
+					cbuf[off + i++] = (char)cx;
+				else {
+					cx -= 0x10000;
+					cbuf[off + i++] = (char)(0xd800 + (cx >> 10));
+					cbuf[off + i++] = (char)(0xdc00 + (cx & 0x3ff));
+				}
+			}
+		}
+		return i;
+	}
+
+	private final int readUTF8(int n, final int numBytes) throws IOException {
+		if (iCurrent + numBytes*3 > iEnd)
+			return -1;
+		for (int i = 0; i < numBytes; i++) {
+			n <<= 6;
+			n += readHex()-128;
+		}
+		return n;
+	}
+
+	private final 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 {
+		if (iEnd <= iCurrent + 1)
+			throw new IOException("Incomplete trailing escape pattern");
+		int h = buff[iCurrent++];
+		int l = buff[iCurrent++];
+		h = fromHexChar(h);
+		l = fromHexChar(l);
+		return (h << 4) + l;
+	}
+
+	private final int fromHexChar(int c) throws IOException {
+		if (c >= '0' && c <= '9')
+			return c - '0';
+		if (c >= 'a' && c <= 'f')
+			return 10 + c - 'a';
+		if (c >= 'A' && c <= 'F')
+			return 10 + c - 'A';
+		throw new IOException("Invalid hex character '"+c+"' found in escape pattern.");
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/95e832e1/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
new file mode 100644
index 0000000..6ecb85d
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonSerializer.java
@@ -0,0 +1,386 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.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.serializer.*;
+import org.apache.juneau.transform.*;
+
+/**
+ * Serializes POJO models to UON (a notation for URL-encoded query parameter values).
+ *
+ * <h5 class='section'>Media types:</h5>
+ * <p>
+ * Handles <code>Accept</code> types: <code>text/uon</code>
+ * <p>
+ * Produces <code>Content-Type</code> types: <code>text/uon</code>
+ *
+ * <h5 class='section'>Description:</h5>
+ * <p>
+ * This serializer provides several serialization options.  Typically, one of the predefined DEFAULT serializers will be sufficient.
+ * However, custom serializers can be constructed to fine-tune behavior.
+ *
+ * <h5 class='section'>Configurable properties:</h5>
+ * <p>
+ * This class has the following properties associated with it:
+ * <ul>
+ * 	<li>{@link UonSerializerContext}
+ * 	<li>{@link BeanContext}
+ * </ul>
+ * <p>
+ * The following shows a sample object defined in Javascript:
+ * </p>
+ * <p class='bcode'>
+ * 	{
+ * 		id: 1,
+ * 		name: <js>'John Smith'</js>,
+ * 		uri: <js>'http://sample/addressBook/person/1'</js>,
+ * 		addressBookUri: <js>'http://sample/addressBook'</js>,
+ * 		birthDate: <js>'1946-08-12T00:00:00Z'</js>,
+ * 		otherIds: <jk>null</jk>,
+ * 		addresses: [
+ * 			{
+ * 				uri: <js>'http://sample/addressBook/address/1'</js>,
+ * 				personUri: <js>'http://sample/addressBook/person/1'</js>,
+ * 				id: 1,
+ * 				street: <js>'100 Main Street'</js>,
+ * 				city: <js>'Anywhereville'</js>,
+ * 				state: <js>'NY'</js>,
+ * 				zip: 12345,
+ * 				isCurrent: <jk>true</jk>,
+ * 			}
+ * 		]
+ * 	}
+ * </p>
+ * <p>
+ * Using the "strict" syntax defined in this document, the equivalent
+ * 	UON notation would be as follows:
+ * </p>
+ * <p class='bcode'>
+ * 	(
+ * 		<ua>id</ua>=<un>1</un>,
+ * 		<ua>name</ua>=<us>'John+Smith'</us>,
+ * 		<ua>uri</ua>=<us>http://sample/addressBook/person/1</us>,
+ * 		<ua>addressBookUri</ua>=<us>http://sample/addressBook</us>,
+ * 		<ua>birthDate</ua>=<us>1946-08-12T00:00:00Z</us>,
+ * 		<ua>otherIds</ua>=<uk>null</uk>,
+ * 		<ua>addresses</ua>=@(
+ * 			(
+ * 				<ua>uri</ua>=<us>http://sample/addressBook/address/1</us>,
+ * 				<ua>personUri</ua>=<us>http://sample/addressBook/person/1</us>,
+ * 				<ua>id</ua>=<un>1</un>,
+ * 				<ua>street</ua>=<us>'100+Main+Street'</us>,
+ * 				<ua>city</ua>=<us>Anywhereville</us>,
+ * 				<ua>state</ua>=<us>NY</us>,
+ * 				<ua>zip</ua>=<un>12345</un>,
+ * 				<ua>isCurrent</ua>=<uk>true</uk>
+ * 			)
+ * 		)
+ * 	)
+ * </p>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode'>
+ * 	<jc>// Serialize a Map</jc>
+ * 	Map m = <jk>new</jk> ObjectMap(<js>"{a:'b',c:1,d:false,e:['f',1,false],g:{h:'i'}}"</js>);
+ *
+ * 	<jc>// Serialize to value equivalent to JSON.</jc>
+ * 	<jc>// Produces "(a=b,c=1,d=false,e=@(f,1,false),g=(h=i))"</jc>
+ * 	String s = UonSerializer.<jsf>DEFAULT</jsf>.serialize(s);
+ *
+ * 	<jc>// Serialize a bean</jc>
+ * 	<jk>public class</jk> Person {
+ * 		<jk>public</jk> Person(String s);
+ * 		<jk>public</jk> String getName();
+ * 		<jk>public int</jk> getAge();
+ * 		<jk>public</jk> Address getAddress();
+ * 		<jk>public boolean</jk> deceased;
+ * 	}
+ *
+ * 	<jk>public class</jk> Address {
+ * 		<jk>public</jk> String getStreet();
+ * 		<jk>public</jk> String getCity();
+ * 		<jk>public</jk> String getState();
+ * 		<jk>public int</jk> getZip();
+ * 	}
+ *
+ * 	Person p = <jk>new</jk> Person(<js>"John Doe"</js>, 23, <js>"123 Main St"</js>, <js>"Anywhere"</js>, <js>"NY"</js>, 12345, <jk>false</jk>);
+ *
+ * 	<jc>// Produces "(name='John Doe',age=23,address=(street='123 Main St',city=Anywhere,state=NY,zip=12345),deceased=false)"</jc>
+ * 	String s = UonSerializer.<jsf>DEFAULT</jsf>.serialize(s);
+ * </p>
+ */
+@Produces("text/uon")
+public class UonSerializer extends WriterSerializer {
+
+	/** Reusable instance of {@link UonSerializer}, all default settings. */
+	public static final UonSerializer DEFAULT = new UonSerializer(PropertyStore.create());
+
+	/** Reusable instance of {@link UonSerializer.Readable}. */
+	public static final UonSerializer DEFAULT_READABLE = new Readable(PropertyStore.create());
+
+	/** Reusable instance of {@link UonSerializer.Encoding}. */
+	public static final UonSerializer DEFAULT_ENCODING = new Encoding(PropertyStore.create());
+
+	/**
+	 * Equivalent to <code><jk>new</jk> UonSerializerBuilder().ws().build();</code>.
+	 */
+	public static class Readable extends UonSerializer {
+
+		/**
+		 * Constructor.
+		 * @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);
+		}
+	}
+
+	/**
+	 * Equivalent to <code><jk>new</jk> UonSerializerBuilder().encoding().build();</code>.
+	 */
+	public static class Encoding extends UonSerializer {
+
+		/**
+		 * Constructor.
+		 * @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);
+		}
+	}
+
+
+	private final UonSerializerContext ctx;
+
+	/**
+	 * Constructor.
+	 * @param propertyStore The property store containing all the settings for this object.
+	 */
+	public UonSerializer(PropertyStore propertyStore) {
+		super(propertyStore);
+		this.ctx = createContext(UonSerializerContext.class);
+	}
+
+	@Override /* CoreObject */
+	public UonSerializerBuilder builder() {
+		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();
+		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.cr(depth-1);
+		out.append(')');
+
+		return out;
+	}
+
+	private SerializerWriter serializeBeanMap(UonSerializerSession session, UonWriter out, BeanMap<?> m, String typeName) throws Exception {
+		int depth = session.getIndent();
+
+		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.addBeanGetterWarning(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.cr(depth-1);
+		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);
+
+		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.cr(depth-1);
+		out.append(')');
+
+		return out;
+	}
+
+
+	//--------------------------------------------------------------------------------
+	// Entry point methods
+	//--------------------------------------------------------------------------------
+
+	@Override /* Serializer */
+	public UonSerializerSession createSession(Object output, ObjectMap op, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType) {
+		return new UonSerializerSession(ctx, op, output, javaMethod, locale, timeZone, mediaType);
+	}
+
+	@Override /* Serializer */
+	protected void doSerialize(SerializerSession session, Object o) throws Exception {
+		UonSerializerSession s = (UonSerializerSession)session;
+		serializeAnything(s, s.getWriter(), o, null, "root", null);
+	}
+}


Mime
View raw message