http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e4dfdf81/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/Common_UonTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/Common_UonTest.java
 
b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/Common_UonTest.java
new file mode 100755
index 0000000..768eaec
--- /dev/null
+++ 
b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/Common_UonTest.java
@@ -0,0 +1,450 @@
+// 
***************************************************************************************************************************
+// * 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.TestUtils.*;
+import static org.apache.juneau.serializer.SerializerContext.*;
+import static org.junit.Assert.*;
+
+import java.net.*;
+import java.net.URI;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.testbeans.*;
+import org.junit.*;
+
+@SuppressWarnings({"serial","javadoc"})
+public class Common_UonTest {
+       UonParser p = UonParser.DEFAULT;
+       UonParser pe = UonParser.DEFAULT_DECODING;
+
+       
//====================================================================================================
+       // Trim nulls from beans
+       
//====================================================================================================
+       @Test
+       public void testTrimNullsFromBeans() throws Exception {
+               UonSerializer s = new UonSerializer.Encoding();
+               A t1 = A.create(), t2;
+
+               s.setProperty(SERIALIZER_trimNullProperties, false);
+               String r = s.serialize(t1);
+               assertEquals("$o(s1=%00,s2=s2)", r);
+               t2 = pe.parse(r, A.class);
+               assertEqualObjects(t1, t2);
+
+               s.setProperty(SERIALIZER_trimNullProperties, true);
+               r = s.serialize(t1);
+               assertEquals("$o(s2=s2)", r);
+               t2 = p.parse(r, A.class);
+               assertEqualObjects(t1, t2);
+       }
+
+       public static class A {
+               public String s1, s2;
+
+               public static A create() {
+                       A t = new A();
+                       t.s2 = "s2";
+                       return t;
+               }
+       }
+
+       
//====================================================================================================
+       // Trim empty maps
+       
//====================================================================================================
+       @Test
+       public void testTrimEmptyMaps() throws Exception {
+               UonSerializer s = UonSerializer.DEFAULT_SIMPLE_ENCODING.clone();
+               B t1 = B.create(), t2;
+               String r;
+
+               s.setProperty(SERIALIZER_trimEmptyMaps, false);
+               r = s.serialize(t1);
+               assertEquals("(f1=(),f2=(f2a=%00,f2b=(s2=s2)))", r);
+               t2 = pe.parse(r, B.class);
+               assertEqualObjects(t1, t2);
+
+               s.setProperty(SERIALIZER_trimEmptyMaps, true);
+               r = s.serialize(t1);
+               assertEquals("(f2=(f2a=%00,f2b=(s2=s2)))", r);
+               t2 = pe.parse(r, B.class);
+               assertNull(t2.f1);
+       }
+
+       public static class B {
+               public TreeMap<String,A> f1, f2;
+
+               public static B create() {
+                       B t = new B();
+                       t.f1 = new TreeMap<String,A>();
+                       t.f2 = new 
TreeMap<String,A>(){{put("f2a",null);put("f2b",A.create());}};
+                       return t;
+               }
+       }
+
+       
//====================================================================================================
+       // Trim empty lists
+       
//====================================================================================================
+       @Test
+       public void testTrimEmptyLists() throws Exception {
+               UonSerializer s = new UonSerializer.Encoding();
+               C t1 = C.create(), t2;
+               String r;
+
+               s.setProperty(SERIALIZER_trimEmptyCollections, false);
+               r = s.serialize(t1);
+               assertEquals("$o(f1=$a(),f2=$a(%00,$o(s2=s2)))", r);
+               t2 = pe.parse(r, C.class);
+               assertEqualObjects(t1, t2);
+
+               s.setProperty(SERIALIZER_trimEmptyCollections, true);
+               r = s.serialize(t1);
+               assertEquals("$o(f2=$a(%00,$o(s2=s2)))", r);
+               t2 = pe.parse(r, C.class);
+               assertNull(t2.f1);
+       }
+
+       public static class C {
+               public List<A> f1, f2;
+
+               public static C create() {
+                       C t = new C();
+                       t.f1 = new LinkedList<A>();
+                       t.f2 = new 
LinkedList<A>(){{add(null);add(A.create());}};
+                       return t;
+               }
+       }
+
+       
//====================================================================================================
+       // Trim empty arrays
+       
//====================================================================================================
+       @Test
+       public void testTrimEmptyArrays() throws Exception {
+               UonSerializer s = new UonSerializer.Encoding();
+               D t1 = D.create(), t2;
+               String r;
+
+               s.setProperty(SERIALIZER_trimEmptyCollections, false);
+               r = s.serialize(t1);
+               assertEquals("$o(f1=$a(),f2=$a(%00,$o(s2=s2)))", r);
+               t2 = pe.parse(r, D.class);
+               assertEqualObjects(t1, t2);
+
+               s.setProperty(SERIALIZER_trimEmptyCollections, true);
+               r = s.serialize(t1);
+               assertEquals("$o(f2=$a(%00,$o(s2=s2)))", r);
+               t2 = pe.parse(r, D.class);
+               assertNull(t2.f1);
+       }
+
+       public static class D {
+               public A[] f1, f2;
+
+               public static D create() {
+                       D t = new D();
+                       t.f1 = new A[]{};
+                       t.f2 = new A[]{null, A.create()};
+                       return t;
+               }
+       }
+
+       
//====================================================================================================
+       // @BeanProperty.properties annotation.
+       
//====================================================================================================
+       @Test
+       public void testBeanPropertyProperies() throws Exception {
+               UonSerializer s = UonSerializer.DEFAULT;
+               UonSerializer ss = UonSerializer.DEFAULT_SIMPLE;
+               String ue = ss.serialize(new E1());
+               
assertEquals("(x1=(f1=1),x2=(f1=1),x3=((f1=1)),x4=((f1=1)),x5=((f1=1)),x6=((f1=1)))",
 ue);
+               ue = s.serialize(new E1());
+               
assertEquals("$o(x1=$o(f1=$n(1)),x2=$o(f1=$n(1)),x3=$a($o(f1=$n(1))),x4=$a($o(f1=$n(1))),x5=$a($o(f1=$n(1))),x6=$a($o(f1=$n(1))))",
 ue);
+       }
+
+       public static class E1 {
+               @BeanProperty(properties="f1") public E2 x1 = new E2();
+               @BeanProperty(properties="f1") public Map<String,Integer> x2 = 
new LinkedHashMap<String,Integer>() {{
+                       put("f1",1); put("f2",2);
+               }};
+               @BeanProperty(properties="f1") public E2[] x3 = {new E2()};
+               @BeanProperty(properties="f1") public List<E2> x4 = new 
LinkedList<E2>() {{
+                       add(new E2());
+               }};
+               @BeanProperty(properties="f1") public ObjectMap[] x5 = {new 
ObjectMap().append("f1",1).append("f2",2)};
+               @BeanProperty(properties="f1") public List<ObjectMap> x6 = new 
LinkedList<ObjectMap>() {{
+                       add(new ObjectMap().append("f1",1).append("f2",2));
+               }};
+       }
+
+       public static class E2 {
+               public int f1 = 1;
+               public int f2 = 2;
+       }
+
+       
//====================================================================================================
+       // @BeanProperty.properties annotation on list of beans.
+       
//====================================================================================================
+       @Test
+       public void testBeanPropertyPropertiesOnListOfBeans() throws Exception {
+               UonSerializer s = UonSerializer.DEFAULT;
+               List<F> l = new LinkedList<F>();
+               F t = new F();
+               t.x1.add(new F());
+               l.add(t);
+               String xml = s.serialize(l);
+               assertEquals("$a($o(x1=$a($o(x2=$n(2))),x2=$n(2)))", xml);
+       }
+
+       public static class F {
+               @BeanProperty(properties="x2") public List<F> x1 = new 
LinkedList<F>();
+               public int x2 = 2;
+       }
+
+       
//====================================================================================================
+       // Test URIAttr - Test that URLs and URIs are serialized and parsed 
correctly.
+       
//====================================================================================================
+       @Test
+       public void testURIAttr() throws Exception {
+               UonSerializer s = UonSerializer.DEFAULT;
+               UonParser p = UonParser.DEFAULT;
+
+               G t = new G();
+               t.uri = new URI("http://uri";);
+               t.f1 = new URI("http://f1";);
+               t.f2 = new URL("http://f2";);
+
+               String r = s.serialize(t);
+               t = p.parse(r, G.class);
+               assertEquals("http://uri";, t.uri.toString());
+               assertEquals("http://f1";, t.f1.toString());
+               assertEquals("http://f2";, t.f2.toString());
+       }
+
+       public static class G {
+               public URI uri;
+               public URI f1;
+               public URL f2;
+       }
+
+       
//====================================================================================================
+       // Test URIs with URI_CONTEXT and URI_AUTHORITY
+       
//====================================================================================================
+       @Test
+       public void testUris() throws Exception {
+               WriterSerializer s = new UonSerializer();
+               TestURI t = new TestURI();
+               String r;
+               String expected;
+
+               s.setProperty(SERIALIZER_relativeUriBase, null);
+               r = s.serialize(t);
+               expected = ""
+                       +"$o("
+                       +"f0=f0/x0,"
+                       +"f1=f1/x1,"
+                       +"f2=/f2/x2,"
+                       +"f3=http://www.apache.org/f3/x3,";
+                       +"f4=f4/x4,"
+                       +"f5=/f5/x5,"
+                       +"f6=http://www.apache.org/f6/x6,";
+                       +"f7=http://www.apache.org/f7/x7,";
+                       +"f8=f8/x8,"
+                       +"f9=f9/x9,"
+                       +"fa=http://www.apache.org/fa/xa#MY_LABEL,";
+                       
+"fb=http://www.apache.org/fb/xb?label~=MY_LABEL&foo~=bar,";
+                       
+"fc=http://www.apache.org/fc/xc?foo~=bar&label~=MY_LABEL,";
+                       
+"fd=http://www.apache.org/fd/xd?label2~=MY_LABEL&foo~=bar,";
+                       
+"fe=http://www.apache.org/fe/xe?foo~=bar&label2~=MY_LABEL";
+                       +")";
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_relativeUriBase, "");  // Same as null.
+               r = s.serialize(t);
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_relativeUriBase, "/cr");
+               r = s.serialize(t);
+               expected = ""
+                       +"$o("
+                       +"f0=/cr/f0/x0,"
+                       +"f1=/cr/f1/x1,"
+                       +"f2=/f2/x2,"
+                       +"f3=http://www.apache.org/f3/x3,";
+                       +"f4=/cr/f4/x4,"
+                       +"f5=/f5/x5,"
+                       +"f6=http://www.apache.org/f6/x6,";
+                       +"f7=http://www.apache.org/f7/x7,";
+                       +"f8=/cr/f8/x8,"
+                       +"f9=/cr/f9/x9,"
+                       +"fa=http://www.apache.org/fa/xa#MY_LABEL,";
+                       
+"fb=http://www.apache.org/fb/xb?label~=MY_LABEL&foo~=bar,";
+                       
+"fc=http://www.apache.org/fc/xc?foo~=bar&label~=MY_LABEL,";
+                       
+"fd=http://www.apache.org/fd/xd?label2~=MY_LABEL&foo~=bar,";
+                       
+"fe=http://www.apache.org/fe/xe?foo~=bar&label2~=MY_LABEL";
+                       +")";
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_relativeUriBase, "/cr/");  // Same as 
above
+               r = s.serialize(t);
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_relativeUriBase, "/");
+               r = s.serialize(t);
+               expected = ""
+                       +"$o("
+                       +"f0=/f0/x0,"
+                       +"f1=/f1/x1,"
+                       +"f2=/f2/x2,"
+                       +"f3=http://www.apache.org/f3/x3,";
+                       +"f4=/f4/x4,"
+                       +"f5=/f5/x5,"
+                       +"f6=http://www.apache.org/f6/x6,";
+                       +"f7=http://www.apache.org/f7/x7,";
+                       +"f8=/f8/x8,"
+                       +"f9=/f9/x9,"
+                       +"fa=http://www.apache.org/fa/xa#MY_LABEL,";
+                       
+"fb=http://www.apache.org/fb/xb?label~=MY_LABEL&foo~=bar,";
+                       
+"fc=http://www.apache.org/fc/xc?foo~=bar&label~=MY_LABEL,";
+                       
+"fd=http://www.apache.org/fd/xd?label2~=MY_LABEL&foo~=bar,";
+                       
+"fe=http://www.apache.org/fe/xe?foo~=bar&label2~=MY_LABEL";
+                       +")";
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_relativeUriBase, null);
+
+               s.setProperty(SERIALIZER_absolutePathUriBase, "http://foo";);
+               r = s.serialize(t);
+               expected = ""
+                       +"$o("
+                       +"f0=f0/x0,"
+                       +"f1=f1/x1,"
+                       +"f2=http://foo/f2/x2,";
+                       +"f3=http://www.apache.org/f3/x3,";
+                       +"f4=f4/x4,"
+                       +"f5=http://foo/f5/x5,";
+                       +"f6=http://www.apache.org/f6/x6,";
+                       +"f7=http://www.apache.org/f7/x7,";
+                       +"f8=f8/x8,"
+                       +"f9=f9/x9,"
+                       +"fa=http://www.apache.org/fa/xa#MY_LABEL,";
+                       
+"fb=http://www.apache.org/fb/xb?label~=MY_LABEL&foo~=bar,";
+                       
+"fc=http://www.apache.org/fc/xc?foo~=bar&label~=MY_LABEL,";
+                       
+"fd=http://www.apache.org/fd/xd?label2~=MY_LABEL&foo~=bar,";
+                       
+"fe=http://www.apache.org/fe/xe?foo~=bar&label2~=MY_LABEL";
+                       +")";
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_absolutePathUriBase, "http://foo/";);
+               r = s.serialize(t);
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_absolutePathUriBase, "");  // Same as 
null.
+               r = s.serialize(t);
+               expected = ""
+                       +"$o("
+                       +"f0=f0/x0,"
+                       +"f1=f1/x1,"
+                       +"f2=/f2/x2,"
+                       +"f3=http://www.apache.org/f3/x3,";
+                       +"f4=f4/x4,"
+                       +"f5=/f5/x5,"
+                       +"f6=http://www.apache.org/f6/x6,";
+                       +"f7=http://www.apache.org/f7/x7,";
+                       +"f8=f8/x8,"
+                       +"f9=f9/x9,"
+                       +"fa=http://www.apache.org/fa/xa#MY_LABEL,";
+                       
+"fb=http://www.apache.org/fb/xb?label~=MY_LABEL&foo~=bar,";
+                       
+"fc=http://www.apache.org/fc/xc?foo~=bar&label~=MY_LABEL,";
+                       
+"fd=http://www.apache.org/fd/xd?label2~=MY_LABEL&foo~=bar,";
+                       
+"fe=http://www.apache.org/fe/xe?foo~=bar&label2~=MY_LABEL";
+                       +")";
+               assertEquals(expected, r);
+       }
+
+       
//====================================================================================================
+       // Validate that you cannot update properties on locked serializer.
+       
//====================================================================================================
+       @Test
+       public void testLockedSerializer() throws Exception {
+               UonSerializer s = new UonSerializer().lock();
+               try {
+                       s.setProperty(JsonSerializerContext.JSON_simpleMode, 
true);
+                       fail("Locked exception not thrown");
+               } catch (LockedException e) {}
+               try {
+                       
s.setProperty(SerializerContext.SERIALIZER_addBeanTypeProperties, true);
+                       fail("Locked exception not thrown");
+               } catch (LockedException e) {}
+               try {
+                       
s.setProperty(BeanContext.BEAN_beanMapPutReturnsOldValue, true);
+                       fail("Locked exception not thrown");
+               } catch (LockedException e) {}
+       }
+
+       
//====================================================================================================
+       // Recursion
+       
//====================================================================================================
+       @Test
+       public void testRecursion() throws Exception {
+               WriterSerializer s = new UonSerializer();
+
+               R1 r1 = new R1();
+               R2 r2 = new R2();
+               R3 r3 = new R3();
+               r1.r2 = r2;
+               r2.r3 = r3;
+               r3.r1 = r1;
+
+               // No recursion detection
+               try {
+                       s.serialize(r1);
+                       fail("Exception expected!");
+               } catch (Exception e) {
+                       String msg = e.getLocalizedMessage();
+                       assertTrue(msg.contains("It's recommended you use the 
SerializerContext.SERIALIZER_detectRecursions setting to help locate the 
loop."));
+               }
+
+               // Recursion detection, no ignore
+               s.setProperty(SERIALIZER_detectRecursions, true);
+               try {
+                       s.serialize(r1);
+                       fail("Exception expected!");
+               } catch (Exception e) {
+                       String msg = e.getLocalizedMessage();
+                       
assertTrue(msg.contains("[0]root:org.apache.juneau.urlencoding.Common_UonTest$R1"));
+                       
assertTrue(msg.contains("->[1]r2:org.apache.juneau.urlencoding.Common_UonTest$R2"));
+                       
assertTrue(msg.contains("->[2]r3:org.apache.juneau.urlencoding.Common_UonTest$R3"));
+                       
assertTrue(msg.contains("->[3]r1:org.apache.juneau.urlencoding.Common_UonTest$R1"));
+               }
+
+               s.setProperty(SERIALIZER_ignoreRecursions, true);
+               assertEquals("$o(name=foo,r2=$o(name=bar,r3=$o(name=baz)))", 
s.serialize(r1));
+       }
+
+       public static class R1 {
+               public String name = "foo";
+               public R2 r2;
+       }
+       public static class R2 {
+               public String name = "bar";
+               public R3 r3;
+       }
+       public static class R3 {
+               public String name = "baz";
+               public R1 r1;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e4dfdf81/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/Common_UrlEncodingTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/Common_UrlEncodingTest.java
 
b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/Common_UrlEncodingTest.java
new file mode 100755
index 0000000..0f0a30f
--- /dev/null
+++ 
b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/Common_UrlEncodingTest.java
@@ -0,0 +1,442 @@
+// 
***************************************************************************************************************************
+// * 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.TestUtils.*;
+import static org.apache.juneau.serializer.SerializerContext.*;
+import static org.junit.Assert.*;
+
+import java.net.*;
+import java.net.URI;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.testbeans.*;
+import org.junit.*;
+
+@SuppressWarnings({"serial","javadoc"})
+public class Common_UrlEncodingTest {
+       UrlEncodingParser p = UrlEncodingParser.DEFAULT;
+
+       
//====================================================================================================
+       // Trim nulls from beans
+       
//====================================================================================================
+       @Test
+       public void testTrimNullsFromBeans() throws Exception {
+               UrlEncodingSerializer s = new UrlEncodingSerializer();
+               A t1 = A.create(), t2;
+
+               s.setProperty(SERIALIZER_trimNullProperties, false);
+               String r = s.serialize(t1);
+               assertEquals("s1=%00&s2=s2", r);
+               t2 = p.parse(r, A.class);
+               assertEqualObjects(t1, t2);
+
+               s.setProperty(SERIALIZER_trimNullProperties, true);
+               r = s.serialize(t1);
+               assertEquals("s2=s2", r);
+               t2 = p.parse(r, A.class);
+               assertEqualObjects(t1, t2);
+       }
+
+       public static class A {
+               public String s1, s2;
+
+               public static A create() {
+                       A t = new A();
+                       t.s2 = "s2";
+                       return t;
+               }
+       }
+
+       
//====================================================================================================
+       // Trim empty maps
+       
//====================================================================================================
+       @Test
+       public void testTrimEmptyMaps() throws Exception {
+               UrlEncodingSerializer s = 
UrlEncodingSerializer.DEFAULT_SIMPLE.clone();
+               B t1 = B.create(), t2;
+               String r;
+
+               s.setProperty(SERIALIZER_trimEmptyMaps, false);
+               r = s.serialize(t1);
+               assertEquals("f1=()&f2=(f2a=%00,f2b=(s2=s2))", r);
+               t2 = p.parse(r, B.class);
+               assertEqualObjects(t1, t2);
+
+               s.setProperty(SERIALIZER_trimEmptyMaps, true);
+               r = s.serialize(t1);
+               assertEquals("f2=(f2a=%00,f2b=(s2=s2))", r);
+               t2 = p.parse(r, B.class);
+               assertNull(t2.f1);
+       }
+
+       public static class B {
+               public TreeMap<String,A> f1, f2;
+
+               public static B create() {
+                       B t = new B();
+                       t.f1 = new TreeMap<String,A>();
+                       t.f2 = new 
TreeMap<String,A>(){{put("f2a",null);put("f2b",A.create());}};
+                       return t;
+               }
+       }
+
+       
//====================================================================================================
+       // Trim empty lists
+       
//====================================================================================================
+       @Test
+       public void testTrimEmptyLists() throws Exception {
+               UrlEncodingSerializer s = new UrlEncodingSerializer();
+               C t1 = C.create(), t2;
+               String r;
+
+               s.setProperty(SERIALIZER_trimEmptyCollections, false);
+               r = s.serialize(t1);
+               assertEquals("f1=$a()&f2=$a(%00,$o(s2=s2))", r);
+               t2 = p.parse(r, C.class);
+               assertEqualObjects(t1, t2);
+
+               s.setProperty(SERIALIZER_trimEmptyCollections, true);
+               r = s.serialize(t1);
+               assertEquals("f2=$a(%00,$o(s2=s2))", r);
+               t2 = p.parse(r, C.class);
+               assertNull(t2.f1);
+       }
+
+       public static class C {
+               public List<A> f1, f2;
+
+               public static C create() {
+                       C t = new C();
+                       t.f1 = new LinkedList<A>();
+                       t.f2 = new 
LinkedList<A>(){{add(null);add(A.create());}};
+                       return t;
+               }
+       }
+
+       
//====================================================================================================
+       // Trim empty arrays
+       
//====================================================================================================
+       @Test
+       public void testTrimEmptyArrays() throws Exception {
+               UrlEncodingSerializer s = new UrlEncodingSerializer();
+               D t1 = D.create(), t2;
+               String r;
+
+               s.setProperty(SERIALIZER_trimEmptyCollections, false);
+               r = s.serialize(t1);
+               assertEquals("f1=$a()&f2=$a(%00,$o(s2=s2))", r);
+               t2 = p.parse(r, D.class);
+               assertEqualObjects(t1, t2);
+
+               s.setProperty(SERIALIZER_trimEmptyCollections, true);
+               r = s.serialize(t1);
+               assertEquals("f2=$a(%00,$o(s2=s2))", r);
+               t2 = p.parse(r, D.class);
+               assertNull(t2.f1);
+       }
+
+       public static class D {
+               public A[] f1, f2;
+
+               public static D create() {
+                       D t = new D();
+                       t.f1 = new A[]{};
+                       t.f2 = new A[]{null, A.create()};
+                       return t;
+               }
+       }
+
+       
//====================================================================================================
+       // @BeanProperty.properties annotation.
+       
//====================================================================================================
+       @Test
+       public void testBeanPropertyProperies() throws Exception {
+               UrlEncodingSerializer s = UrlEncodingSerializer.DEFAULT;
+               UrlEncodingSerializer ss = UrlEncodingSerializer.DEFAULT_SIMPLE;
+               String ue = ss.serialize(new E1());
+               
assertEquals("x1=(f1=1)&x2=(f1=1)&x3=((f1=1))&x4=((f1=1))&x5=((f1=1))&x6=((f1=1))",
 ue);
+               ue = s.serialize(new E1());
+               
assertEquals("x1=$o(f1=$n(1))&x2=$o(f1=$n(1))&x3=$a($o(f1=$n(1)))&x4=$a($o(f1=$n(1)))&x5=$a($o(f1=$n(1)))&x6=$a($o(f1=$n(1)))",
 ue);
+       }
+
+       public static class E1 {
+               @BeanProperty(properties="f1") public E2 x1 = new E2();
+               @BeanProperty(properties="f1") public Map<String,Integer> x2 = 
new LinkedHashMap<String,Integer>() {{
+                       put("f1",1); put("f2",2);
+               }};
+               @BeanProperty(properties="f1") public E2[] x3 = {new E2()};
+               @BeanProperty(properties="f1") public List<E2> x4 = new 
LinkedList<E2>() {{
+                       add(new E2());
+               }};
+               @BeanProperty(properties="f1") public ObjectMap[] x5 = {new 
ObjectMap().append("f1",1).append("f2",2)};
+               @BeanProperty(properties="f1") public List<ObjectMap> x6 = new 
LinkedList<ObjectMap>() {{
+                       add(new ObjectMap().append("f1",1).append("f2",2));
+               }};
+       }
+
+       public static class E2 {
+               public int f1 = 1;
+               public int f2 = 2;
+       }
+
+       
//====================================================================================================
+       // @BeanProperty.properties annotation on list of beans.
+       
//====================================================================================================
+       @Test
+       public void testBeanPropertyPropertiesOnListOfBeans() throws Exception {
+               UrlEncodingSerializer s = UrlEncodingSerializer.DEFAULT;
+               List<F> l = new LinkedList<F>();
+               F t = new F();
+               t.x1.add(new F());
+               l.add(t);
+               ObjectMap m = new ObjectMap().append("t", l);
+               String xml = s.serialize(m);
+               assertEquals("t=$a($o(x1=$a($o(x2=$n(2))),x2=$n(2)))", xml);
+               xml = s.serialize(l);
+               assertEquals("$n(0)=$o(x1=$a($o(x2=$n(2))),x2=$n(2))", xml);
+       }
+
+       public static class F {
+               @BeanProperty(properties="x2") public List<F> x1 = new 
LinkedList<F>();
+               public int x2 = 2;
+       }
+
+       
//====================================================================================================
+       // Test URIAttr - Test that URLs and URIs are serialized and parsed 
correctly.
+       
//====================================================================================================
+       @Test
+       public void testURIAttr() throws Exception {
+               UrlEncodingSerializer s = UrlEncodingSerializer.DEFAULT;
+               UrlEncodingParser p = UrlEncodingParser.DEFAULT;
+
+               G t = new G();
+               t.uri = new URI("http://uri";);
+               t.f1 = new URI("http://f1";);
+               t.f2 = new URL("http://f2";);
+
+               String r = s.serialize(t);
+               t = p.parse(r, G.class);
+               assertEquals("http://uri";, t.uri.toString());
+               assertEquals("http://f1";, t.f1.toString());
+               assertEquals("http://f2";, t.f2.toString());
+       }
+
+       public static class G {
+               public URI uri;
+               public URI f1;
+               public URL f2;
+       }
+
+       
//====================================================================================================
+       // Test URIs with URI_CONTEXT and URI_AUTHORITY
+       
//====================================================================================================
+       @Test
+       public void testUris() throws Exception {
+               WriterSerializer s = new UrlEncodingSerializer();
+               TestURI t = new TestURI();
+               String r;
+               String expected = "";
+
+               s.setProperty(SERIALIZER_relativeUriBase, null);
+               r = s.serialize(t);
+               expected = ""
+                       +"f0=f0/x0"
+                       +"&f1=f1/x1"
+                       +"&f2=/f2/x2"
+                       +"&f3=http://www.apache.org/f3/x3";
+                       +"&f4=f4/x4"
+                       +"&f5=/f5/x5"
+                       +"&f6=http://www.apache.org/f6/x6";
+                       +"&f7=http://www.apache.org/f7/x7";
+                       +"&f8=f8/x8"
+                       +"&f9=f9/x9"
+                       +"&fa=http://www.apache.org/fa/xa%23MY_LABEL";
+                       
+"&fb=http://www.apache.org/fb/xb?label=MY_LABEL%26foo=bar";
+                       
+"&fc=http://www.apache.org/fc/xc?foo=bar%26label=MY_LABEL";
+                       
+"&fd=http://www.apache.org/fd/xd?label2=MY_LABEL%26foo=bar";
+                       
+"&fe=http://www.apache.org/fe/xe?foo=bar%26label2=MY_LABEL";;
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_relativeUriBase, "");  // Same as null.
+               r = s.serialize(t);
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_relativeUriBase, "/cr");
+               r = s.serialize(t);
+               expected = ""
+                       +"f0=/cr/f0/x0"
+                       +"&f1=/cr/f1/x1"
+                       +"&f2=/f2/x2"
+                       +"&f3=http://www.apache.org/f3/x3";
+                       +"&f4=/cr/f4/x4"
+                       +"&f5=/f5/x5"
+                       +"&f6=http://www.apache.org/f6/x6";
+                       +"&f7=http://www.apache.org/f7/x7";
+                       +"&f8=/cr/f8/x8"
+                       +"&f9=/cr/f9/x9"
+                       +"&fa=http://www.apache.org/fa/xa%23MY_LABEL";
+                       
+"&fb=http://www.apache.org/fb/xb?label=MY_LABEL%26foo=bar";
+                       
+"&fc=http://www.apache.org/fc/xc?foo=bar%26label=MY_LABEL";
+                       
+"&fd=http://www.apache.org/fd/xd?label2=MY_LABEL%26foo=bar";
+                       
+"&fe=http://www.apache.org/fe/xe?foo=bar%26label2=MY_LABEL";;
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_relativeUriBase, "/cr/");  // Same as 
above
+               r = s.serialize(t);
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_relativeUriBase, "/");
+               r = s.serialize(t);
+               expected = ""
+                       +"f0=/f0/x0"
+                       +"&f1=/f1/x1"
+                       +"&f2=/f2/x2"
+                       +"&f3=http://www.apache.org/f3/x3";
+                       +"&f4=/f4/x4"
+                       +"&f5=/f5/x5"
+                       +"&f6=http://www.apache.org/f6/x6";
+                       +"&f7=http://www.apache.org/f7/x7";
+                       +"&f8=/f8/x8"
+                       +"&f9=/f9/x9"
+                       +"&fa=http://www.apache.org/fa/xa%23MY_LABEL";
+                       
+"&fb=http://www.apache.org/fb/xb?label=MY_LABEL%26foo=bar";
+                       
+"&fc=http://www.apache.org/fc/xc?foo=bar%26label=MY_LABEL";
+                       
+"&fd=http://www.apache.org/fd/xd?label2=MY_LABEL%26foo=bar";
+                       
+"&fe=http://www.apache.org/fe/xe?foo=bar%26label2=MY_LABEL";;
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_relativeUriBase, null);
+
+               s.setProperty(SERIALIZER_absolutePathUriBase, "http://foo";);
+               r = s.serialize(t);
+               expected = ""
+                       +"f0=f0/x0"
+                       +"&f1=f1/x1"
+                       +"&f2=http://foo/f2/x2";
+                       +"&f3=http://www.apache.org/f3/x3";
+                       +"&f4=f4/x4"
+                       +"&f5=http://foo/f5/x5";
+                       +"&f6=http://www.apache.org/f6/x6";
+                       +"&f7=http://www.apache.org/f7/x7";
+                       +"&f8=f8/x8"
+                       +"&f9=f9/x9"
+                       +"&fa=http://www.apache.org/fa/xa%23MY_LABEL";
+                       
+"&fb=http://www.apache.org/fb/xb?label=MY_LABEL%26foo=bar";
+                       
+"&fc=http://www.apache.org/fc/xc?foo=bar%26label=MY_LABEL";
+                       
+"&fd=http://www.apache.org/fd/xd?label2=MY_LABEL%26foo=bar";
+                       
+"&fe=http://www.apache.org/fe/xe?foo=bar%26label2=MY_LABEL";;
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_absolutePathUriBase, "http://foo/";);
+               r = s.serialize(t);
+               assertEquals(expected, r);
+
+               s.setProperty(SERIALIZER_absolutePathUriBase, "");  // Same as 
null.
+               r = s.serialize(t);
+               expected = ""
+                       +"f0=f0/x0"
+                       +"&f1=f1/x1"
+                       +"&f2=/f2/x2"
+                       +"&f3=http://www.apache.org/f3/x3";
+                       +"&f4=f4/x4"
+                       +"&f5=/f5/x5"
+                       +"&f6=http://www.apache.org/f6/x6";
+                       +"&f7=http://www.apache.org/f7/x7";
+                       +"&f8=f8/x8"
+                       +"&f9=f9/x9"
+                       +"&fa=http://www.apache.org/fa/xa%23MY_LABEL";
+                       
+"&fb=http://www.apache.org/fb/xb?label=MY_LABEL%26foo=bar";
+                       
+"&fc=http://www.apache.org/fc/xc?foo=bar%26label=MY_LABEL";
+                       
+"&fd=http://www.apache.org/fd/xd?label2=MY_LABEL%26foo=bar";
+                       
+"&fe=http://www.apache.org/fe/xe?foo=bar%26label2=MY_LABEL";;
+               assertEquals(expected, r);
+       }
+
+       
//====================================================================================================
+       // Validate that you cannot update properties on locked serializer.
+       
//====================================================================================================
+       @Test
+       public void testLockedSerializer() throws Exception {
+               UrlEncodingSerializer s = new UrlEncodingSerializer().lock();
+               try {
+                       s.setProperty(JsonSerializerContext.JSON_simpleMode, 
true);
+                       fail("Locked exception not thrown");
+               } catch (LockedException e) {}
+               try {
+                       
s.setProperty(SerializerContext.SERIALIZER_addBeanTypeProperties, true);
+                       fail("Locked exception not thrown");
+               } catch (LockedException e) {}
+               try {
+                       
s.setProperty(BeanContext.BEAN_beanMapPutReturnsOldValue, true);
+                       fail("Locked exception not thrown");
+               } catch (LockedException e) {}
+       }
+
+       
//====================================================================================================
+       // Recursion
+       
//====================================================================================================
+       @Test
+       public void testRecursion() throws Exception {
+               WriterSerializer s = new UrlEncodingSerializer();
+
+               R1 r1 = new R1();
+               R2 r2 = new R2();
+               R3 r3 = new R3();
+               r1.r2 = r2;
+               r2.r3 = r3;
+               r3.r1 = r1;
+
+               // No recursion detection
+               try {
+                       s.serialize(r1);
+                       fail("Exception expected!");
+               } catch (Exception e) {
+                       String msg = e.getLocalizedMessage();
+                       assertTrue(msg.contains("It's recommended you use the 
SerializerContext.SERIALIZER_detectRecursions setting to help locate the 
loop."));
+               }
+
+               // Recursion detection, no ignore
+               s.setProperty(SERIALIZER_detectRecursions, true);
+               try {
+                       s.serialize(r1);
+                       fail("Exception expected!");
+               } catch (Exception e) {
+                       String msg = e.getLocalizedMessage();
+                       
assertTrue(msg.contains("[0]root:org.apache.juneau.urlencoding.Common_UrlEncodingTest$R1"));
+                       
assertTrue(msg.contains("->[1]r2:org.apache.juneau.urlencoding.Common_UrlEncodingTest$R2"));
+                       
assertTrue(msg.contains("->[2]r3:org.apache.juneau.urlencoding.Common_UrlEncodingTest$R3"));
+                       
assertTrue(msg.contains("->[3]r1:org.apache.juneau.urlencoding.Common_UrlEncodingTest$R1"));
+               }
+
+               s.setProperty(SERIALIZER_ignoreRecursions, true);
+               assertEquals("name=foo&r2=$o(name=bar,r3=$o(name=baz))", 
s.serialize(r1));
+       }
+
+       public static class R1 {
+               public String name = "foo";
+               public R2 r2;
+       }
+       public static class R2 {
+               public String name = "bar";
+               public R3 r3;
+       }
+       public static class R3 {
+               public String name = "baz";
+               public R1 r1;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e4dfdf81/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/DTOs.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/DTOs.java 
b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/DTOs.java
new file mode 100755
index 0000000..6e3728f
--- /dev/null
+++ b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/DTOs.java
@@ -0,0 +1,141 @@
+// 
***************************************************************************************************************************
+// * 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 java.util.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.urlencoding.annotation.*;
+
+@SuppressWarnings("javadoc")
+public class DTOs {
+
+       @Bean(sort=true)
+       public static class A {
+               public String a;
+               public int b;
+               public boolean c;
+
+               public static A create() {
+                       A t = new A();
+                       t.a = "a";
+                       t.b = 1;
+                       t.c = true;
+                       return t;
+               }
+
+       }
+
+       @SuppressWarnings("serial")
+       @Bean(sort=true)
+       public static class B {
+               public String[] f01;
+               public List<String> f02;
+               public int[] f03;
+               public List<Integer> f04;
+               public String[][] f05;
+               public List<String[]> f06;
+               public A[] f07;
+               public List<A> f08;
+               public A[][] f09;
+               public List<List<A>> f10;
+
+               private String[] f11;
+               private List<String> f12;
+               private int[] f13;
+               private List<Integer> f14;
+               private String[][] f15;
+               private List<String[]> f16;
+               private A[] f17;
+               private List<A> f18;
+               private A[][] f19;
+               private List<List<A>> f20;
+
+               public String[] getF11() { return f11; }
+               public List<String> getF12() { return f12; }
+               public int[] getF13() { return f13; }
+               public List<Integer> getF14() { return f14; }
+               public String[][] getF15() { return f15; }
+               public List<String[]> getF16() { return f16; }
+               public A[] getF17() { return f17; }
+               public List<A> getF18() { return f18; }
+               public A[][] getF19() { return f19; }
+               public List<List<A>> getF20() { return f20; }
+
+               public void setF11(String[] f11) { this.f11 = f11; }
+               public void setF12(List<String> f12) { this.f12 = f12; }
+               public void setF13(int[] f13) { this.f13 = f13; }
+               public void setF14(List<Integer> f14) { this.f14 = f14; }
+               public void setF15(String[][] f15) { this.f15 = f15; }
+               public void setF16(List<String[]> f16) { this.f16 = f16; }
+               public void setF17(A[] f17) { this.f17 = f17; }
+               public void setF18(List<A> f18) { this.f18 = f18; }
+               public void setF19(A[][] f19) { this.f19 = f19; }
+               public void setF20(List<List<A>> f20) { this.f20 = f20; }
+
+               static B create() {
+                       B t = new B();
+                       t.f01 = new String[]{"a","b"};
+                       t.f02 = new ArrayList<String>(){{add("c");add("d");}};
+                       t.f03 = new int[]{1,2};
+                       t.f04 = new ArrayList<Integer>(){{add(3);add(4);}};
+                       t.f05 = new String[][]{{"e","f"},{"g","h"}};
+                       t.f06 = new ArrayList<String[]>(){{add(new 
String[]{"i","j"});add(new String[]{"k","l"});}};
+                       t.f07 = new A[]{A.create(),A.create()};
+                       t.f08 = new 
ArrayList<A>(){{add(A.create());add(A.create());}};
+                       t.f09 = new A[][]{{A.create()},{A.create()}};
+                       t.f10 = new 
ArrayList<List<A>>(){{add(Arrays.asList(A.create()));add(Arrays.asList(A.create()));}};
+                       t.setF11(new String[]{"a","b"});
+                       t.setF12(new ArrayList<String>(){{add("c");add("d");}});
+                       t.setF13(new int[]{1,2});
+                       t.setF14(new ArrayList<Integer>(){{add(3);add(4);}});
+                       t.setF15(new String[][]{{"e","f"},{"g","h"}});
+                       t.setF16(new ArrayList<String[]>(){{add(new 
String[]{"i","j"});add(new String[]{"k","l"});}});
+                       t.setF17(new A[]{A.create(),A.create()});
+                       t.setF18(new 
ArrayList<A>(){{add(A.create());add(A.create());}});
+                       t.setF19(new A[][]{{A.create()},{A.create()}});
+                       t.setF20(new 
ArrayList<List<A>>(){{add(Arrays.asList(A.create()));add(Arrays.asList(A.create()));}});
+                       return t;
+               }
+       }
+
+       @UrlEncoding(expandedParams=true)
+       @Bean(sort=true)
+       public static class C extends B {
+               @SuppressWarnings("serial")
+               static C create() {
+                       C t = new C();
+                       t.f01 = new String[]{"a","b"};
+                       t.f02 = new ArrayList<String>(){{add("c");add("d");}};
+                       t.f03 = new int[]{1,2};
+                       t.f04 = new ArrayList<Integer>(){{add(3);add(4);}};
+                       t.f05 = new String[][]{{"e","f"},{"g","h"}};
+                       t.f06 = new ArrayList<String[]>(){{add(new 
String[]{"i","j"});add(new String[]{"k","l"});}};
+                       t.f07 = new A[]{A.create(),A.create()};
+                       t.f08 = new 
ArrayList<A>(){{add(A.create());add(A.create());}};
+                       t.f09 = new A[][]{{A.create()},{A.create()}};
+                       t.f10 = new 
ArrayList<List<A>>(){{add(Arrays.asList(A.create()));add(Arrays.asList(A.create()));}};
+                       t.setF11(new String[]{"a","b"});
+                       t.setF12(new ArrayList<String>(){{add("c");add("d");}});
+                       t.setF13(new int[]{1,2});
+                       t.setF14(new ArrayList<Integer>(){{add(3);add(4);}});
+                       t.setF15(new String[][]{{"e","f"},{"g","h"}});
+                       t.setF16(new ArrayList<String[]>(){{add(new 
String[]{"i","j"});add(new String[]{"k","l"});}});
+                       t.setF17(new A[]{A.create(),A.create()});
+                       t.setF18(new 
ArrayList<A>(){{add(A.create());add(A.create());}});
+                       t.setF19(new A[][]{{A.create()},{A.create()}});
+                       t.setF20(new 
ArrayList<List<A>>(){{add(Arrays.asList(A.create()));add(Arrays.asList(A.create()));}});
+                       return t;
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e4dfdf81/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UonParserReaderTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UonParserReaderTest.java
 
b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UonParserReaderTest.java
new file mode 100755
index 0000000..ee37cb0
--- /dev/null
+++ 
b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UonParserReaderTest.java
@@ -0,0 +1,218 @@
+// 
***************************************************************************************************************************
+// * 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.junit.Assert.*;
+
+import java.io.*;
+
+import org.apache.juneau.*;
+import org.junit.*;
+
+@SuppressWarnings({"javadoc","resource"})
+public class UonParserReaderTest {
+
+       
//====================================================================================================
+       // Basic tests
+       
//====================================================================================================
+       @Test
+       public void testBasic() throws Exception {
+
+               UonReader r;
+               String s, in;
+               r = r("f", true);
+               assertEquals('f', r.read());
+               assertEquals(-1, r.read());
+
+               r = r("%66", true);
+               assertEquals('f', r.read());
+               assertEquals(-1, r.read());
+
+               r = r("%7D", true);
+               assertEquals('}', r.read());
+               assertEquals(-1, r.read());
+
+               r = r("%7D%7D", true);
+               assertEquals('}', r.read());
+               assertEquals('}', r.read());
+               assertEquals(-1, r.read());
+
+               r = r("%00%00", true);
+               r.mark();
+               assertEquals(0, r.read());
+               assertEquals(0, r.read());
+               assertEquals("\u0000\u0000", r.getMarked());
+               assertEquals(-1, r.read());
+
+               in = escape("\u0080");
+               r = r(in, true);
+               assertEquals('\u0080', r.read());
+               assertEquals(-1, r.read());
+
+               in = escape("\u0800");
+               r = r(in, true);
+               assertEquals('\u0800', r.read());
+               assertEquals(-1, r.read());
+
+               in = escape("\uffff");
+               r = r(in, true);
+               assertEquals('\uffff', r.read());
+               assertEquals(-1, r.read());
+
+               // 2-byte codepoint
+               s = "¢";
+               r = r(escape(s), true);
+               assertEquals(s.codePointAt(0), r.read());
+               assertEquals(-1, r.read());
+
+               // 3-byte codepoint
+               s = "€";
+               r = r(escape(s), true);
+               assertEquals(s.codePointAt(0), r.readCodePoint());
+               assertEquals(-1, r.read());
+
+               // 4-byte codepoint
+               s = "𤭢";
+               r = r(escape(s), true);
+               assertEquals(s.codePointAt(0), r.readCodePoint());
+               assertEquals(-1, r.read());
+
+               s = "𤭢𤭢";
+               r = r(escape(s), true);
+               assertEquals(s.codePointAt(0), r.readCodePoint());
+               assertEquals(s.codePointAt(2), r.readCodePoint());
+               assertEquals(-1, r.read());
+
+               // Multiple codepoints
+               s = "¢€𤭢¢€𤭢";
+               in = escape(s);
+               r = r(in, true);
+               assertEquals(s.codePointAt(0), r.readCodePoint());
+               assertEquals(s.codePointAt(1), r.readCodePoint());
+               assertEquals(s.codePointAt(2), r.readCodePoint());
+               assertEquals(s.codePointAt(4), r.readCodePoint());
+               assertEquals(s.codePointAt(5), r.readCodePoint());
+               assertEquals(s.codePointAt(6), r.readCodePoint());
+               assertEquals(-1, r.read());
+
+               // Multiple codepoints read in small chunks.
+               s = "¢€𤭢¢€𤭢";
+               String s2;
+               int i;
+               in = escape(s);
+               r = r(in, true);
+               char[] buff = new char[2];
+               i = r.read(buff, 0, buff.length);
+               s2 = new String(buff, 0, i);
+               assertEquals("¢", s2);
+               i = r.read(buff, 0, buff.length);
+               s2 = new String(buff, 0, i);
+               assertEquals("€", s2);
+               i = r.read(buff, 0, buff.length);
+               s2 = new String(buff, 0, i);
+               assertEquals("𤭢", s2);
+               i = r.read(buff, 0, buff.length);
+               s2 = new String(buff, 0, i);
+               assertEquals("¢", s2);
+               i = r.read(buff, 0, buff.length);
+               s2 = new String(buff, 0, i);
+               assertEquals("€", s2);
+               i = r.read(buff, 0, buff.length);
+               s2 = new String(buff, 0, i);
+               assertEquals("𤭢", s2);
+               i = r.read(buff, 0, buff.length);
+               assertEquals(-1, i);
+
+               // Multiple codepoints read in slightly larger chunks.
+               s = "¢€𤭢¢€𤭢";
+               in = escape(s);
+               r = r(in, true);
+               buff = new char[3];
+               i = r.read(buff, 0, buff.length);
+               s2 = new String(buff, 0, i);
+               assertEquals("¢€", s2);
+               i = r.read(buff, 0, buff.length);
+               s2 = new String(buff, 0, i);
+               assertEquals("𤭢", s2);
+               i = r.read(buff, 0, buff.length);
+               s2 = new String(buff, 0, i);
+               assertEquals("¢€", s2);
+               i = r.read(buff, 0, buff.length);
+               s2 = new String(buff, 0, i);
+               assertEquals("𤭢", s2);
+               i = r.read(buff, 0, buff.length);
+               assertEquals(-1, i);
+
+               s = "¢€𤭢¢€𤭢";
+               in = escape(s);
+               r = r(in, true);
+               buff = new char[4];
+               i = r.read(buff, 0, buff.length);
+               s2 = new String(buff, 0, i);
+               assertEquals("¢€𤭢", s2);
+               i = r.read(buff, 0, buff.length);
+               s2 = new String(buff, 0, i);
+               assertEquals("¢€𤭢", s2);
+               i = r.read(buff, 0, buff.length);
+               assertEquals(-1, i);
+
+               // Reader that only returns 1 character at a time;
+               s = "x¢€𤭢x¢€𤭢";
+               in = "x" + escape("¢€𤭢") + "x" + escape("¢€𤭢");
+               r = new UonReader(new SlowStringReader(in), true);
+               assertEquals(s.codePointAt(0), r.readCodePoint());
+               assertEquals(s.codePointAt(1), r.readCodePoint());
+               assertEquals(s.codePointAt(2), r.readCodePoint());
+               assertEquals(s.codePointAt(3), r.readCodePoint());
+               assertEquals(s.codePointAt(5), r.readCodePoint());
+               assertEquals(s.codePointAt(6), r.readCodePoint());
+               assertEquals(s.codePointAt(7), r.readCodePoint());
+               assertEquals(s.codePointAt(8), r.readCodePoint());
+               assertEquals(-1, r.readCodePoint());
+       }
+
+       private String escape(String s) throws UnsupportedEncodingException {
+               StringBuilder sb = new StringBuilder();
+               byte[] b = s.getBytes("UTF-8");
+               for (int i = 0; i < b.length; i++)
+                       sb.append('%').append(TestUtils.toHex(b[i]));
+               return sb.toString();
+       }
+
+       private UonReader r(String in, boolean decodeChars) {
+               return new UonReader(in, decodeChars);
+       }
+
+       private static class SlowStringReader extends Reader {
+
+               String s;
+               int i = 0;
+
+               SlowStringReader(String s) {
+                       this.s = s;
+               }
+
+               @Override /* Reader */
+               public int read(char[] cbuf, int off, int len) throws 
IOException {
+                       if (i >= s.length())
+                               return -1;
+                       cbuf[off] = s.charAt(i++);
+                       return 1;
+               }
+
+               @Override /* Reader */
+               public void close() throws IOException {
+               }
+
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e4dfdf81/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UonParserTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UonParserTest.java
 
b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UonParserTest.java
new file mode 100755
index 0000000..b18f791
--- /dev/null
+++ 
b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UonParserTest.java
@@ -0,0 +1,542 @@
+// 
***************************************************************************************************************************
+// * 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.junit.Assert.*;
+
+import java.util.*;
+
+import org.apache.juneau.parser.*;
+import org.junit.*;
+
+@SuppressWarnings({"rawtypes","unchecked","javadoc"})
+public class UonParserTest {
+
+       static UonParser p = UonParser.DEFAULT;
+       static UonParser pe = UonParser.DEFAULT_DECODING;
+
+       
//====================================================================================================
+       // Basic test
+       
//====================================================================================================
+       @Test
+       public void testBasic() throws Exception {
+
+               String t;
+               Map m;
+
+               // Simple string
+               // Top level
+               t = "a";
+               assertEquals("a", p.parse(t, String.class));
+               assertEquals("a", p.parse(t, Object.class));
+               assertEquals("a", pe.parse(t, String.class));
+               t = "(a)";
+               assertEquals("a", p.parse(t, String.class));
+               assertEquals("a", p.parse(t, Object.class));
+               t = "$s(a)";
+               assertEquals("a", p.parse(t, String.class));
+
+               // 2nd level
+               t = "$o(a=a)";
+               assertEquals("a", p.parse(t, Map.class).get("a"));
+               assertEquals("a", pe.parse(t, Map.class).get("a"));
+
+               t = "(a=a)";
+               assertEquals("a", p.parse(t, Map.class).get("a"));
+               assertEquals("a", pe.parse(t, Map.class).get("a"));
+
+               // Simple map
+               // Top level
+               t = "$o(a=b,c=$n(123),d=$b(false),e=$b(true),f=%00)";
+               m = p.parse(t, Map.class);
+               assertEquals("b", m.get("a"));
+               assertTrue(m.get("c") instanceof Number);
+               assertEquals(123, m.get("c"));
+               assertTrue(m.get("d") instanceof Boolean);
+               assertEquals(Boolean.FALSE, m.get("d"));
+               assertTrue(m.get("e") instanceof Boolean);
+               assertEquals(Boolean.TRUE, m.get("e"));
+               m = pe.parse(t, Map.class);
+               assertNull(m.get("f"));
+
+               t = "(a=true)";
+               m = p.parseMap(t, HashMap.class, String.class, Boolean.class);
+               assertTrue(m.get("a") instanceof Boolean);
+               assertEquals("true", m.get("a").toString());
+
+               // null
+               // Top level
+               t = "%00";
+               assertEquals("%00", p.parse(t, Object.class));
+               assertNull(pe.parse(t, Object.class));
+
+               // 2nd level
+               t = "$o(%00=%00)";
+               m = p.parse(t, Map.class);
+               assertEquals("%00", m.get("%00"));
+               m = pe.parse(t, Map.class);
+               assertTrue(m.containsKey(null));
+               assertNull(m.get(null));
+
+               t = "(%00=%00)";
+               m = p.parse(t, Map.class);
+               assertEquals("%00", m.get("%00"));
+               m = pe.parse(t, Map.class);
+               assertTrue(m.containsKey(null));
+               assertNull(m.get(null));
+
+               t = "(\u0000=\u0000)";
+               m = p.parse(t, Map.class);
+               assertTrue(m.containsKey(null));
+               assertNull(m.get(null));
+               m = pe.parse(t, Map.class);
+               assertTrue(m.containsKey(null));
+               assertNull(m.get(null));
+
+               // 3rd level
+               t = "$o(%00=$o(%00=%00))";
+               m = p.parse(t, Map.class);
+               assertEquals("%00", ((Map)m.get("%00")).get("%00"));
+               m = pe.parse(t, Map.class);
+               assertTrue(((Map)m.get(null)).containsKey(null));
+               assertNull(((Map)m.get(null)).get(null));
+
+               // Empty array
+               // Top level
+               t = "$a()";
+               List l = (List)p.parse(t, Object.class);
+               assertTrue(l.isEmpty());
+               t = "()";
+               l = p.parse(t, List.class);
+               assertTrue(l.isEmpty());
+
+               // 2nd level in map
+               t = "$o(x=$a())";
+               m = p.parseMap(t, HashMap.class, String.class, List.class);
+               assertTrue(m.containsKey("x"));
+               assertTrue(((List)m.get("x")).isEmpty());
+               m = (Map)p.parse(t, Object.class);
+               assertTrue(m.containsKey("x"));
+               assertTrue(((List)m.get("x")).isEmpty());
+               t = "(x=())";
+               m = p.parseMap(t, HashMap.class, String.class, List.class);
+               assertTrue(m.containsKey("x"));
+               assertTrue(((List)m.get("x")).isEmpty());
+
+               // Empty 2 dimensional array
+               t = "$a($a())";
+               l = (List)p.parse(t, Object.class);
+               assertTrue(l.size() == 1);
+               l = (List)l.get(0);
+               assertTrue(l.isEmpty());
+               t = "(())";
+               l = p.parseCollection(t, LinkedList.class, List.class);
+               assertTrue(l.size() == 1);
+               l = (List)l.get(0);
+               assertTrue(l.isEmpty());
+
+               // Array containing empty string
+               // Top level
+               t = "$a(())";
+               l = (List)p.parse(t, Object.class);
+               assertTrue(l.size() == 1);
+               assertEquals("", l.get(0));
+               t = "(())";
+               l = p.parseCollection(t, List.class, String.class);
+               assertTrue(l.size() == 1);
+               assertEquals("", l.get(0));
+
+               // 2nd level
+               t = "$o(()=$a(()))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("", ((List)m.get("")).get(0));
+               t = "(=(()))";
+               m = p.parseMap(t, HashMap.class, String.class, List.class);
+               assertEquals("", ((List)m.get("")).get(0));
+
+               // Array containing 3 empty strings
+               t = "$a(,,)";
+               l = (List)p.parse(t, Object.class);
+               assertTrue(l.size() == 3);
+               assertEquals("", l.get(0));
+               assertEquals("", l.get(1));
+               assertEquals("", l.get(2));
+               t = "(,,)";
+               l = p.parseCollection(t, List.class, Object.class);
+               assertTrue(l.size() == 3);
+               assertEquals("", l.get(0));
+               assertEquals("", l.get(1));
+               assertEquals("", l.get(2));
+
+               // String containing \u0000
+               // Top level
+               t = "$s(\u0000)";
+               assertEquals("\u0000", p.parse(t, Object.class));
+               t = "(\u0000)";
+               assertEquals("\u0000", p.parse(t, String.class));
+               assertEquals("\u0000", p.parse(t, Object.class));
+
+               // 2nd level
+               t = "$o((\u0000)=(\u0000))";
+               m = (Map)p.parse(t, Object.class);
+               assertTrue(m.size() == 1);
+               assertEquals("\u0000", m.get("\u0000"));
+               t = "((\u0000)=(\u0000))";
+               m = p.parseMap(t, HashMap.class, String.class, String.class);
+               assertTrue(m.size() == 1);
+               assertEquals("\u0000", m.get("\u0000"));
+               m = p.parseMap(t, HashMap.class, String.class, Object.class);
+               assertTrue(m.size() == 1);
+               assertEquals("\u0000", m.get("\u0000"));
+
+               // Boolean
+               // Top level
+               t = "$b(false)";
+               Boolean b = (Boolean)p.parse(t, Object.class);
+               assertEquals(Boolean.FALSE, b);
+               b = p.parse(t, Boolean.class);
+               assertEquals(Boolean.FALSE, b);
+               t = "false";
+               b = p.parse(t, Boolean.class);
+               assertEquals(Boolean.FALSE, b);
+
+               // 2nd level
+               t = "$o(x=$b(false))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals(Boolean.FALSE, m.get("x"));
+               t = "(x=$b(false))";
+               m = p.parseMap(t, HashMap.class, String.class, Object.class);
+               assertEquals(Boolean.FALSE, m.get("x"));
+               t = "(x=false)";
+               m = p.parseMap(t, HashMap.class, String.class, Boolean.class);
+               assertEquals(Boolean.FALSE, m.get("x"));
+
+               // Number
+               // Top level
+               t = "$n(123)";
+               Integer i = (Integer)p.parse(t, Object.class);
+               assertEquals(123, i.intValue());
+               i = p.parse(t, Integer.class);
+               assertEquals(123, i.intValue());
+               Double d = p.parse(t, Double.class);
+               assertEquals(123, d.intValue());
+               Float f = p.parse(t, Float.class);
+               assertEquals(123, f.intValue());
+               t = "123";
+               i = p.parse(t, Integer.class);
+               assertEquals(123, i.intValue());
+
+               // 2nd level
+               t = "$o(x=$n(123))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals(123, ((Integer)m.get("x")).intValue());
+               t = "(x=123)";
+               m = p.parseMap(t, HashMap.class, String.class, Number.class);
+               assertEquals(123, ((Integer)m.get("x")).intValue());
+               m = p.parseMap(t, HashMap.class, String.class, Double.class);
+               assertEquals(123, ((Double)m.get("x")).intValue());
+
+               // Unencoded chars
+               // Top level
+               t = "x;/?:@-_.!*'";
+               assertEquals("x;/?:@-_.!*'", p.parse(t, Object.class));
+               assertEquals("x;/?:@-_.!*'", pe.parse(t, Object.class));
+
+               // 2nd level
+               t = "$o(x;/?:@-_.!*'=x;/?:@-_.!*')";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("x;/?:@-_.!*'", m.get("x;/?:@-_.!*'"));
+               m = p.parseMap(t, HashMap.class, String.class, Object.class);
+               assertEquals("x;/?:@-_.!*'", m.get("x;/?:@-_.!*'"));
+               m = p.parseMap(t, HashMap.class, String.class, String.class);
+               assertEquals("x;/?:@-_.!*'", m.get("x;/?:@-_.!*'"));
+
+               // Encoded chars
+               // Top level
+               t = "x{}|\\^[]`<>#%\"&+";
+               assertEquals("x{}|\\^[]`<>#%\"&+", p.parse(t, Object.class));
+               assertEquals("x{}|\\^[]`<>#%\"&+", p.parse(t, String.class));
+               try {
+                       assertEquals("x{}|\\^[]`<>#%\"&+", pe.parse(t, 
Object.class));
+                       fail("Expected parse exception from invalid hex 
sequence.");
+               } catch (ParseException e) {
+                       // Good.
+               }
+               t = "x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B";
+               assertEquals("x{}|\\^[]`<>#%\"&+", pe.parse(t, Object.class));
+               assertEquals("x{}|\\^[]`<>#%\"&+", pe.parse(t, String.class));
+
+               // 2nd level
+               t = "$o(x{}|\\^[]`<>#%\"&+=x{}|\\^[]`<>#%\"&+)";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("x{}|\\^[]`<>#%\"&+", m.get("x{}|\\^[]`<>#%\"&+"));
+               try {
+                       m = (Map)pe.parse(t, Object.class);
+                       fail("Expected parse exception from invalid hex 
sequence.");
+               } catch (ParseException e) {
+                       // Good.
+               }
+               t = 
"$o(x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B=x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B)";
+               m = (Map)pe.parse(t, Object.class);
+               assertEquals("x{}|\\^[]`<>#%\"&+", m.get("x{}|\\^[]`<>#%\"&+"));
+
+               // Special chars
+               // Top level
+               t = "x~$~,~(~)";
+               assertEquals("x$,()", p.parse(t, Object.class));
+               t = "(x~$~,~(~))";
+               assertEquals("x$,()", p.parse(t, Object.class));
+               t = "$s(x~$~,~(~))";
+               assertEquals("x$,()", p.parse(t, Object.class));
+
+               // 2nd level
+               // Note behavior on serializeParams() is different since 
2nd-level is top level.
+               t = "$o(x~$~,~(~)=x~$~,~(~))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("x$,()", m.get("x$,()"));
+               t = "$o((x~$~,~(~))=(x~$~,~(~)))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("x$,()", m.get("x$,()"));
+               t = "$o($s(x~$~,~(~))=$s(x~$~,~(~)))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("x$,()", m.get("x$,()"));
+
+               // Equals sign
+               // Gets encoded at top level, and encoded+escaped at 2nd level.
+               // Top level
+               t = "x=";
+               assertEquals("x=", p.parse(t, Object.class));
+               t = "x%3D";
+               assertEquals("x=", pe.parse(t, Object.class));
+
+               // 2nd level
+               t = "$o(x~==x~=)";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("x=", m.get("x="));
+               t = "$o((x~=)=(x~=))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("x=", m.get("x="));
+               t = "$o($s(x~=)=$s(x~=))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("x=", m.get("x="));
+               t = "(x~==x~=)";
+               m = p.parseMap(t, HashMap.class, String.class, Object.class);
+               assertEquals("x=", m.get("x="));
+               t = "((x~=)=(x~=))";
+               m = p.parseMap(t, HashMap.class, String.class, Object.class);
+               assertEquals("x=", m.get("x="));
+               t = "($s(x~=)=$s(x~=))";
+               m = p.parseMap(t, HashMap.class, String.class, Object.class);
+               assertEquals("x=", m.get("x="));
+               t = "$o(x~%3D=x~%3D)";
+               m = (Map)pe.parse(t, Object.class);
+               assertEquals("x=", m.get("x="));
+               t = "$o((x~%3D)=(x~%3D))";
+               m = (Map)pe.parse(t, Object.class);
+               assertEquals("x=", m.get("x="));
+               t = "$o($s(x~%3D)=$s(x~%3D))";
+               m = (Map)pe.parse(t, Object.class);
+               assertEquals("x=", m.get("x="));
+               t = "(x~%3D=x~%3D)";
+               m = pe.parseMap(t, HashMap.class, String.class, Object.class);
+               assertEquals("x=", m.get("x="));
+               t = "((x~%3D)=(x~%3D))";
+               m = pe.parseMap(t, HashMap.class, String.class, Object.class);
+               assertEquals("x=", m.get("x="));
+               t = "($s(x~%3D)=$s(x~%3D))";
+               m = pe.parseMap(t, HashMap.class, String.class, Object.class);
+               assertEquals("x=", m.get("x="));
+
+               // String starting with parenthesis
+               // Top level
+               t = "~(~)";
+               assertEquals("()", p.parse(t, Object.class));
+               assertEquals("()", p.parse(t, String.class));
+
+               t = "(~(~))";
+               assertEquals("()", p.parse(t, Object.class));
+               assertEquals("()", p.parse(t, String.class));
+               t = "$s(~(~))";
+               assertEquals("()", p.parse(t, Object.class));
+               assertEquals("()", p.parse(t, String.class));
+
+               // 2nd level
+               t = "$o((~(~))=(~(~)))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("()", m.get("()"));
+               t = "((~(~))=(~(~)))";
+               m = p.parseMap(t, HashMap.class, String.class, Object.class);
+               assertEquals("()", m.get("()"));
+               t = "($s(~(~))=$s(~(~)))";
+               m = p.parseMap(t, HashMap.class, String.class, Object.class);
+               assertEquals("()", m.get("()"));
+
+               // String starting with $
+               // Top level
+               t = "($a)";
+               assertEquals("$a", p.parse(t, Object.class));
+               t = "($a)";
+               assertEquals("$a", p.parse(t, Object.class));
+
+               // 2nd level
+               t = "$o(($a)=($a))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("$a", m.get("$a"));
+               t = "(($a)=($a))";
+               m = p.parseMap(t, HashMap.class, String.class, Object.class);
+               assertEquals("$a", m.get("$a"));
+
+               // Blank string
+               // Top level
+               t = "";
+               assertEquals("", p.parse(t, Object.class));
+               assertEquals("", pe.parse(t, Object.class));
+
+               // 2nd level
+               t = "$o(=)";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("", m.get(""));
+               t = "(=)";
+               m = p.parseMap(t, HashMap.class, String.class, Object.class);
+               assertEquals("", m.get(""));
+
+               // 3rd level
+               t = "$o(=$o(=))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("", ((Map)m.get("")).get(""));
+               t = "(=(=))";
+               m = p.parseMap(t, HashMap.class, String.class, HashMap.class);
+               assertEquals("", ((Map)m.get("")).get(""));
+
+               // Newline character
+               // Top level
+               t = "(%0A)";
+               assertEquals("\n", pe.parse(t, Object.class));
+               assertEquals("%0A", p.parse(t, Object.class));
+
+               // 2nd level
+               t = "$o((%0A)=(%0A))";
+               m = (Map)pe.parse(t, Object.class);
+               assertEquals("\n", m.get("\n"));
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("%0A", m.get("%0A"));
+
+               // 3rd level
+               t = "$o((%0A)=$o((%0A)=(%0A)))";
+               m = (Map)pe.parse(t, Object.class);
+               assertEquals("\n", ((Map)m.get("\n")).get("\n"));
+       }
+
+       
//====================================================================================================
+       // Unicode character test
+       
//====================================================================================================
+       @Test
+       public void testUnicodeChars() throws Exception {
+               String t;
+               Map m;
+
+               // 2-byte UTF-8 character
+               // Top level
+               t = "¢";
+               assertEquals("¢", p.parse(t, Object.class));
+               assertEquals("¢", p.parse(t, String.class));
+               t = "%C2%A2";
+               assertEquals("¢", pe.parse(t, Object.class));
+               assertEquals("¢", pe.parse(t, String.class));
+
+               // 2nd level
+               t = "$o(¢=¢)";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("¢", m.get("¢"));
+               t = "$o(%C2%A2=%C2%A2)";
+               m = (Map)pe.parse(t, Object.class);
+               assertEquals("¢", m.get("¢"));
+
+               // 3rd level
+               t = "$o(¢=$o(¢=¢))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("¢", ((Map)m.get("¢")).get("¢"));
+               t = "$o(%C2%A2=$o(%C2%A2=%C2%A2))";
+               m = (Map)pe.parse(t, Object.class);
+               assertEquals("¢", ((Map)m.get("¢")).get("¢"));
+
+               // 3-byte UTF-8 character
+               // Top level
+               t = "€";
+               assertEquals("€", p.parse(t, Object.class));
+               assertEquals("€", p.parse(t, String.class));
+               t = "%E2%82%AC";
+               assertEquals("€", pe.parse(t, Object.class));
+               assertEquals("€", pe.parse(t, String.class));
+
+               // 2nd level
+               t = "$o(€=€)";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("€", m.get("€"));
+               t = "$o(%E2%82%AC=%E2%82%AC)";
+               m = (Map)pe.parse(t, Object.class);
+               assertEquals("€", m.get("€"));
+
+               // 3rd level
+               t = "$o(€=$o(€=€))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("€", ((Map)m.get("€")).get("€"));
+               t = "$o(%E2%82%AC=$o(%E2%82%AC=%E2%82%AC))";
+               m = (Map)pe.parse(t, Object.class);
+               assertEquals("€", ((Map)m.get("€")).get("€"));
+
+               // 4-byte UTF-8 character
+               // Top level
+               t = "𤭢";
+               assertEquals("𤭢", p.parse(t, Object.class));
+               assertEquals("𤭢", p.parse(t, String.class));
+               t = "%F0%A4%AD%A2";
+               assertEquals("𤭢", pe.parse(t, Object.class));
+               assertEquals("𤭢", pe.parse(t, String.class));
+
+               // 2nd level
+               t = "$o(𤭢=𤭢)";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("𤭢", m.get("𤭢"));
+               t = "$o(%F0%A4%AD%A2=%F0%A4%AD%A2)";
+               m = (Map)pe.parse(t, Object.class);
+               assertEquals("𤭢", m.get("𤭢"));
+
+               // 3rd level
+               t = "$o(𤭢=$o(𤭢=𤭢))";
+               m = (Map)p.parse(t, Object.class);
+               assertEquals("𤭢", ((Map)m.get("𤭢")).get("𤭢"));
+               t = "$o(%F0%A4%AD%A2=$o(%F0%A4%AD%A2=%F0%A4%AD%A2))";
+               m = (Map)pe.parse(t, Object.class);
+               assertEquals("𤭢", ((Map)m.get("𤭢")).get("𤭢"));
+       }
+
+       
//====================================================================================================
+       // Test simple bean
+       
//====================================================================================================
+       @Test
+       public void testSimpleBean() throws Exception {
+               UonParser p = UonParser.DEFAULT;
+               A t;
+
+               String s = "(f1=foo,f2=123)";
+               t = p.parse(s, A.class);
+               assertEquals("foo", t.f1);
+               assertEquals(123, t.f2);
+       }
+
+       public static class A {
+               public String f1;
+               public int f2;
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e4dfdf81/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UonSerializerTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UonSerializerTest.java
 
b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UonSerializerTest.java
new file mode 100755
index 0000000..3787f90
--- /dev/null
+++ 
b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UonSerializerTest.java
@@ -0,0 +1,461 @@
+// 
***************************************************************************************************************************
+// * 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.junit.Assert.*;
+
+import org.apache.juneau.*;
+import org.junit.*;
+
+@SuppressWarnings("javadoc")
+public class UonSerializerTest {
+
+       static UonSerializer s = UonSerializer.DEFAULT_ENCODING;
+       static UonSerializer ss = UonSerializer.DEFAULT_SIMPLE_ENCODING;
+       static UonSerializer su = UonSerializer.DEFAULT;
+       static UonSerializer ssu = UonSerializer.DEFAULT_SIMPLE;
+       static UonSerializer sr = UonSerializer.DEFAULT_READABLE;
+
+
+       
//====================================================================================================
+       // Basic test
+       
//====================================================================================================
+       @Test
+       public void testBasic() throws Exception {
+
+               Object t;
+
+               // Simple string
+               // Top level
+               t = "a";
+               assertEquals("a", s.serialize(t));
+               assertEquals("a", ss.serialize(t));
+               assertEquals("a", su.serialize(t));
+               assertEquals("a", ssu.serialize(t));
+               assertEquals("a", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{a:'a'}");
+               assertEquals("$o(a=a)", s.serialize(t));
+               assertEquals("(a=a)", ss.serialize(t));
+               assertEquals("$o(a=a)", su.serialize(t));
+               assertEquals("(a=a)", ssu.serialize(t));
+               assertEquals("$o(\n\ta=a\n)", sr.serialize(t));
+
+               // Simple map
+               // Top level
+               t = new ObjectMap("{a:'b',c:123,d:false,e:true,f:null}");
+               assertEquals("$o(a=b,c=$n(123),d=$b(false),e=$b(true),f=%00)", 
s.serialize(t));
+               assertEquals("(a=b,c=123,d=false,e=true,f=%00)", 
ss.serialize(t));
+               
assertEquals("$o(a=b,c=$n(123),d=$b(false),e=$b(true),f=\u0000)", 
su.serialize(t));
+               assertEquals("(a=b,c=123,d=false,e=true,f=\u0000)", 
ssu.serialize(t));
+               
assertEquals("$o(\n\ta=b,\n\tc=$n(123),\n\td=$b(false),\n\te=$b(true),\n\tf=\u0000\n)",
 sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{a:{a:'b',c:123,d:false,e:true,f:null}}");
+               
assertEquals("$o(a=$o(a=b,c=$n(123),d=$b(false),e=$b(true),f=%00))", 
s.serialize(t));
+               assertEquals("(a=(a=b,c=123,d=false,e=true,f=%00))", 
ss.serialize(t));
+               
assertEquals("$o(a=$o(a=b,c=$n(123),d=$b(false),e=$b(true),f=\u0000))", 
su.serialize(t));
+               assertEquals("(a=(a=b,c=123,d=false,e=true,f=\u0000))", 
ssu.serialize(t));
+               
assertEquals("$o(\n\ta=$o(\n\t\ta=b,\n\t\tc=$n(123),\n\t\td=$b(false),\n\t\te=$b(true),\n\t\tf=\u0000\n\t)\n)",
 sr.serialize(t));
+
+               // Simple map with primitives as literals
+               t = new 
ObjectMap("{a:'b',c:'123',d:'false',e:'true',f:'null'}");
+               assertEquals("$o(a=b,c=123,d=false,e=true,f=null)", 
s.serialize(t));
+               assertEquals("(a=b,c=123,d=false,e=true,f=null)", 
ss.serialize(t));
+               assertEquals("$o(a=b,c=123,d=false,e=true,f=null)", 
su.serialize(t));
+               assertEquals("(a=b,c=123,d=false,e=true,f=null)", 
ssu.serialize(t));
+               
assertEquals("$o(\n\ta=b,\n\tc=123,\n\td=false,\n\te=true,\n\tf=null\n)", 
sr.serialize(t));
+
+               // null
+               // Note that serializeParams is always encoded.
+               // Top level
+               t = null;
+               assertEquals("%00", s.serialize(t));
+               assertEquals("%00", ss.serialize(t));
+               assertEquals("\u0000", su.serialize(t));
+               assertEquals("\u0000", ssu.serialize(t));
+               assertEquals("\u0000", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{null:null}");
+               assertEquals("$o(%00=%00)", s.serialize(t));
+               assertEquals("(%00=%00)", ss.serialize(t));
+               assertEquals("$o(\u0000=\u0000)", su.serialize(t));
+               assertEquals("(\u0000=\u0000)", ssu.serialize(t));
+               assertEquals("$o(\n\t\u0000=\u0000\n)", sr.serialize(t));
+
+               // 3rd level
+               t = new ObjectMap("{null:{null:null}}");
+               assertEquals("$o(%00=$o(%00=%00))", s.serialize(t));
+               assertEquals("(%00=(%00=%00))", ss.serialize(t));
+               assertEquals("$o(\u0000=$o(\u0000=\u0000))", su.serialize(t));
+               assertEquals("(\u0000=(\u0000=\u0000))", ssu.serialize(t));
+               assertEquals("$o(\n\t\u0000=$o(\n\t\t\u0000=\u0000\n\t)\n)", 
sr.serialize(t));
+
+               // Empty array
+               // Top level
+               t = new String[0];
+               assertEquals("$a()", s.serialize(t));
+               assertEquals("()", ss.serialize(t));
+               assertEquals("$a()", su.serialize(t));
+               assertEquals("()", ssu.serialize(t));
+               assertEquals("$a()", sr.serialize(t));
+
+               // 2nd level in map
+               t = new ObjectMap("{x:[]}");
+               assertEquals("$o(x=$a())", s.serialize(t));
+               assertEquals("(x=())", ss.serialize(t));
+               assertEquals("$o(x=$a())", su.serialize(t));
+               assertEquals("(x=())", ssu.serialize(t));
+               assertEquals("$o(\n\tx=$a()\n)", sr.serialize(t));
+
+               // Empty 2 dimensional array
+               t = new String[1][0];
+               assertEquals("$a($a())", s.serialize(t));
+               assertEquals("(())", ss.serialize(t));
+               assertEquals("$a($a())", su.serialize(t));
+               assertEquals("(())", ssu.serialize(t));
+               assertEquals("$a(\n\t$a()\n)", sr.serialize(t));
+
+               // Array containing empty string
+               // Top level
+               t = new String[]{""};
+               assertEquals("$a(())", s.serialize(t));
+               assertEquals("(())", ss.serialize(t));
+               assertEquals("$a(())", su.serialize(t));
+               assertEquals("(())", ssu.serialize(t));
+               assertEquals("$a(\n\t()\n)", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{x:['']}");
+               assertEquals("$o(x=$a(()))", s.serialize(t));
+               assertEquals("(x=(()))", ss.serialize(t));
+               assertEquals("$o(x=$a(()))", su.serialize(t));
+               assertEquals("(x=(()))", ssu.serialize(t));
+               assertEquals("$o(\n\tx=$a(\n\t\t()\n\t)\n)", sr.serialize(t));
+
+               // Array containing 3 empty strings
+               t = new String[]{"","",""};
+               assertEquals("$a(,,)", s.serialize(t));
+               assertEquals("(,,)", ss.serialize(t));
+               assertEquals("$a(,,)", su.serialize(t));
+               assertEquals("(,,)", ssu.serialize(t));
+               assertEquals("$a(\n\t(),\n\t(),\n\t()\n)", sr.serialize(t));
+
+               // String containing \u0000
+               // Top level
+               t = "\u0000";
+               assertEquals("(%00)", s.serialize(t));
+               assertEquals("(%00)", ss.serialize(t));
+               assertEquals("(\u0000)", su.serialize(t));
+               assertEquals("(\u0000)", ssu.serialize(t));
+               assertEquals("(\u0000)", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{'\u0000':'\u0000'}");
+               assertEquals("$o((%00)=(%00))", s.serialize(t));
+               assertEquals("((%00)=(%00))", ss.serialize(t));
+               assertEquals("$o((\u0000)=(\u0000))", su.serialize(t));
+               assertEquals("((\u0000)=(\u0000))", ssu.serialize(t));
+               assertEquals("$o(\n\t(\u0000)=(\u0000)\n)", sr.serialize(t));
+
+               // Boolean
+               // Top level
+               t = false;
+               assertEquals("$b(false)", s.serialize(t));
+               assertEquals("false", ss.serialize(t));
+               assertEquals("$b(false)", su.serialize(t));
+               assertEquals("false", ssu.serialize(t));
+               assertEquals("$b(false)", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{x:false}");
+               assertEquals("$o(x=$b(false))", s.serialize(t));
+               assertEquals("(x=false)", ss.serialize(t));
+               assertEquals("$o(x=$b(false))", su.serialize(t));
+               assertEquals("(x=false)", ssu.serialize(t));
+               assertEquals("$o(\n\tx=$b(false)\n)", sr.serialize(t));
+
+               // Number
+               // Top level
+               t = 123;
+               assertEquals("$n(123)", s.serialize(t));
+               assertEquals("123", ss.serialize(t));
+               assertEquals("$n(123)", su.serialize(t));
+               assertEquals("123", ssu.serialize(t));
+               assertEquals("$n(123)", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{x:123}");
+               assertEquals("$o(x=$n(123))", s.serialize(t));
+               assertEquals("(x=123)", ss.serialize(t));
+               assertEquals("$o(x=$n(123))", su.serialize(t));
+               assertEquals("(x=123)", ssu.serialize(t));
+               assertEquals("$o(\n\tx=$n(123)\n)", sr.serialize(t));
+
+               // Unencoded chars
+               // Top level
+               t = "x;/?:@-_.!*'";
+               assertEquals("x;/?:@-_.!*'", s.serialize(t));
+               assertEquals("x;/?:@-_.!*'", ss.serialize(t));
+               assertEquals("x;/?:@-_.!*'", su.serialize(t));
+               assertEquals("x;/?:@-_.!*'", ssu.serialize(t));
+               assertEquals("x;/?:@-_.!*'", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{x:'x;/?:@-_.!*\\''}");
+               assertEquals("$o(x=x;/?:@-_.!*')", s.serialize(t));
+               assertEquals("(x=x;/?:@-_.!*')", ss.serialize(t));
+               assertEquals("$o(x=x;/?:@-_.!*')", su.serialize(t));
+               assertEquals("(x=x;/?:@-_.!*')", ssu.serialize(t));
+               assertEquals("$o(\n\tx=x;/?:@-_.!*'\n)", sr.serialize(t));
+
+               // Encoded chars
+               // Top level
+               t = "x{}|\\^[]`<>#%\"&+";
+               assertEquals("x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B", 
s.serialize(t));
+               assertEquals("x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B", 
ss.serialize(t));
+               assertEquals("x{}|\\^[]`<>#%\"&+", su.serialize(t));
+               assertEquals("x{}|\\^[]`<>#%\"&+", ssu.serialize(t));
+               assertEquals("x{}|\\^[]`<>#%\"&+", sr.serialize(t));
+
+               // 2nd level
+               t = new 
ObjectMap("{'x{}|\\\\^[]`<>#%\"&+':'x{}|\\\\^[]`<>#%\"&+'}");
+               
assertEquals("$o(x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B=x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B)",
 s.serialize(t));
+               
assertEquals("(x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B=x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B)",
 ss.serialize(t));
+               assertEquals("$o(x{}|\\^[]`<>#%\"&+=x{}|\\^[]`<>#%\"&+)", 
su.serialize(t));
+               assertEquals("(x{}|\\^[]`<>#%\"&+=x{}|\\^[]`<>#%\"&+)", 
ssu.serialize(t));
+               assertEquals("$o(\n\tx{}|\\^[]`<>#%\"&+=x{}|\\^[]`<>#%\"&+\n)", 
sr.serialize(t));
+
+               // Escaped chars
+               // Top level
+               t = "x$,()~";
+               assertEquals("x$,()~", s.serialize(t));
+               assertEquals("x$,()~", ss.serialize(t));
+               assertEquals("x$,()~", su.serialize(t));
+               assertEquals("x$,()~", ssu.serialize(t));
+               assertEquals("x$,()~", sr.serialize(t));
+
+               // 2nd level
+               // Note behavior on serializeParams() is different since 
2nd-level is top level.
+               t = new ObjectMap("{'x$,()~':'x$,()~'}");
+               assertEquals("$o(x$~,~(~)~~=x$~,~(~)~~)", s.serialize(t));
+               assertEquals("(x$~,~(~)~~=x$~,~(~)~~)", ss.serialize(t));
+               assertEquals("$o(x$~,~(~)~~=x$~,~(~)~~)", su.serialize(t));
+               assertEquals("(x$~,~(~)~~=x$~,~(~)~~)", ssu.serialize(t));
+               assertEquals("$o(\n\tx$~,~(~)~~=x$~,~(~)~~\n)", 
sr.serialize(t));
+
+               // 3rd level
+               // Note behavior on serializeParams().
+               t = new ObjectMap("{'x$,()~':{'x$,()~':'x$,()~'}}");
+               assertEquals("$o(x$~,~(~)~~=$o(x$~,~(~)~~=x$~,~(~)~~))", 
s.serialize(t));
+               assertEquals("(x$~,~(~)~~=(x$~,~(~)~~=x$~,~(~)~~))", 
ss.serialize(t));
+               assertEquals("$o(x$~,~(~)~~=$o(x$~,~(~)~~=x$~,~(~)~~))", 
su.serialize(t));
+               assertEquals("(x$~,~(~)~~=(x$~,~(~)~~=x$~,~(~)~~))", 
ssu.serialize(t));
+               
assertEquals("$o(\n\tx$~,~(~)~~=$o(\n\t\tx$~,~(~)~~=x$~,~(~)~~\n\t)\n)", 
sr.serialize(t));
+
+               // Equals sign
+               // Gets encoded at top level, and encoded+escaped at 2nd level.
+               // Top level
+               t = "x=";
+               assertEquals("x=", s.serialize(t));
+               assertEquals("x=", ss.serialize(t));
+               assertEquals("x=", su.serialize(t));
+               assertEquals("x=", ssu.serialize(t));
+               assertEquals("x=", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{'x=':'x='}");
+               assertEquals("$o(x~==x~=)", s.serialize(t));
+               assertEquals("(x~==x~=)", ss.serialize(t));
+               assertEquals("$o(x~==x~=)", su.serialize(t));
+               assertEquals("(x~==x~=)", ssu.serialize(t));
+               assertEquals("$o(\n\tx~==x~=\n)", sr.serialize(t));
+
+               // 3rd level
+               t = new ObjectMap("{'x=':{'x=':'x='}}");
+               assertEquals("$o(x~==$o(x~==x~=))", s.serialize(t));
+               assertEquals("(x~==(x~==x~=))", ss.serialize(t));
+               assertEquals("$o(x~==$o(x~==x~=))", su.serialize(t));
+               assertEquals("(x~==(x~==x~=))", ssu.serialize(t));
+               assertEquals("$o(\n\tx~==$o(\n\t\tx~==x~=\n\t)\n)", 
sr.serialize(t));
+
+               // String starting with parenthesis
+               // Top level
+               t = "()";
+               assertEquals("(~(~))", s.serialize(t));
+               assertEquals("(~(~))", ss.serialize(t));
+               assertEquals("(~(~))", su.serialize(t));
+               assertEquals("(~(~))", ssu.serialize(t));
+               assertEquals("(~(~))", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{'()':'()'}");
+               assertEquals("$o((~(~))=(~(~)))", s.serialize(t));
+               assertEquals("((~(~))=(~(~)))", ss.serialize(t));
+               assertEquals("$o((~(~))=(~(~)))", su.serialize(t));
+               assertEquals("((~(~))=(~(~)))", ssu.serialize(t));
+               assertEquals("$o(\n\t(~(~))=(~(~))\n)", sr.serialize(t));
+
+               // String starting with $
+               // Top level
+               t = "$a";
+               assertEquals("($a)", s.serialize(t));
+               assertEquals("($a)", ss.serialize(t));
+               assertEquals("($a)", su.serialize(t));
+               assertEquals("($a)", ssu.serialize(t));
+               assertEquals("($a)", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{$a:'$a'}");
+               assertEquals("$o(($a)=($a))", s.serialize(t));
+               assertEquals("(($a)=($a))", ss.serialize(t));
+               assertEquals("$o(($a)=($a))", su.serialize(t));
+               assertEquals("(($a)=($a))", ssu.serialize(t));
+               assertEquals("$o(\n\t($a)=($a)\n)", sr.serialize(t));
+
+               // Blank string
+               // Top level
+               t = "";
+               assertEquals("", s.serialize(t));
+               assertEquals("", ss.serialize(t));
+               assertEquals("", su.serialize(t));
+               assertEquals("", ssu.serialize(t));
+               assertEquals("", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{'':''}");
+               assertEquals("$o(=)", s.serialize(t));
+               assertEquals("(=)", ss.serialize(t));
+               assertEquals("$o(=)", su.serialize(t));
+               assertEquals("(=)", ssu.serialize(t));
+               assertEquals("$o(\n\t()=()\n)", sr.serialize(t));
+
+               // 3rd level
+               t = new ObjectMap("{'':{'':''}}");
+               assertEquals("$o(=$o(=))", s.serialize(t));
+               assertEquals("(=(=))", ss.serialize(t));
+               assertEquals("$o(=$o(=))", su.serialize(t));
+               assertEquals("(=(=))", ssu.serialize(t));
+               assertEquals("$o(\n\t()=$o(\n\t\t()=()\n\t)\n)", 
sr.serialize(t));
+
+               // Newline character
+               // Top level
+               t = "\n";
+               assertEquals("%0A", s.serialize(t));
+               assertEquals("%0A", ss.serialize(t));
+               assertEquals("\n", su.serialize(t));
+               assertEquals("\n", ssu.serialize(t));
+               assertEquals("(\n)", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{'\n':'\n'}");
+               assertEquals("$o(%0A=%0A)", s.serialize(t));
+               assertEquals("(%0A=%0A)", ss.serialize(t));
+               assertEquals("$o(\n=\n)", su.serialize(t));
+               assertEquals("(\n=\n)", ssu.serialize(t));
+               assertEquals("$o(\n\t(\n)=(\n)\n)", sr.serialize(t));
+
+               // 3rd level
+               t = new ObjectMap("{'\n':{'\n':'\n'}}");
+               assertEquals("$o(%0A=$o(%0A=%0A))", s.serialize(t));
+               assertEquals("(%0A=(%0A=%0A))", ss.serialize(t));
+               assertEquals("$o(\n=$o(\n=\n))", su.serialize(t));
+               assertEquals("(\n=(\n=\n))", ssu.serialize(t));
+               assertEquals("$o(\n\t(\n)=$o(\n\t\t(\n)=(\n)\n\t)\n)", 
sr.serialize(t));
+       }
+
+       
//====================================================================================================
+       // Unicode characters test
+       
//====================================================================================================
+       @Test
+       public void testUnicodeChars() throws Exception {
+               Object t;
+
+               // 2-byte UTF-8 character
+               // Top level
+               t = "¢";
+               assertEquals("%C2%A2", s.serialize(t));
+               assertEquals("%C2%A2", ss.serialize(t));
+               assertEquals("¢", su.serialize(t));
+               assertEquals("¢", ssu.serialize(t));
+               assertEquals("¢", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{'¢':'¢'}");
+               assertEquals("$o(%C2%A2=%C2%A2)", s.serialize(t));
+               assertEquals("(%C2%A2=%C2%A2)", ss.serialize(t));
+               assertEquals("$o(¢=¢)", su.serialize(t));
+               assertEquals("(¢=¢)", ssu.serialize(t));
+               assertEquals("$o(\n\t¢=¢\n)", sr.serialize(t));
+
+               // 3rd level
+               t = new ObjectMap("{'¢':{'¢':'¢'}}");
+               assertEquals("$o(%C2%A2=$o(%C2%A2=%C2%A2))", s.serialize(t));
+               assertEquals("(%C2%A2=(%C2%A2=%C2%A2))", ss.serialize(t));
+               assertEquals("$o(¢=$o(¢=¢))", su.serialize(t));
+               assertEquals("(¢=(¢=¢))", ssu.serialize(t));
+               assertEquals("$o(\n\t¢=$o(\n\t\t¢=¢\n\t)\n)", 
sr.serialize(t));
+
+               // 3-byte UTF-8 character
+               // Top level
+               t = "€";
+               assertEquals("%E2%82%AC", s.serialize(t));
+               assertEquals("%E2%82%AC", ss.serialize(t));
+               assertEquals("€", su.serialize(t));
+               assertEquals("€", ssu.serialize(t));
+               assertEquals("€", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{'€':'€'}");
+               assertEquals("$o(%E2%82%AC=%E2%82%AC)", s.serialize(t));
+               assertEquals("(%E2%82%AC=%E2%82%AC)", ss.serialize(t));
+               assertEquals("$o(€=€)", su.serialize(t));
+               assertEquals("(€=€)", ssu.serialize(t));
+               assertEquals("$o(\n\t€=€\n)", sr.serialize(t));
+
+               // 3rd level
+               t = new ObjectMap("{'€':{'€':'€'}}");
+               assertEquals("$o(%E2%82%AC=$o(%E2%82%AC=%E2%82%AC))", 
s.serialize(t));
+               assertEquals("(%E2%82%AC=(%E2%82%AC=%E2%82%AC))", 
ss.serialize(t));
+               assertEquals("$o(€=$o(€=€))", su.serialize(t));
+               assertEquals("(€=(€=€))", ssu.serialize(t));
+               assertEquals("$o(\n\t€=$o(\n\t\t€=€\n\t)\n)", 
sr.serialize(t));
+
+               // 4-byte UTF-8 character
+               // Top level
+               t = "𤭢";
+               assertEquals("%F0%A4%AD%A2", s.serialize(t));
+               assertEquals("%F0%A4%AD%A2", ss.serialize(t));
+               assertEquals("𤭢", su.serialize(t));
+               assertEquals("𤭢", ssu.serialize(t));
+               assertEquals("𤭢", sr.serialize(t));
+
+               // 2nd level
+               t = new ObjectMap("{'𤭢':'𤭢'}");
+               assertEquals("$o(%F0%A4%AD%A2=%F0%A4%AD%A2)", s.serialize(t));
+               assertEquals("(%F0%A4%AD%A2=%F0%A4%AD%A2)", ss.serialize(t));
+               assertEquals("$o(𤭢=𤭢)", su.serialize(t));
+               assertEquals("(𤭢=𤭢)", ssu.serialize(t));
+               assertEquals("$o(\n\t𤭢=𤭢\n)", sr.serialize(t));
+
+               // 3rd level
+               t = new ObjectMap("{'𤭢':{'𤭢':'𤭢'}}");
+               assertEquals("$o(%F0%A4%AD%A2=$o(%F0%A4%AD%A2=%F0%A4%AD%A2))", 
s.serialize(t));
+               assertEquals("(%F0%A4%AD%A2=(%F0%A4%AD%A2=%F0%A4%AD%A2))", 
ss.serialize(t));
+               assertEquals("$o(𤭢=$o(𤭢=𤭢))", su.serialize(t));
+               assertEquals("(𤭢=(𤭢=𤭢))", ssu.serialize(t));
+               assertEquals("$o(\n\t𤭢=$o(\n\t\t𤭢=𤭢\n\t)\n)", 
sr.serialize(t));
+       }
+}
\ No newline at end of file

Reply via email to