juneau-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jamesbog...@apache.org
Subject [juneau] branch master updated: Support for POJO builders.
Date Mon, 29 Jan 2018 02:14:04 GMT
This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new a65aedf  Support for POJO builders.
a65aedf is described below

commit a65aedfdace05de4aae83e8c963bdb798a52ee72
Author: JamesBognar <jamesbognar@apache.org>
AuthorDate: Sun Jan 28 21:13:22 2018 -0500

    Support for POJO builders.
---
 .../a/rttests/RoundTripBeanInheritanceTest.java    |  11 +-
 .../apache/juneau/transforms/BuilderComboTest.java | 446 +++++++++++++++++++++
 .../org/apache/juneau/jena/RdfParserSession.java   |  15 +-
 .../src/main/java/org/apache/juneau/BeanMap.java   |  30 +-
 .../src/main/java/org/apache/juneau/BeanMeta.java  |  34 +-
 .../java/org/apache/juneau/BeanPropertyMeta.java   |  39 +-
 .../src/main/java/org/apache/juneau/ClassMeta.java |  25 +-
 .../java/org/apache/juneau/annotation/Builder.java |  71 ++++
 .../apache/juneau/csv/CsvSerializerSession.java    |  16 +-
 .../org/apache/juneau/html/HtmlParserSession.java  |  18 +-
 .../apache/juneau/html/HtmlSerializerSession.java  |  48 +--
 .../org/apache/juneau/internal/ClassUtils.java     |  41 +-
 .../org/apache/juneau/json/JsonParserSession.java  |  12 +-
 .../juneau/json/JsonSchemaSerializerSession.java   |   3 +-
 .../apache/juneau/json/JsonSerializerSession.java  |  28 +-
 .../juneau/msgpack/MsgPackParserSession.java       |  15 +-
 .../juneau/msgpack/MsgPackSerializerSession.java   |  20 +-
 .../main/java/org/apache/juneau/parser/Parser.java |   8 +-
 .../java/org/apache/juneau/transform/Builder.java  |  30 ++
 .../org/apache/juneau/transform/BuilderSwap.java   | 231 +++++++++++
 .../org/apache/juneau/uon/UonParserSession.java    |  14 +-
 .../apache/juneau/uon/UonSerializerSession.java    |  28 +-
 .../urlencoding/UrlEncodingParserSession.java      |  13 +-
 .../urlencoding/UrlEncodingSerializerSession.java  |  58 +--
 .../org/apache/juneau/xml/XmlParserSession.java    |  20 +-
 .../juneau/xml/XmlSchemaSerializerSession.java     | 118 +++---
 .../apache/juneau/xml/XmlSerializerSession.java    | 138 ++++---
 .../juneau/yaml/proto/YamlParserSession.java       |  12 +-
 .../juneau/yaml/proto/YamlSerializerSession.java   |  28 +-
 juneau-doc/src/main/javadoc/overview.html          | 140 ++++++-
 30 files changed, 1398 insertions(+), 312 deletions(-)

diff --git a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanInheritanceTest.java b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanInheritanceTest.java
index dfe8dd6..925fd90 100755
--- a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanInheritanceTest.java
+++ b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanInheritanceTest.java
@@ -13,9 +13,7 @@
 package org.apache.juneau.a.rttests;
 
 import static org.apache.juneau.TestUtils.*;
-import static org.junit.Assert.*;
 
-import org.apache.juneau.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.serializer.*;
 import org.junit.*;
@@ -48,14 +46,7 @@ public class RoundTripBeanInheritanceTest extends RoundTripTest {
 
 		A3 t3 = new A3();
 		t3.init();
-		try {
-			ClassMeta<?> cm = BeanContext.DEFAULT.getClassMeta(A3.class);
-			assertEquals("No properties detected on bean class", cm.getNotABeanReason());
-			roundTrip(t3, A3.class);
-			fail("Exception expected");
-		} catch (ParseException e) {
-		} catch (SerializeException e) {
-		} catch (InvalidDataConversionException e) {}
+		roundTrip(t3, A3.class);
 	}
 
 
