juneau-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jamesbog...@apache.org
Subject [39/49] incubator-juneau git commit: Separate RDF and JAX-RS support into separate projects.
Date Sun, 22 Jan 2017 00:39:38 GMT
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e4dfdf81/juneau-core-test/src/test/java/org/apache/juneau/html/BasicHtmlTest.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/html/BasicHtmlTest.java b/juneau-core-test/src/test/java/org/apache/juneau/html/BasicHtmlTest.java
new file mode 100644
index 0000000..b7a86dd
--- /dev/null
+++ b/juneau-core-test/src/test/java/org/apache/juneau/html/BasicHtmlTest.java
@@ -0,0 +1,1616 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.html;
+
+import static org.junit.Assert.*;
+
+import java.util.*;
+
+import org.apache.juneau.annotation.*;
+import org.junit.*;
+import org.junit.runner.*;
+import org.junit.runners.*;
+
+@RunWith(Parameterized.class)
+@SuppressWarnings({"javadoc","serial"})
+public class BasicHtmlTest {
+
+	private static final HtmlSerializer
+		s1 = HtmlSerializer.DEFAULT_SQ,
+		s2 = HtmlSerializer.DEFAULT_SQ_READABLE;
+	private static final HtmlParser parser = HtmlParser.DEFAULT;
+
+	@Parameterized.Parameters
+	public static Collection<Object[]> getParameters() {
+		return Arrays.asList(new Object[][] {
+
+			{
+				"SimpleTypes-1",
+				"foo",
+				"<string>foo</string>",
+				"<string>foo</string>",
+			},
+			{
+				"SimpleTypes-2",
+				true,
+				"<boolean>true</boolean>",
+				"<boolean>true</boolean>",
+			},
+			{
+				"SimpleTypes-3",
+				123,
+				"<number>123</number>",
+				"<number>123</number>",
+			},
+			{
+				"SimpleTypes-4",
+				1.23f,
+				"<number>1.23</number>",
+				"<number>1.23</number>",
+			},
+			{
+				"SimpleTypes-5",
+				null,
+				"<null/>",
+				"<null/>",
+			},
+			{
+				"Arrays-1",
+				new String[]{"foo"},
+				"<ul><li>foo</li></ul>",
+				"<ul>\n\t<li>foo</li>\n</ul>\n",
+			},
+			{
+				"Arrays-2",
+				new String[]{null},
+				"<ul><li><null/></li></ul>",
+				"<ul>\n\t<li><null/></li>\n</ul>\n",
+			},
+			{
+				"Arrays-3",
+				new Object[]{"foo",123,true},
+				"<ul><li>foo</li><li><number>123</number></li><li><boolean>true</boolean></li></ul>",
+				"<ul>\n\t<li>foo</li>\n\t<li><number>123</number></li>\n\t<li><boolean>true</boolean></li>\n</ul>\n",
+			},
+			{
+				"Arrays-4",
+				new int[]{123},
+				"<ul><li>123</li></ul>",
+				"<ul>\n\t<li>123</li>\n</ul>\n",
+			},
+			{
+				"Arrays-5",
+				new boolean[]{true},
+				"<ul><li>true</li></ul>",
+				"<ul>\n\t<li>true</li>\n</ul>\n",
+			},
+			{
+				"Arrays-6",
+				new String[][]{{"foo"}},
+				"<ul><li><ul><li>foo</li></ul></li></ul>",
+				"<ul>\n\t<li>\n\t\t<ul>\n\t\t\t<li>foo</li>\n\t\t</ul>\n\t</li>\n</ul>\n",
+			},
+			{
+				"MapWithStrings",
+				new MapWithStrings().append("k1", "v1").append("k2", null),
+				"<table>"
+					+"<tr>"
+						+"<td>k1</td>"
+						+"<td>v1</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>k2</td>"
+						+"<td><null/></td>"
+					+"</tr>"
+				+"</table>",
+				"<table>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>k1</td>\n"
+						+"\t\t<td>v1</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>k2</td>\n"
+						+"\t\t<td><null/></td>\n"
+					+"\t</tr>\n"
+				+"</table>\n",
+			},
+			{
+				"MapsWithNumbers",
+				new MapWithNumbers().append("k1", 123).append("k2", 1.23).append("k3", null),
+				"<table>"
+					+"<tr>"
+						+"<td>k1</td>"
+						+"<td>123</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>k2</td>"
+						+"<td>1.23</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>k3</td>"
+						+"<td><null/></td>"
+					+"</tr>"
+				+"</table>",
+				"<table>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>k1</td>\n"
+						+"\t\t<td>123</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>k2</td>\n"
+						+"\t\t<td>1.23</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>k3</td>\n"
+						+"\t\t<td><null/></td>\n"
+					+"\t</tr>\n"
+				+"</table>\n",
+			},
+			{
+				"MapWithObjects",
+				new MapWithObjects().append("k1", "v1").append("k2", 123).append("k3", 1.23).append("k4", true).append("k5", null),
+				"<table>"
+					+"<tr>"
+						+"<td>k1</td>"
+						+"<td>v1</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>k2</td>"
+						+"<td><number>123</number></td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>k3</td>"
+						+"<td><number>1.23</number></td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>k4</td>"
+						+"<td><boolean>true</boolean></td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>k5</td>"
+						+"<td><null/></td>"
+					+"</tr>"
+				+"</table>",
+				"<table>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>k1</td>\n"
+						+"\t\t<td>v1</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>k2</td>\n"
+						+"\t\t<td><number>123</number></td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>k3</td>\n"
+						+"\t\t<td><number>1.23</number></td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>k4</td>\n"
+						+"\t\t<td><boolean>true</boolean></td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>k5</td>\n"
+						+"\t\t<td><null/></td>\n"
+					+"\t</tr>\n"
+				+"</table>\n",
+			},
+			{
+				"ListWithStrings",
+				new ListWithStrings().append("foo").append(null),
+				"<ul><li>foo</li><li><null/></li></ul>",
+				"<ul>\n\t<li>foo</li>\n\t<li><null/></li>\n</ul>\n",
+			},
+			{
+				"ListWithNumbers",
+				new ListWithNumbers().append(123).append(1.23).append(null),
+				"<ul><li>123</li><li>1.23</li><li><null/></li></ul>",
+				"<ul>\n\t<li>123</li>\n\t<li>1.23</li>\n\t<li><null/></li>\n</ul>\n",
+			},
+			{
+				"ListWithObjects",
+				new ListWithObjects().append("foo").append(123).append(1.23).append(true).append(null),
+				"<ul><li>foo</li><li><number>123</number></li><li><number>1.23</number></li><li><boolean>true</boolean></li><li><null/></li></ul>",
+				"<ul>\n\t<li>foo</li>\n\t<li><number>123</number></li>\n\t<li><number>1.23</number></li>\n\t<li><boolean>true</boolean></li>\n\t<li><null/></li>\n</ul>\n",
+			},
+			{
+				"BeanWithNormalProperties",
+				new BeanWithNormalProperties().init(),
+				"<table>"
+					+"<tr>"
+						+"<td>a</td>"
+						+"<td>foo</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>b</td>"
+						+"<td>123</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>c</td>"
+						+"<td>bar</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>d</td>"
+						+"<td><number>456</number></td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>e</td>"
+						+"<td>"
+							+"<table>"
+								+"<tr>"
+									+"<td>h</td>"
+									+"<td>qux</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>f</td>"
+						+"<td>"
+							+"<ul>"
+								+"<li>baz</li>"
+							+"</ul>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>g</td>"
+						+"<td>"
+							+"<ul>"
+								+"<li>789</li>"
+							+"</ul>"
+						+"</td>"
+					+"</tr>"
+				+"</table>",
+				"<table>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>a</td>\n"
+						+"\t\t<td>foo</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b</td>\n"
+						+"\t\t<td>123</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>c</td>\n"
+						+"\t\t<td>bar</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>d</td>\n"
+						+"\t\t<td><number>456</number></td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>e</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>h</td>\n"
+									+"\t\t\t\t\t<td>qux</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>f</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<ul>\n"
+								+"\t\t\t\t<li>baz</li>\n"
+							+"\t\t\t</ul>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>g</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<ul>\n"
+								+"\t\t\t\t<li>789</li>\n"
+							+"\t\t\t</ul>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+				+"</table>\n",
+			},
+			{
+				"BeanWithMapProperties",
+				new BeanWithMapProperties().init(),
+				"<table>"
+					+"<tr>"
+						+"<td>a</td>"
+						+"<td>"
+							+"<table>"
+								+"<tr>"
+									+"<td>k1</td>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>b</td>"
+						+"<td>"
+							+"<table>"
+								+"<tr>"
+									+"<td>k2</td>"
+									+"<td>123</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>c</td>"
+						+"<td>"
+							+"<table>"
+								+"<tr>"
+									+"<td>k3</td>"
+									+"<td>bar</td>"
+								+"</tr>"
+								+"<tr>"
+									+"<td>k4</td>"
+									+"<td><number>456</number></td>"
+								+"</tr>"
+								+"<tr>"
+									+"<td>k5</td>"
+									+"<td><boolean>true</boolean></td>"
+								+"</tr>"
+								+"<tr>"
+									+"<td>k6</td>"
+									+"<td><null/></td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+				+"</table>",
+				"<table>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>a</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>k1</td>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>k2</td>\n"
+									+"\t\t\t\t\t<td>123</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>c</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>k3</td>\n"
+									+"\t\t\t\t\t<td>bar</td>\n"
+								+"\t\t\t\t</tr>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>k4</td>\n"
+									+"\t\t\t\t\t<td><number>456</number></td>\n"
+								+"\t\t\t\t</tr>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>k5</td>\n"
+									+"\t\t\t\t\t<td><boolean>true</boolean></td>\n"
+								+"\t\t\t\t</tr>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>k6</td>\n"
+									+"\t\t\t\t\t<td><null/></td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+				+"</table>\n"
+		   },
+			{
+				"BeanWithTypeName",
+				new BeanWithTypeName().init(),
+				"<table _type='X'>"
+					+"<tr>"
+						+"<td>a</td>"
+						+"<td>123</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>b</td>"
+						+"<td>foo</td>"
+					+"</tr>"
+				+"</table>",
+				"<table _type='X'>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>a</td>\n"
+						+"\t\t<td>123</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b</td>\n"
+						+"\t\t<td>foo</td>\n"
+					+"\t</tr>\n"
+				+"</table>\n",
+			},
+			{
+				"BeanWithPropertiesWithTypeNames",
+				new BeanWithPropertiesWithTypeNames().init(),
+				"<table>"
+					+"<tr>"
+						+"<td>b1</td>"
+						+"<td>"
+							+"<table>"
+								+"<tr>"
+									+"<td>b</td>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>b2</td>"
+						+"<td>"
+							+"<table _type='B'>"
+								+"<tr>"
+									+"<td>b</td>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+				+"</table>",
+				"<table>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b1</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>b</td>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b2</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='B'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>b</td>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+				+"</table>\n"
+			},
+			{
+				"BeanWithPropertiesWithArrayTypeNames",
+				new BeanWithPropertiesWithArrayTypeNames().init(),
+				"<table>"
+					+"<tr>"
+						+"<td>b1</td>"
+						+"<td>"
+							+"<table _type='array'>"
+								+"<tr>"
+									+"<th>b</th>"
+								+"</tr>"
+								+"<tr>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>b2</td>"
+						+"<td>"
+							+"<table _type='B^'>"
+								+"<tr>"
+									+"<th>b</th>"
+								+"</tr>"
+								+"<tr>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>b3</td>"
+						+"<td>"
+							+"<table _type='array'>"
+								+"<tr>"
+									+"<th>b</th>"
+								+"</tr>"
+								+"<tr _type='B'>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+				+"</table>",
+				"<table>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b1</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='array'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<th>b</th>\n"
+								+"\t\t\t\t</tr>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b2</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='B^'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<th>b</th>\n"
+								+"\t\t\t\t</tr>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b3</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='array'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<th>b</th>\n"
+								+"\t\t\t\t</tr>\n"
+								+"\t\t\t\t<tr _type='B'>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+				+"</table>\n",
+			},
+			{
+				"BeanWithPropertiesWithArrayTypeNames",
+				new BeanWithPropertiesWith2dArrayTypeNames().init(),
+				"<table>"
+					+"<tr>"
+						+"<td>b1</td>"
+						+"<td>"
+							+"<ul>"
+								+"<li>"
+									+"<table _type='array'>"
+										+"<tr>"
+											+"<th>b</th>"
+										+"</tr>"
+										+"<tr>"
+											+"<td>foo</td>"
+										+"</tr>"
+									+"</table>"
+								+"</li>"
+							+"</ul>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>b2</td>"
+						+"<td>"
+								+"<ul _type='B^^'>"
+									+"<li>"
+										+"<table _type='array'>"
+											+"<tr>"
+												+"<th>b</th>"
+											+"</tr>"
+											+"<tr>"
+												+"<td>foo</td>"
+											+"</tr>"
+										+"</table>"
+									+"</li>"
+								+"</ul>"
+							+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>b3</td>"
+						+"<td>"
+							+"<ul>"
+								+"<li>"
+									+"<table _type='array'>"
+										+"<tr>"
+											+"<th>b</th>"
+										+"</tr>"
+										+"<tr _type='B'>"
+											+"<td>foo</td>"
+										+"</tr>"
+									+"</table>"
+								+"</li>"
+							+"</ul>"
+						+"</td>"
+					+"</tr>"
+				+"</table>",
+				"<table>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b1</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<ul>\n"
+								+"\t\t\t\t<li>\n"
+									+"\t\t\t\t\t<table _type='array'>\n"
+										+"\t\t\t\t\t\t<tr>\n"
+											+"\t\t\t\t\t\t\t<th>b</th>\n"
+										+"\t\t\t\t\t\t</tr>\n"
+										+"\t\t\t\t\t\t<tr>\n"
+											+"\t\t\t\t\t\t\t<td>foo</td>\n"
+										+"\t\t\t\t\t\t</tr>\n"
+									+"\t\t\t\t\t</table>\n"
+								+"\t\t\t\t</li>\n"
+							+"\t\t\t</ul>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b2</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<ul _type='B^^'>\n"
+								+"\t\t\t\t<li>\n"
+									+"\t\t\t\t\t<table _type='array'>\n"
+										+"\t\t\t\t\t\t<tr>\n"
+											+"\t\t\t\t\t\t\t<th>b</th>\n"
+										+"\t\t\t\t\t\t</tr>\n"
+										+"\t\t\t\t\t\t<tr>\n"
+											+"\t\t\t\t\t\t\t<td>foo</td>\n"
+										+"\t\t\t\t\t\t</tr>\n"
+									+"\t\t\t\t\t</table>\n"
+								+"\t\t\t\t</li>\n"
+							+"\t\t\t</ul>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b3</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<ul>\n"
+								+"\t\t\t\t<li>\n"
+									+"\t\t\t\t\t<table _type='array'>\n"
+										+"\t\t\t\t\t\t<tr>\n"
+											+"\t\t\t\t\t\t\t<th>b</th>\n"
+										+"\t\t\t\t\t\t</tr>\n"
+										+"\t\t\t\t\t\t<tr _type='B'>\n"
+											+"\t\t\t\t\t\t\t<td>foo</td>\n"
+										+"\t\t\t\t\t\t</tr>\n"
+									+"\t\t\t\t\t</table>\n"
+								+"\t\t\t\t</li>\n"
+							+"\t\t\t</ul>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+				+"</table>\n"
+			},
+			{
+				"BeanWithPropertiesWithMapTypeNames",
+				new BeanWithPropertiesWithMapTypeNames().init(),
+				"<table>"
+					+"<tr>"
+						+"<td>b1</td>"
+						+"<td>"
+							+"<table>"
+								+"<tr>"
+									+"<td>k1</td>"
+									+"<td>"
+										+"<table>"
+											+"<tr>"
+												+"<td>b</td>"
+												+"<td>foo</td>"
+											+"</tr>"
+										+"</table>"
+									+"</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>b2</td>"
+						+"<td>"
+							+"<table>"
+								+"<tr>"
+									+"<td>k2</td>"
+									+"<td>"
+										+"<table _type='B'>"
+											+"<tr>"
+												+"<td>b</td>"
+												+"<td>foo</td>"
+											+"</tr>"
+										+"</table>"
+									+"</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+				+"</table>",
+				"<table>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b1</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>k1</td>\n"
+									+"\t\t\t\t\t<td>\n"
+										+"\t\t\t\t\t\t<table>\n"
+											+"\t\t\t\t\t\t\t<tr>\n"
+												+"\t\t\t\t\t\t\t\t<td>b</td>\n"
+												+"\t\t\t\t\t\t\t\t<td>foo</td>\n"
+											+"\t\t\t\t\t\t\t</tr>\n"
+										+"\t\t\t\t\t\t</table>\n"
+									+"\t\t\t\t\t</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b2</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>k2</td>\n"
+									+"\t\t\t\t\t<td>\n"
+										+"\t\t\t\t\t\t<table _type='B'>\n"
+											+"\t\t\t\t\t\t\t<tr>\n"
+												+"\t\t\t\t\t\t\t\t<td>b</td>\n"
+												+"\t\t\t\t\t\t\t\t<td>foo</td>\n"
+											+"\t\t\t\t\t\t\t</tr>\n"
+										+"\t\t\t\t\t\t</table>\n"
+									+"\t\t\t\t\t</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+				+"</table>\n",
+			},
+			{
+				"LinkBean-1",
+				new LinkBean().init(),
+				"<a href='http://apache.org'>foo</a>",
+				"<a href='http://apache.org'>foo</a>"
+			},
+			{
+				"LinkBean-2",
+				new LinkBean[]{new LinkBean().init(),new LinkBean().init()},
+				"<ul><li><a href='http://apache.org'>foo</a></li><li><a href='http://apache.org'>foo</a></li></ul>",
+				"<ul>\n\t<li><a href='http://apache.org'>foo</a></li>\n\t<li><a href='http://apache.org'>foo</a></li>\n</ul>\n"
+			},
+			{
+				"ListWithLinkBeans",
+				new ListWithLinkBeans().append(new LinkBean().init()).append(new LinkBean().init()),
+				"<ul><li><a href='http://apache.org'>foo</a></li><li><a href='http://apache.org'>foo</a></li></ul>",
+				"<ul>\n\t<li><a href='http://apache.org'>foo</a></li>\n\t<li><a href='http://apache.org'>foo</a></li>\n</ul>\n"
+			},
+			{
+				"BeanWithLinkBeanProperties",
+				new BeanWithLinkBeanProperties().init(),
+				"<table>"
+					+"<tr>"
+						+"<td>a</td>"
+						+"<td><a href='http://apache.org'>foo</a></td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>b</td>"
+						+"<td>"
+							+"<ul>"
+								+"<li><a href='http://apache.org'>foo</a></li>"
+							+"</ul>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>c</td>"
+						+"<td>"
+							+"<table>"
+								+"<tr>"
+									+"<td>c1</td>"
+									+"<td><a href='http://apache.org'>foo</a></td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+				+"</table>",
+				"<table>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>a</td>\n"
+						+"\t\t<td><a href='http://apache.org'>foo</a></td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<ul>\n"
+								+"\t\t\t\t<li><a href='http://apache.org'>foo</a></li>\n"
+							+"\t\t\t</ul>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>c</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>c1</td>\n"
+									+"\t\t\t\t\t<td><a href='http://apache.org'>foo</a></td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+				+"</table>\n"
+			},
+			{
+				"BeanWithSpecialCharacters",
+				new BeanWithSpecialCharacters().init(),
+				"<table><tr><td>a</td><td><br/><bs/><ff/><tb>&#x2003;</tb></td></tr></table>",
+				"<table>\n\t<tr>\n\t\t<td>a</td>\n\t\t<td><br/><bs/><ff/><tb>&#x2003;</tb></td>\n\t</tr>\n</table>\n"
+			},
+			{
+				"BeanWithNullProperties",
+				new BeanWithNullProperties(),
+				"<table></table>",
+				"<table>\n</table>\n"
+			},
+			{
+				"BeanWithAbstractFields",
+				new BeanWithAbstractFields().init(),
+				"<table>"
+					+"<tr>"
+						+"<td>a</td>"
+						+"<td>"
+							+"<table>"
+								+"<tr>"
+									+"<td>a</td>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>ia</td>"
+						+"<td>"
+							+"<table _type='A'>"
+								+"<tr>"
+									+"<td>a</td>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>aa</td>"
+						+"<td>"
+							+"<table _type='A'>"
+								+"<tr>"
+									+"<td>a</td>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>o</td>"
+						+"<td>"
+							+"<table _type='A'>"
+								+"<tr>"
+									+"<td>a</td>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+				+"</table>",
+				"<table>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>a</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>a</td>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>ia</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='A'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>a</td>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>aa</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='A'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>a</td>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>o</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='A'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>a</td>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+				+"</table>\n"
+			},
+			{
+				"BeanWithAbstractArrayFields",
+				new BeanWithAbstractArrayFields().init(),
+				"<table>"
+					+"<tr>"
+						+"<td>a</td>"
+						+"<td>"
+							+"<table _type='array'>"
+								+"<tr>"
+									+"<th>a</th>"
+								+"</tr>"
+								+"<tr>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>ia1</td>"
+						+"<td>"
+							+"<table _type='A^'>"
+								+"<tr>"
+									+"<th>a</th>"
+								+"</tr>"
+								+"<tr>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>ia2</td>"
+						+"<td>"
+							+"<table _type='array'>"
+								+"<tr>"
+									+"<th>a</th>"
+								+"</tr>"
+								+"<tr _type='A'>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+							+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>aa1</td>"
+						+"<td>"
+							+"<table _type='A^'>"
+								+"<tr>"
+									+"<th>a</th>"
+								+"</tr>"
+								+"<tr>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>aa2</td>"
+						+"<td>"
+							+"<table _type='array'>"
+								+"<tr>"
+									+"<th>a</th>"
+								+"</tr>"
+								+"<tr _type='A'>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>o1</td>"
+						+"<td>"
+							+"<table _type='A^'>"
+								+"<tr>"
+									+"<th>a</th>"
+								+"</tr>"
+								+"<tr>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>o2</td>"
+						+"<td>"
+							+"<table _type='array'>"
+								+"<tr>"
+									+"<th>a</th>"
+								+"</tr>"
+								+"<tr _type='A'>"
+									+"<td>foo</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+				+"</table>",
+				"<table>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>a</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='array'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<th>a</th>\n"
+								+"\t\t\t\t</tr>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>ia1</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='A^'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<th>a</th>\n"
+								+"\t\t\t\t</tr>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>ia2</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='array'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<th>a</th>\n"
+								+"\t\t\t\t</tr>\n"
+								+"\t\t\t\t<tr _type='A'>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>aa1</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='A^'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<th>a</th>\n"
+								+"\t\t\t\t</tr>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>aa2</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='array'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<th>a</th>\n"
+								+"\t\t\t\t</tr>\n"
+								+"\t\t\t\t<tr _type='A'>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>o1</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='A^'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<th>a</th>\n"
+								+"\t\t\t\t</tr>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>o2</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table _type='array'>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<th>a</th>\n"
+								+"\t\t\t\t</tr>\n"
+								+"\t\t\t\t<tr _type='A'>\n"
+									+"\t\t\t\t\t<td>foo</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+				+"</table>\n",
+			},
+			{
+				"BeanWithAbstractMapFields",
+				new BeanWithAbstractMapFields().init(),
+				"<table>"
+					+"<tr>"
+						+"<td>a</td>"
+						+"<td>"
+							+"<table>"
+								+"<tr>"
+									+"<td>k1</td>"
+									+"<td>"
+										+"<table>"
+											+"<tr>"
+												+"<td>a</td>"
+												+"<td>foo</td>"
+											+"</tr>"
+										+"</table>"
+									+"</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>b</td>"
+						+"<td>"
+							+"<table>"
+								+"<tr>"
+									+"<td>k2</td>"
+									+"<td>"
+										+"<table _type='A'>"
+											+"<tr>"
+												+"<td>a</td>"
+												+"<td>foo</td>"
+											+"</tr>"
+										+"</table>"
+									+"</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+					+"<tr>"
+						+"<td>c</td>"
+						+"<td>"
+							+"<table>"
+								+"<tr>"
+									+"<td>k3</td>"
+									+"<td>"
+										+"<table _type='A'>"
+											+"<tr>"
+												+"<td>a</td>"
+												+"<td>foo</td>"
+											+"</tr>"
+										+"</table>"
+									+"</td>"
+								+"</tr>"
+							+"</table>"
+						+"</td>"
+					+"</tr>"
+				+"</table>",
+				"<table>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>a</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>k1</td>\n"
+									+"\t\t\t\t\t<td>\n"
+										+"\t\t\t\t\t\t<table>\n"
+											+"\t\t\t\t\t\t\t<tr>\n"
+												+"\t\t\t\t\t\t\t\t<td>a</td>\n"
+												+"\t\t\t\t\t\t\t\t<td>foo</td>\n"
+											+"\t\t\t\t\t\t\t</tr>\n"
+										+"\t\t\t\t\t\t</table>\n"
+									+"\t\t\t\t\t</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>b</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>k2</td>\n"
+									+"\t\t\t\t\t<td>\n"
+										+"\t\t\t\t\t\t<table _type='A'>\n"
+											+"\t\t\t\t\t\t\t<tr>\n"
+												+"\t\t\t\t\t\t\t\t<td>a</td>\n"
+												+"\t\t\t\t\t\t\t\t<td>foo</td>\n"
+											+"\t\t\t\t\t\t\t</tr>\n"
+										+"\t\t\t\t\t\t</table>\n"
+									+"\t\t\t\t\t</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+					+"\t<tr>\n"
+						+"\t\t<td>c</td>\n"
+						+"\t\t<td>\n"
+							+"\t\t\t<table>\n"
+								+"\t\t\t\t<tr>\n"
+									+"\t\t\t\t\t<td>k3</td>\n"
+									+"\t\t\t\t\t<td>\n"
+										+"\t\t\t\t\t\t<table _type='A'>\n"
+											+"\t\t\t\t\t\t\t<tr>\n"
+												+"\t\t\t\t\t\t\t\t<td>a</td>\n"
+												+"\t\t\t\t\t\t\t\t<td>foo</td>\n"
+											+"\t\t\t\t\t\t\t</tr>\n"
+										+"\t\t\t\t\t\t</table>\n"
+									+"\t\t\t\t\t</td>\n"
+								+"\t\t\t\t</tr>\n"
+							+"\t\t\t</table>\n"
+						+"\t\t</td>\n"
+					+"\t</tr>\n"
+				+"</table>\n"
+			}
+		});
+	}
+
+	private String label, e1, e2;
+	private Object in;
+
+	public BasicHtmlTest(String label, Object in, String e1, String e2) throws Exception {
+		this.label = label;
+		this.in = in;
+		this.e1 = e1;
+		this.e2 = e2;
+	}
+
+	@Test
+	public void serializeNormal() {
+		try {
+			String r = s1.serialize(in);
+			assertEquals(label + " serialize-normal failed", e1, r);
+		} catch (AssertionError e) {
+			throw e;
+		} catch (Throwable e) {
+			throw new RuntimeException(label + " test failed", e);
+		}
+	}
+
+	@Test
+	public void parseNormal() {
+		try {
+			String r = s1.serialize(in);
+			Class<?> c = in == null ? Object.class : in.getClass();
+			Object o = parser.parse(r, c);
+			r = s1.serialize(o);
+			assertEquals(label + " parse-normal failed", e1, r);
+		} catch (AssertionError e) {
+			throw e;
+		} catch (Throwable e) {
+			throw new RuntimeException(label + " test failed", e);
+		}
+	}
+
+	@Test
+	public void serializeReadable() {
+		try {
+			String r = s2.serialize(in);
+			assertEquals(label + " serialize-readable failed", e2, r);
+		} catch (AssertionError e) {
+			throw e;
+		} catch (Throwable e) {
+			throw new RuntimeException(label + " test failed", e);
+		}
+	}
+
+	@Test
+	public void parseReadable() {
+		try {
+			String r = s2.serialize(in);
+			Class<?> c = in == null ? Object.class : in.getClass();
+			Object o = parser.parse(r, c);
+			r = s2.serialize(o);
+			assertEquals(label + " parse-readable failed", e2, r);
+		} catch (AssertionError e) {
+			throw e;
+		} catch (Throwable e) {
+			throw new RuntimeException(label + " test failed", e);
+		}
+	}
+
+
+	//--------------------------------------------------------------------------------
+	// Test beans
+	//--------------------------------------------------------------------------------
+
+	public static class MapWithStrings extends LinkedHashMap<String,String> {
+		public MapWithStrings append(String key, String value) {
+			put(key, value);
+			return this;
+		}
+	}
+
+	public static class MapWithNumbers extends LinkedHashMap<String,Number> {
+		public MapWithNumbers append(String key, Number value) {
+			put(key, value);
+			return this;
+		}
+	}
+
+	public static class MapWithObjects extends LinkedHashMap<String,Object> {
+		public MapWithObjects append(String key, Object value) {
+			put(key, value);
+			return this;
+		}
+	}
+
+	public static class ListWithStrings extends ArrayList<String> {
+		public ListWithStrings append(String value) {
+			this.add(value);
+			return this;
+		}
+	}
+
+	public static class ListWithNumbers extends ArrayList<Number> {
+		public ListWithNumbers append(Number value) {
+			this.add(value);
+			return this;
+		}
+	}
+
+	public static class ListWithObjects extends ArrayList<Object> {
+		public ListWithObjects append(Object value) {
+			this.add(value);
+			return this;
+		}
+	}
+
+	public static class BeanWithNormalProperties {
+		public String a;
+		public int b;
+		public Object c;
+		public Object d;
+		public Bean1a e;
+		public String[] f;
+		public int[] g;
+
+		BeanWithNormalProperties init() {
+			a = "foo";
+			b = 123;
+			c = "bar";
+			d = 456;
+			e = new Bean1a().init();
+			f = new String[]{ "baz" };
+			g = new int[]{ 789 };
+			return this;
+		}
+	}
+
+	public static class Bean1a {
+		public String h;
+
+		Bean1a init() {
+			h = "qux";
+			return this;
+		}
+	}
+
+	public static class BeanWithMapProperties {
+		@BeanProperty(type=MapWithStrings.class)
+		public Map<String,String> a;
+		@BeanProperty(type=MapWithNumbers.class)
+		public Map<String,Number> b;
+		@BeanProperty(type=MapWithObjects.class)
+		public Map<String,Object> c;
+
+		BeanWithMapProperties init() {
+			a = new MapWithStrings().append("k1","foo");
+			b = new MapWithNumbers().append("k2",123);
+			c = new MapWithObjects().append("k3","bar").append("k4",456).append("k5",true).append("k6",null);
+			return this;
+		}
+	}
+
+	@Bean(typeName="X")
+	public static class BeanWithTypeName {
+		public int a;
+		public String b;
+
+		BeanWithTypeName init() {
+			a = 123;
+			b = "foo";
+			return this;
+		}
+	}
+
+	@Bean(beanDictionary={B.class})
+	public static class BeanWithPropertiesWithTypeNames {
+		public B b1;
+		public Object b2;
+
+		BeanWithPropertiesWithTypeNames init() {
+			b1 = new B().init();
+			b2 = new B().init();
+			return this;
+		}
+	}
+
+	@Bean(beanDictionary={B.class})
+	public static class BeanWithPropertiesWithArrayTypeNames {
+		public B[] b1;
+		public Object[] b2;
+		public Object[] b3;
+
+		BeanWithPropertiesWithArrayTypeNames init() {
+			b1 = new B[]{new B().init()};
+			b2 = new B[]{new B().init()};
+			b3 = new Object[]{new B().init()};
+			return this;
+		}
+	}
+
+	@Bean(beanDictionary={B.class})
+	public static class BeanWithPropertiesWith2dArrayTypeNames {
+		public B[][] b1;
+		public Object[][] b2;
+		public Object[][] b3;
+
+		BeanWithPropertiesWith2dArrayTypeNames init() {
+			b1 = new B[][]{{new B().init()}};
+			b2 = new B[][]{{new B().init()}};
+			b3 = new Object[][]{{new B().init()}};
+			return this;
+		}
+	}
+
+	@Bean(beanDictionary={B.class})
+	public static class BeanWithPropertiesWithMapTypeNames {
+		public Map<String,B> b1;
+		public Map<String,Object> b2;
+
+		BeanWithPropertiesWithMapTypeNames init() {
+			b1 = new HashMap<String,B>();
+			b1.put("k1", new B().init());
+			b2 = new HashMap<String,Object>();
+			b2.put("k2", new B().init());
+			return this;
+		}
+	}
+
+	@Bean(typeName="B")
+	public static class B {
+		public String b;
+
+		B init() {
+			b = "foo";
+			return this;
+		}
+	}
+
+	@HtmlLink(nameProperty="a",hrefProperty="b")
+	public static class LinkBean {
+		public String a;
+		public String b;
+
+		LinkBean init() {
+			a = "foo";
+			b = "http://apache.org";
+			return this;
+		}
+	}
+
+	public static class ListWithLinkBeans extends ArrayList<LinkBean> {
+		public ListWithLinkBeans append(LinkBean value) {
+			this.add(value);
+			return this;
+		}
+	}
+
+	public static class BeanWithLinkBeanProperties {
+		public LinkBean a;
+		public List<LinkBean> b;
+		public Map<String,LinkBean> c;
+
+		BeanWithLinkBeanProperties init() {
+			a = new LinkBean().init();
+			b = new ListWithLinkBeans().append(new LinkBean().init());
+			c = new LinkedHashMap<String,LinkBean>();
+			c.put("c1", new LinkBean().init());
+			return this;
+		}
+	}
+
+	public static class BeanWithSpecialCharacters {
+		public String a;
+
+		BeanWithSpecialCharacters init() {
+			a = "\n\b\f\t";
+			return this;
+		}
+	}
+
+	public static class BeanWithNullProperties {
+		public String a;
+		public String[] b;
+	}
+
+	@Bean(beanDictionary={A.class})
+	public static class BeanWithAbstractFields {
+		public A a;
+		public IA ia;
+		public AA aa;
+		public Object o;
+
+		BeanWithAbstractFields init() {
+			ia = new A().init();
+			aa = new A().init();
+			a = new A().init();
+			o = new A().init();
+			return this;
+		}
+	}
+
+	@Bean(beanDictionary={A.class})
+	public static class BeanWithAbstractArrayFields {
+		public A[] a;
+		public IA[] ia1, ia2;
+		public AA[] aa1, aa2;
+		public Object[] o1, o2;
+
+		BeanWithAbstractArrayFields init() {
+			a = new A[]{new A().init()};
+			ia1 = new A[]{new A().init()};
+			aa1 = new A[]{new A().init()};
+			o1 = new A[]{new A().init()};
+			ia2 = new IA[]{new A().init()};
+			aa2 = new AA[]{new A().init()};
+			o2 = new Object[]{new A().init()};
+			return this;
+		}
+	}
+
+	@Bean(beanDictionary={A.class})
+	public static class BeanWithAbstractMapFields {
+		public Map<String,A> a;
+		public Map<String,AA> b;
+		public Map<String,Object> c;
+
+		BeanWithAbstractMapFields init() {
+			a = new HashMap<String,A>();
+			b = new HashMap<String,AA>();
+			c = new HashMap<String,Object>();
+			a.put("k1", new A().init());
+			b.put("k2", new A().init());
+			c.put("k3", new A().init());
+			return this;
+		}
+	}
+
+	public static interface IA {
+		public String getA();
+		public void setA(String a);
+	}
+
+	public static abstract class AA implements IA {}
+
+	@Bean(typeName="A")
+	public static class A extends AA {
+		private String a;
+
+		@Override
+		public String getA() {
+			return a;
+		}
+
+		@Override
+		public void setA(String a) {
+			this.a = a;
+		}
+
+		A init() {
+			this.a = "foo";
+			return this;
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e4dfdf81/juneau-core-test/src/test/java/org/apache/juneau/html/CommonParserTest.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/html/CommonParserTest.java b/juneau-core-test/src/test/java/org/apache/juneau/html/CommonParserTest.java
new file mode 100755
index 0000000..abc948f
--- /dev/null
+++ b/juneau-core-test/src/test/java/org/apache/juneau/html/CommonParserTest.java
@@ -0,0 +1,164 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.html;
+
+import static org.apache.juneau.BeanContext.*;
+import static org.apache.juneau.serializer.SerializerContext.*;
+import static org.junit.Assert.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.parser.*;
+import org.junit.*;
+
+@SuppressWarnings({"rawtypes","serial","javadoc"})
+public class CommonParserTest {
+
+	//====================================================================================================
+	// testFromSerializer
+	//====================================================================================================
+	@Test
+	public void testFromSerializer() throws Exception {
+		ReaderParser p = HtmlParser.DEFAULT.clone().setClassLoader(getClass().getClassLoader()).addToDictionary(A1.class);
+		Map m = null;
+		String in;
+
+		in = "<table _type='object'><tr><th><string>key</string></th><th><string>value</string></th></tr><tr><td><string>a</string></td><td><number>1</number></td></tr></table>";
+		m = (Map)p.parse(in, Object.class);
+		assertEquals(1, m.get("a"));
+
+		in = "<table _type='object'><tr><th><string>key</string></th><th><string>value</string></th></tr><tr><td><string>a</string></td><td><number>1</number></td></tr><tr><td><string>b</string></td><td><string>foo bar</string></td></tr></table>";
+		m = (Map)p.parse(in, Object.class);
+		assertEquals(1, m.get("a"));
+		assertEquals("foo bar", m.get("b"));
+
+		in = "<table _type='object'><tr><th><string>key</string></th><th><string>value</string></th></tr><tr><td><string>a</string></td><td><number>1</number></td></tr><tr><td><string>b</string></td><td><string>foo bar</string></td></tr><tr><td><string>c</string></td><td><boolean>false</boolean></td></tr></table>";
+		m = (Map)p.parse(in, Object.class);
+		assertEquals(1, m.get("a"));
+		assertEquals(false, m.get("c"));
+
+		in = " <table _type='object'> <tr> <th> <string> key </string> </th> <th> <string> value </string> </th> </tr> <tr> <td> <string> a </string> </td> <td> <number> 1 </number> </td> </tr> <tr> <td> <string> b </string> </td> <td> <string> foo </string> </td> </tr> <tr> <td> <string> c </string> </td> <td> <boolean> false </boolean> </td> </tr> </table> ";
+		m = (Map)p.parse(in, Object.class);
+		assertEquals(1, m.get("a"));
+		assertEquals("foo", m.get("b"));
+		assertEquals(false, m.get("c"));
+
+		in = "<table _type='array'><tr><th>attribute</th></tr><tr><td><string>value</string></td></tr><tr><td><string>value</string></td></tr></table>";
+		ObjectList jl = (ObjectList)p.parse(in, Object.class);
+		assertEquals("value", jl.getObjectMap(0).getString("attribute"));
+		assertEquals("value", jl.getObjectMap(1).getString("attribute"));
+
+		A1 t1 = new A1();
+		A2 t2 = new A2();
+		t2.add(new A3("name0","value0"));
+		t2.add(new A3("name1","value1"));
+		t1.list = t2;
+		in = new HtmlSerializer().setProperty(SERIALIZER_addBeanTypeProperties, true).serialize(t1);
+		t1 = (A1)p.parse(in, Object.class);
+		assertEquals("value1", t1.list.get(1).value);
+
+		in = HtmlSerializer.DEFAULT.serialize(t1);
+		t1 = p.parse(in, A1.class);
+		assertEquals("value1", t1.list.get(1).value);
+	}
+
+	@Bean(typeName="A1")
+	public static class A1 {
+		public A2 list;
+	}
+
+	public static class A2 extends LinkedList<A3> {
+	}
+
+	public static class A3 {
+		public String name, value;
+		public A3(){}
+		public A3(String name, String value) {
+			this.name = name;
+			this.value = value;
+		}
+	}
+
+	//====================================================================================================
+	// Correct handling of unknown properties.
+	//====================================================================================================
+	@Test
+	public void testCorrectHandlingOfUnknownProperties() throws Exception {
+		ReaderParser p = new HtmlParser().setProperty(BEAN_ignoreUnknownBeanProperties, true);
+		B t;
+
+		String in = "<table _type='object'><tr><th><string>key</string></th><th><string>value</string></th></tr><tr><td><string>a</string></td><td><number>1</number></td></tr><tr><td><string>unknown</string></td><td><number>1</number></td></tr><tr><td><string>b</string></td><td><number>2</number></td></tr></table>";
+		t = p.parse(in, B.class);
+		assertEquals(t.a, 1);
+		assertEquals(t.b, 2);
+
+		try {
+			p = new HtmlParser();
+			p.parse(in, B.class);
+			fail("Exception expected");
+		} catch (ParseException e) {}
+	}
+
+	public static class B {
+		public int a, b;
+	}
+
+	//====================================================================================================
+	// Writing to Collection properties with no setters.
+	//====================================================================================================
+	@Test
+	public void testCollectionPropertiesWithNoSetters() throws Exception {
+		ReaderParser p = HtmlParser.DEFAULT;
+
+		String in = "<table _type='object'><tr><th><string>key</string></th><th><string>value</string></th></tr><tr><td><string>ints</string></td><td><ul><li><number>1</number></li><li><number>2</number></li><li><number>3</number></li></ul></td></tr><tr><td><string>beans</string></td><td><table _type='array'><tr><th>a</th><th>b</th></tr><tr><td><number>1</number></td><td><number>2</number></td></tr></table></td></tr></table>";
+		C t = p.parse(in, C.class);
+		assertEquals(t.getInts().size(), 3);
+		assertEquals(t.getBeans().get(0).b, 2);
+	}
+
+	public static class C {
+		private Collection<Integer> ints = new LinkedList<Integer>();
+		private List<B> beans = new LinkedList<B>();
+		public Collection<Integer> getInts() {
+			return ints;
+		}
+		public List<B> getBeans() {
+			return beans;
+		}
+	}
+
+	//====================================================================================================
+	// Parser listeners.
+	//====================================================================================================
+	@Test
+	public void testParserListeners() throws Exception {
+		final List<String> events = new LinkedList<String>();
+		HtmlParser p = new HtmlParser().setProperty(BEAN_ignoreUnknownBeanProperties, true);
+		p.addListener(
+			new ParserListener() {
+				@Override /* ParserListener */
+				public <T> void onUnknownProperty(String propertyName, Class<T> beanClass, T bean, int line, int col) {
+					events.add(propertyName + "," + line + "," + col);
+				}
+			}
+		);
+
+		String in = "<table _type='object'><tr><th><string>key</string></th><th><string>value</string></th></tr><tr><td><string>a</string></td><td><number>1</number></td></tr><tr><td><string>unknown</string></td><td><string>/foo</string></td></tr><tr><td><string>b</string></td><td><number>2</number></td></tr></table>";
+		p.parse(in, B.class);
+		assertEquals(1, events.size());
+		assertEquals("unknown,-1,-1", events.get(0));
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e4dfdf81/juneau-core-test/src/test/java/org/apache/juneau/html/CommonTest.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/html/CommonTest.java b/juneau-core-test/src/test/java/org/apache/juneau/html/CommonTest.java
new file mode 100755
index 0000000..e5f316d
--- /dev/null
+++ b/juneau-core-test/src/test/java/org/apache/juneau/html/CommonTest.java
@@ -0,0 +1,675 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.html;
+
+import static org.apache.juneau.BeanContext.*;
+import static org.apache.juneau.TestUtils.*;
+import static org.apache.juneau.html.HtmlSerializerContext.*;
+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.serializer.*;
+import org.apache.juneau.testbeans.*;
+import org.apache.juneau.xml.*;
+import org.junit.*;
+
+@SuppressWarnings({"serial","javadoc"})
+public class CommonTest {
+
+	//====================================================================================================
+	// Trim nulls from beans
+	//====================================================================================================
+	@Test
+	public void testTrimNullsFromBeans() throws Exception {
+		HtmlSerializer s = new HtmlSerializer.Sq().setProperty(HTML_addKeyValueTableHeaders, true);
+		HtmlParser p = HtmlParser.DEFAULT;
+		A t1 = A.create(), t2;
+
+		s.setProperty(SERIALIZER_trimNullProperties, false);
+		String r = s.serialize(t1);
+		assertEquals("<table><tr><th>key</th><th>value</th></tr><tr><td>s1</td><td><null/></td></tr><tr><td>s2</td><td>s2</td></tr></table>", r);
+		t2 = p.parse(r, A.class);
+		assertEqualObjects(t1, t2);
+
+		s.setProperty(SERIALIZER_trimNullProperties, true);
+		r = s.serialize(t1);
+		assertEquals("<table><tr><th>key</th><th>value</th></tr><tr><td>s2</td><td>s2</td></tr></table>", 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 {
+		HtmlSerializer s = new HtmlSerializer.Sq().setProperty(HTML_addKeyValueTableHeaders, true);
+		HtmlParser p = HtmlParser.DEFAULT;
+		B t1 = B.create(), t2;
+		String r;
+
+		s.setProperty(SERIALIZER_trimEmptyMaps, false);
+		r = s.serialize(t1);
+		assertEquals("<table><tr><th>key</th><th>value</th></tr><tr><td>f1</td><td><table><tr><th>key</th><th>value</th></tr></table></td></tr><tr><td>f2</td><td><table><tr><th>key</th><th>value</th></tr><tr><td>f2a</td><td><null/></td></tr><tr><td>f2b</td><td><table><tr><th>key</th><th>value</th></tr><tr><td>s2</td><td>s2</td></tr></table></td></tr></table></td></tr></table>", r);
+		t2 = p.parse(r, B.class);
+		assertEqualObjects(t1, t2);
+
+		s.setProperty(SERIALIZER_trimEmptyMaps, true);
+		r = s.serialize(t1);
+		assertEquals("<table><tr><th>key</th><th>value</th></tr><tr><td>f2</td><td><table><tr><th>key</th><th>value</th></tr><tr><td>f2a</td><td><null/></td></tr><tr><td>f2b</td><td><table><tr><th>key</th><th>value</th></tr><tr><td>s2</td><td>s2</td></tr></table></td></tr></table></td></tr></table>", 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 {
+		HtmlSerializer s = new HtmlSerializer.Sq().setProperty(HTML_addKeyValueTableHeaders, true);
+		HtmlParser p = HtmlParser.DEFAULT;
+		C t1 = C.create(), t2;
+		String r;
+
+		s.setProperty(SERIALIZER_trimEmptyCollections, false);
+		r = s.serialize(t1);
+		assertEquals("<table><tr><th>key</th><th>value</th></tr><tr><td>f1</td><td><ul></ul></td></tr><tr><td>f2</td><td><table _type='array'><tr><th>s2</th></tr><tr><null/></tr><tr><td>s2</td></tr></table></td></tr></table>", r);
+		t2 = p.parse(r, C.class);
+		assertEqualObjects(t1, t2);
+
+		s.setProperty(SERIALIZER_trimEmptyCollections, true);
+		r = s.serialize(t1);
+		assertEquals("<table><tr><th>key</th><th>value</th></tr><tr><td>f2</td><td><table _type='array'><tr><th>s2</th></tr><tr><null/></tr><tr><td>s2</td></tr></table></td></tr></table>", 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 {
+		HtmlSerializer s = new HtmlSerializer.Sq().setProperty(HTML_addKeyValueTableHeaders, true);
+		HtmlParser p = HtmlParser.DEFAULT;
+		D t1 = D.create(), t2;
+		String r;
+
+		s.setProperty(SERIALIZER_trimEmptyCollections, false);
+		r = s.serialize(t1);
+		assertEquals(
+			"<table>"
+				+"<tr><th>key</th><th>value</th></tr>"
+				+"<tr>"
+					+"<td>f1</td>"
+					+"<td><ul></ul></td>"
+				+"</tr>"
+				+"<tr>"
+					+"<td>f2</td>"
+					+"<td>"
+						+"<table _type='array'>"
+							+"<tr><th>s2</th></tr>"
+							+"<tr><null/></tr>"
+							+"<tr><td>s2</td></tr>"
+						+"</table>"
+					+"</td>"
+				+"</tr>"
+			+"</table>",
+			r);
+		t2 = p.parse(r, D.class);
+		assertEqualObjects(t1, t2);
+
+		s.setProperty(SERIALIZER_trimEmptyCollections, true);
+		r = s.serialize(t1);
+		assertEquals(
+			"<table>"
+				+"<tr><th>key</th><th>value</th></tr>"
+				+"<tr>"
+					+"<td>f2</td>"
+					+"<td>"
+						+"<table _type='array'>"
+							+"<tr><th>s2</th></tr>"
+							+"<tr><null/></tr>"
+							+"<tr><td>s2</td></tr>"
+						+"</table>"
+					+"</td>"
+				+"</tr>"
+			+"</table>",
+			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 testBeanPropertyProperties() throws Exception {
+		HtmlSerializer s = HtmlSerializer.DEFAULT_SQ.clone().setProperty(HTML_addKeyValueTableHeaders, true);
+		E1 t = new E1();
+		String r;
+
+		r = s.serialize(t);
+		assertEquals(
+			"<table>"
+				+"<tr>"
+					+"<th>key</th>"
+					+"<th>value</th>"
+				+"</tr>"
+				+"<tr>"
+					+"<td>x1</td>"
+					+"<td>"
+						+"<table>"
+							+"<tr><th>key</th><th>value</th></tr>"
+							+"<tr><td>f1</td><td>1</td></tr>"
+						+"</table>"
+					+"</td>"
+				+"</tr>"
+				+"<tr>"
+					+"<td>x2</td>"
+					+"<td>"
+						+"<table>"
+							+"<tr><th>key</th><th>value</th></tr>"
+							+"<tr><td>f1</td><td>3</td></tr>"
+						+"</table>"
+					+"</td>"
+				+"</tr>"
+				+"<tr>"
+					+"<td>x3</td>"
+					+"<td>"
+						+"<table _type='array'>"
+							+"<tr><th>f1</th></tr>"
+							+"<tr><td>1</td></tr>"
+						+"</table>"
+					+"</td>"
+				+"</tr>"
+				+"<tr>"
+					+"<td>x4</td>"
+					+"<td>"
+						+"<table _type='array'>"
+							+"<tr><th>f1</th></tr>"
+							+"<tr><td>1</td></tr>"
+						+"</table>"
+					+"</td>"
+				+"</tr>"
+				+"<tr>"
+					+"<td>x5</td>"
+					+"<td>"
+						+"<table _type='array'>"
+							+"<tr><th>f1</th></tr>"
+							+"<tr><td><number>5</number></td></tr>"
+						+"</table>"
+					+"</td>"
+				+"</tr>"
+				+"<tr>"
+					+"<td>x6</td>"
+					+"<td>"
+						+"<table _type='array'>"
+							+"<tr><th>f1</th></tr>"
+							+"<tr><td><number>7</number></td></tr>"
+						+"</table>"
+					+"</td>"
+				+"</tr>"
+			+"</table>",
+		r);
+		r = s.getSchemaSerializer().serialize(new E1());
+		assertTrue(r.indexOf("f2") == -1);
+	}
+
+	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",3); put("f2",4);
+		}};
+		@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",5).append("f2",6)};
+		@BeanProperty(properties="f1") public List<ObjectMap> x6 = new LinkedList<ObjectMap>() {{
+			add(new ObjectMap().append("f1",7).append("f2",8));
+		}};
+	}
+
+	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 {
+		HtmlSerializer s = HtmlSerializer.DEFAULT_SQ;
+		List<F> l = new LinkedList<F>();
+		F t = new F();
+		t.x1.add(new F());
+		l.add(t);
+		String html = s.serialize(l);
+		assertEquals(
+			"<table _type='array'>"
+				+"<tr><th>x1</th><th>x2</th></tr>"
+				+"<tr>"
+					+"<td>"
+						+"<table _type='array'>"
+							+"<tr><th>x2</th></tr>"
+							+"<tr><td>2</td></tr>"
+						+"</table>"
+					+"</td>"
+					+"<td>2</td>"
+				+"</tr>"
+			+"</table>", html);
+	}
+
+	public static class F {
+		@BeanProperty(properties="x2") public List<F> x1 = new LinkedList<F>();
+		public int x2 = 2;
+	}
+
+	//====================================================================================================
+	// Test that URLs and URIs are serialized and parsed correctly.
+	//====================================================================================================
+	@Test
+	public void testURIAttr() throws Exception {
+		HtmlSerializer s = HtmlSerializer.DEFAULT_SQ;
+		HtmlParser p = HtmlParser.DEFAULT;
+
+		G t = new G();
+		t.uri = new URI("http://uri");
+		t.f1 = new URI("http://f1");
+		t.f2 = new URL("http://f2");
+
+		String html = s.serialize(t);
+		t = p.parse(html, 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 {
+		HtmlSerializer s = new HtmlSerializer.Sq().setProperty(HTML_uriAnchorText, PROPERTY_NAME).setProperty(SERIALIZER_useIndentation, false).setProperty(HTML_addKeyValueTableHeaders, true);
+		TestURI t = new TestURI();
+		String r;
+		String expected;
+
+		s.setProperty(SERIALIZER_relativeUriBase, null);
+		r = strip(s.serialize(t));
+		expected = ""
+			+"\n[f0]=<a href='f0/x0'>f0</a>"
+			+"\n[f1]=<a href='f1/x1'>f1</a>"
+			+"\n[f2]=<a href='/f2/x2'>f2</a>"
+			+"\n[f3]=<a href='http://www.apache.org/f3/x3'>f3</a>"
+			+"\n[f4]=<a href='f4/x4'>f4</a>"
+			+"\n[f5]=<a href='/f5/x5'>f5</a>"
+			+"\n[f6]=<a href='http://www.apache.org/f6/x6'>f6</a>"
+			+"\n[f7]=<a href='http://www.apache.org/f7/x7'>f7</a>"
+			+"\n[f8]=<a href='f8/x8'>f8</a>"
+			+"\n[f9]=<a href='f9/x9'>f9</a>"
+			+"\n[fa]=<a href='http://www.apache.org/fa/xa#MY_LABEL'>fa</a>"
+			+"\n[fb]=<a href='http://www.apache.org/fb/xb?label=MY_LABEL&foo=bar'>MY_LABEL</a>"
+			+"\n[fc]=<a href='http://www.apache.org/fc/xc?foo=bar&label=MY_LABEL'>MY_LABEL</a>"
+			+"\n[fd]=<a href='http://www.apache.org/fd/xd?label2=MY_LABEL&foo=bar'>fd</a>"
+			+"\n[fe]=<a href='http://www.apache.org/fe/xe?foo=bar&label2=MY_LABEL'>fe</a>"
+		;
+		assertEquals(expected, r);
+
+		s.setProperty(SERIALIZER_relativeUriBase, "");  // Same as null.
+		r = strip(s.serialize(t));
+		expected = ""
+			+"\n[f0]=<a href='f0/x0'>f0</a>"
+			+"\n[f1]=<a href='f1/x1'>f1</a>"
+			+"\n[f2]=<a href='/f2/x2'>f2</a>"
+			+"\n[f3]=<a href='http://www.apache.org/f3/x3'>f3</a>"
+			+"\n[f4]=<a href='f4/x4'>f4</a>"
+			+"\n[f5]=<a href='/f5/x5'>f5</a>"
+			+"\n[f6]=<a href='http://www.apache.org/f6/x6'>f6</a>"
+			+"\n[f7]=<a href='http://www.apache.org/f7/x7'>f7</a>"
+			+"\n[f8]=<a href='f8/x8'>f8</a>"
+			+"\n[f9]=<a href='f9/x9'>f9</a>"
+			+"\n[fa]=<a href='http://www.apache.org/fa/xa#MY_LABEL'>fa</a>"
+			+"\n[fb]=<a href='http://www.apache.org/fb/xb?label=MY_LABEL&foo=bar'>MY_LABEL</a>"
+			+"\n[fc]=<a href='http://www.apache.org/fc/xc?foo=bar&label=MY_LABEL'>MY_LABEL</a>"
+			+"\n[fd]=<a href='http://www.apache.org/fd/xd?label2=MY_LABEL&foo=bar'>fd</a>"
+			+"\n[fe]=<a href='http://www.apache.org/fe/xe?foo=bar&label2=MY_LABEL'>fe</a>"
+		;
+		assertEquals(expected, r);
+
+		s.setProperty(SERIALIZER_relativeUriBase, "/cr");
+		r = strip(s.serialize(t));
+		expected = ""
+			+"\n[f0]=<a href='/cr/f0/x0'>f0</a>"
+			+"\n[f1]=<a href='/cr/f1/x1'>f1</a>"
+			+"\n[f2]=<a href='/f2/x2'>f2</a>"
+			+"\n[f3]=<a href='http://www.apache.org/f3/x3'>f3</a>"
+			+"\n[f4]=<a href='/cr/f4/x4'>f4</a>"
+			+"\n[f5]=<a href='/f5/x5'>f5</a>"
+			+"\n[f6]=<a href='http://www.apache.org/f6/x6'>f6</a>"
+			+"\n[f7]=<a href='http://www.apache.org/f7/x7'>f7</a>"
+			+"\n[f8]=<a href='/cr/f8/x8'>f8</a>"
+			+"\n[f9]=<a href='/cr/f9/x9'>f9</a>"
+			+"\n[fa]=<a href='http://www.apache.org/fa/xa#MY_LABEL'>fa</a>"
+			+"\n[fb]=<a href='http://www.apache.org/fb/xb?label=MY_LABEL&foo=bar'>MY_LABEL</a>"
+			+"\n[fc]=<a href='http://www.apache.org/fc/xc?foo=bar&label=MY_LABEL'>MY_LABEL</a>"
+			+"\n[fd]=<a href='http://www.apache.org/fd/xd?label2=MY_LABEL&foo=bar'>fd</a>"
+			+"\n[fe]=<a href='http://www.apache.org/fe/xe?foo=bar&label2=MY_LABEL'>fe</a>"
+		;
+		assertEquals(expected, r);
+
+		s.setProperty(SERIALIZER_relativeUriBase, "/cr/");  // Same as above
+		r = strip(s.serialize(t));
+		expected = ""
+			+"\n[f0]=<a href='/cr/f0/x0'>f0</a>"
+			+"\n[f1]=<a href='/cr/f1/x1'>f1</a>"
+			+"\n[f2]=<a href='/f2/x2'>f2</a>"
+			+"\n[f3]=<a href='http://www.apache.org/f3/x3'>f3</a>"
+			+"\n[f4]=<a href='/cr/f4/x4'>f4</a>"
+			+"\n[f5]=<a href='/f5/x5'>f5</a>"
+			+"\n[f6]=<a href='http://www.apache.org/f6/x6'>f6</a>"
+			+"\n[f7]=<a href='http://www.apache.org/f7/x7'>f7</a>"
+			+"\n[f8]=<a href='/cr/f8/x8'>f8</a>"
+			+"\n[f9]=<a href='/cr/f9/x9'>f9</a>"
+			+"\n[fa]=<a href='http://www.apache.org/fa/xa#MY_LABEL'>fa</a>"
+			+"\n[fb]=<a href='http://www.apache.org/fb/xb?label=MY_LABEL&foo=bar'>MY_LABEL</a>"
+			+"\n[fc]=<a href='http://www.apache.org/fc/xc?foo=bar&label=MY_LABEL'>MY_LABEL</a>"
+			+"\n[fd]=<a href='http://www.apache.org/fd/xd?label2=MY_LABEL&foo=bar'>fd</a>"
+			+"\n[fe]=<a href='http://www.apache.org/fe/xe?foo=bar&label2=MY_LABEL'>fe</a>"
+		;
+		assertEquals(expected, r);
+
+		s.setProperty(SERIALIZER_relativeUriBase, "/");
+		r = strip(s.serialize(t));
+		expected = ""
+			+"\n[f0]=<a href='/f0/x0'>f0</a>"
+			+"\n[f1]=<a href='/f1/x1'>f1</a>"
+			+"\n[f2]=<a href='/f2/x2'>f2</a>"
+			+"\n[f3]=<a href='http://www.apache.org/f3/x3'>f3</a>"
+			+"\n[f4]=<a href='/f4/x4'>f4</a>"
+			+"\n[f5]=<a href='/f5/x5'>f5</a>"
+			+"\n[f6]=<a href='http://www.apache.org/f6/x6'>f6</a>"
+			+"\n[f7]=<a href='http://www.apache.org/f7/x7'>f7</a>"
+			+"\n[f8]=<a href='/f8/x8'>f8</a>"
+			+"\n[f9]=<a href='/f9/x9'>f9</a>"
+			+"\n[fa]=<a href='http://www.apache.org/fa/xa#MY_LABEL'>fa</a>"
+			+"\n[fb]=<a href='http://www.apache.org/fb/xb?label=MY_LABEL&foo=bar'>MY_LABEL</a>"
+			+"\n[fc]=<a href='http://www.apache.org/fc/xc?foo=bar&label=MY_LABEL'>MY_LABEL</a>"
+			+"\n[fd]=<a href='http://www.apache.org/fd/xd?label2=MY_LABEL&foo=bar'>fd</a>"
+			+"\n[fe]=<a href='http://www.apache.org/fe/xe?foo=bar&label2=MY_LABEL'>fe</a>"
+		;
+		assertEquals(expected, r);
+
+		s.setProperty(SERIALIZER_relativeUriBase, null);
+
+		s.setProperty(SERIALIZER_absolutePathUriBase, "http://foo");
+		r = strip(s.serialize(t));
+		expected = ""
+			+"\n[f0]=<a href='f0/x0'>f0</a>"
+			+"\n[f1]=<a href='f1/x1'>f1</a>"
+			+"\n[f2]=<a href='http://foo/f2/x2'>f2</a>"
+			+"\n[f3]=<a href='http://www.apache.org/f3/x3'>f3</a>"
+			+"\n[f4]=<a href='f4/x4'>f4</a>"
+			+"\n[f5]=<a href='http://foo/f5/x5'>f5</a>"
+			+"\n[f6]=<a href='http://www.apache.org/f6/x6'>f6</a>"
+			+"\n[f7]=<a href='http://www.apache.org/f7/x7'>f7</a>"
+			+"\n[f8]=<a href='f8/x8'>f8</a>"
+			+"\n[f9]=<a href='f9/x9'>f9</a>"
+			+"\n[fa]=<a href='http://www.apache.org/fa/xa#MY_LABEL'>fa</a>"
+			+"\n[fb]=<a href='http://www.apache.org/fb/xb?label=MY_LABEL&foo=bar'>MY_LABEL</a>"
+			+"\n[fc]=<a href='http://www.apache.org/fc/xc?foo=bar&label=MY_LABEL'>MY_LABEL</a>"
+			+"\n[fd]=<a href='http://www.apache.org/fd/xd?label2=MY_LABEL&foo=bar'>fd</a>"
+			+"\n[fe]=<a href='http://www.apache.org/fe/xe?foo=bar&label2=MY_LABEL'>fe</a>"
+		;
+		assertEquals(expected, r);
+
+		s.setProperty(SERIALIZER_absolutePathUriBase, "http://foo/");
+		r = strip(s.serialize(t));
+		expected = ""
+			+"\n[f0]=<a href='f0/x0'>f0</a>"
+			+"\n[f1]=<a href='f1/x1'>f1</a>"
+			+"\n[f2]=<a href='http://foo/f2/x2'>f2</a>"
+			+"\n[f3]=<a href='http://www.apache.org/f3/x3'>f3</a>"
+			+"\n[f4]=<a href='f4/x4'>f4</a>"
+			+"\n[f5]=<a href='http://foo/f5/x5'>f5</a>"
+			+"\n[f6]=<a href='http://www.apache.org/f6/x6'>f6</a>"
+			+"\n[f7]=<a href='http://www.apache.org/f7/x7'>f7</a>"
+			+"\n[f8]=<a href='f8/x8'>f8</a>"
+			+"\n[f9]=<a href='f9/x9'>f9</a>"
+			+"\n[fa]=<a href='http://www.apache.org/fa/xa#MY_LABEL'>fa</a>"
+			+"\n[fb]=<a href='http://www.apache.org/fb/xb?label=MY_LABEL&foo=bar'>MY_LABEL</a>"
+			+"\n[fc]=<a href='http://www.apache.org/fc/xc?foo=bar&label=MY_LABEL'>MY_LABEL</a>"
+			+"\n[fd]=<a href='http://www.apache.org/fd/xd?label2=MY_LABEL&foo=bar'>fd</a>"
+			+"\n[fe]=<a href='http://www.apache.org/fe/xe?foo=bar&label2=MY_LABEL'>fe</a>"
+		;
+		assertEquals(expected, r);
+
+		s.setProperty(SERIALIZER_absolutePathUriBase, "");  // Same as null.
+		r = strip(s.serialize(t));
+		expected = ""
+			+"\n[f0]=<a href='f0/x0'>f0</a>"
+			+"\n[f1]=<a href='f1/x1'>f1</a>"
+			+"\n[f2]=<a href='/f2/x2'>f2</a>"
+			+"\n[f3]=<a href='http://www.apache.org/f3/x3'>f3</a>"
+			+"\n[f4]=<a href='f4/x4'>f4</a>"
+			+"\n[f5]=<a href='/f5/x5'>f5</a>"
+			+"\n[f6]=<a href='http://www.apache.org/f6/x6'>f6</a>"
+			+"\n[f7]=<a href='http://www.apache.org/f7/x7'>f7</a>"
+			+"\n[f8]=<a href='f8/x8'>f8</a>"
+			+"\n[f9]=<a href='f9/x9'>f9</a>"
+			+"\n[fa]=<a href='http://www.apache.org/fa/xa#MY_LABEL'>fa</a>"
+			+"\n[fb]=<a href='http://www.apache.org/fb/xb?label=MY_LABEL&foo=bar'>MY_LABEL</a>"
+			+"\n[fc]=<a href='http://www.apache.org/fc/xc?foo=bar&label=MY_LABEL'>MY_LABEL</a>"
+			+"\n[fd]=<a href='http://www.apache.org/fd/xd?label2=MY_LABEL&foo=bar'>fd</a>"
+			+"\n[fe]=<a href='http://www.apache.org/fe/xe?foo=bar&label2=MY_LABEL'>fe</a>"
+		;
+		assertEquals(expected, r);
+	}
+
+	private String strip(String html) {
+		return html
+			.replace("<table><tr><th>key</th><th>value</th></tr>", "")
+			.replace("</table>", "")
+			.replace("<tr><td>", "\n[")
+			.replace("</td><td>", "]=")
+			.replace("</td></tr>", "");
+	}
+
+	//====================================================================================================
+	// Validate that you cannot update properties on locked serializer.
+	//====================================================================================================
+	@Test
+	public void testLockedSerializer() throws Exception {
+		HtmlSerializer s = new HtmlSerializer().lock();
+		try {
+			s.setProperty(XmlSerializerContext.XML_enableNamespaces, 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 {
+		HtmlSerializer s = new HtmlSerializer.Sq().setProperty(HTML_addKeyValueTableHeaders, true);
+
+		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]<noname>:org.apache.juneau.html.CommonTest$R1"));
+			assertTrue(msg.contains("->[1]r2:org.apache.juneau.html.CommonTest$R2"));
+			assertTrue(msg.contains("->[2]r3:org.apache.juneau.html.CommonTest$R3"));
+			assertTrue(msg.contains("->[3]r1:org.apache.juneau.html.CommonTest$R1"));
+		}
+
+		s.setProperty(SERIALIZER_ignoreRecursions, true);
+		assertEquals("<table><tr><th>key</th><th>value</th></tr><tr><td>name</td><td>foo</td></tr><tr><td>r2</td><td><table><tr><th>key</th><th>value</th></tr><tr><td>name</td><td>bar</td></tr><tr><td>r3</td><td><table><tr><th>key</th><th>value</th></tr><tr><td>name</td><td>baz</td></tr></table></td></tr></table></td></tr></table>",
+			s.serialize(r1));
+
+		// Make sure this doesn't blow up.
+		s.getSchemaSerializer().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;
+	}
+
+	//====================================================================================================
+	// Basic bean
+	//====================================================================================================
+	@Test
+	public void testBasicBean() throws Exception {
+		WriterSerializer s = new HtmlSerializer.Sq().setProperty(SERIALIZER_trimNullProperties, false).setProperty(BEAN_sortProperties, true).setProperty(HTML_addKeyValueTableHeaders, true);
+
+		J a = new J();
+		a.setF1("J");
+		a.setF2(100);
+		a.setF3(true);
+		assertEquals(
+			"<table>"
+				+"<tr><th>key</th><th>value</th></tr>"
+				+"<tr><td>f1</td><td>J</td></tr>"
+				+"<tr><td>f2</td><td>100</td></tr>"
+				+"<tr><td>f3</td><td>true</td></tr>"
+			+"</table>",
+			s.serialize(a));
+	}
+
+	public static class J {
+		private String f1 = null;
+		private int f2 = -1;
+		private boolean f3 = false;
+
+		public String getF1() {
+			return this.f1;
+		}
+
+		public void setF1(String f1) {
+			this.f1 = f1;
+		}
+
+		public int getF2() {
+			return this.f2;
+		}
+
+		public void setF2(int f2) {
+			this.f2 = f2;
+		}
+
+		public boolean isF3() {
+			return this.f3;
+		}
+
+		public void setF3(boolean f3) {
+			this.f3 = f3;
+		}
+
+		@Override /* Object */
+		public String toString() {
+			return ("J(f1: " + this.getF1() + ", f2: " + this.getF2() + ")");
+		}
+	}
+}


Mime
View raw message