http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingContext.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingContext.java
 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingContext.java
new file mode 100644
index 0000000..2bf3e5b
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingContext.java
@@ -0,0 +1,68 @@
+/***************************************************************************************************************************
+ * 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.urlencoding;
+
+/**
+ * Configurable properties on the {@link UrlEncodingSerializer} and {@link 
UrlEncodingParser} classes.
+ * <p>
+ *     Use the {@link UrlEncodingSerializer#setProperty(String, Object)} and
+ *     {@link UrlEncodingParser#setProperty(String, Object)} methods to set 
property values.
+ *
+ * @author James Bognar ([email protected])
+ */
+public final class UrlEncodingContext implements Cloneable {
+
+       /**
+        * Serialize bean property collections/arrays as separate key/value 
pairs ({@link Boolean}, default=<jk>false</jk>).
+        * <p>
+        *      If <jk>false</jk>, serializing the array <code>[1,2,3]</code> 
results in <code>?key=$a(1,2,3)</code>.
+        *      If <jk>true</jk>, serializing the same array results in 
<code>?key=1&key=2&key=3</code>.
+        * <p>
+        *      Example:
+        * <p class='bcode'>
+        *      <jk>public class</jk> A {
+        *              <jk>public</jk> String[] f1 = 
{<js>"a"</js>,<js>"b"</js>};
+        *              <jk>public</jk> List&lt;String&gt; f2 = <jk>new</jk> 
LinkedList&lt;String&gt;(Arrays.<jsm>asList</jsm>(<jk>new</jk> 
String[]{<js>"c"</js>,<js>"d"</js>}));
+        *      }
+        *
+        *      UrlEncodingSerializer s1 = <jk>new</jk> UrlEncodingParser();
+        *      UrlEncodingSerializer s2 = <jk>new</jk> 
UrlEncodingParser().setProperty(UrlEncodingContext.<jsf>URLENC_expandedParams</jsf>,
 <jk>true</jk>);
+        *
+        *      String s1 = p1.serialize(<jk>new</jk> A()); <jc>// Produces 
"f1=(a,b)&f2=(c,d)"</jc>
+        *      String s2 = p2.serialize(<jk>new</jk> A()); <jc>// Produces 
"f1=a&f1=b&f2=c&f2=d"</jc>
+        * </p>
+        * <p>
+        *      <b>Important note:</b>  If parsing multi-part parameters, it's 
highly recommended to use Collections or Lists
+        *      as bean property types instead of arrays since arrays have to 
be recreated from scratch every time a value
+        *      is added to it.
+        * <p>
+        *      This option only applies to beans.
+        */
+       public static final String URLENC_expandedParams = 
"UrlEncoding.expandedParams";
+
+       boolean
+               expandedParams = false;
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       @Override /* Cloneable */
+       public UrlEncodingContext clone() {
+               try {
+                       return (UrlEncodingContext)super.clone();
+               } catch (CloneNotSupportedException e) {
+                       throw new RuntimeException(e); // Shouldn't happen
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/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
new file mode 100644
index 0000000..d759965
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
@@ -0,0 +1,554 @@
+/***************************************************************************************************************************
+ * 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.urlencoding;
+
+import static org.apache.juneau.urlencoding.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 URL-encoded text into POJO models.
+ *
+ *
+ * <h6 class='topic'>Media types</h6>
+ * <p>
+ *     Handles <code>Content-Type</code> types: 
<code>application/x-www-form-urlencoded</code>
+ *
+ *
+ * <h6 class='topic'>Description</h6>
+ * <p>
+ *     Parses URL-Encoded text (e.g. <js>"foo=bar&baz=bing"</js>) into POJOs.
+ * <p>
+ *     Expects parameter values to be in UON notation.
+ * <p>
+ *     This parser uses a state machine, which makes it very fast and 
efficient.
+ *
+ *
+ * <h6 class='topic'>Configurable properties</h6>
+ * <p>
+ *     This class has the following properties associated with it:
+ * <ul>
+ *     <li>{@link UonParserContext}
+ *     <li>{@link BeanContext}
+ * </ul>
+ *
+ *
+ * @author James Bognar ([email protected])
+ */
+@SuppressWarnings({ "rawtypes", "unchecked", "hiding" })
+@Consumes("application/x-www-form-urlencoded")
+public class UrlEncodingParser extends UonParser {
+
+       /** Reusable instance of {@link UrlEncodingParser}. */
+       public static final UrlEncodingParser DEFAULT = new 
UrlEncodingParser().lock();
+
+       /** Reusable instance of {@link UrlEncodingParser}. */
+       public static final UrlEncodingParser DEFAULT_WS_AWARE = new 
UrlEncodingParser().setProperty(UON_whitespaceAware, true).lock();
+
+       /**
+        * Constructor.
+        */
+       public UrlEncodingParser() {
+               setProperty(UON_decodeChars, true);
+       }
+
+       private <T> T parseAnything(UrlEncodingParserSession session, 
ClassMeta<T> nt, ParserReader r, Object outer) throws Exception {
+
+               BeanContext bc = session.getBeanContext();
+               if (nt == null)
+                       nt = (ClassMeta<T>)object();
+               PojoTransform<T,Object> transform = 
(PojoTransform<T,Object>)nt.getPojoTransform();
+               ClassMeta<?> ft = nt.getTransformedClassMeta();
+
+               int c = r.peek();
+               if (c == '?')
+                       r.read();
+
+               Object o;
+
+               if (ft.isObject()) {
+                       ObjectMap m = new ObjectMap(bc);
+                       parseIntoMap(session, r, m, bc.string(), bc.object());
+                       o = m.cast();
+               } else if (ft.isMap()) {
+                       Map m = (ft.canCreateNewInstance() ? 
(Map)ft.newInstance() : new ObjectMap(bc));
+                       o = parseIntoMap(session, r, m, ft.getKeyType(), 
ft.getValueType());
+               } else if (ft.canCreateNewInstanceFromObjectMap(outer)) {
+                       ObjectMap m = new ObjectMap(bc);
+                       parseIntoMap(session, r, m, string(), object());
+                       o = ft.newInstanceFromObjectMap(outer, m);
+               } else if (ft.canCreateNewBean(outer)) {
+                       BeanMap m = bc.newBeanMap(outer, ft.getInnerClass());
+                       m = parseIntoBeanMap(session, r, m);
+                       o = m == null ? null : m.getBean();
+               } else {
+                       // It could be a non-bean with _class attribute.
+                       ObjectMap m = new ObjectMap(bc);
+                       ClassMeta<Object> valueType = object();
+                       parseIntoMap(session, r, m, string(), valueType);
+                       if (m.containsKey("_class"))
+                               o = m.cast();
+                       else if (m.containsKey("_value"))
+                               o = 
session.getBeanContext().convertToType(m.get("_value"), ft);
+                       else if (ft.isCollection()) {
+                               // ?1=foo&2=bar...
+                               Collection c2 = ft.canCreateNewInstance() ? 
(Collection)ft.newInstance() : new ObjectList(bc);
+                               Map<Integer,Object> t = new 
TreeMap<Integer,Object>();
+                               for (Map.Entry<String,Object> e : m.entrySet()) 
{
+                                       String k = e.getKey();
+                                       if (StringUtils.isNumeric(k))
+                                               t.put(Integer.valueOf(k), 
bc.convertToType(e.getValue(), ft.getElementType()));
+                               }
+                               c2.addAll(t.values());
+                               o = c2;
+                       } else {
+                               if (ft.getNotABeanReason() != null)
+                                       throw new ParseException(session, 
"Class ''{0}'' could not be instantiated as application/x-www-form-urlencoded.  
Reason: ''{1}''", ft, ft.getNotABeanReason());
+                               throw new ParseException(session, "Malformed 
application/x-www-form-urlencoded input for class ''{0}''.", ft);
+                       }
+               }
+
+               if (transform != null && o != null)
+                       o = transform.normalize(o, nt);
+
+               if (outer != null)
+                       setParent(nt, 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) 
throws Exception {
+
+               if (keyType == null)
+                       keyType = (ClassMeta<K>)string();
+
+               int c = r.peek();
+               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;
+               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 = 
session.trim(session.getBeanContext().convertToType(attr, 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') {
+                                               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.
+                                               V value = 
(V)(valueType.isString() ? super.parseString(session, r.unread(), true) : 
super.parseAnything(session, valueType, r.unread(), m, true));
+
+                                               // 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);
+                                                               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.peek();
+               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("_class")) {
+                                                       BeanPropertyMeta pMeta 
= m.getPropertyMeta(currAttr);
+                                                       if (pMeta == null) {
+                                                               if 
(m.getMeta().isSubTyped()) {
+                                                                       
m.put(currAttr, "");
+                                                               } else {
+                                                                       
onUnknownProperty(session, 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, cm.newInstance());
+                                                               
session.setCurrentProperty(null);
+                                                       }
+                                               }
+                                               if (c == -1)
+                                                       return m;
+                                               state = S1;
+                                       } else {
+                                               if (! 
currAttr.equals("_class")) {
+                                                       BeanPropertyMeta pMeta 
= m.getPropertyMeta(currAttr);
+                                                       if (pMeta == null) {
+                                                               if 
(m.getMeta().isSubTyped()) {
+                                                                       Object 
value = parseAnything(session, object(), r.unread(), m.getBean(false), true);
+                                                                       
m.put(currAttr, value);
+                                                               } else {
+                                                                       
onUnknownProperty(session, currAttr, m, currAttrLine, currAttrCol);
+                                                                       
parseAnything(session, object(), r.unread(), m.getBean(false), true); // 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);
+                                                                       
setName(et, value, currAttr);
+                                                                       
pMeta.add(m, value);
+                                                               } else {
+                                                                       
ClassMeta<?> cm = pMeta.getClassMeta();
+                                                                       Object 
value = parseAnything(session, cm, r.unread(), m.getBean(false), true);
+                                                                       
setName(cm, value, currAttr);
+                                                                       
pMeta.set(m, 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.
+        *
+        * @param qs The query string to parse.
+        * @return A sorted {@link TreeMap} of query string entries.
+        * @throws Exception
+        */
+       public Map<String,String[]> parseIntoSimpleMap(String qs) throws 
Exception {
+
+               Map<String,String[]> m = new TreeMap<String,String[]>();
+
+               if (StringUtils.isEmpty(qs))
+                       return m;
+
+               UonReader r = new UonReader(qs, true);
+
+               final int S1=1; // Looking for attrName start.
+               final int S2=2; // Found attrName start, looking for = or & or 
end.
+               final int S3=3; // Found =, looking for valStart.
+               final int S4=4; // Found valStart, looking for & or end.
+
+               try {
+                       int c = r.peek();
+                       if (c == '?')
+                               r.read();
+
+                       int state = S1;
+                       String currAttr = null;
+                       while (c != -1) {
+                               c = r.read();
+                               if (state == S1) {
+                                       if (c != -1) {
+                                               r.unread();
+                                               r.mark();
+                                               state = S2;
+                                       }
+                               } else if (state == S2) {
+                                       if (c == -1) {
+                                               add(m, r.getMarked(), null);
+                                       } else if (c == '\u0001') {
+                                               m.put(r.getMarked(0,-1), null);
+                                               state = S1;
+                                       } else if (c == '\u0002') {
+                                               currAttr = r.getMarked(0,-1);
+                                               state = S3;
+                                       }
+                               } else if (state == S3) {
+                                       if (c == -1 || c == '\u0001') {
+                                               add(m, currAttr, "");
+                                       } else {
+                                               if (c == '\u0002')
+                                                       r.replace('=');
+                                               r.unread();
+                                               r.mark();
+                                               state = S4;
+                                       }
+                               } else if (state == S4) {
+                                       if (c == -1) {
+                                               add(m, currAttr, r.getMarked());
+                                       } else if (c == '\u0001') {
+                                               add(m, currAttr, 
r.getMarked(0,-1));
+                                               state = S1;
+                                       } else if (c == '\u0002') {
+                                               r.replace('=');
+                                       }
+                               }
+                       }
+               } finally {
+                       r.close();
+               }
+
+               return m;
+       }
+
+       private static void add(Map<String,String[]> m, String key, String val) 
{
+               boolean b = m.containsKey(key);
+               if (val == null) {
+                       if (! b)
+                               m.put(key, null);
+               } else if (b && m.get(key) != null) {
+                       m.put(key, ArrayUtils.append(m.get(key), val));
+               } else {
+                       m.put(key, new String[]{val});
+               }
+       }
+
+       private Object[] parseArgs(UrlEncodingParserSession session, 
ParserReader r, ClassMeta<?>[] argTypes) throws Exception {
+               // TODO - This can be made more efficient.
+               BeanContext bc = session.getBeanContext();
+               ClassMeta<TreeMap<Integer,String>> cm = 
bc.getMapClassMeta(TreeMap.class, Integer.class, String.class);
+               TreeMap<Integer,String> m = parseAnything(session, cm, r, 
session.getOuter());
+               Object[] vals = m.values().toArray(new Object[m.size()]);
+               if (vals.length != argTypes.length)
+                       throw new ParseException(session, "Argument lengths 
don't match.  vals={0}, argTypes={1}", vals.length, argTypes.length);
+               for (int i = 0; i < vals.length; i++) {
+                       String s = String.valueOf(vals[i]);
+                       vals[i] = super.parseAnything(session, argTypes[i], new 
UonReader(s, false), session.getOuter(), true);
+               }
+
+               return vals;
+       }
+
+       /**
+        * Parses a single query parameter value into the specified class type.
+        *
+        * @param in The input query string value.
+        * @param type The class type of the object to create.
+        * @return A new instance of the specified type.
+        * @throws ParseException
+        */
+       public <T> T parseParameter(CharSequence in, ClassMeta<T> type) throws 
ParseException {
+               if (in == null)
+                       return null;
+               UonParserSession session = createParameterContext(in);
+               try {
+                       UonReader r = session.getReader();
+                       return super.parseAnything(session, type, r, null, 
true);
+               } catch (ParseException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new ParseException(session, e);
+               } finally {
+                       session.close();
+               }
+       }
+
+       /**
+        * Parses a single query parameter value into the specified class type.
+        *
+        * @param in The input query string value.
+        * @param type The class type of the object to create.
+        * @return A new instance of the specified type.
+        * @throws ParseException
+        */
+       public <T> T parseParameter(CharSequence in, Class<T> type) throws 
ParseException {
+               return parseParameter(in, getBeanContext().getClassMeta(type));
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       @Override /* Parser */
+       public UrlEncodingParserSession createSession(Object input, ObjectMap 
properties, Method javaMethod, Object outer) {
+               return new 
UrlEncodingParserSession(getContext(UrlEncodingParserContext.class), 
getBeanContext(), input, properties, javaMethod, outer);
+       }
+
+       @Override /* Parser */
+       protected <T> T doParse(ParserSession session, ClassMeta<T> type) 
throws Exception {
+               UrlEncodingParserSession s = (UrlEncodingParserSession)session;
+               type = s.getBeanContext().normalizeClassMeta(type);
+               UonReader r = s.getReader();
+               T o = parseAnything(s, type, r, s.getOuter());
+               return o;
+       }
+
+       @Override /* ReaderParser */
+       protected Object[] doParseArgs(ParserSession session, ClassMeta<?>[] 
argTypes) throws Exception {
+               UrlEncodingParserSession uctx = 
(UrlEncodingParserSession)session;
+               UonReader r = uctx.getReader();
+               Object[] a = parseArgs(uctx, r, argTypes);
+               return a;
+       }
+
+       @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.peek() == '?')
+                       r.read();
+               m = parseIntoMap(s, r, m, 
s.getBeanContext().getClassMeta(keyType), 
s.getBeanContext().getClassMeta(valueType));
+               return m;
+       }
+
+       @Override /* Parser */
+       public UrlEncodingParser setProperty(String property, Object value) 
throws LockedException {
+               super.setProperty(property, value);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public UrlEncodingParser setProperties(ObjectMap properties) throws 
LockedException {
+               super.setProperties(properties);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public UrlEncodingParser addNotBeanClasses(Class<?>...classes) throws 
LockedException {
+               super.addNotBeanClasses(classes);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public UrlEncodingParser addTransforms(Class<?>...classes) throws 
LockedException {
+               super.addTransforms(classes);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public <T> UrlEncodingParser addImplClass(Class<T> interfaceClass, 
Class<? extends T> implClass) throws LockedException {
+               super.addImplClass(interfaceClass, implClass);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public UrlEncodingParser setClassLoader(ClassLoader classLoader) throws 
LockedException {
+               super.setClassLoader(classLoader);
+               return this;
+       }
+
+       @Override /* Lockable */
+       public UrlEncodingParser lock() {
+               super.lock();
+               return this;
+       }
+
+       @Override /* Lockable */
+       public UrlEncodingParser clone() {
+               UrlEncodingParser c = (UrlEncodingParser)super.clone();
+               return c;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserContext.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserContext.java
 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserContext.java
new file mode 100644
index 0000000..20a0e2a
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserContext.java
@@ -0,0 +1,81 @@
+/***************************************************************************************************************************
+ * 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.urlencoding;
+
+import org.apache.juneau.*;
+
+/**
+ * Configurable properties on the {@link UrlEncodingParser} class.
+ * <p>
+ * Context properties are set by calling {@link 
ContextFactory#setProperty(String, Object)} on the context factory
+ * returned {@link CoreApi#getContextFactory()}.
+ * <p>
+ * The following convenience methods are also provided for setting context 
properties:
+ * <ul>
+ *     <li>{@link UrlEncodingParser#setProperty(String,Object)}
+ *     <li>{@link UrlEncodingParser#setProperties(ObjectMap)}
+ *     <li>{@link UrlEncodingParser#addNotBeanClasses(Class[])}
+ *     <li>{@link UrlEncodingParser#addTransforms(Class[])}
+ *     <li>{@link UrlEncodingParser#addImplClass(Class,Class)}
+ * </ul>
+ * <p>
+ * See {@link ContextFactory} for more information about context properties.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class UrlEncodingParserContext extends UonParserContext {
+
+       /**
+        * Serialize bean property collections/arrays as separate key/value 
pairs ({@link Boolean}, default=<jk>false</jk>).
+        * <p>
+        *      If <jk>false</jk>, serializing the array <code>[1,2,3]</code> 
results in <code>?key=$a(1,2,3)</code>.
+        *      If <jk>true</jk>, serializing the same array results in 
<code>?key=1&key=2&key=3</code>.
+        * <p>
+        *      Example:
+        * <p class='bcode'>
+        *      <jk>public class</jk> A {
+        *              <jk>public</jk> String[] f1 = 
{<js>"a"</js>,<js>"b"</js>};
+        *              <jk>public</jk> List&lt;String&gt; f2 = <jk>new</jk> 
LinkedList&lt;String&gt;(Arrays.<jsm>asList</jsm>(<jk>new</jk> 
String[]{<js>"c"</js>,<js>"d"</js>}));
+        *      }
+        *
+        *      UrlEncodingSerializer s1 = <jk>new</jk> UrlEncodingParser();
+        *      UrlEncodingSerializer s2 = <jk>new</jk> 
UrlEncodingParser().setProperty(UrlEncodingContext.<jsf>URLENC_expandedParams</jsf>,
 <jk>true</jk>);
+        *
+        *      String s1 = p1.serialize(<jk>new</jk> A()); <jc>// Produces 
"f1=(a,b)&f2=(c,d)"</jc>
+        *      String s2 = p2.serialize(<jk>new</jk> A()); <jc>// Produces 
"f1=a&f1=b&f2=c&f2=d"</jc>
+        * </p>
+        * <p>
+        *      <b>Important note:</b>  If parsing multi-part parameters, it's 
highly recommended to use Collections or Lists
+        *      as bean property types instead of arrays since arrays have to 
be recreated from scratch every time a value
+        *      is added to it.
+        * <p>
+        *      This option only applies to beans.
+        */
+       public static final String URLENC_expandedParams = 
"UrlEncoding.expandedParams";
+
+
+       final boolean
+               expandedParams;
+
+       /**
+        * Constructor.
+        * <p>
+        * Typically only called from {@link ContextFactory#getContext(Class)}.
+        *
+        * @param cf The factory that created this context.
+        */
+       public UrlEncodingParserContext(ContextFactory cf) {
+               super(cf);
+               this.expandedParams = cf.getProperty(URLENC_expandedParams, 
boolean.class, false);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/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
new file mode 100644
index 0000000..d5a9177
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java
@@ -0,0 +1,77 @@
+/***************************************************************************************************************************
+ * 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.urlencoding;
+
+import static org.apache.juneau.urlencoding.UrlEncodingParserContext.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+
+import org.apache.juneau.*;
+
+/**
+ * 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.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class UrlEncodingParserSession extends UonParserSession {
+
+       private final boolean expandedParams;
+
+       /**
+        * 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 beanContext The bean context being used.
+        * @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.
+        */
+       public UrlEncodingParserSession(UrlEncodingParserContext ctx, 
BeanContext beanContext, Object input, ObjectMap op, Method javaMethod, Object 
outer) {
+               super(ctx, beanContext, input, op, javaMethod, outer);
+               if (op == null || op.isEmpty()) {
+                       expandedParams = ctx.expandedParams;
+               } else {
+                       expandedParams = op.getBoolean(URLENC_expandedParams, 
false);
+               }
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified bean property should be 
expanded as multiple key-value pairs.
+        *
+        * @param pMeta The metadata on the bean property.
+        * @return <jk>true</jk> if the specified bean property should be 
expanded as multiple key-value pairs.
+        */
+       public final boolean shouldUseExpandedParams(BeanPropertyMeta<?> pMeta) 
{
+               ClassMeta<?> cm = pMeta.getClassMeta();
+               if (cm.isArray() || cm.isCollection()) {
+                       if (expandedParams)
+                               return true;
+                       if 
(pMeta.getBeanMeta().getClassMeta().getUrlEncodingMeta().isExpandedParams())
+                               return true;
+               }
+               return false;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java
 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java
new file mode 100644
index 0000000..fd04afb
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java
@@ -0,0 +1,463 @@
+/***************************************************************************************************************************
+ * 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.urlencoding;
+
+import static org.apache.juneau.urlencoding.UonSerializerContext.*;
+import static org.apache.juneau.urlencoding.UrlEncodingSerializerContext.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.transform.*;
+
+/**
+ * Serializes POJO models to URL-encoded notation with UON-encoded values (a 
notation for URL-encoded query paramter values).
+ *
+ *
+ * <h6 class='topic'>Media types</h6>
+ * <p>
+ *     Handles <code>Accept</code> types: 
<code>application/x-www-form-urlencoded</code>
+ * <p>
+ *     Produces <code>Content-Type</code> types: 
<code>application/x-www-form-urlencoded</code>
+ *
+ *
+ * <h6 class='topic'>Description</h6>
+ * <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.
+ *
+ *
+ * <h6 class='topic'>Configurable properties</h6>
+ * <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
+ *             URL-encoded notation would be as follows:
+ * </p>
+ * <p class='bcode'>
+ *     <xa>id</xa>=$n(<xs>1</xs>)
+ *     &amp;<xa>name</xa>=<xs>John+Smith</xs>,
+ *     &amp;<xa>uri</xa>=<xs>http://sample/addressBook/person/1</xs>,
+ *     &amp;<xa>addressBookUri</xa>=<xs>http://sample/addressBook</xs>,
+ *     &amp;<xa>birthDate</xa>=<xs>1946-08-12T00:00:00Z</xs>,
+ *     &amp;<xa>otherIds</xa>=<xs>%00</xs>,
+ *     &amp;<xa>addresses</xa>=$a(
+ *             $o(
+ *                     
<xa>uri</xa>=<xs>http://sample/addressBook/address/1</xs>,
+ *                     
<xa>personUri</xa>=<xs>http://sample/addressBook/person/1</xs>,
+ *                     <xa>id</xa>=$n(<xs>1</xs>),
+ *                     <xa>street</xa>=<xs>100+Main+Street</xs>,
+ *                     <xa>city</xa>=<xs>Anywhereville</xs>,
+ *                     <xa>state</xa>=<xs>NY</xs>,
+ *                     <xa>zip</xa>=$n(<xs>12345</xs>),
+ *                     <xa>isCurrent</xa>=$b(<xs>true</xs>)
+ *             )
+ *     )
+ * </p>
+ * <p>
+ *     A secondary "lax" syntax is available when the data type of the
+ *             values are already known on the receiving end of the 
transmission:
+ * </p>
+ * <p class='bcode'>
+ *     <xa>id</xa>=<xs>1</xs>,
+ *     &amp;<xa>name</xa>=<xs>John+Smith</xs>,
+ *     &amp;<xa>uri</xa>=<xs>http://sample/addressBook/person/1</xs>,
+ *     &amp;<xa>addressBookUri</xa>=<xs>http://sample/addressBook</xs>,
+ *     &amp;<xa>birthDate</xa>=<xs>1946-08-12T00:00:00Z</xs>,
+ *     &amp;<xa>otherIds</xa>=<xs>%00</xs>,
+ *     &amp;<xa>addresses</xa>=(
+ *             (
+ *                     
<xa>uri</xa>=<xs>http://sample/addressBook/address/1</xs>,
+ *                     
<xa>personUri</xa>=<xs>http://sample/addressBook/person/1</xs>,
+ *                     <xa>id</xa>=<xs>1</xs>,
+ *                     <xa>street</xa>=<xs>100+Main+Street</xs>,
+ *                     <xa>city</xa>=<xs>Anywhereville</xs>,
+ *                     <xa>state</xa>=<xs>NY</xs>,
+ *                     <xa>zip</xa>=<xs>12345</xs>,
+ *                     <xa>isCurrent</xa>=<xs>true</xs>
+ *             )
+ *     )
+ * </p>
+ *
+ *
+ * <h6 class='topic'>Examples</h6>
+ * <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&amp;c=$n(1)&amp;d=$b(false)&amp;e=$a(f,$n(1),$b(false))&amp;g=$o(h=i)"</jc>
+ *     String s = UrlEncodingSerializer.<jsf>DEFAULT</jsf>.serialize(s);
+ *
+ *     <jc>// Serialize to simplified value (for when data type is already 
known by receiver).</jc>
+ *     <jc>// Produces 
"a=b&amp;c=1&amp;d=false&amp;e=(f,1,false)&amp;g=(h=i))"</jc>
+ *     String s = UrlEncodingSerializer.<jsf>DEFAULT_SIMPLE</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&amp;age=23&amp;address=$o(street=123+Main+St,city=Anywhere,state=NY,zip=$n(12345))&amp;deceased=$b(false)"</jc>
+ *     String s = UrlEncodingSerializer.<jsf>DEFAULT</jsf>.serialize(s);
+ *
+ *     <jc>// Produces 
"name=John+Doe&amp;age=23&amp;address=(street=123+Main+St,city=Anywhere,state=NY,zip=12345)&amp;deceased=false)"</jc>
+ *     String s = UrlEncodingSerializer.<jsf>DEFAULT_SIMPLE</jsf>.serialize(s);
+ * </p>
+ *
+ * @author James Bognar ([email protected])
+ */
+@Produces("application/x-www-form-urlencoded")
+@SuppressWarnings("hiding")
+public class UrlEncodingSerializer extends UonSerializer {
+
+       /** Reusable instance of {@link UrlEncodingSerializer}, all default 
settings. */
+       public static final UrlEncodingSerializer DEFAULT = new 
UrlEncodingSerializer().lock();
+
+       /** Reusable instance of {@link UrlEncodingSerializer.Simple}. */
+       public static final UrlEncodingSerializer DEFAULT_SIMPLE = new 
Simple().lock();
+
+       /** Reusable instance of {@link UrlEncodingSerializer.SimpleExpanded}. 
*/
+       public static final UrlEncodingSerializer DEFAULT_SIMPLE_EXPANDED = new 
SimpleExpanded().lock();
+
+       /** Reusable instance of {@link UrlEncodingSerializer.Readable}. */
+       public static final UrlEncodingSerializer DEFAULT_READABLE = new 
Readable().lock();
+
+       /**
+        * Constructor.
+        */
+       public UrlEncodingSerializer() {
+               setProperty(UON_encodeChars, true);
+       }
+
+       /**
+        * Equivalent to <code><jk>new</jk> 
UrlEncodingSerializer().setProperty(UonSerializerContext.<jsf>UON_simpleMode</jsf>,<jk>true</jk>);</code>.
+        */
+       
@Produces(value={"application/x-www-form-urlencoded-simple"},contentType="application/x-www-form-urlencoded")
+       public static class Simple extends UrlEncodingSerializer {
+               /** Constructor */
+               public Simple() {
+                       setProperty(UON_simpleMode, true);
+               }
+       }
+
+       /**
+        * Equivalent to <code><jk>new</jk> 
UrlEncodingSerializer().setProperty(UonSerializerContext.<jsf>UON_simpleMode</jsf>,<jk>true</jk>).setProperty(UonSerializerContext.<jsf>URLENC_expandedParams</jsf>,<jk>true</jk>);</code>.
+        */
+       
@Produces(value={"application/x-www-form-urlencoded-simple"},contentType="application/x-www-form-urlencoded")
+       public static class SimpleExpanded extends Simple {
+               /** Constructor */
+               public SimpleExpanded() {
+                       setProperty(URLENC_expandedParams, true);
+               }
+       }
+
+       /**
+        * Equivalent to <code><jk>new</jk> 
UrlEncodingSerializer().setProperty(UonSerializerContext.<jsf>UON_useWhitespace</jsf>,<jk>true</jk>);</code>.
+        */
+       public static class Readable extends UrlEncodingSerializer {
+               /** Constructor */
+               public Readable() {
+                       setProperty(UON_useWhitespace, true);
+               }
+       }
+
+       /**
+        * Workhorse method. Determines the type of object, and then calls the
+        * appropriate type-specific serialization method.
+        */
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       private SerializerWriter serializeAnything(UrlEncodingSerializerSession 
session, UonWriter out, Object o) throws Exception {
+               BeanContext bc = session.getBeanContext();
+
+               boolean addClassAttr;           // Add "_class" attribute to 
element?
+               ClassMeta<?> aType;                     // The actual type
+               ClassMeta<?> gType;                     // The generic type
+
+               aType = session.push("root", o, object());
+               session.indent--;
+               if (aType == null)
+                       aType = object();
+
+               gType = aType.getTransformedClassMeta();
+               addClassAttr = (session.isAddClassAttrs());
+
+               // Transform if necessary
+               PojoTransform transform = aType.getPojoTransform();             
                // The transform
+               if (transform != null) {
+                       o = transform.transform(o);
+
+                       // If the transform's getTransformedClass() method 
returns Object, we need to figure out
+                       // the actual type now.
+                       if (gType.isObject())
+                               gType = bc.getClassMetaForObject(o);
+               }
+
+               if (gType.isMap()) {
+                       if (o instanceof BeanMap)
+                               serializeBeanMap(session, out, (BeanMap)o, 
addClassAttr);
+                       else
+                               serializeMap(session, out, (Map)o, gType);
+               } else if (gType.hasToObjectMapMethod()) {
+                       serializeMap(session, out, gType.toObjectMap(o), gType);
+               } else if (gType.isBean()) {
+                       serializeBeanMap(session, out, bc.forBean(o), 
addClassAttr);
+               } else if (gType.isCollection()) {
+                       serializeMap(session, out, 
getCollectionMap((Collection)o), bc.getMapClassMeta(Map.class, Integer.class, 
gType.getElementType()));
+               } else {
+                       // All other types can't be serialized as key/value 
pairs, so we create a
+                       // mock key/value pair with a "_value" key.
+                       out.append("_value=");
+                       super.serializeAnything(session, out, o, null, null, 
null, false, true);
+               }
+
+               session.pop();
+               return out;
+       }
+
+       private Map<Integer,Object> getCollectionMap(Collection<?> c) {
+               Map<Integer,Object> m = new TreeMap<Integer,Object>();
+               int i = 0;
+               for (Object o : c)
+                       m.put(i++, o);
+               return m;
+       }
+
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       private SerializerWriter serializeMap(UrlEncodingSerializerSession 
session, UonWriter out, Map m, ClassMeta<?> type) throws Exception {
+
+               m = session.sort(m);
+
+               ClassMeta<?> keyType = type.getKeyType(), valueType = 
type.getValueType();
+
+               int depth = session.getIndent();
+               boolean addAmp = false;
+
+               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);
+
+
+                       if (session.shouldUseExpandedParams(value)) {
+                               Iterator i = value instanceof Collection ? 
((Collection)value).iterator() : ArrayUtils.iterator(value);
+                               while (i.hasNext()) {
+                                       if (addAmp)
+                                               out.cr(depth).append('&');
+                                       out.appendObject(key, false, true, 
true).append('=');
+                                       super.serializeAnything(session, out, 
i.next(), null, (key == null ? null : key.toString()), null, false, true);
+                                       addAmp = true;
+                               }
+                       } else {
+                               if (addAmp)
+                                       out.cr(depth).append('&');
+                               out.appendObject(key, false, true, 
true).append('=');
+                               super.serializeAnything(session, out, value, 
valueType, (key == null ? null : key.toString()), null, false, true);
+                               addAmp = true;
+                       }
+               }
+
+               return out;
+       }
+
+       @SuppressWarnings({ "rawtypes" })
+       private SerializerWriter serializeBeanMap(UrlEncodingSerializerSession 
session, UonWriter out, BeanMap<?> m, boolean addClassAttr) throws Exception {
+               int depth = session.getIndent();
+
+               boolean addAmp = false;
+
+               for (BeanPropertyValue p : m.getValues(addClassAttr, 
session.isTrimNulls())) {
+                       BeanPropertyMeta pMeta = p.getMeta();
+
+                       String key = p.getName();
+                       Object value = p.getValue();
+                       Throwable t = p.getThrown();
+                       if (t != null)
+                               session.addBeanGetterWarning(pMeta, t);
+
+                       if (session.canIgnoreValue(pMeta.getClassMeta(), key, 
value))
+                               continue;
+
+                       if (value != null && 
session.shouldUseExpandedParams(pMeta)) {
+                               ClassMeta cm = pMeta.getClassMeta();
+                               // Transformed object array bean properties may 
be transformed resulting in ArrayLists,
+                               // so we need to check type if we think it's an 
array.
+                               Iterator i = (cm.isCollection() || value 
instanceof Collection) ? ((Collection)value).iterator() : 
ArrayUtils.iterator(value);
+                               while (i.hasNext()) {
+                                       if (addAmp)
+                                               out.cr(depth).append('&');
+
+                                       out.appendObject(key, false, true, 
true).append('=');
+
+                                       super.serializeAnything(session, out, 
i.next(), pMeta.getClassMeta().getElementType(), key, pMeta, false, true);
+
+                                       addAmp = true;
+                               }
+                       } else {
+                               if (addAmp)
+                                       out.cr(depth).append('&');
+
+                               out.appendObject(key, false, true, 
true).append('=');
+
+                               super.serializeAnything(session, out, value, 
pMeta.getClassMeta(), key, pMeta, false, true);
+
+                               addAmp = true;
+                       }
+
+               }
+               return out;
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Methods for constructing individual parameter values.
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Converts the specified object to a string using this serializers 
{@link BeanContext#convertToType(Object, Class)} method
+        *      and runs {@link URLEncoder#encode(String,String)} against the 
results.
+        * Useful for constructing URL parts.
+        *
+        * @param o The object to serialize.
+        * @return The serialized object.
+        */
+       public String serializeUrlPart(Object o) {
+               try {
+                       // Shortcut for simple types.
+                       ClassMeta<?> cm = 
getBeanContext().getClassMetaForObject(o);
+                       if (cm != null)
+                               if (cm.isCharSequence() || cm.isNumber() || 
cm.isBoolean())
+                                       return o.toString();
+
+                       StringWriter w = new StringWriter();
+                       UonSerializerSession s = createSession(w, null, null);
+                       super.doSerialize(s, o);
+                       return w.toString();
+               } catch (Exception e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       @Override /* Serializer */
+       public UrlEncodingSerializerSession createSession(Object output, 
ObjectMap properties, Method javaMethod) {
+               return new 
UrlEncodingSerializerSession(getContext(UrlEncodingSerializerContext.class), 
getBeanContext(), output, properties, javaMethod);
+       }
+
+       @Override /* Serializer */
+       protected void doSerialize(SerializerSession session, Object o) throws 
Exception {
+               UrlEncodingSerializerSession s = 
(UrlEncodingSerializerSession)session;
+               serializeAnything(s, s.getWriter(), o);
+       }
+
+       @Override /* CoreApi */
+       public UrlEncodingSerializer setProperty(String property, Object value) 
throws LockedException {
+               super.setProperty(property, value);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public UrlEncodingSerializer setProperties(ObjectMap properties) throws 
LockedException {
+               super.setProperties(properties);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public UrlEncodingSerializer addNotBeanClasses(Class<?>...classes) 
throws LockedException {
+               super.addNotBeanClasses(classes);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public UrlEncodingSerializer addTransforms(Class<?>...classes) throws 
LockedException {
+               super.addTransforms(classes);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public <T> UrlEncodingSerializer addImplClass(Class<T> interfaceClass, 
Class<? extends T> implClass) throws LockedException {
+               super.addImplClass(interfaceClass, implClass);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public UrlEncodingSerializer setClassLoader(ClassLoader classLoader) 
throws LockedException {
+               super.setClassLoader(classLoader);
+               return this;
+       }
+
+       @Override /* Lockable */
+       public UrlEncodingSerializer lock() {
+               super.lock();
+               return this;
+       }
+
+       @Override /* Lockable */
+       public UrlEncodingSerializer clone() {
+               UrlEncodingSerializer c = (UrlEncodingSerializer)super.clone();
+               return c;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerContext.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerContext.java
 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerContext.java
new file mode 100644
index 0000000..0ba2c09
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerContext.java
@@ -0,0 +1,81 @@
+/***************************************************************************************************************************
+ * 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.urlencoding;
+
+import org.apache.juneau.*;
+
+/**
+ * Configurable properties on the {@link UrlEncodingSerializer} class.
+ * <p>
+ * Context properties are set by calling {@link 
ContextFactory#setProperty(String, Object)} on the context factory
+ * returned {@link CoreApi#getContextFactory()}.
+ * <p>
+ * The following convenience methods are also provided for setting context 
properties:
+ * <ul>
+ *     <li>{@link UrlEncodingSerializer#setProperty(String,Object)}
+ *     <li>{@link UrlEncodingSerializer#setProperties(ObjectMap)}
+ *     <li>{@link UrlEncodingSerializer#addNotBeanClasses(Class[])}
+ *     <li>{@link UrlEncodingSerializer#addTransforms(Class[])}
+ *     <li>{@link UrlEncodingSerializer#addImplClass(Class,Class)}
+ * </ul>
+ * <p>
+ * See {@link ContextFactory} for more information about context properties.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class UrlEncodingSerializerContext extends UonSerializerContext {
+
+       /**
+        * Serialize bean property collections/arrays as separate key/value 
pairs ({@link Boolean}, default=<jk>false</jk>).
+        * <p>
+        * If <jk>false</jk>, serializing the array <code>[1,2,3]</code> 
results in <code>?key=$a(1,2,3)</code>.
+        * If <jk>true</jk>, serializing the same array results in 
<code>?key=1&key=2&key=3</code>.
+        * <p>
+        * Example:
+        * <p class='bcode'>
+        *      <jk>public class</jk> A {
+        *              <jk>public</jk> String[] f1 = 
{<js>"a"</js>,<js>"b"</js>};
+        *              <jk>public</jk> List&lt;String&gt; f2 = <jk>new</jk> 
LinkedList&lt;String&gt;(Arrays.<jsm>asList</jsm>(<jk>new</jk> 
String[]{<js>"c"</js>,<js>"d"</js>}));
+        *      }
+        *
+        *      UrlEncodingSerializer s1 = <jk>new</jk> UrlEncodingParser();
+        *      UrlEncodingSerializer s2 = <jk>new</jk> 
UrlEncodingParser().setProperty(UrlEncodingContext.<jsf>URLENC_expandedParams</jsf>,
 <jk>true</jk>);
+        *
+        *      String s1 = p1.serialize(<jk>new</jk> A()); <jc>// Produces 
"f1=(a,b)&f2=(c,d)"</jc>
+        *      String s2 = p2.serialize(<jk>new</jk> A()); <jc>// Produces 
"f1=a&f1=b&f2=c&f2=d"</jc>
+        * </p>
+        * <p>
+        * <b>Important note:</b>  If parsing multi-part parameters, it's 
highly recommended to use Collections or Lists
+        * as bean property types instead of arrays since arrays have to be 
recreated from scratch every time a value
+        * is added to it.
+        * <p>
+        * This option only applies to beans.
+        */
+       public static final String URLENC_expandedParams = 
"UrlEncoding.expandedParams";
+
+
+       final boolean
+               expandedParams;
+
+       /**
+        * Constructor.
+        * <p>
+        * Typically only called from {@link ContextFactory#getContext(Class)}.
+        *
+        * @param cf The factory that created this context.
+        */
+       public UrlEncodingSerializerContext(ContextFactory cf) {
+               super(cf);
+               this.expandedParams = cf.getProperty(URLENC_expandedParams, 
boolean.class, false);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java
 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java
new file mode 100644
index 0000000..11e5a9e
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java
@@ -0,0 +1,86 @@
+/***************************************************************************************************************************
+ * 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.urlencoding;
+
+import static org.apache.juneau.urlencoding.UrlEncodingParserContext.*;
+
+import java.lang.reflect.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.json.*;
+
+/**
+ * Session object that lives for the duration of a single use of {@link 
UrlEncodingSerializer}.
+ * <p>
+ * This class is NOT thread safe.  It is meant to be discarded after one-time 
use.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class UrlEncodingSerializerSession extends UonSerializerSession {
+
+       private final boolean expandedParams;
+
+       /**
+        * 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 beanContext The bean context being used.
+        * @param output The output object.  See {@link 
JsonSerializerSession#getWriter()} for valid class types.
+        * @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.
+        */
+       public UrlEncodingSerializerSession(UrlEncodingSerializerContext ctx, 
BeanContext beanContext, Object output, ObjectMap op, Method javaMethod) {
+               super(ctx, beanContext, output, op, javaMethod);
+               if (op == null || op.isEmpty()) {
+                       expandedParams = ctx.expandedParams;
+               } else {
+                       expandedParams = op.getBoolean(URLENC_expandedParams, 
false);
+               }
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified bean property should be 
expanded as multiple key-value pairs.
+        *
+        * @param pMeta The metadata on the bean property.
+        * @return <jk>true</jk> if the specified bean property should be 
expanded as multiple key-value pairs.
+        */
+       public final boolean shouldUseExpandedParams(BeanPropertyMeta<?> pMeta) 
{
+               ClassMeta<?> cm = pMeta.getClassMeta();
+               if (cm.isArray() || cm.isCollection()) {
+                       if (expandedParams)
+                               return true;
+                       if 
(pMeta.getBeanMeta().getClassMeta().getUrlEncodingMeta().isExpandedParams())
+                               return true;
+               }
+               return false;
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified value should be represented 
as an expanded parameter list.
+        *
+        * @param value The value to check.
+        * @return <jk>true</jk> if the specified value should be represented 
as an expanded parameter list.
+        */
+       public final boolean shouldUseExpandedParams(Object value) {
+               if (value == null || ! expandedParams)
+                       return false;
+               ClassMeta<?> cm = 
getBeanContext().getClassMetaForObject(value).getTransformedClassMeta();
+               if (cm.isArray() || cm.isCollection()) {
+                       if (expandedParams)
+                               return true;
+               }
+               return false;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/urlencoding/annotation/UrlEncoding.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/annotation/UrlEncoding.java
 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/annotation/UrlEncoding.java
new file mode 100644
index 0000000..66dd263
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/annotation/UrlEncoding.java
@@ -0,0 +1,41 @@
+/***************************************************************************************************************************
+ * 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.urlencoding.annotation;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.*;
+
+import org.apache.juneau.urlencoding.*;
+
+/**
+ * Annotation that can be applied to classes, fields, and methods to tweak how
+ * they are handled by {@link UrlEncodingSerializer} and {@link 
UrlEncodingParser}.
+ *
+ * @author James Bognar ([email protected])
+ */
+@Documented
+@Target({TYPE})
+@Retention(RUNTIME)
+@Inherited
+public @interface UrlEncoding {
+
+       /**
+        * When true, bean properties of type array or Collection will be 
expanded into multiple key=value pairings.
+        * <p>
+        * This annotation is identical in behavior to using the {@link 
UrlEncodingContext#URLENC_expandedParams}
+        * property, but applies to only instances of this bean.
+        */
+       boolean expandedParams() default false;
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/urlencoding/annotation/package.html
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/annotation/package.html
 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/annotation/package.html
new file mode 100644
index 0000000..6391eae
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/annotation/package.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<!--
+/***************************************************************************************************************************
+ * 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.
+ *
+ 
***************************************************************************************************************************/
+ -->
+<html>
+<head>
+       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+       <style type="text/css">
+               /* For viewing in Page Designer */
+               @IMPORT url("../../../../../../../javadoc.css");
+
+               /* For viewing in REST interface */
+               @IMPORT url("../htdocs/javadoc.css");
+               body { 
+                       margin: 20px; 
+               }       
+       </style>
+       <script>
+               /* Replace all @code and @link tags. */ 
+               window.onload = function() {
+                       document.body.innerHTML = 
document.body.innerHTML.replace(/\{\@code ([^\}]+)\}/g, '<code>$1</code>');
+                       document.body.innerHTML = 
document.body.innerHTML.replace(/\{\@link (([^\}]+)\.)?([^\.\}]+)\}/g, 
'<code>$3</code>');
+               }
+       </script>
+</head>
+<body>
+<p>URL-Encoding annotations</p>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/urlencoding/doc-files/Example_HTML.png
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/doc-files/Example_HTML.png
 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/doc-files/Example_HTML.png
new file mode 100644
index 0000000..ab74763
Binary files /dev/null and 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/doc-files/Example_HTML.png
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/urlencoding/doc-files/Example_UrlEncoding.png
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/doc-files/Example_UrlEncoding.png
 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/doc-files/Example_UrlEncoding.png
new file mode 100644
index 0000000..34de8a7
Binary files /dev/null and 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/doc-files/Example_UrlEncoding.png
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/urlencoding/doc-files/rfc_uon.txt
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/urlencoding/doc-files/rfc_uon.txt 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/doc-files/rfc_uon.txt
new file mode 100644
index 0000000..c79c9c5
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/urlencoding/doc-files/rfc_uon.txt
@@ -0,0 +1,352 @@
+Network Working Group                                          J. Bognar
+Request for Comments: 9999                                     C. Chaney  
+Category: Informational                                              IBM
+                                                                Jan 2014
+
+                            ***DRAFT***
+               URI Object Notation (UON): Generic Syntax
+
+
+About this document
+
+   This memo provides information for the Internet community.  It does
+   not specify an Internet standard of any kind.  Distribution of this
+   memo is unlimited.
+
+Copyright Notice
+
+   Copyright (C) IBM Corp. 2014.  All Rights Reserved.
+
+Abstract
+
+   This document describes a grammar that builds upon RFC2396
+   (Uniform Resource Identifiers).  Its purpose is to define a 
+   generalized object notation for URI query parameter values similar in 
+   concept to Javascript Object Notation (RFC4627).  The goal is a 
+   syntax such that any data structure defined in JSON can be losslessly 
+   defined in an equivalent URI-based grammar, yet be fully compliant 
+   with the RFC2396 specification. 
+
+   This grammar provides the ability to construct the following data 
+   structures in URL parameter values: 
+       
+      OBJECT
+      ARRAY
+      NUMBER
+      BOOLEAN
+      STRING
+      NULL
+
+   Example:
+
+      The following shows a sample object defined in Javascript:
+
+         var x = { 
+            id: 1, 
+            name: 'John Smith', 
+            uri: 'http://sample/addressBook/person/1', 
+            addressBookUri: 'http://sample/addressBook', 
+            birthDate: '1946-08-12T00:00:00Z', 
+            otherIds: null,
+            addresses: [ 
+               { 
+                  uri: 'http://sample/addressBook/address/1', 
+                  personUri: 'http://sample/addressBook/person/1', 
+                  id: 1, 
+                  street: '100 Main Street', 
+                  city: 'Anywhereville', 
+                  state: 'NY', 
+                  zip: 12345, 
+                  isCurrent: true,
+               } 
+            ] 
+         } 
+
+      Using the "strict" syntax defined in this document, the equivalent 
+      UON notation would be as follows:
+
+         x=$o(id=$n(1),name=John+Smith,uri=http://sample/
+         addressBook/person/1,addressBookUri=http://sample/
+         addressBook,birthDate=1946-08-12T00:00:00Z,otherIds=%00,
+         addresses=$a($o(uri=http://sample/addressBook/
+         address/1,personUri=http://sample/addressBook/
+         person/1,id=$n(1),street=100+Main+Street,city=
+         Anywhereville,state=NY,zip=$n(12345),isCurrent=$b(true)))) 
+
+      A secondary "lax" syntax is available when the data type of the
+      values are already known on the receiving end of the transmission:
+
+         x=(id=1,name=John+Smith,uri=http://sample/
+         addressBook/person/1,addressBookUri=http://sample/
+         addressBook,birthDate=1946-08-12T00:00:00Z,otherIds=%00,
+         addresses=((uri=http://sample/addressBook/
+         address/1,personUri=http://sample/addressBook/
+         person/1,id=1,street=100+Main+Street,city=
+         Anywhereville,state=NY,zip=12345,isCurrent=true))) 
+
+      Values represented in strict mode can be losslessly converted
+      back and forth into a JSON model without any additional
+      information.  Values represented in lax mode cannot.
+
+1. Language constraints
+
+   The grammar syntax is constrained to usage of characters allowed by 
+      URI notation:
+
+      uric       = reserved | unreserved | escaped
+      reserved   = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
+                   "$" | ","
+      unreserved = alphanum | mark
+      mark       = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+
+   In particular, the URI specification disallows the following 
+   characters in unencoded form:
+
+      unwise     = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
+      delims     = "<" | ">" | "#" | "%" | <">
+
+   The exclusion of {} and [] characters eliminates the possibility of 
+   using JSON as parameter values.
+
+
+2. Grammar constructs
+
+   The grammar consists of the following language constructs:
+   
+      Objects - Values consisting of one or more child name/value pairs.
+      Arrays - Values consisting of zero or more child values.
+      Booleans - Values consisting of true/false values.
+      Numbers - Decimal and floating point values.
+      Strings - Everything else.
+
+2.1. Objects 
+
+   Objects are values consisting of one or more child name/value pairs.
+   The $o() construct is used to define an object.
+
+   Example:  A simple map with two key/value pairs:
+
+      a1=$o(b1=x1,b2=x2)
+
+   Example:  A nested map:
+   
+      a1=$o(b1=$o(c1=x1,c2=x2))
+
+   When the data type is already known to be an object on the receiving 
+   end, then the type flag can be removed from the construct to produce
+   a simplified value. 
+
+   Example:  A nested map using "lax" syntax:
+
+     a1=(b1=(c1=x1,c2=x2))
+
+2.2. Arrays
+
+   Arrays are values consisting of zero or more child values.
+   The $a() construct is used to define an array.
+
+   Example:  An array of two string values:
+
+      a1=$a(x1,x2)
+
+   Example:  A 2-dimensional array:
+
+      a1=$a($a(x1,x2),$a(x3,x4))
+
+   Example:  An array of objects:
+
+      a1=$a($o(b1=x1,b2=x2),$o(c1=x1,c2=x2))
+
+   When the data type is already known to be an array on the receiving 
+   end, then the type flag can be removed from the construct to produce
+   a simplified value. 
+   
+   Example:  An array of objects using "lax" syntax:
+
+      a1=((b1=x1,b2=x2),(c1=x1,c2=x2))
+
+2.3. Booleans
+
+   Booleans are values that can only take on values "true" or "false".  
+   The $b() construct is used to define a boolean.
+   
+   Example:  Two boolean values:
+
+      a1=$b(true)&a2=$b(false)
+   
+   When the data type is already known to be a boolean on the receiving 
+   end, then the type flag and parentheses can be removed from the 
+   construct to produce a simplified value. 
+
+   Example:  Two boolean values using "lax" syntax:
+
+      a1=true&a2=false
+
+2.4. Numbers
+
+   The $n() construct is used to define a number.
+   Both decimal and float numbers are supported.
+   
+   Example:  Two numerical values, one decimal and one float:
+
+      a1=$n(123)&a2=$n(1.23e1)
+
+   When the data type is already known to be a number on the receiving 
+   end, then the type flag and parentheses can be removed from the 
+   construct to produce a simplified value. 
+   
+   Example:  Two numerical values using "lax" syntax:
+
+      a1=123&a2=1.23e1
+
+2.5. Strings
+
+   Anything not conforming to one of the constructs described above 
+   are treated as simple strings.  
+   
+   Example:  A simple string value:
+
+      a1=foobar
+   
+   The tilde character (~) is used for escaping characters to prevent 
+   them from being confused with syntax characters.  
+
+   The following characters must be escaped in string literals:  
+
+      $ , ( ) ~ =
+   
+   For example, the string literal "$o(b1=x)" should be 
+   represented as follows:
+
+      a1=~$o~(b1~=x~)
+   
+   In addition, strings can optionally be enclosed in parentheses
+   when needed to handle ambiguous cases.
+   
+   The following two values are equivalent:
+   
+      a1=foobar
+      a1=(foobar)
+      
+   Using parentheses, the following construct can be used to represent
+   an empty string:
+   
+      a1=()
+   
+   The purpose for this is to handle a potential ambiguity in the 
+   representation of an empty array ([]) vs. an array containing one
+   empty string ([""]).  An array containing one empty string is 
+   represented as follows:
+
+      a1=$a(())
+
+   Without this construct, there would not be a way to tell the 
+   difference between an empty array and an array containing an empty    
+   string:
+
+      a1=$a()
+
+   Note that an array consisting of two empty strings does not suffer 
+   from this ambiguity, and the use of parenthesis is optional in 
+   this case: 
+
+      a1=$a(,)
+
+2.7. Null values
+
+   Nulls are represented by ASCII '0' as an escaped hex sequence:
+
+      a1=%00
+
+   Note that a string consisting of a single null character can be 
+   represented with the following construct:
+
+      a1=(%00)
+
+2.8. Top-level attribute names
+
+   Top-level attribute names (e.g. "a1" in "&a1=foobar") are treated
+   as strings but for one exception.  The '=' character must be
+   encoded so as not to be confused as a key/value separator.
+   Note that the '=' character must also be escaped per the UON
+   notation.
+   
+   For example, the UON equivalent of {"a=b":"a=b"} constructed as
+   a top-level query parameter string would be as follows:
+   
+      a~%3Db=a~=b
+      
+   Note that the '=' character is encoded in the attribute name,
+   but it is not necessary to have it encoded in the attribute value.
+
+2.9. URL-encoded characters
+
+   UON notation allows for any character, even UON grammar
+   characters, to be URL-encoded.
+   
+   The following query strings are fully equivalent in structure:
+   
+     a1=$o(b1=x1,b2=x2)
+     %61%31=%24%6F%28%62%31%3D%78%31%2C%62%32%3D%78%32%29
+
+
+3. BNF
+
+   The following BNF describes the syntax for top-level URI query 
+   parameter values (e.g. ?<attrname>=<value>).
+
+   attrname    = (string | null)
+   value       = (var | string | null)
+
+   string      = ("(" litchar* ")") | litchar*
+   null        = "%00"
+   
+   var         = ovar | avar | nvar | bvar
+   ovar        = ovar_strict | ovar_lax
+   avar        = avar_strict | avar_lax
+   nvar        = nvar_strict | nvar_lax
+   bvar        = bvar_strict | bvar_lax
+   ovar_strict = "$o(" [pairs] ")"
+   ovar_lax    =   "(" [pairs] ")"
+   avar_strict = "$a(" [values] ")"
+   avar_lax    =   "(" [values] ")"
+   nvar_strict = "$n(" number ")"
+   nvar_lax    =       number
+   bvar_strict = "$b(" boolean ")" 
+   bvar_lax    =       boolean
+
+   pairs       = pair ["," pairs]
+   pair        = key "=" value 
+   values      = value ["," values]
+   key         = (string | null)
+   boolean     = "true" | "false"
+
+   escape_seq  = "~" escaped
+   encode_seq  = "%" digithex digithex
+
+   number      = [-] (decimal | float) [exp]
+   decimal     = "0" | (digit19 digit*)
+   float       = decimal "." digit+
+   exp         = "e" [("+" | "-")] digit+
+
+   litchar     = unencoded | encode_seq | escape_seq
+   escaped     = "$" | "," | "(" | ")" | "~" | "=" 
+   unencoded   = alpha | digit | 
+                 ";" | "/" | "?" | ":" | "@" |
+                 "-" | "_" | "." | "!" | "*" | "'" 
+   alpha       = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" |
+                 "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" |
+                 "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" |
+                 "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" |         
    
+                 "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" |
+                 "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
+   digit       = "0" | digit19
+   digit19     = "1" | "2" | "3" | "4" | "5" | "6" | "7" |
+                 "8" | "9"
+   digithex    = digit | 
+                 "A" | "B" | "C" | "D" | "E" | "F" |
+                 "a" | "b" | "c" | "d" | "e" | "f"
+
+
+
+

Reply via email to