diff --git a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/transforms/BuilderComboTest.java b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/transforms/BuilderComboTest.java
new file mode 100644
index 0000000..e0a14cf
--- /dev/null
+++ b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/transforms/BuilderComboTest.java
@@ -0,0 +1,446 @@
+// ***************************************************************************************************************************
+// * 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.transforms;
+
+import static org.apache.juneau.TestUtils.*;
+import static org.junit.Assert.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.serializer.*;
+import org.junit.runner.*;
+import org.junit.runners.*;
+
+/**
+ * Exhaustive serialization tests for the CalendarSwap class.
+ */
+@RunWith(Parameterized.class)
+@SuppressWarnings({"javadoc"})
+public class BuilderComboTest extends ComboRoundTripTest {
+
+	@Parameterized.Parameters
+	public static Collection<Object[]> getParameters() {
+		return Arrays.asList(new Object[][] {
+			{ 	/* 0 */
+				new ComboInput<A>(
+					"A",
+					A.class,
+					new A(null).init(),
+					/* Json */		"{a:1}",
+					/* JsonT */		"{a:1}",
+					/* JsonR */		"{\n\ta: 1\n}",
+					/* Xml */		"<object><a>1</a></object>",
+					/* XmlT */		"<object><a>1</a></object>",
+					/* XmlR */		"<object>\n\t<a>1</a>\n</object>\n",
+					/* XmlNs */		"<object><a>1</a></object>",
+					/* Html */		"<table><tr><td>a</td><td>1</td></tr></table>",
+					/* HtmlT */		"<table><tr><td>a</td><td>1</td></tr></table>",
+					/* HtmlR */		"<table>\n\t<tr>\n\t\t<td>a</td>\n\t\t<td>1</td>\n\t</tr>\n</table>\n",
+					/* Uon */		"(a=1)",
+					/* UonT */		"(a=1)",
+					/* UonR */		"(\n\ta=1\n)",
+					/* UrlEnc */		"a=1",
+					/* UrlEncT */	"a=1",
+					/* UrlEncR */	"a=1",
+					/* MsgPack */	"81A16101",
+					/* MsgPackT */	"81A16101",
+					/* RdfXml */		"<rdf:RDF>\n<rdf:Description>\n<jp:a>1</jp:a>\n</rdf:Description>\n</rdf:RDF>\n",
+					/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:a>1</jp:a>\n</rdf:Description>\n</rdf:RDF>\n",
+					/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:a>1</jp:a>\n  </rdf:Description>\n</rdf:RDF>\n"
+				)
+				{
+					@Override
+					public void verify(A o) {
+						assertType(A.class, o);
+						assertTrue(o.createdByBuilder);
+					}
+				}
+			},
+			{ 	/* 1 */
+				new ComboInput<B>(
+					"B",
+					B.class,
+					new B(null).init(),
+					/* Json */		"{a:1}",
+					/* JsonT */		"{a:1}",
+					/* JsonR */		"{\n\ta: 1\n}",
+					/* Xml */		"<object><a>1</a></object>",
+					/* XmlT */		"<object><a>1</a></object>",
+					/* XmlR */		"<object>\n\t<a>1</a>\n</object>\n",
+					/* XmlNs */		"<object><a>1</a></object>",
+					/* Html */		"<table><tr><td>a</td><td>1</td></tr></table>",
+					/* HtmlT */		"<table><tr><td>a</td><td>1</td></tr></table>",
+					/* HtmlR */		"<table>\n\t<tr>\n\t\t<td>a</td>\n\t\t<td>1</td>\n\t</tr>\n</table>\n",
+					/* Uon */		"(a=1)",
+					/* UonT */		"(a=1)",
+					/* UonR */		"(\n\ta=1\n)",
+					/* UrlEnc */		"a=1",
+					/* UrlEncT */	"a=1",
+					/* UrlEncR */	"a=1",
+					/* MsgPack */	"81A16101",
+					/* MsgPackT */	"81A16101",
+					/* RdfXml */		"<rdf:RDF>\n<rdf:Description>\n<jp:a>1</jp:a>\n</rdf:Description>\n</rdf:RDF>\n",
+					/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:a>1</jp:a>\n</rdf:Description>\n</rdf:RDF>\n",
+					/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:a>1</jp:a>\n  </rdf:Description>\n</rdf:RDF>\n"
+				)
+				{
+					@Override
+					public void verify(B o) {
+						assertType(B.class, o);
+						assertTrue(o.createdByBuilder);
+					}
+				}
+			},
+			{ 	/* 2 */
+				new ComboInput<C>(
+					"C",
+					C.class,
+					new C(null).init(),
+					/* Json */		"{a:1}",
+					/* JsonT */		"{a:1}",
+					/* JsonR */		"{\n\ta: 1\n}",
+					/* Xml */		"<object><a>1</a></object>",
+					/* XmlT */		"<object><a>1</a></object>",
+					/* XmlR */		"<object>\n\t<a>1</a>\n</object>\n",
+					/* XmlNs */		"<object><a>1</a></object>",
+					/* Html */		"<table><tr><td>a</td><td>1</td></tr></table>",
+					/* HtmlT */		"<table><tr><td>a</td><td>1</td></tr></table>",
+					/* HtmlR */		"<table>\n\t<tr>\n\t\t<td>a</td>\n\t\t<td>1</td>\n\t</tr>\n</table>\n",
+					/* Uon */		"(a=1)",
+					/* UonT */		"(a=1)",
+					/* UonR */		"(\n\ta=1\n)",
+					/* UrlEnc */		"a=1",
+					/* UrlEncT */	"a=1",
+					/* UrlEncR */	"a=1",
+					/* MsgPack */	"81A16101",
+					/* MsgPackT */	"81A16101",
+					/* RdfXml */		"<rdf:RDF>\n<rdf:Description>\n<jp:a>1</jp:a>\n</rdf:Description>\n</rdf:RDF>\n",
+					/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:a>1</jp:a>\n</rdf:Description>\n</rdf:RDF>\n",
+					/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:a>1</jp:a>\n  </rdf:Description>\n</rdf:RDF>\n"
+				)
+				{
+					@Override
+					public void verify(C o) {
+						assertType(C.class, o);
+						assertTrue(o.createdByBuilder);
+					}
+				}
+			},
+			{ 	/* 3 */
+				new ComboInput<D>(
+					"D",
+					D.class,
+					new D(null).init(),
+					/* Json */		"{a:1}",
+					/* JsonT */		"{a:1}",
+					/* JsonR */		"{\n\ta: 1\n}",
+					/* Xml */		"<object><a>1</a></object>",
+					/* XmlT */		"<object><a>1</a></object>",
+					/* XmlR */		"<object>\n\t<a>1</a>\n</object>\n",
+					/* XmlNs */		"<object><a>1</a></object>",
+					/* Html */		"<table><tr><td>a</td><td>1</td></tr></table>",
+					/* HtmlT */		"<table><tr><td>a</td><td>1</td></tr></table>",
+					/* HtmlR */		"<table>\n\t<tr>\n\t\t<td>a</td>\n\t\t<td>1</td>\n\t</tr>\n</table>\n",
+					/* Uon */		"(a=1)",
+					/* UonT */		"(a=1)",
+					/* UonR */		"(\n\ta=1\n)",
+					/* UrlEnc */		"a=1",
+					/* UrlEncT */	"a=1",
+					/* UrlEncR */	"a=1",
+					/* MsgPack */	"81A16101",
+					/* MsgPackT */	"81A16101",
+					/* RdfXml */		"<rdf:RDF>\n<rdf:Description>\n<jp:a>1</jp:a>\n</rdf:Description>\n</rdf:RDF>\n",
+					/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:a>1</jp:a>\n</rdf:Description>\n</rdf:RDF>\n",
+					/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:a>1</jp:a>\n  </rdf:Description>\n</rdf:RDF>\n"
+				)
+				{
+					@Override
+					public void verify(D o) {
+						assertType(D.class, o);
+						assertTrue(o.createdByBuilder);
+					}
+				}
+			},
+			{ 	/* 4 */
+				new ComboInput<E>(
+					"E",
+					E.class,
+					new E(null).init(),
+					/* Json */		"{a:1}",
+					/* JsonT */		"{a:1}",
+					/* JsonR */		"{\n\ta: 1\n}",
+					/* Xml */		"<object><a>1</a></object>",
+					/* XmlT */		"<object><a>1</a></object>",
+					/* XmlR */		"<object>\n\t<a>1</a>\n</object>\n",
+					/* XmlNs */		"<object><a>1</a></object>",
+					/* Html */		"<table><tr><td>a</td><td>1</td></tr></table>",
+					/* HtmlT */		"<table><tr><td>a</td><td>1</td></tr></table>",
+					/* HtmlR */		"<table>\n\t<tr>\n\t\t<td>a</td>\n\t\t<td>1</td>\n\t</tr>\n</table>\n",
+					/* Uon */		"(a=1)",
+					/* UonT */		"(a=1)",
+					/* UonR */		"(\n\ta=1\n)",
+					/* UrlEnc */		"a=1",
+					/* UrlEncT */	"a=1",
+					/* UrlEncR */	"a=1",
+					/* MsgPack */	"81A16101",
+					/* MsgPackT */	"81A16101",
+					/* RdfXml */		"<rdf:RDF>\n<rdf:Description>\n<jp:a>1</jp:a>\n</rdf:Description>\n</rdf:RDF>\n",
+					/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:a>1</jp:a>\n</rdf:Description>\n</rdf:RDF>\n",
+					/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:a>1</jp:a>\n  </rdf:Description>\n</rdf:RDF>\n"
+				)
+				{
+					@Override
+					public void verify(E o) {
+						assertType(E.class, o);
+						assertTrue(o.createdByBuilder);
+					}
+				}
+			},
+			{ 	/* 5 */
+				new ComboInput<H>(
+					"H",
+					H.class,
+					new H(null).init(),
+					/* Json */		"{fooBar:1}",
+					/* JsonT */		"{fooBar:1}",
+					/* JsonR */		"{\n\tfooBar: 1\n}",
+					/* Xml */		"<object><fooBar>1</fooBar></object>",
+					/* XmlT */		"<object><fooBar>1</fooBar></object>",
+					/* XmlR */		"<object>\n\t<fooBar>1</fooBar>\n</object>\n",
+					/* XmlNs */		"<object><fooBar>1</fooBar></object>",
+					/* Html */		"<table><tr><td>fooBar</td><td>1</td></tr></table>",
+					/* HtmlT */		"<table><tr><td>fooBar</td><td>1</td></tr></table>",
+					/* HtmlR */		"<table>\n\t<tr>\n\t\t<td>fooBar</td>\n\t\t<td>1</td>\n\t</tr>\n</table>\n",
+					/* Uon */		"(fooBar=1)",
+					/* UonT */		"(fooBar=1)",
+					/* UonR */		"(\n\tfooBar=1\n)",
+					/* UrlEnc */		"fooBar=1",
+					/* UrlEncT */	"fooBar=1",
+					/* UrlEncR */	"fooBar=1",
+					/* MsgPack */	"81A6666F6F42617201",
+					/* MsgPackT */	"81A6666F6F42617201",
+					/* RdfXml */		"<rdf:RDF>\n<rdf:Description>\n<jp:fooBar>1</jp:fooBar>\n</rdf:Description>\n</rdf:RDF>\n",
+					/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:fooBar>1</jp:fooBar>\n</rdf:Description>\n</rdf:RDF>\n",
+					/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:fooBar>1</jp:fooBar>\n  </rdf:Description>\n</rdf:RDF>\n"
+				)
+				{
+					@Override
+					public void verify(H o) {
+						assertType(H.class, o);
+						assertTrue(o.createdByBuilder);
+					}
+				}
+			},
+		});
+	}
+
+	public BuilderComboTest(ComboInput<?> comboInput) {
+		super(comboInput);
+	}
+
+	@Override
+	protected Serializer applySettings(Serializer s) throws Exception {
+		return s.builder().trimNullProperties(false).build();
+	}
+
+	@Override
+	protected Parser applySettings(Parser p) throws Exception {
+		return (Parser) p.builder().build();
+	}
+
+	//--------------------------------------------------------------------------------
+	// Typical builder scenario
+	//--------------------------------------------------------------------------------
+
+	public static class A {
+		public int a;
+		boolean createdByBuilder;
+		
+		private A(ABuilder x) {
+			if (x != null)
+				this.a = x.a;
+		}
+
+		public A init() {
+			a = 1;
+			return this;
+		}
+		
+		public static ABuilder create() {
+			return new ABuilder();
+		}
+	}
+
+	public static class ABuilder {
+		public int a;
+		
+		public A build() {
+			A x = new A(this);
+			x.createdByBuilder = true;
+			return x;
+		}
+	}
+
+	//--------------------------------------------------------------------------------
+	// Builder detected through POJO constructor.
+	//--------------------------------------------------------------------------------
+	public static class B {
+		public int a;
+		boolean createdByBuilder;
+		
+		public B(BBuilder x) {
+			if (x != null) {
+				this.a = x.a;
+				createdByBuilder = true;
+			}
+		}
+
+		public B init() {
+			a = 1;
+			return this;
+		}
+	}
+
+	public static class BBuilder implements org.apache.juneau.transform.Builder<B> {
+		public int a;
+	}
+
+	//--------------------------------------------------------------------------------
+	// Same as B, but should Builder.build() method.
+	//--------------------------------------------------------------------------------
+	
+	public static class C {
+		public int a;
+		boolean createdByBuilder;
+		
+		public C(CBuilder x) {
+			if (x != null) {
+				this.a = x.a;
+			}
+		}
+
+		public C init() {
+			a = 1;
+			return this;
+		}
+	}
+
+	public static class CBuilder implements org.apache.juneau.transform.Builder<B> {
+		public int a;
+		
+		public C build() {
+			C x = new C(this);
+			x.createdByBuilder = true;
+			return x;
+		}
+	}
+
+	//--------------------------------------------------------------------------------
+	// @Builder annotation on POJO class.
+	//--------------------------------------------------------------------------------
+	
+	@org.apache.juneau.annotation.Builder(DBuilder.class)
+	public static class D {
+		public int a;
+		boolean createdByBuilder;
+		
+		public D(DBuilder x) {
+			if (x != null) {
+				this.a = x.a;
+				createdByBuilder = true;
+			}
+		}
+
+		public D init() {
+			a = 1;
+			return this;
+		}
+	}
+
+	public static class DBuilder {
+		public int a;
+	}
+
+	//--------------------------------------------------------------------------------
+	// @Builder annotation on POJO class, but uses build() method on builder.
+	//--------------------------------------------------------------------------------
+	
+	@org.apache.juneau.annotation.Builder(EBuilder.class)
+	public static class E {
+		public int a;
+		boolean createdByBuilder;
+		
+		public E(EBuilder x) {
+			if (x != null) {
+				this.a = x.a;
+			}
+		}
+
+		public E init() {
+			a = 1;
+			return this;
+		}
+	}
+
+	public static class EBuilder {
+		public int a;
+
+		public E build() {
+			E x = new E(this);
+			x.createdByBuilder = true;
+			return x;
+		}
+	}
+
+	//--------------------------------------------------------------------------------
+	// Builder with typical method setters.
+	//--------------------------------------------------------------------------------
+
+	public static class H {
+		public int fooBar;
+		boolean createdByBuilder;
+		
+		private H(HBuilder x) {
+			if (x != null)
+				this.fooBar = x.fooBar;
+		}
+
+		public H init() {
+			fooBar = 1;
+			return this;
+		}
+		
+		public static HBuilder create() {
+			return new HBuilder();
+		}
+	}
+
+	public static class HBuilder {
+		private int fooBar;
+		
+		public H build() {
+			H x = new H(this);
+			x.createdByBuilder = true;
+			return x;
+		}
+		
+		@BeanProperty
+		public HBuilder fooBar(int fooBar) {
+			this.fooBar = fooBar;
+			return this;
+		}
+	}
+}
diff --git a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfParserSession.java b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfParserSession.java
index bf43e7b..c779c57 100644
--- a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfParserSession.java
+++ b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfParserSession.java
@@ -231,7 +231,14 @@ public class RdfParserSession extends ReaderParserSession {
 		if (eType == null)
 			eType = object();
 		PojoSwap<T,Object> swap = (PojoSwap<T,Object>)eType.getPojoSwap(this);
-		ClassMeta<?> sType = swap == null ? eType : swap.getSwapClassMeta(this);
+		BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this);
+		ClassMeta<?> sType = null;
+		if (builder != null)
+			sType = builder.getBuilderClassMeta(this);
+		else if (swap != null)
+			sType = swap.getSwapClassMeta(this);
+		else
+			sType = eType;
 		setCurrentClass(sType);
 
 		if (! sType.canCreateNewInstance(outer)) {
@@ -319,6 +326,12 @@ public class RdfParserSession extends ReaderParserSession {
 			}
 			if (sType.isArray() || sType.isArgs())
 				o = toArray(sType, (Collection)o);
+		} else if (builder != null) {
+			Resource r = n.asResource();
+			if (! urisVisited.add(r))
+				return null;
+			BeanMap<?> bm = toBeanMap(builder.create(this, eType));
+			o = builder.build(this, parseIntoBeanMap(r, bm).getBean(), eType);
 		} else if (sType.canCreateNewBean(outer)) {
 			Resource r = n.asResource();
 			if (! urisVisited.add(r))
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
index 611f6e8..7e74345 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
@@ -447,23 +447,25 @@ public class BeanMap<T> extends AbstractMap<String,Object> implements Delegate<T
 			if (v != null)
 				l.add(v);
 		for (BeanPropertyMeta bpm : properties) {
-			try {
-				if (bpm.isDyna()) {
-					for (String pName : bpm.getDynaMap(bean).keySet()) {
-						Object val = bpm.get(this, pName);
+			if (bpm.canRead()) {
+				try {
+					if (bpm.isDyna()) {
+						for (String pName : bpm.getDynaMap(bean).keySet()) {
+							Object val = bpm.get(this, pName);
+							if (val != null || ! ignoreNulls)
+								l.add(new BeanPropertyValue(bpm, pName, val, null));
+						}
+					} else {
+						Object val = bpm.get(this, null);
 						if (val != null || ! ignoreNulls)
-							l.add(new BeanPropertyValue(bpm, pName, val, null));
+							l.add(new BeanPropertyValue(bpm, bpm.getName(), val, null));
 					}
-				} else {
-					Object val = bpm.get(this, null);
-					if (val != null || ! ignoreNulls)
-						l.add(new BeanPropertyValue(bpm, bpm.getName(), val, null));
+				} catch (Error e) {
+					// Errors should always be uncaught.
+					throw e;
+				} catch (Throwable t) {
+					l.add(new BeanPropertyValue(bpm, bpm.getName(), null, t));
 				}
-			} catch (Error e) {
-				// Errors should always be uncaught.
-				throw e;
-			} catch (Throwable t) {
-				l.add(new BeanPropertyValue(bpm, bpm.getName(), null, t));
 			}
 		}
 		if (meta.sortProperties && meta.dynaProperty != null)
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
index c9d86b9..4b8bdd8 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
@@ -129,7 +129,7 @@ public class BeanMeta<T> {
 		this.extMeta = b.extMeta;
 		this.beanRegistry = b.beanRegistry;
 		this.typePropertyName = b.typePropertyName;
-		this.typeProperty = BeanPropertyMeta.builder(this, typePropertyName).rawMetaType(ctx.string()).beanRegistry(beanRegistry).build();
+		this.typeProperty = BeanPropertyMeta.builder(this, typePropertyName).canRead().canWrite().rawMetaType(ctx.string()).beanRegistry(beanRegistry).build();
 		this.sortProperties = b.sortProperties;
 	}
 
@@ -443,7 +443,7 @@ public class BeanMeta<T> {
 		private String findPropertyName(Field f, Set<String> fixedBeanProps) {
 			BeanProperty bp = f.getAnnotation(BeanProperty.class);
 			String name = bpName(bp);
-			if (! name.isEmpty()) {
+			if (name != null && ! name.isEmpty()) {
 				if (fixedBeanProps.isEmpty() || fixedBeanProps.contains(name))
 					return name;
 				return null;  // Could happen if filtered via BEAN_includeProperties/BEAN_excludeProperties.
@@ -522,9 +522,9 @@ public class BeanMeta<T> {
 			else if (b.field != null)
 				pt = b.field.getType();
 
-			// Doesn't match if no getter/field defined.
+			// Matches if only a setter is defined.
 			if (pt == null)
-				return false;
+				return true;
 
 			// Doesn't match if not same type or super type as getter/field.
 			if (! isParentClass(type, pt))
@@ -585,17 +585,31 @@ public class BeanMeta<T> {
 					} else if (n.startsWith("is") && (rt.equals(Boolean.TYPE) || rt.equals(Boolean.class))) {
 						isGetter = true;
 						n = n.substring(2);
-					} else if (! bpName.isEmpty()) {
+					} else if (bpName != null) {
 						isGetter = true;
-						n = bpName;
+						if (bpName.isEmpty()) {
+							if (n.startsWith("get"))
+								n = n.substring(3);
+							else if (n.startsWith("is"))
+								n = n.substring(2);
+							bpName = n;
+						} else {
+							n = bpName;
+						}
 					}
 				} else if (pt.length == 1) {
 					if (n.startsWith("set") && (isParentClass(rt, c) || rt.equals(Void.TYPE))) {
 						isSetter = true;
 						n = n.substring(3);
-					} else if (! bpName.isEmpty()) {
+					} else if (bpName != null) {
 						isSetter = true;
-						n = bpName;
+						if (bpName.isEmpty()) {
+							if (n.startsWith("set"))
+								n = n.substring(3);
+							bpName = n;
+						} else {
+							n = bpName;
+						}
 					}
 				} else if (pt.length == 2) {
 					if ("*".equals(bpName)) {
@@ -605,7 +619,7 @@ public class BeanMeta<T> {
 				}
 				n = pn.getPropertyName(n);
 				if (isGetter || isSetter) {
-					if (! bpName.isEmpty()) {
+					if (bpName != null && ! bpName.isEmpty()) {
 						n = bpName;
 						if (! fixedBeanProps.isEmpty())
 							if (! fixedBeanProps.contains(n))
@@ -789,7 +803,7 @@ public class BeanMeta<T> {
 
 	static final String bpName(BeanProperty bp) {
 		if (bp == null)
-			return "";
+			return null;
 		if (! bp.name().isEmpty())
 			return bp.name();
 		return bp.value();
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
index 56f360d..1f9c64a 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
@@ -68,7 +68,8 @@ public final class BeanPropertyMeta {
 
 	private final Object overrideValue;                       // The bean property value (if it's an overridden delegate).
 	private final BeanPropertyMeta delegateFor;               // The bean property that this meta is a delegate for.
-
+	private final boolean canRead, canWrite;
+	
 	/**
 	 * Creates a builder for {@link #BeanPropertyMeta} objects.
 	 * 
@@ -97,6 +98,7 @@ public final class BeanPropertyMeta {
 		Object overrideValue;
 		BeanPropertyMeta delegateFor;
 		MetadataMap extMeta = new MetadataMap();
+		boolean canRead, canWrite;
 
 		Builder(BeanMeta<?> beanMeta, String name) {
 			this.beanMeta = beanMeta;
@@ -148,16 +150,29 @@ public final class BeanPropertyMeta {
 			this.delegateFor = delegateFor;
 			return this;
 		}
+		
+		Builder canRead() {
+			this.canRead = true;
+			return this;
+		}
+
+		Builder canWrite() {
+			this.canWrite = true;
+			return this;
+		}
 
 		boolean validate(BeanContext f, BeanRegistry parentBeanRegistry, Map<Class<?>,Class<?>[]> typeVarImpls) throws Exception {
 
 			List<Class<?>> bdClasses = new ArrayList<>();
 
-			if (field == null && getter == null)
+			if (field == null && getter == null && setter == null)
 				return false;
 
 			if (field == null && setter == null && f.beansRequireSettersForGetters && ! isConstructorArg)
 				return false;
+			
+			canRead |= (field != null || getter != null);
+			canWrite |= (field != null || setter != null);
 
 			if (field != null) {
 				BeanProperty p = field.getAnnotation(BeanProperty.class);
@@ -340,6 +355,8 @@ public final class BeanPropertyMeta {
 		this.delegateFor = b.delegateFor;
 		this.extMeta = b.extMeta;
 		this.isDyna = b.isDyna;
+		this.canRead = b.canRead;
+		this.canWrite = b.canWrite;
 	}
 
 	/**
@@ -1095,4 +1112,22 @@ public final class BeanPropertyMeta {
 	public String toString() {
 		return name + ": " + this.rawTypeMeta.getInnerClass().getName() + ", field=["+field+"], getter=["+getter+"], setter=["+setter+"]";
 	}
+
+	/**
+	 * Returns <jk>true</jk> if this property can be read.
+	 * 
+	 * @return <jk>true</jk> if this property can be read.
+	 */
+	public boolean canRead() {
+		return canRead;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if this property can be written.
+	 * 
+	 * @return <jk>true</jk> if this property can be written.
+	 */
+	public boolean canWrite() {
+		return canWrite;
+	}
 }
\ No newline at end of file
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
index bf53232..f5576bb 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
@@ -97,6 +97,7 @@ public final class ClassMeta<T> implements Type {
 		childUnswapMap;                                      // Maps swap subclasses to PojoSwaps.
 	private final PojoSwap<T,?>[] pojoSwaps;                // The object POJO swaps associated with this bean (if it has any).
 	private final BeanFilter beanFilter;                    // The bean filter associated with this bean (if it has one).
+	private final BuilderSwap<T,?> builderSwap;             // The builder swap associated with this bean (if it has one).
 	private final MetadataMap extMeta;                      // Extended metadata
 	private final BeanContext beanContext;                  // The bean context that created this object.
 	private final ClassMeta<?>
@@ -169,6 +170,7 @@ public final class ClassMeta<T> implements Type {
 			this.remoteableMethods = builder.remoteableMethods;
 			this.beanFilter = beanFilter;
 			this.pojoSwaps = builder.pojoSwaps.isEmpty() ? null : builder.pojoSwaps.toArray(new PojoSwap[builder.pojoSwaps.size()]);
+			this.builderSwap = builder.builderSwap;
 			this.extMeta = new MetadataMap();
 			this.keyType = builder.keyType;
 			this.valueType = builder.valueType;
@@ -240,6 +242,7 @@ public final class ClassMeta<T> implements Type {
 		this.dictionaryName = mainType.dictionaryName;
 		this.notABeanReason = mainType.notABeanReason;
 		this.pojoSwaps = mainType.pojoSwaps;
+		this.builderSwap = mainType.builderSwap;
 		this.beanFilter = mainType.beanFilter;
 		this.extMeta = mainType.extMeta;
 		this.initException = mainType.initException;
@@ -286,6 +289,7 @@ public final class ClassMeta<T> implements Type {
 		this.dictionaryName = null;
 		this.notABeanReason = null;
 		this.pojoSwaps = null;
+		this.builderSwap = null;
 		this.beanFilter = null;
 		this.extMeta = new MetadataMap();
 		this.initException = null;
@@ -324,8 +328,7 @@ public final class ClassMeta<T> implements Type {
 		ClassMeta<?>
 			keyType = null,
 			valueType = null,
-			elementType = null,
-			serializedClassMeta = null;
+			elementType = null;
 		String
 			typePropertyName = null,
 			notABeanReason = null,
@@ -333,6 +336,7 @@ public final class ClassMeta<T> implements Type {
 		Throwable initException = null;
 		BeanMeta beanMeta = null;
 		List<PojoSwap> pojoSwaps = new ArrayList<>();
+		BuilderSwap builderSwap;
 		InvocationHandler invocationHandler = null;
 		BeanRegistry beanRegistry = null;
 		PojoSwap<?,?>[] childPojoSwaps;
@@ -583,6 +587,9 @@ public final class ClassMeta<T> implements Type {
 
 			if (pojoSwaps != null)
 				this.pojoSwaps.addAll(Arrays.asList(pojoSwaps));
+			
+			if (beanContext != null)
+				this.builderSwap = BuilderSwap.findSwapFromPojoClass(c, beanContext.beanConstructorVisibility, beanContext.beanMethodVisibility);
 
 			findPojoSwaps(this.pojoSwaps);
 
@@ -645,10 +652,6 @@ public final class ClassMeta<T> implements Type {
 			if (beanMeta != null)
 				dictionaryName = beanMeta.getDictionaryName();
 
-			serializedClassMeta = (this.pojoSwaps.isEmpty() ? ClassMeta.this : findClassMeta(this.pojoSwaps.get(0).getSwapClass()));
-			if (serializedClassMeta == null)
-				serializedClassMeta = ClassMeta.this;
-
 			if (beanMeta != null && beanContext != null && beanContext.useInterfaceProxies && innerClass.isInterface())
 				invocationHandler = new BeanProxyInvocationHandler<T>(beanMeta);
 
@@ -1278,6 +1281,16 @@ public final class ClassMeta<T> implements Type {
 		}
 		return null;
 	}
+	
+	/**
+	 * Returns the builder swap associated with this class.
+	 * 
+	 * @param session The current bean session.
+	 * @return The builder swap associated with this class, or <jk>null</jk> if it doesn't exist.
+	 */
+	public BuilderSwap<T,?> getBuilderSwap(BeanSession session) {
+		return builderSwap;
+	}
 
 	/**
 	 * Returns the {@link BeanMeta} associated with this class.
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Builder.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Builder.java
new file mode 100644
index 0000000..a5c1fea
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Builder.java
@@ -0,0 +1,71 @@
+// ***************************************************************************************************************************
+// * 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.annotation;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.*;
+
+/**
+ * Identifies a class as a builder for a POJO class.
+ * 
+ * 
+ * <h6 class='figure'>Example:</h6>
+ * <p class='bcode'>
+ * 	
+ * 	<jc>// POJO class.</jc>
+ * 	<ja>@Builder</ja>(MyBeanBuilder.<jk>class</jk>)
+ * 	<jk>public class</jk> MyBean {
+ * 
+ * 		<jc>// Read-only properties.</jc>
+ * 		<jk>public final</jk> String <jf>foo</jf>;
+ * 		<jk>public final int</jk> <jf>bar</jf>;
+ * 
+ * 		<jc>// Constructor that takes in a builder.</jc>
+ * 		<jk>public</jk> MyBean(MyBeanBuilder b) {
+ * 			<jk>this</jk>.<jf>foo</jf> = b.foo;
+ * 			<jk>this</jk>.<jf>bar</jf> = b.bar;
+ * 		}
+ * 	}
+ * 	
+ * 	<jc>// Builder class.</jc>
+ * 	<jk>public class</jk> MyBeanBuilder {
+ * 		<jk>public</jk> String <jf>foo</jf>;
+ * 		<jk>public int</jk> <jf>bar</jf>;
+ * 			
+ * 		<jc>// Method that creates the bean.</jc>
+ * 		<jk>public</jk> MyBean build() {
+ * 			<jk>return new</jk> MyBean(<jk>this</jk>);
+ * 		}
+ * 		
+ * 		<jc>// Bean property setters.</jc>
+ * 	}
+ * </p>
+ * 
+ * <h5 class='topic'>Documentation</h5>
+ * <ul>
+ * 	<li><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.PojoBuilders">Overview &gt; POJO Builders</a>
+ * </ul>
+ */
+@Documented
+@Target({TYPE})
+@Retention(RUNTIME)
+@Inherited
+public @interface Builder {
+
+	/**
+	 * The builder for this class.
+	 */
+	Class<?> value() default Null.class;
+}
\ No newline at end of file
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
index fcac49a..76dcda8 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
@@ -60,18 +60,22 @@ public final class CsvSerializerSession extends WriterSerializerSession {
 					BeanMeta<?> bm = entryType.getBeanMeta();
 					int i = 0;
 					for (BeanPropertyMeta pm : bm.getPropertyMetas()) {
-						if (i++ > 0)
-							w.append(',');
-						append(w, pm.getName());
+						if (pm.canRead()) {
+							if (i++ > 0)
+								w.append(',');
+							append(w, pm.getName());
+						}
 					}
 					w.append('\n');
 					for (Object o2 : l) {
 						i = 0;
 						BeanMap<?> bean = toBeanMap(o2);
 						for (BeanPropertyMeta pm : bm.getPropertyMetas()) {
-							if (i++ > 0)
-								w.append(',');
-							append(w, pm.get(bean, pm.getName()));
+							if (pm.canRead()) {
+								if (i++ > 0)
+									w.append(',');
+								append(w, pm.get(bean, pm.getName()));
+							}
 						}
 						w.append('\n');
 					}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlParserSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlParserSession.java
index f7d5d84..b1d2779 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlParserSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlParserSession.java
@@ -83,8 +83,15 @@ public final class HtmlParserSession extends XmlParserSession {
 
 		if (eType == null)
 			eType = (ClassMeta<T>)object();
-		PojoSwap<T,Object> transform = (PojoSwap<T,Object>)eType.getPojoSwap(this);
-		ClassMeta<?> sType = transform == null ? eType : transform.getSwapClassMeta(this);
+		PojoSwap<T,Object> swap = (PojoSwap<T,Object>)eType.getPojoSwap(this);
+		BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this);
+		ClassMeta<?> sType = null;
+		if (builder != null)
+			sType = builder.getBuilderClassMeta(this);
+		else if (swap != null)
+			sType = swap.getSwapClassMeta(this);
+		else
+			sType = eType;
 		setCurrentClass(sType);
 
 		int event = r.getEventType();
@@ -208,6 +215,9 @@ public final class HtmlParserSession extends XmlParserSession {
 				} else if (sType.isMap()) {
 					o = parseIntoMap(r, (Map)(sType.canCreateNewInstance(outer) ? sType.newInstance(outer)
 						: new ObjectMap(this)), sType.getKeyType(), sType.getValueType(), pMeta);
+				} else if (builder != null) {
+					BeanMap m = toBeanMap(builder.create(this, eType));
+					o = builder.build(this, parseIntoBean(r, m).getBean(), eType);
 				} else if (sType.canCreateNewBean(outer)) {
 					BeanMap m = newBeanMap(outer, sType.getInnerClass());
 					o = parseIntoBean(r, m).getBean();
@@ -256,8 +266,8 @@ public final class HtmlParserSession extends XmlParserSession {
 		if (! isValid)
 			throw new XmlParseException(r.getLocation(), "Unexpected tag ''{0}'' for type ''{1}''", tag, eType);
 
-		if (transform != null && o != null)
-			o = transform.unswap(this, o, eType);
+		if (swap != null && o != null)
+			o = swap.unswap(this, o, eType);
 
 		if (outer != null)
 			setParent(eType, o, outer);
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
index 067f12b..75cfbf5 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
@@ -600,30 +600,32 @@ public class HtmlSerializerSession extends XmlSerializerSession {
 					for (Object k : th) {
 						BeanMapEntry p = m2.getProperty(toString(k));
 						BeanPropertyMeta pMeta = p.getMeta();
-						Object value = p.getValue();
-
-						String link = null, anchorText = null;
-						if (! pMeta.getClassMeta().isCollectionOrArray()) {
-							link = m2.resolveVars(getLink(pMeta));
-							anchorText = m2.resolveVars(getAnchorText(pMeta));
+						if (pMeta.canRead()) {
+							Object value = p.getValue();
+
+							String link = null, anchorText = null;
+							if (! pMeta.getClassMeta().isCollectionOrArray()) {
+								link = m2.resolveVars(getLink(pMeta));
+								anchorText = m2.resolveVars(getAnchorText(pMeta));
+							}
+
+							if (anchorText != null)
+								value = anchorText;
+
+							String style = getStyle(this, pMeta, value);
+							out.oTag(i+2, "td");
+							if (style != null)
+								out.attr("style", style);
+							out.cTag();
+							if (link != null)
+								out.oTag("a").attrUri("href", link).cTag();
+							ContentResult cr = serializeAnything(out, value, pMeta.getClassMeta(), p.getKey().toString(), 2, pMeta, false);
+							if (cr == CR_NORMAL)
+								out.i(i+2);
+							if (link != null)
+								out.eTag("a");
+							out.eTag("td").nl(i+2);
 						}
-
-						if (anchorText != null)
-							value = anchorText;
-
-						String style = getStyle(this, pMeta, value);
-						out.oTag(i+2, "td");
-						if (style != null)
-							out.attr("style", style);
-						out.cTag();
-						if (link != null)
-							out.oTag("a").attrUri("href", link).cTag();
-						ContentResult cr = serializeAnything(out, value, pMeta.getClassMeta(), p.getKey().toString(), 2, pMeta, false);
-						if (cr == CR_NORMAL)
-							out.i(i+2);
-						if (link != null)
-							out.eTag("a");
-						out.eTag("td").nl(i+2);
 					}
 				}
 				out.ie(i+1).eTag("tr").nl(i+1);
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ClassUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ClassUtils.java
index 936f80d..3b7c237 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ClassUtils.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ClassUtils.java
@@ -584,20 +584,39 @@ public final class ClassUtils {
 	 * 	Can be subtypes of the actual constructor argument types.
 	 * @return The matching constructor, or <jk>null</jk> if constructor could not be found.
 	 */
-	@SuppressWarnings("unchecked")
 	public static <T> Constructor<T> findPublicConstructor(Class<T> c, boolean fuzzyArgs, Class<?>...argTypes) {
+		return findConstructor(c, Visibility.PUBLIC, fuzzyArgs, argTypes);
+	}
+	
+	/**
+	 * Finds a constructor with the specified parameters without throwing an exception.
+	 * 
+	 * @param c The class to search for a constructor.
+	 * @param vis The minimum visibility.
+	 * @param fuzzyArgs 
+	 * 	Use fuzzy-arg matching.
+	 * 	Find a constructor that best matches the specified args.
+	 * @param argTypes
+	 * 	The argument types in the constructor.
+	 * 	Can be subtypes of the actual constructor argument types.
+	 * @return The matching constructor, or <jk>null</jk> if constructor could not be found.
+	 */
+	@SuppressWarnings("unchecked")
+	public static <T> Constructor<T> findConstructor(Class<T> c, Visibility vis, boolean fuzzyArgs, Class<?>...argTypes) {
 		ConstructorCacheEntry cce = CONSTRUCTOR_CACHE.get(c);
-		if (cce != null && argsMatch(cce.paramTypes, argTypes)) 
+		if (cce != null && argsMatch(cce.paramTypes, argTypes) && cce.isVisible(vis)) 
 			return (Constructor<T>)cce.constructor;
 	
 		if (fuzzyArgs) {
 			int bestCount = -1;
 			Constructor<?> bestMatch = null;
-			for (Constructor<?> n : c.getConstructors()) {
-				int m = fuzzyArgsMatch(n.getParameterTypes(), argTypes);
-				if (m > bestCount) {
-					bestCount = m;
-					bestMatch = n;
+			for (Constructor<?> n : c.getDeclaredConstructors()) {
+				if (vis.isVisible(n)) {
+					int m = fuzzyArgsMatch(n.getParameterTypes(), argTypes);
+					if (m > bestCount) {
+						bestCount = m;
+						bestMatch = n;
+					}
 				}
 			}
 			if (bestCount >= 0) 
@@ -606,7 +625,7 @@ public final class ClassUtils {
 		} 
 		
 		for (Constructor<?> n : c.getConstructors()) {
-			if (argsMatch(n.getParameterTypes(), argTypes)) {
+			if (argsMatch(n.getParameterTypes(), argTypes) && vis.isVisible(n)) {
 				CONSTRUCTOR_CACHE.put(c, new ConstructorCacheEntry(c, n));
 				return (Constructor<T>)n;
 			}
@@ -615,6 +634,8 @@ public final class ClassUtils {
 		return null;
 	}
 	
+	
+	
 	private static final class ConstructorCacheEntry {
 		final Constructor<?> constructor;
 		final Class<?>[] paramTypes;
@@ -623,6 +644,10 @@ public final class ClassUtils {
 			this.constructor = constructor;
 			this.paramTypes = constructor.getParameterTypes();
 		}
+		
+		boolean isVisible(Visibility vis) {
+			return vis.isVisible(constructor);
+		}
 	}
 	
 	/**
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonParserSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonParserSession.java
index a42b712..b3fc575 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonParserSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonParserSession.java
@@ -117,7 +117,14 @@ public final class JsonParserSession extends ReaderParserSession {
 		if (eType == null)
 			eType = object();
 		PojoSwap<T,Object> swap = (PojoSwap<T,Object>)eType.getPojoSwap(this);
-		ClassMeta<?> sType = swap == null ? eType : swap.getSwapClassMeta(this);
+		BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this);
+		ClassMeta<?> sType = null;
+		if (builder != null)
+			sType = builder.getBuilderClassMeta(this);
+		else if (swap != null)
+			sType = swap.getSwapClassMeta(this);
+		else
+			sType = eType;
 		setCurrentClass(sType);
 		String wrapperAttr = sType.getExtendedMeta(JsonClassMeta.class).getWrapperAttr();
 
@@ -178,6 +185,9 @@ public final class JsonParserSession extends ReaderParserSession {
 				Collection l = (sType.canCreateNewInstance(outer) ? (Collection)sType.newInstance() : new ObjectList(this));
 				o = parseIntoCollection2(r, l, sType, pMeta);
 			}
+		} else if (builder != null) {
+			BeanMap m = toBeanMap(builder.create(this, eType));
+			o = builder.build(this, parseIntoBeanMap2(r, m).getBean(), eType);
 		} else if (sType.canCreateNewBean(outer)) {
 			BeanMap m = newBeanMap(outer, sType.getInnerClass());
 			o = parseIntoBeanMap2(r, m).getBean();
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonSchemaSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonSchemaSerializerSession.java
index 0054fbf..95aab8b 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonSchemaSerializerSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonSchemaSerializerSession.java
@@ -109,7 +109,8 @@ public class JsonSchemaSerializerSession extends JsonSerializerSession {
 					bm = new BeanMetaFiltered(bm, pNames);
 				for (Iterator<BeanPropertyMeta> i = bm.getPropertyMetas().iterator(); i.hasNext();) {
 					BeanPropertyMeta p = i.next();
-					properties.put(p.getName(), getSchema(p.getClassMeta(), p.getName(), p.getProperties()));
+					if (p.canRead())
+						properties.put(p.getName(), getSchema(p.getClassMeta(), p.getName(), p.getProperties()));
 				}
 				out.put("properties", properties);
 			}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonSerializerSession.java
index 3396055..d792f6c 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonSerializerSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonSerializerSession.java
@@ -189,24 +189,26 @@ public class JsonSerializerSession extends WriterSerializerSession {
 		boolean addComma = false;
 		for (BeanPropertyValue p : m.getValues(isTrimNulls(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null)) {
 			BeanPropertyMeta pMeta = p.getMeta();
-			ClassMeta<?> cMeta = p.getClassMeta();
-			String key = p.getName();
-			Object value = p.getValue();
-			Throwable t = p.getThrown();
-			if (t != null)
-				onBeanGetterException(pMeta, t);
+			if (pMeta.canRead()) {
+				ClassMeta<?> cMeta = p.getClassMeta();
+				String key = p.getName();
+				Object value = p.getValue();
+				Throwable t = p.getThrown();
+				if (t != null)
+					onBeanGetterException(pMeta, t);
 
-			if (canIgnoreValue(cMeta, key, value))
-				continue;
+				if (canIgnoreValue(cMeta, key, value))
+					continue;
 
-			if (addComma)
-				out.append(',').smi(i);
+				if (addComma)
+					out.append(',').smi(i);
 
-			out.cr(i).attr(key).append(':').s(i);
+				out.cr(i).attr(key).append(':').s(i);
 
-			serializeAnything(out, value, cMeta, key, pMeta);
+				serializeAnything(out, value, cMeta, key, pMeta);
 
-			addComma = true;
+				addComma = true;
+			}
 		}
 		out.cre(i-1).append('}');
 		return out;
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java
index 400818c..e537bb9 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java
@@ -58,7 +58,14 @@ public final class MsgPackParserSession extends InputStreamParserSession {
 		if (eType == null)
 			eType = object();
 		PojoSwap<T,Object> swap = (PojoSwap<T,Object>)eType.getPojoSwap(this);
-		ClassMeta<?> sType = swap == null ? eType : swap.getSwapClassMeta(this);
+		BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this);
+		ClassMeta<?> sType = null;
+		if (builder != null)
+			sType = builder.getBuilderClassMeta(this);
+		else if (swap != null)
+			sType = swap.getSwapClassMeta(this);
+		else
+			sType = eType;
 		setCurrentClass(sType);
 
 		Object o = null;
@@ -110,9 +117,9 @@ public final class MsgPackParserSession extends InputStreamParserSession {
 				} else {
 					throw new ParseException(loc(is), "Invalid data type {0} encountered for parse type {1}", dt, sType);
 				}
-			} else if (sType.canCreateNewBean(outer)) {
+			} else if (builder != null || sType.canCreateNewBean(outer)) {
 				if (dt == MAP) {
-					BeanMap m = newBeanMap(outer, sType.getInnerClass());
+					BeanMap m = builder == null ? newBeanMap(outer, sType.getInnerClass()) : toBeanMap(builder.create(this, eType));
 					for (int i = 0; i < length; i++) {
 						String pName = parseAnything(string(), is, m.getBean(false), null);
 						BeanPropertyMeta bpm = m.getPropertyMeta(pName);
@@ -128,7 +135,7 @@ public final class MsgPackParserSession extends InputStreamParserSession {
 							bpm.set(m, pName, value);
 						}
 					}
-					o = m.getBean();
+					o = builder == null ? m.getBean() : builder.build(this, m.getBean(), eType);
 				} else {
 					throw new ParseException(loc(is), "Invalid data type {0} encountered for parse type {1}", dt, sType);
 				}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerSession.java
index a836d8c..a60aa5b 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerSession.java
@@ -195,15 +195,17 @@ public final class MsgPackSerializerSession extends OutputStreamSerializerSessio
 
 		for (BeanPropertyValue p : values) {
 			BeanPropertyMeta pMeta = p.getMeta();
-			ClassMeta<?> cMeta = p.getClassMeta();
-			String key = p.getName();
-			Object value = p.getValue();
-			Throwable t = p.getThrown();
-			if (t != null)
-				onBeanGetterException(pMeta, t);
-			else {
-				serializeAnything(out, key, null, null, null);
-				serializeAnything(out, value, cMeta, key, pMeta);
+			if (pMeta.canRead()) {
+				ClassMeta<?> cMeta = p.getClassMeta();
+				String key = p.getName();
+				Object value = p.getValue();
+				Throwable t = p.getThrown();
+				if (t != null)
+					onBeanGetterException(pMeta, t);
+				else {
+					serializeAnything(out, key, null, null, null);
+					serializeAnything(out, value, cMeta, key, pMeta);
+				}
 			}
 		}
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/Parser.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/Parser.java
index aa36e1e..2b7be72 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/Parser.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/Parser.java
@@ -475,10 +475,14 @@ public abstract class Parser extends BeanContext {
 	 * 		.set(<jsf>PARSER_unbuffered</jsf>, <jk>true</jk>)
 	 * 		.build();
 	 * 
+	 * 	<jc>// If you're calling parse on the same input multiple times, use a session instead of the parser directly.</jc>
+	 * 	<jc>// It's more efficient because we don't need to recalc the session settings again. </jc>
+	 * 	ReaderParserSession s = p.createSession();
+	 * 	
 	 * 	<jc>// Read input with multiple POJOs</jc>
 	 * 	Reader json = <jk>new</jk> StringReader(<js>"{foo:'bar'}{foo:'baz'}"</js>);
-	 * 	MyBean myBean1 = p.parse(json, MyBean.<jk>class</jk>);
-	 * 	MyBean myBean2 = p.parse(json, MyBean.<jk>class</jk>);
+	 * 	MyBean myBean1 = s.parse(json, MyBean.<jk>class</jk>);
+	 * 	MyBean myBean2 = s.parse(json, MyBean.<jk>class</jk>);
 	 * </p>
 	 * 
 	 * <h5 class='section'>Notes:</h5>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/Builder.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/Builder.java
new file mode 100644
index 0000000..fd0e4fa
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/Builder.java
@@ -0,0 +1,30 @@
+// ***************************************************************************************************************************
+// * 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.transform;
+
+/**
+ * Identifies a class as being a builder for a bean class.
+ * 
+ * <p>
+ * This interface has no methods to implement.
+ * <br>It's purpose is to identify a class as a builder when it's used on a constructor of the built class.
+ * 
+ * <h5 class='topic'>Documentation</h5>
+ * <ul>
+ * 	<li><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.PojoBuilders">Overview &gt; POJO Builders</a>
+ * </ul>
+ * 
+ * @param <T> The type of objects that this builder creates. 
+ */
+public interface Builder<T> {
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/BuilderSwap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/BuilderSwap.java
new file mode 100644
index 0000000..645cfbb
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/BuilderSwap.java
@@ -0,0 +1,231 @@
+// ***************************************************************************************************************************
+// * 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.transform;
+
+import static org.apache.juneau.internal.ClassUtils.*;
+
+import java.lang.reflect.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+
+/**
+ * Specialized transform for builder classes.
+ * 
+ * <h5 class='topic'>Documentation</h5>
+ * <ul>
+ * 	<li><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.PojoBuilders">Overview &gt; POJO Builders</a>
+ * </ul>
+ * 
+ * @param <T> The bean class.
+ * @param <B> The builder class.
+ */
+@SuppressWarnings("unchecked")
+public class BuilderSwap<T,B> {
+
+	private final Class<T> pojoClass;
+	private final Class<B> builderClass;
+	private final Constructor<T> pojoConstructor;      // public Pojo(Builder);
+	private final Constructor<B> builderConstructor;   // public Builder();
+	private final Method createBuilderMethod;          // Builder create();
+	private final Method createPojoMethod;             // Pojo build();
+	private ClassMeta<?> builderClassMeta;
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param pojoClass The POJO class created by the builder class. 
+	 * @param builderClass The builder class.
+	 * @param pojoConstructor The POJO constructor that takes in a builder as a parameter.
+	 * @param builderConstructor The builder no-arg constructor.
+	 * @param createBuilderMethod The static create() method on the POJO class.
+	 * @param createPojoMethod The build() method on the builder class. 
+	 */
+	protected BuilderSwap(Class<T> pojoClass, Class<B> builderClass, Constructor<T> pojoConstructor, Constructor<B> builderConstructor, Method createBuilderMethod, Method createPojoMethod) {
+		this.pojoClass = pojoClass;
+		this.builderClass = builderClass;
+		this.pojoConstructor = pojoConstructor;
+		this.builderConstructor = builderConstructor;
+		this.createBuilderMethod = createBuilderMethod;
+		this.createPojoMethod = createPojoMethod;
+	}
+	
+	/**
+	 * The POJO class.
+	 * 
+	 * @return The POJO class.
+	 */
+	public Class<T> getPojoClass() {
+		return pojoClass;
+	}
+
+	/**
+	 * The builder class.
+	 * 
+	 * @return The builder class.
+	 */
+	public Class<B> getBuilderClass() {
+		return builderClass;
+	}
+	
+	/**
+	 * Returns the {@link ClassMeta} of the transformed class type.
+	 * 
+	 * <p>
+	 * This value is cached for quick lookup.
+	 * 
+	 * @param session
+	 * 	The bean context to use to get the class meta.
+	 * 	This is always going to be the same bean context that created this swap.
+	 * @return The {@link ClassMeta} of the transformed class type.
+	 */
+	public ClassMeta<?> getBuilderClassMeta(BeanSession session) {
+		if (builderClassMeta == null)
+			builderClassMeta = session.getClassMeta(getBuilderClass());
+		return builderClassMeta;
+	}
+
+	/**
+	 * Creates a new builder object.
+	 * 
+	 * @param session The current bean session.
+	 * @param hint A hint about the class type.
+	 * @return A new POJO.
+	 * @throws Exception
+	 */
+	public B create(BeanSession session, ClassMeta<?> hint) throws Exception {
+		if (createBuilderMethod != null)
+			return (B)createBuilderMethod.invoke(null);
+		return builderConstructor.newInstance();
+	}
+	
+	/**
+	 * Creates a new POJO from the specified builder.
+	 * 
+	 * @param session The current bean session.
+	 * @param builder The POJO builder.
+	 * @param hint A hint about the class type.
+	 * @return A new POJO.
+	 * @throws Exception
+	 */
+	public T build(BeanSession session, B builder, ClassMeta<?> hint) throws Exception {
+		if (createPojoMethod != null)
+			return (T)createPojoMethod.invoke(builder);
+		return pojoConstructor.newInstance(builder);
+	}
+	
+	/**
+	 * Creates a BuilderSwap from the specified builder class if it qualifies as one.
+	 * 
+	 * @param builderClass The potential builder class.
+	 * @param cVis Minimum constructor visibility.
+	 * @param mVis Minimum method visibility.
+	 * @return A new swap instance, or <jk>null</jk> if class wasn't a builder class.
+	 */
+	@SuppressWarnings("rawtypes")
+	public static BuilderSwap<?,?> findSwapFromBuilderClass(Class<?> builderClass, Visibility cVis, Visibility mVis) {
+		if (! isPublic(builderClass))
+			return null;
+		
+		Class<?> pojoClass = resolveParameterType(Builder.class, 0, builderClass);
+
+		Method createPojoMethod, createBuilderMethod;
+		Constructor<?> pojoConstructor, builderConstructor;
+		
+		createPojoMethod = findCreatePojoMethod(builderClass);
+		if (createPojoMethod != null)
+			pojoClass = createPojoMethod.getReturnType();
+		
+		if (pojoClass == null)
+			return null;
+
+		pojoConstructor = findConstructor(pojoClass, cVis, false, builderClass);
+		if (pojoConstructor == null)
+			return null;
+		
+		builderConstructor = findNoArgConstructor(builderClass, cVis);
+		createBuilderMethod = findBuilderCreateMethod(pojoClass);
+		if (builderConstructor == null && createBuilderMethod == null)
+			return null;
+		
+		return new BuilderSwap(pojoClass, builderClass, pojoConstructor, builderConstructor, createBuilderMethod, createPojoMethod);
+	}
+	
+
+	/**
+	 * Creates a BuilderSwap from the specified POJO class if it has one.
+	 * 
+	 * @param pojoClass The POJO class to check.
+	 * @param cVis Minimum constructor visibility.
+	 * @param mVis Minimum method visibility.
+	 * @return A new swap instance, or <jk>null</jk> if class didn't have a builder class.
+	 */
+	@SuppressWarnings("rawtypes")
+	public static BuilderSwap<?,?> findSwapFromPojoClass(Class<?> pojoClass, Visibility cVis, Visibility mVis) {
+		Class<?> builderClass = null;
+		Method pojoCreateMethod, builderCreateMethod;
+		Constructor<?> pojoConstructor = null, builderConstructor;
+
+		org.apache.juneau.annotation.Builder b = pojoClass.getAnnotation(org.apache.juneau.annotation.Builder.class);
+		
+		if (b != null && b.value() != Null.class) 
+			builderClass = b.value();
+		
+		builderCreateMethod = findBuilderCreateMethod(pojoClass);
+
+		if (builderClass == null && builderCreateMethod != null) 
+			builderClass = builderCreateMethod.getReturnType();
+		
+		if (builderClass == null) {
+			for (Constructor cc : pojoClass.getConstructors()) {
+				if (cVis.isVisible(cc)) {
+					Class<?>[] pt = cc.getParameterTypes();
+					if (pt.length == 1 && isParentClass(Builder.class, pt[0])) {
+						pojoConstructor = cc;
+						builderClass = pt[0];
+					}
+				}
+			}
+		}
+		
+		if (builderClass == null)
+			return null;
+		
+		builderConstructor = findNoArgConstructor(builderClass, cVis);
+		if (builderConstructor == null && builderCreateMethod == null)
+			return null;
+
+		pojoCreateMethod = findCreatePojoMethod(builderClass);
+		if (pojoConstructor == null)
+			pojoConstructor = findConstructor(pojoClass, cVis, false, builderClass);
+		
+		if (pojoConstructor == null && pojoCreateMethod == null)
+			return null;
+
+		return new BuilderSwap(pojoClass, builderClass, pojoConstructor, builderConstructor, builderCreateMethod, pojoCreateMethod);
+	}
+
+	private static Method findBuilderCreateMethod(Class<?> pojoClass) {
+		for (Method m : pojoClass.getDeclaredMethods()) 
+			if (isPublic(m) && isStatic(m) && m.getName().equals("create") && m.getReturnType() != Void.class)
+				return m;
+		return null;
+	}
+		
+	private static Method findCreatePojoMethod(Class<?> builderClass) {
+		for (Method m : builderClass.getDeclaredMethods()) 
+			if ("build".equals(m.getName()) && ! (isStatic(m) || m.getReturnType() == Void.class)) 
+				return m;
+		return null;
+	}
+}
\ No newline at end of file
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonParserSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonParserSession.java
index ad7906a..6f9e9ad 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonParserSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonParserSession.java
@@ -131,7 +131,15 @@ public class UonParserSession extends ReaderParserSession {
 		if (eType == null)
 			eType = object();
 		PojoSwap<T,Object> swap = (PojoSwap<T,Object>)eType.getPojoSwap(this);
-		ClassMeta<?> sType = swap == null ? eType : swap.getSwapClassMeta(this);
+		BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this);
+		ClassMeta<?> sType = null;
+		if (builder != null)
+			sType = builder.getBuilderClassMeta(this);
+		else if (swap != null)
+			sType = swap.getSwapClassMeta(this);
+		else
+			sType = eType;
+		setCurrentClass(sType);
 
 		Object o = null;
 
@@ -210,6 +218,10 @@ public class UonParserSession extends ReaderParserSession {
 				);
 				o = parseIntoCollection(r, l, sType, isUrlParamValue, pMeta);
 			}
+		} else if (builder != null) {
+			BeanMap m = toBeanMap(builder.create(this, eType));
+			m = parseIntoBeanMap(r, m);
+			o = m == null ? null : builder.build(this, m.getBean(), eType);
 		} else if (sType.canCreateNewBean(outer)) {
 			BeanMap m = newBeanMap(outer, sType.getInnerClass());
 			m = parseIntoBeanMap(r, m);
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
index 7730c9c..28e7b89 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
@@ -221,25 +221,27 @@ public class UonSerializerSession extends WriterSerializerSession {
 
 		for (BeanPropertyValue p : m.getValues(isTrimNulls(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null)) {
 			BeanPropertyMeta pMeta = p.getMeta();
-			ClassMeta<?> cMeta = p.getClassMeta();
+			if (pMeta.canRead()) {
+				ClassMeta<?> cMeta = p.getClassMeta();
 
-			String key = p.getName();
-			Object value = p.getValue();
-			Throwable t = p.getThrown();
-			if (t != null)
-				onBeanGetterException(pMeta, t);
+				String key = p.getName();
+				Object value = p.getValue();
+				Throwable t = p.getThrown();
+				if (t != null)
+					onBeanGetterException(pMeta, t);
 
-			if (canIgnoreValue(cMeta, key, value))
-				continue;
+				if (canIgnoreValue(cMeta, key, value))
+					continue;
 
-			if (addComma)
-				out.append(',');
+				if (addComma)
+					out.append(',');
 
-			out.cr(indent).appendObject(key, false).append('=');
+				out.cr(indent).appendObject(key, false).append('=');
 
-			serializeAnything(out, value, cMeta, key, pMeta);
+				serializeAnything(out, value, cMeta, key, pMeta);
 
-			addComma = true;
+				addComma = true;
+			}
 		}
 
 		if (m.size() > 0)
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java
index 2207577..0134cea 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java
@@ -96,7 +96,14 @@ public class UrlEncodingParserSession extends UonParserSession {
 		if (eType == null)
 			eType = (ClassMeta<T>)object();
 		PojoSwap<T,Object> swap = (PojoSwap<T,Object>)eType.getPojoSwap(this);
-		ClassMeta<?> sType = swap == null ? eType : swap.getSwapClassMeta(this);
+		BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this);
+		ClassMeta<?> sType = null;
+		if (builder != null)
+			sType = builder.getBuilderClassMeta(this);
+		else if (swap != null)
+			sType = swap.getSwapClassMeta(this);
+		else
+			sType = eType;
 
 		int c = r.peekSkipWs();
 		if (c == '?')
@@ -114,6 +121,10 @@ public class UrlEncodingParserSession extends UonParserSession {
 		} else if (sType.isMap()) {
 			Map m = (sType.canCreateNewInstance() ? (Map)sType.newInstance() : new ObjectMap(this));
 			o = parseIntoMap2(r, m, sType, m);
+		} else if (builder != null) {
+			BeanMap m = toBeanMap(builder.create(this, eType));
+			m = parseIntoBeanMap(r, m);
+			o = m == null ? null : builder.build(this, m.getBean(), eType);
 		} else if (sType.canCreateNewBean(outer)) {
 			BeanMap m = newBeanMap(outer, sType.getInnerClass());
 			m = parseIntoBeanMap(r, m);
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java
index f0d3bfc..39b95a3 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java
@@ -222,43 +222,45 @@ public class UrlEncodingSerializerSession extends UonSerializerSession {
 
 		for (BeanPropertyValue p : m.getValues(isTrimNulls(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null)) {
 			BeanPropertyMeta pMeta = p.getMeta();
-			ClassMeta<?> cMeta = p.getClassMeta();
-			ClassMeta<?> sMeta = cMeta.getSerializedClassMeta(this);
-
-			String key = p.getName();
-			Object value = p.getValue();
-			Throwable t = p.getThrown();
-			if (t != null)
-				onBeanGetterException(pMeta, t);
-
-			if (canIgnoreValue(sMeta, key, value))
-				continue;
-
-			if (value != null && shouldUseExpandedParams(pMeta)) {
-				// Transformed object array bean properties may be transformed resulting in ArrayLists,
-				// so we need to check type if we think it's an array.
-				Iterator i = (sMeta.isCollection() || value instanceof Collection) ? ((Collection)value).iterator() : iterator(value);
-				while (i.hasNext()) {
+			if (pMeta.canRead()) {
+				ClassMeta<?> cMeta = p.getClassMeta();
+				ClassMeta<?> sMeta = cMeta.getSerializedClassMeta(this);
+
+				String key = p.getName();
+				Object value = p.getValue();
+				Throwable t = p.getThrown();
+				if (t != null)
+					onBeanGetterException(pMeta, t);
+
+				if (canIgnoreValue(sMeta, key, value))
+					continue;
+
+				if (value != null && shouldUseExpandedParams(pMeta)) {
+					// Transformed object array bean properties may be transformed resulting in ArrayLists,
+					// so we need to check type if we think it's an array.
+					Iterator i = (sMeta.isCollection() || value instanceof Collection) ? ((Collection)value).iterator() : iterator(value);
+					while (i.hasNext()) {
+						if (addAmp)
+							out.cr(indent).append('&');
+
+						out.appendObject(key, true).append('=');
+
+						super.serializeAnything(out, i.next(), cMeta.getElementType(), key, pMeta);
+
+						addAmp = true;
+					}
+				} else {
 					if (addAmp)
 						out.cr(indent).append('&');
 
 					out.appendObject(key, true).append('=');
 
-					super.serializeAnything(out, i.next(), cMeta.getElementType(), key, pMeta);
+					super.serializeAnything(out, value, cMeta, key, pMeta);
 
 					addAmp = true;
 				}
-			} else {
-				if (addAmp)
-					out.cr(indent).append('&');
-
-				out.appendObject(key, true).append('=');
-
-				super.serializeAnything(out, value, cMeta, key, pMeta);
-
-				addAmp = true;
+				
 			}
-
 		}
 		return out;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlParserSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlParserSession.java
index 630bce6..f1bc15e 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlParserSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlParserSession.java
@@ -286,7 +286,14 @@ public class XmlParserSession extends ReaderParserSession {
 		if (eType == null)
 			eType = (ClassMeta<T>)object();
 		PojoSwap<T,Object> swap = (PojoSwap<T,Object>)eType.getPojoSwap(this);
-		ClassMeta<?> sType = swap == null ? eType : swap.getSwapClassMeta(this);
+		BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this);
+		ClassMeta<?> sType = null;
+		if (builder != null)
+			sType = builder.getBuilderClassMeta(this);
+		else if (swap != null)
+			sType = swap.getSwapClassMeta(this);
+		else
+			sType = eType;
 		setCurrentClass(sType);
 
 		String wrapperAttr = (isRoot && preserveRootElement) ? r.getName().getLocalPart() : null;
@@ -352,19 +359,20 @@ public class XmlParserSession extends ReaderParserSession {
 			o = parseIntoCollection(r, l, sType, pMeta);
 		} else if (sType.isNumber()) {
 			o = parseNumber(getElementText(r), (Class<? extends Number>)sType.getInnerClass());
-		} else if (sType.canCreateNewBean(outer)) {
+		} else if (builder != null || sType.canCreateNewBean(outer)) {
 			if (sType.getExtendedMeta(XmlClassMeta.class).getFormat() == COLLAPSED) {
 				String fieldName = r.getLocalName();
-				BeanMap<?> m = newBeanMap(outer, sType.getInnerClass());
+				BeanMap<?> m = builder != null ? toBeanMap(builder.create(this, eType)) : newBeanMap(outer, sType.getInnerClass());
 				BeanPropertyMeta bpm = m.getMeta().getExtendedMeta(XmlBeanMeta.class).getPropertyMeta(fieldName);
 				ClassMeta<?> cm = m.getMeta().getClassMeta();
 				Object value = parseAnything(cm, currAttr, r, m.getBean(false), false, null);
 				setName(cm, value, currAttr);
 				bpm.set(m, currAttr, value);
-				o = m.getBean();
+				o = builder != null ? builder.build(this, m.getBean(), eType) : m.getBean();
 			} else {
-				BeanMap m = newBeanMap(outer, sType.getInnerClass());
-				o = parseIntoBean(r, m).getBean();
+				BeanMap m = builder != null ? toBeanMap(builder.create(this, eType)) : newBeanMap(outer, sType.getInnerClass());
+				m = parseIntoBean(r, m);
+				o = builder != null ? builder.build(this, m.getBean(), eType) : m.getBean();
 			}
 		} else if (sType.isArray() || sType.isArgs()) {
 			ArrayList l = (ArrayList)parseIntoCollection(r, new ArrayList(), sType, pMeta);
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSchemaSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSchemaSerializerSession.java
index 9a3762f..86c4f2b 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSchemaSerializerSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSchemaSerializerSession.java
@@ -349,9 +349,11 @@ public class XmlSchemaSerializerSession extends XmlSerializerSession {
 					boolean hasChildElements = false;
 
 					for (BeanPropertyMeta pMeta : bm.getPropertyMetas()) {
-						XmlFormat pMetaFormat = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getXmlFormat();
-						if (pMetaFormat != XmlFormat.ATTR)
-							hasChildElements = true;
+						if (pMeta.canRead()) {
+							XmlFormat pMetaFormat = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getXmlFormat();
+							if (pMetaFormat != XmlFormat.ATTR)
+								hasChildElements = true;
+						}
 					}
 
 					XmlBeanMeta xbm2 = bm.getExtendedMeta(XmlBeanMeta.class);
@@ -369,17 +371,20 @@ public class XmlSchemaSerializerSession extends XmlSerializerSession {
 						boolean hasCollapsed = false;
 
 						for (BeanPropertyMeta pMeta : bm.getPropertyMetas()) {
-							XmlBeanPropertyMeta xmlMeta = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class);
-							if (xmlMeta.getXmlFormat() != ATTR) {
-								if (xmlMeta.getNamespace() != null) {
-									ClassMeta<?> ct2 = pMeta.getClassMeta();
-									Namespace cNs = first(xmlMeta.getNamespace(), ct2.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs);
-									// Child element is in another namespace.
-									schemas.queueElement(cNs, pMeta.getName(), ct2);
-									hasOtherNsElement = true;
+							if (pMeta.canRead()) {
+								XmlBeanPropertyMeta xmlMeta = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class);
+								if (xmlMeta.getXmlFormat() != ATTR) {
+									if (xmlMeta.getNamespace() != null) {
+										ClassMeta<?> ct2 = pMeta.getClassMeta();
+										Namespace cNs = first(xmlMeta.getNamespace(), ct2.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs);
+										// Child element is in another namespace.
+										schemas.queueElement(cNs, pMeta.getName(), ct2);
+										hasOtherNsElement = true;
+									}
+									if (xmlMeta.getXmlFormat() == COLLAPSED)
+										hasCollapsed = true;
 								}
-								if (xmlMeta.getXmlFormat() == COLLAPSED)
-									hasCollapsed = true;
+								
 							}
 						}
 
@@ -396,30 +401,31 @@ public class XmlSchemaSerializerSession extends XmlSerializerSession {
 						} else {
 							w.sTag(i+1, "all").nl(i+1);
 							for (BeanPropertyMeta pMeta : bm.getPropertyMetas()) {
-								XmlBeanPropertyMeta xmlMeta = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class);
-								if (xmlMeta.getXmlFormat() != ATTR) {
-									boolean isCollapsed = xmlMeta.getXmlFormat() == COLLAPSED;
-									ClassMeta<?> ct2 = pMeta.getClassMeta();
-									String childName = pMeta.getName();
-									if (isCollapsed) {
-										if (xmlMeta.getChildName() != null)
-											childName = xmlMeta.getChildName();
-										ct2 = pMeta.getClassMeta().getElementType();
+								if (pMeta.canRead()) {
+									XmlBeanPropertyMeta xmlMeta = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class);
+									if (xmlMeta.getXmlFormat() != ATTR) {
+										boolean isCollapsed = xmlMeta.getXmlFormat() == COLLAPSED;
+										ClassMeta<?> ct2 = pMeta.getClassMeta();
+										String childName = pMeta.getName();
+										if (isCollapsed) {
+											if (xmlMeta.getChildName() != null)
+												childName = xmlMeta.getChildName();
+											ct2 = pMeta.getClassMeta().getElementType();
+										}
+										Namespace cNs = first(xmlMeta.getNamespace(), ct2.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs);
+										if (xmlMeta.getNamespace() == null) {
+											w.oTag(i+2, "element")
+												.attr("name", XmlUtils.encodeElementName(childName), false)
+												.attr("type", getXmlType(cNs, ct2))
+												.attr("minOccurs", 0);
+
+											w.ceTag().nl(i+2);
+										} else {
+											// Child element is in another namespace.
+											schemas.queueElement(cNs, pMeta.getName(), ct2);
+											hasOtherNsElement = true;
+										}
 									}
-									Namespace cNs = first(xmlMeta.getNamespace(), ct2.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs);
-									if (xmlMeta.getNamespace() == null) {
-										w.oTag(i+2, "element")
-											.attr("name", XmlUtils.encodeElementName(childName), false)
-											.attr("type", getXmlType(cNs, ct2))
-											.attr("minOccurs", 0);
-
-										w.ceTag().nl(i+2);
-									} else {
-										// Child element is in another namespace.
-										schemas.queueElement(cNs, pMeta.getName(), ct2);
-										hasOtherNsElement = true;
-									}
-
 								}
 							}
 							w.eTag(i+1, "all").nl(i+1);
@@ -428,26 +434,28 @@ public class XmlSchemaSerializerSession extends XmlSerializerSession {
 					}
 
 					for (BeanPropertyMeta pMeta : bm.getExtendedMeta(XmlBeanMeta.class).getAttrProperties().values()) {
-						Namespace pNs = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace();
-						if (pNs == null)
-							pNs = defaultNs;
-
-						// If the bean attribute has a different namespace than the bean, then it needs to
-						// be added as a top-level entry in the appropriate schema file.
-						if (pNs != targetNs) {
-							schemas.queueAttribute(pNs, pMeta.getName(), pMeta.getClassMeta());
-							w.oTag(i+1, "attribute")
-								//.attr("name", pMeta.getName(), true)
-								.attr("ref", pNs.getName() + ':' + pMeta.getName())
-								.ceTag().nl(i+1);
-						}
+						if (pMeta.canRead()) {
+							Namespace pNs = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace();
+							if (pNs == null)
+								pNs = defaultNs;
+
+							// If the bean attribute has a different namespace than the bean, then it needs to
+							// be added as a top-level entry in the appropriate schema file.
+							if (pNs != targetNs) {
+								schemas.queueAttribute(pNs, pMeta.getName(), pMeta.getClassMeta());
+								w.oTag(i+1, "attribute")
+									//.attr("name", pMeta.getName(), true)
+									.attr("ref", pNs.getName() + ':' + pMeta.getName())
+									.ceTag().nl(i+1);
+							}
 
-						// Otherwise, it's just a plain attribute of this bean.
-						else {
-							w.oTag(i+1, "attribute")
-								.attr("name", pMeta.getName(), true)
-								.attr("type", getXmlAttrType(pMeta.getClassMeta()))
-								.ceTag().nl(i+1);
+							// Otherwise, it's just a plain attribute of this bean.
+							else {
+								w.oTag(i+1, "attribute")
+									.attr("name", pMeta.getName(), true)
+									.attr("type", getXmlAttrType(pMeta.getClassMeta()))
+									.ceTag().nl(i+1);
+							}
 						}
 					}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java
index 935dfd4..81583ea 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java
@@ -210,9 +210,11 @@ public class XmlSerializerSession extends WriterSerializerSession {
 
 				if (innerType.isBean()) {
 					for (BeanPropertyMeta bpm : innerType.getBeanMeta().getPropertyMetas()) {
-						ns = bpm.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace();
-						if (ns != null && ns.uri != null)
-							addNamespace(ns);
+						if (bpm.canRead()) {
+							ns = bpm.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace();
+							if (ns != null && ns.uri != null)
+								addNamespace(ns);
+						}
 					}
 
 				} else if (innerType.isMap()) {
@@ -562,40 +564,42 @@ public class XmlSerializerSession extends WriterSerializerSession {
 			String n = p.getName();
 			if (attrs.contains(n) || attrs.contains("*") || n.equals(attrsProperty)) {
 				BeanPropertyMeta pMeta = p.getMeta();
-				ClassMeta<?> cMeta = p.getClassMeta();
-
-				String key = p.getName();
-				Object value = p.getValue();
-				Throwable t = p.getThrown();
-				if (t != null)
-					onBeanGetterException(pMeta, t);
-
-				if (canIgnoreValue(cMeta, key, value))
-					continue;
-
-				Namespace ns = (enableNamespaces && pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace() != elementNs ? pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace() : null);
-
-				if (pMeta.isUri()  ) {
-					out.attrUri(ns, key, value);
-				} else if (n.equals(attrsProperty)) {
-					if (value instanceof BeanMap) {
-						BeanMap<?> bm2 = (BeanMap)value;
-						for (BeanPropertyValue p2 : bm2.getValues(true)) {
-							String key2 = p2.getName();
-							Object value2 = p2.getValue();
-							Throwable t2 = p2.getThrown();
-							if (t2 != null)
-								onBeanGetterException(pMeta, t);
-							out.attr(ns, key2, value2);
-						}
-					} else /* Map */ {
-						Map m2 = (Map)value;
-						for (Map.Entry e : (Set<Map.Entry>)(m2.entrySet())) {
-							out.attr(ns, toString(e.getKey()), e.getValue());
+				if (pMeta.canRead()) {
+					ClassMeta<?> cMeta = p.getClassMeta();
+
+					String key = p.getName();
+					Object value = p.getValue();
+					Throwable t = p.getThrown();
+					if (t != null)
+						onBeanGetterException(pMeta, t);
+
+					if (canIgnoreValue(cMeta, key, value))
+						continue;
+
+					Namespace ns = (enableNamespaces && pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace() != elementNs ? pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace() : null);
+
+					if (pMeta.isUri()  ) {
+						out.attrUri(ns, key, value);
+					} else if (n.equals(attrsProperty)) {
+						if (value instanceof BeanMap) {
+							BeanMap<?> bm2 = (BeanMap)value;
+							for (BeanPropertyValue p2 : bm2.getValues(true)) {
+								String key2 = p2.getName();
+								Object value2 = p2.getValue();
+								Throwable t2 = p2.getThrown();
+								if (t2 != null)
+									onBeanGetterException(pMeta, t);
+								out.attr(ns, key2, value2);
+							}
+						} else /* Map */ {
+							Map m2 = (Map)value;
+							for (Map.Entry e : (Set<Map.Entry>)(m2.entrySet())) {
+								out.attr(ns, toString(e.getKey()), e.getValue());
+							}
 						}
+					} else {
+						out.attr(ns, key, value);
 					}
-				} else {
-					out.attr(ns, key, value);
 				}
 			}
 		}
@@ -607,39 +611,41 @@ public class XmlSerializerSession extends WriterSerializerSession {
 
 		for (BeanPropertyValue p : lp) {
 			BeanPropertyMeta pMeta = p.getMeta();
-			ClassMeta<?> cMeta = p.getClassMeta();
+			if (pMeta.canRead()) {
+				ClassMeta<?> cMeta = p.getClassMeta();
 
-			String n = p.getName();
-			if (n.equals(contentProperty)) {
-				content = p.getValue();
-				contentType = p.getClassMeta();
-				hasContent = true;
-				cf = xbm.getContentFormat();
-				if (cf.isOneOf(MIXED,MIXED_PWS,TEXT,TEXT_PWS,XMLTEXT))
-					isMixed = true;
-				if (cf.isOneOf(MIXED_PWS, TEXT_PWS))
-					preserveWhitespace = true;
-				if (contentType.isCollection() && ((Collection)content).isEmpty())
-					hasContent = false;
-				else if (contentType.isArray() && Array.getLength(content) == 0)
-					hasContent = false;
-			} else if (elements.contains(n) || collapsedElements.contains(n) || elements.contains("*") || collapsedElements.contains("*") ) {
-				String key = p.getName();
-				Object value = p.getValue();
-				Throwable t = p.getThrown();
-				if (t != null)
-					onBeanGetterException(pMeta, t);
-
-				if (canIgnoreValue(cMeta, key, value))
-					continue;
-
-				if (! hasChildren) {
-					hasChildren = true;
-					out.appendIf(! isCollapsed, '>').nlIf(! isMixed, indent);
-				}
+				String n = p.getName();
+				if (n.equals(contentProperty)) {
+					content = p.getValue();
+					contentType = p.getClassMeta();
+					hasContent = true;
+					cf = xbm.getContentFormat();
+					if (cf.isOneOf(MIXED,MIXED_PWS,TEXT,TEXT_PWS,XMLTEXT))
+						isMixed = true;
+					if (cf.isOneOf(MIXED_PWS, TEXT_PWS))
+						preserveWhitespace = true;
+					if (contentType.isCollection() && ((Collection)content).isEmpty())
+						hasContent = false;
+					else if (contentType.isArray() && Array.getLength(content) == 0)
+						hasContent = false;
+				} else if (elements.contains(n) || collapsedElements.contains(n) || elements.contains("*") || collapsedElements.contains("*") ) {
+					String key = p.getName();
+					Object value = p.getValue();
+					Throwable t = p.getThrown();
+					if (t != null)
+						onBeanGetterException(pMeta, t);
+
+					if (canIgnoreValue(cMeta, key, value))
+						continue;
+
+					if (! hasChildren) {
+						hasChildren = true;
+						out.appendIf(! isCollapsed, '>').nlIf(! isMixed, indent);
+					}
 
-				XmlBeanPropertyMeta xbpm = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class);
-				serializeAnything(out, value, cMeta, key, xbpm.getNamespace(), false, xbpm.getXmlFormat(), isMixed, false, pMeta);
+					XmlBeanPropertyMeta xbpm = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class);
+					serializeAnything(out, value, cMeta, key, xbpm.getNamespace(), false, xbpm.getXmlFormat(), isMixed, false, pMeta);
+				}
 			}
 		}
 		if (! hasContent)
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/proto/YamlParserSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/proto/YamlParserSession.java
index b494a41..2f33098 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/proto/YamlParserSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/proto/YamlParserSession.java
@@ -113,7 +113,14 @@ public final class YamlParserSession extends ReaderParserSession {
 		if (eType == null)
 			eType = object();
 		PojoSwap<T,Object> swap = (PojoSwap<T,Object>)eType.getPojoSwap(this);
-		ClassMeta<?> sType = swap == null ? eType : swap.getSwapClassMeta(this);
+		BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this);
+		ClassMeta<?> sType = null;
+		if (builder != null)
+			sType = builder.getBuilderClassMeta(this);
+		else if (swap != null)
+			sType = swap.getSwapClassMeta(this);
+		else
+			sType = eType;
 		setCurrentClass(sType);
 
 		Object o = null;
@@ -171,6 +178,9 @@ public final class YamlParserSession extends ReaderParserSession {
 				Collection l = (sType.canCreateNewInstance(outer) ? (Collection)sType.newInstance() : new ObjectList(this));
 				o = parseIntoCollection2(r, l, sType, pMeta);
 			}
+		} else if (builder != null) {
+			BeanMap m = toBeanMap(builder.create(this, eType));
+			o = builder.build(this, parseIntoBeanMap2(r, m).getBean(), eType);
 		} else if (sType.canCreateNewBean(outer)) {
 			BeanMap m = newBeanMap(outer, sType.getInnerClass());
 			o = parseIntoBeanMap2(r, m).getBean();
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/proto/YamlSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/proto/YamlSerializerSession.java
index f299a7a..21436d8 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/proto/YamlSerializerSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/proto/YamlSerializerSession.java
@@ -174,24 +174,26 @@ public class YamlSerializerSession extends WriterSerializerSession {
 		boolean addComma = false;
 		for (BeanPropertyValue p : m.getValues(isTrimNulls(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null)) {
 			BeanPropertyMeta pMeta = p.getMeta();
-			ClassMeta<?> cMeta = p.getClassMeta();
-			String key = p.getName();
-			Object value = p.getValue();
-			Throwable t = p.getThrown();
-			if (t != null)
-				onBeanGetterException(pMeta, t);
+			if (pMeta.canRead()) {
+				ClassMeta<?> cMeta = p.getClassMeta();
+				String key = p.getName();
+				Object value = p.getValue();
+				Throwable t = p.getThrown();
+				if (t != null)
+					onBeanGetterException(pMeta, t);
 
-			if (canIgnoreValue(cMeta, key, value))
-				continue;
+				if (canIgnoreValue(cMeta, key, value))
+					continue;
 
-			if (addComma)
-				out.append(',').smi(i);
+				if (addComma)
+					out.append(',').smi(i);
 
-			out.cr(i).attr(key).append(':').s(i);
+				out.cr(i).attr(key).append(':').s(i);
 
-			serializeAnything(out, value, cMeta, key, pMeta);
+				serializeAnything(out, value, cMeta, key, pMeta);
 
-			addComma = true;
+				addComma = true;
+			}
 		}
 		out.cre(i-1).append('}');
 		return out;
diff --git a/juneau-doc/src/main/javadoc/overview.html b/juneau-doc/src/main/javadoc/overview.html
index de1b32f..404ba88 100644
--- a/juneau-doc/src/main/javadoc/overview.html
+++ b/juneau-doc/src/main/javadoc/overview.html
@@ -105,6 +105,7 @@
 				<li><p><a class='doclink' href='#juneau-marshall.BeanIgnoreAnnotation'>@BeanIgnore Annotation</a></p>
 				<li><p><a class='doclink' href='#juneau-marshall.NamePropertyAnnotation'>@NameProperty Annotation</a></p>
 				<li><p><a class='doclink' href='#juneau-marshall.ParentPropertyAnnotation'>@ParentProperty Annotation</a></p>
+				<li><p><a class='doclink' href='#juneau-marshall.PojoBuilders'>POJO Builders</a></p>
 				<li><p><a class='doclink' href='#juneau-marshall.URIs'>URIs</a></p>
 				<li><p><a class='doclink' href='#juneau-marshall.BeanFilters'>BeanFilters</a></p>
 				<li><p><a class='doclink' href='#juneau-marshall.InterfaceFilters'>Interface filters</a></p>
@@ -2347,8 +2348,117 @@
 			</div>
 
 			<!-- =================================================================================================== -->
+			<a id="juneau-marshall.PojoBuilders"></a>
+			<h4 class='topic' onclick='toggle(this)'>2.1.7.14 - POJO Builders</h4>
+			<div class='topic'>
+				<p>
+					Juneau parsers can use builders to instantiate POJOs.
+					<br>This is useful in cases where you want to create beans with read-only properties.
+					<br>Note that while it's possible to do this using the {@link org.apache.juneau.annotation.BeanConstructor @BeanConstructor}
+					annotation, using builders can often be cleaner.
+				</p>
+				<p>
+					A typical builder usage is shown below:
+				</p>
+				<p class='bcode'>
+	MyBean b = MyBean.<jsm>create</jsm>().foo(<js>"foo"</js>).bar(123).build();
+				</p>								
+				<p>
+					The code for such a builder is shown below:
+				</p>
+				<p class='bcode'>
+	<jk>public class</jk> MyBean {
+	
+		<jc>// Read-only properties.</jc>
+		<jk>public final</jk> String <jf>foo</jf>;
+		<jk>public final int</jk> <jf>bar</jf>;
+		
+		<jc>// Private constructor.</jc>
+		<jk>private</jk> MyBean(MyBeanBuilder b) {
+			<jk>this</jk>.<jf>foo</jf> = b.foo;
+			<jk>this</jk>.<jf>bar</jf> = b.bar;
+		}
+
+		<jc>// Static method that creates a builder.</jc>
+		<jk>public static</jk> MyBeanBuilder <jsm>create</jsm>() {
+			<jk>return new</jk> MyBeanBuilder();
+		}
+
+		<jc>// Builder class.</jc>
+		<jk>public static class</jk> MyBeanBuilder {
+			<jk>private</jk> String <jf>foo</jf>;
+			<jk>private int</jk> <jf>bar</jf>;
+			
+			<jc>// Method that creates the bean.</jc>
+			<jk>public</jk> MyBean build() {
+				<jk>return new</jk> MyBean(<jk>this</jk>);
+			}
+			
+			<jc>// Bean property setters.</jc>
+			
+			<ja>@BeanProperty</ja>
+			<jk>public</jk> MyBeanBuilder foo(String foo) {
+				<jk>this</jk>.<jf>foo</jf> = foo;
+				<jk>return this</jk>;
+			}
+			
+			<ja>@BeanProperty</ja>
+			<jk>public</jk> MyBeanBuilder bar(<jk>int</jk> bar) {
+				<jk>this</jk>.<jf>bar</jf> = bar;
+				<jk>return this</jk>;
+			}
+		}
+	}
+				</p>
+				<p>
+					Builders MUST be beans with one or more writable properties.
+					<br>The bean properties themselves do not need to be readable (e.g. setters without getters).
+				</p>
+				<p>
+					Builders require two parts:
+				</p>
+				<ol>
+					<li>A way to detect and instantiate a builder using reflection.
+					<li>A way to instantiate a POJO from a builder.
+				</ol>
+				<p>
+					The first can be accomplished through any of the following:
+				</p>
+				<ul class='spaced-list'>
+					<li>A static <code>create()</code> method on the POJO class that returns a builder instance.
+						<p class='bcode'>
+	<jk>public static</jk> MyBuilder <jsm>create</jsm>() {...}
+						</p>
+					<li>A public constructor on the POJO class that takes in a single parameter that implements the {@link org.apache.juneau.transform.Builder} interface.
+						<br>The builder class must have a public no-arg constructor.
+						<p class='bcode'>
+	<jk>public</jk> MyPojo(MyBuilder b) {...}
+						</p>
+					<li>A {@link org.apache.juneau.annotation.Builder @Builder} annotation on the POJO class.
+						<br>The builder class must have a public no-arg constructor.
+						<p class='bcode'>
+	<ja>@Builder</ja>(MyBuilder.<jk>class</jk>)
+	<jk>public class</jk> MyPojo {...}
+						</p>
+				</ul>
+				<p>
+					The second can be accomplished through any of the following:
+				</p>
+				<ul class='spaced-list'>
+					<li>The existence of a <code>build()</code> method on the builder class.
+						<p class='bcode'>
+	<jk>public</jk> MyPojo build() {...}
+						</p>
+					<li>The existence of a public constructor on the POJO class that takes in the builder instance.
+						<p class='bcode'>
+	<jk>public</jk> MyPojo(MyBuilder b) {...}
+						</p>
+				</ul>
+			</div>
+			
+			<!-- =================================================================================================== -->
 			<a id="juneau-marshall.URIs"></a>
-			<h4 class='topic' onclick='toggle(this)'>2.1.7.14 - URIs</h4>
+			<h4 class='topic' onclick='toggle(this)'>2.1.7.15 - URIs</h4>
 			<div class='topic'>
 				<p>
 					Juneau serializers have sophisticated support for transforming relative URIs to absolute form.
@@ -2477,7 +2587,7 @@
 
 			<!-- ======================================================================================================= -->
 			<a id="juneau-marshall.BeanFilters"></a>
-			<h4 class='topic' onclick='toggle(this)'>2.1.7.15 - BeanFilter Class</h4>
+			<h4 class='topic' onclick='toggle(this)'>2.1.7.16 - BeanFilter Class</h4>
 			<div class='topic'>
 				<p>
 					The {@link org.apache.juneau.transform.BeanFilter} class is the programmatic equivalent to the
@@ -2566,7 +2676,7 @@
 
 			<!-- =================================================================================================== -->
 			<a id="juneau-marshall.InterfaceFilters"></a>
-			<h4 class='topic' onclick='toggle(this)'>2.1.7.16 - Interface Filters</h4>
+			<h4 class='topic' onclick='toggle(this)'>2.1.7.17 - Interface Filters</h4>
 			<div class='topic'>
 				<p>
 					Occasionally, you may want to limit bean properties to only those defined on a parent class or interface.  
@@ -2670,7 +2780,7 @@
 			
 			<!-- =================================================================================================== -->
 			<a id="juneau-marshall.StopClasses"></a>
-			<h4 class='topic' onclick='toggle(this)'>2.1.7.17 - Stop Classes</h4>
+			<h4 class='topic' onclick='toggle(this)'>2.1.7.18 - Stop Classes</h4>
 			<div class='topic'>
 				<p>
 					Whereas interface filters limit properties defined on child classes, stop filters 
@@ -2712,7 +2822,7 @@
 
 			<!-- =================================================================================================== -->
 			<a id="juneau-marshall.BypassSerialization"></a>
-			<h4 class='topic' onclick='toggle(this)'>2.1.7.18 - Bypass Serialization using Readers and InputStreams</h4>
+			<h4 class='topic' onclick='toggle(this)'>2.1.7.19 - Bypass Serialization using Readers and InputStreams</h4>
 			<div class='topic'>
 				<p>
 					Juneau serializers treat instances of <code>Readers</code> and <code>InputStreams</code> special by 
@@ -3054,7 +3164,8 @@
 			</p>
 			<h6 class='figure'>Examples:</h6>
 			<p class='bcode'>
-	ReaderParser p = JsonParser.<jsm>create</jsm>().unbuffered().build();
+	<jc>// If you're calling parse on the same input multiple times, use a session instead of the parser directly.</jc>
+	ReaderParserSession p = JsonParser.<jsm>create</jsm>().unbuffered().build().createSession();
 	Object x;
 	Reader r;
 
@@ -12595,9 +12706,17 @@
 			<li>
 				New {@link org.apache.juneau.parser.Parser#PARSER_autoCloseStreams} setting allows input streams and
 				readers passed into parsers to be automatically closed after parsing.
+			<li>
+				Syntax changed on unswap method on {@link org.apache.juneau.transform.Surrogate} classes.
+				<br>It's now a regular method instead of a static method.
+			<li>
+				{@link org.apache.juneau.annotation.Swap @Swap} annotation can now be used with 
+				{@link org.apache.juneau.transform.Surrogate} classes.
+			<li>
+				New support for <a class='doclink' href='#juneau-marshall.PojoBuilders'>POJO Builders</a>.	
 		</ul>
 
-		<h6 class='topic'>juneau-marshall</h6>
+		<h6 class='topic'>juneau-dto</h6>
 		<ul class='spaced-list'>
 			<li>
 				Enhancements to Swagger DTO:
@@ -12614,12 +12733,6 @@
 						Setter methods that take in beans and collections of beans can now take in 
 						JSON strings.
 				</ul>
-			<li>
-				Syntax changed on unswap method on {@link org.apache.juneau.transform.Surrogate} classes.
-				<br>It's now a regular method instead of a static method.
-			<li>
-				{@link org.apache.juneau.annotation.Swap @Swap} annotation can now be used with 
-				{@link org.apache.juneau.transform.Surrogate} classes.
 		</ul>
 		
 		<h6 class='topic'>juneau-rest-server</h6>
@@ -17738,4 +17851,3 @@
 
 </div>
 </body>								
-	

-- 
To stop receiving notification emails like this one, please contact
jamesbognar@apache.org.

Mime
View raw message