http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/plaintext/package.html
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/plaintext/package.html 
b/juneau-core/src/main/java/org/apache/juneau/plaintext/package.html
new file mode 100644
index 0000000..e53f25a
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/plaintext/package.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<!--
+/***************************************************************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *  
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.  See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ 
***************************************************************************************************************************/
+ -->
+<html>
+<head>
+       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+       <style type="text/css">
+               /* For viewing in Page Designer */
+               @IMPORT url("../../../../../../javadoc.css");
+
+               /* For viewing in REST interface */
+               @IMPORT url("../htdocs/javadoc.css");
+               body { 
+                       margin: 20px; 
+               }       
+       </style>
+       <script>
+               /* Replace all @code and @link tags. */ 
+               window.onload = function() {
+                       document.body.innerHTML = 
document.body.innerHTML.replace(/\{\@code ([^\}]+)\}/g, '<code>$1</code>');
+                       document.body.innerHTML = 
document.body.innerHTML.replace(/\{\@link (([^\}]+)\.)?([^\.\}]+)\}/g, 
'<code>$3</code>');
+               }
+       </script>
+</head>
+<body>
+<p>Plain-text serialization and parsing support</p>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/serializer/OutputStreamSerializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/serializer/OutputStreamSerializer.java
 
b/juneau-core/src/main/java/org/apache/juneau/serializer/OutputStreamSerializer.java
new file mode 100644
index 0000000..b829f77
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/serializer/OutputStreamSerializer.java
@@ -0,0 +1,65 @@
+/***************************************************************************************************************************
+ * 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.serializer;
+
+import java.io.*;
+
+import org.apache.juneau.annotation.*;
+
+/**
+ * Subclass of {@link Serializer} for byte-based serializers.
+ *
+ *
+ * <h6 class='topic'>Description</h6>
+ * <p>
+ *     This class is typically the parent class of all byte-based serializers.
+ *     It has 1 abstract method to implement...
+ * <ul>
+ *     <li>{@link #doSerialize(SerializerSession, Object)}
+ * </ul>
+ *
+ *
+ * <h6 class='topic'>@Produces annotation</h6>
+ * <p>
+ *     The media types that this serializer can produce is specified through 
the {@link Produces @Produces} annotation.
+ * <p>
+ *     However, the media types can also be specified programmatically by 
overriding the {@link #getMediaTypes()}
+ *             and {@link #getResponseContentType()} methods.
+ *
+ * @author James Bognar ([email protected])
+ */
+public abstract class OutputStreamSerializer extends Serializer {
+
+       @Override /* Serializer */
+       public boolean isWriterSerializer() {
+               return false;
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Other methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Convenience method for serializing an object to a 
<code><jk>byte</jk></code>.
+        *
+        * @param o The object to serialize.
+        * @return The output serialized to a string.
+        * @throws SerializeException If a problem occurred trying to convert 
the output.
+        */
+       @Override
+       public final byte[] serialize(Object o) throws SerializeException {
+               ByteArrayOutputStream baos = new ByteArrayOutputStream();
+               serialize(createSession(baos), o);
+               return baos.toByteArray();
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/serializer/SerializeException.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializeException.java
 
b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializeException.java
new file mode 100644
index 0000000..a43b95a
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializeException.java
@@ -0,0 +1,105 @@
+/***************************************************************************************************************************
+ * 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.serializer;
+
+import java.text.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.json.*;
+
+/**
+ * General exception thrown whenever an error occurs during serialization.
+ *
+ * @author James Bognar ([email protected])
+ */
+public final class SerializeException extends FormattedException {
+
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * Constructor.
+        *
+        * @param session The serializer session to extract information from.
+        * @param message The exception message containing {@link 
MessageFormat}-style arguments.
+        * @param args Message arguments.
+        */
+       public SerializeException(SerializerSession session, String message, 
Object...args) {
+               super(getMessage(session, message, args));
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param message The exception message containing {@link 
MessageFormat}-style arguments.
+        * @param args Message arguments.
+        */
+       public SerializeException(String message, Object...args) {
+               super(getMessage(null, message, args));
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param session The serializer session to extract information from.
+        * @param causedBy The inner exception.
+        */
+       public SerializeException(SerializerSession session, Exception 
causedBy) {
+               super(causedBy, getMessage(session, causedBy.getMessage()));
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param causedBy The inner exception.
+        */
+       public SerializeException(Exception causedBy) {
+               super(causedBy, getMessage(null, causedBy.getMessage()));
+       }
+
+       private static String getMessage(SerializerSession session, String msg, 
Object... args) {
+               if (args.length != 0)
+                       msg = MessageFormat.format(msg, args);
+               if (session != null) {
+                       Map<String,Object> m = session.getLastLocation();
+                       if (m != null && ! m.isEmpty())
+                               msg = "Serialize exception occurred at " + 
JsonSerializer.DEFAULT_LAX.toString(m) + ".  " + msg;
+               }
+               return msg;
+       }
+
+       /**
+        * Returns the highest-level <code>ParseException</code> in the stack 
trace.
+        * Useful for JUnit testing of error conditions.
+        *
+        * @return The root parse exception, or this exception if there isn't 
one.
+        */
+       public SerializeException getRootCause() {
+               SerializeException t = this;
+               while (! (t.getCause() == null || ! (t.getCause() instanceof 
SerializeException)))
+                       t = (SerializeException)t.getCause();
+               return t;
+       }
+
+       /**
+        * Sets the inner cause for this exception.
+        *
+        * @param cause The inner cause.
+        * @return This object (for method chaining).
+        */
+       @Override /* Throwable */
+       public synchronized SerializeException initCause(Throwable cause) {
+               super.initCause(cause);
+               return this;
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/serializer/Serializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/serializer/Serializer.java 
b/juneau-core/src/main/java/org/apache/juneau/serializer/Serializer.java
new file mode 100644
index 0000000..c730705
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/serializer/Serializer.java
@@ -0,0 +1,335 @@
+/***************************************************************************************************************************
+ * 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.serializer;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.text.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.soap.*;
+
+/**
+ * Parent class for all Juneau serializers.
+ *
+ *
+ * <h6 class='topic'>Description</h6>
+ * <p>
+ *     Base serializer class that serves as the parent class for all 
serializers.
+ * <p>
+ *     Subclasses should extend directly from {@link OutputStreamSerializer} 
or {@link WriterSerializer}.
+ *
+ *
+ * <h6 class='topic'>@Produces annotation</h6>
+ * <p>
+ *     The media types that this serializer can produce is specified through 
the {@link Produces @Produces} annotation.
+ * <p>
+ *     However, the media types can also be specified programmatically by 
overriding the {@link #getMediaTypes()}
+ *             and {@link #getResponseContentType()} methods.
+ *
+ *
+ * <h6 class='topic'>Configurable properties</h6>
+ *     See {@link SerializerContext} for a list of configurable properties 
that can be set on this class
+ *     using the {@link #setProperty(String, Object)} method.
+ *
+ * @author James Bognar ([email protected])
+ */
+public abstract class Serializer extends CoreApi {
+
+       private final String[] mediaTypes;
+       private final MediaRange[] mediaRanges;
+       private final String contentType;
+
+       // Hidden constructors to force subclass from OuputStreamSerializer or 
WriterSerializer.
+       Serializer() {
+               Produces p = ReflectionUtils.getAnnotation(Produces.class, 
getClass());
+               if (p == null)
+                       throw new RuntimeException(MessageFormat.format("Class 
''{0}'' is missing the @Produces annotation", getClass().getName()));
+               this.mediaTypes = p.value();
+               for (int i = 0; i < mediaTypes.length; i++) {
+                       mediaTypes[i] = 
mediaTypes[i].toLowerCase(Locale.ENGLISH);
+               }
+
+               List<MediaRange> l = new LinkedList<MediaRange>();
+               for (int i = 0; i < mediaTypes.length; i++)
+                       
l.addAll(Arrays.asList(MediaRange.parse(mediaTypes[i])));
+               mediaRanges = l.toArray(new MediaRange[l.size()]);
+
+               String ct = p.contentType().isEmpty() ? p.value()[0] : 
p.contentType();
+               contentType = ct.isEmpty() ? null : ct;
+       }
+
+       /**
+        * Returns <jk>true</jk> if this parser subclasses from {@link 
WriterSerializer}.
+        *
+        * @return <jk>true</jk> if this parser subclasses from {@link 
WriterSerializer}.
+        */
+       public abstract boolean isWriterSerializer();
+
+       
//--------------------------------------------------------------------------------
+       // Abstract methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Serializes a POJO to the specified output stream or writer.
+        * <p>
+        * This method should NOT close the context object.
+        * @param session The serializer session object return by {@link 
#createSession(Object, ObjectMap, Method)}.<br>
+        *      If <jk>null</jk>, session is created using {@link 
#createSession(Object)}.
+        * @param o The object to serialize.
+        *
+        * @throws Exception If thrown from underlying stream, or if the input 
contains a syntax error or is malformed.
+        */
+       protected abstract void doSerialize(SerializerSession session, Object 
o) throws Exception;
+
+       /**
+        * Shortcut method for serializing objects directly to either a 
<code>String</code> or <code><jk>byte</jk>[]</code>
+        *      depending on the serializer type.
+        * <p>
+        *
+        * @param o The object to serialize.
+        * @return The serialized object.
+        *      <br>Character-based serializers will return a 
<code>String</code>
+        *      <br>Stream-based serializers will return a 
<code><jk>byte</jk>[]</code>
+        * @throws SerializeException If a problem occurred trying to convert 
the output.
+        */
+       public abstract Object serialize(Object o) throws SerializeException;
+
+       
//--------------------------------------------------------------------------------
+       // Other methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Serialize the specified object using the specified session.
+        *
+        * @param session The serializer session object return by {@link 
#createSession(Object, ObjectMap, Method)}.<br>
+        *      If <jk>null</jk>, session is created using {@link 
#createSession(Object)}.
+        * @param o The object to serialize.
+        * @throws SerializeException If a problem occurred trying to convert 
the output.
+        */
+       public final void serialize(SerializerSession session, Object o) throws 
SerializeException {
+               try {
+                       doSerialize(session, o);
+               } catch (SerializeException e) {
+                       throw e;
+               } catch (StackOverflowError e) {
+                       throw new SerializeException(session, "Stack overflow 
occurred.  This can occur when trying to serialize models containing loops.  
It's recommended you use the SerializerContext.SERIALIZER_detectRecursions 
setting to help locate the loop.").initCause(e);
+               } catch (Exception e) {
+                       throw new SerializeException(session, e);
+               } finally {
+                       session.close();
+               }
+       }
+
+       /**
+        * Serializes a POJO to the specified output stream or writer.
+        * <p>
+        * Equivalent to calling <code>serializer.serialize(o, out, 
<jk>null</jk>);</code>
+        *
+        * @param o The object to serialize.
+        * @param output The output object.
+        *      <br>Character-based serializers can handle the following output 
class types:
+        *      <ul>
+        *              <li>{@link Writer}
+        *              <li>{@link OutputStream} - Output will be written as 
UTF-8 encoded stream.
+        *              <li>{@link File} - Output will be written as 
system-default encoded stream.
+        *      </ul>
+        *      <br>Stream-based serializers can handle the following output 
class types:
+        *      <ul>
+        *              <li>{@link OutputStream}
+        *              <li>{@link File}
+        *      </ul>
+        * @throws SerializeException If a problem occurred trying to convert 
the output.
+        */
+       public final void serialize(Object o, Object output) throws 
SerializeException {
+               SerializerSession session = createSession(output);
+               serialize(session, o);
+       }
+
+       /**
+        * Create the session object that will be passed in to the serialize 
method.
+        * <p>
+        *      It's up to implementers to decide what the session object looks 
like, although typically
+        *      it's going to be a subclass of {@link SerializerSession}.
+        *
+        * @param output The output object.
+        *      <br>Character-based serializers can handle the following output 
class types:
+        *      <ul>
+        *              <li>{@link Writer}
+        *              <li>{@link OutputStream} - Output will be written as 
UTF-8 encoded stream.
+        *              <li>{@link File} - Output will be written as 
system-default encoded stream.
+        *      </ul>
+        *      <br>Stream-based serializers can handle the following output 
class types:
+        *      <ul>
+        *              <li>{@link OutputStream}
+        *              <li>{@link File}
+        *      </ul>
+        * @param properties Optional additional properties.
+        * @param javaMethod Java method that invoked this serializer.
+        *      When using the REST API, this is the Java method invoked by the 
REST call.
+        *      Can be used to access annotations defined on the method or 
class.
+        * @return The new session.
+        */
+       public SerializerSession createSession(Object output, ObjectMap 
properties, Method javaMethod) {
+               return new 
SerializerSession(getContext(SerializerContext.class), getBeanContext(), 
output, properties, javaMethod);
+       }
+
+       /**
+        * Create a basic session object without overriding properties or 
specifying <code>javaMethod</code>.
+        * <p>
+        * Equivalent to calling <code>createSession(<jk>null</jk>, 
<jk>null</jk>)</code>.
+        *
+        * @param output The output object.
+        *      <br>Character-based serializers can handle the following output 
class types:
+        *      <ul>
+        *              <li>{@link Writer}
+        *              <li>{@link OutputStream} - Output will be written as 
UTF-8 encoded stream.
+        *              <li>{@link File} - Output will be written as 
system-default encoded stream.
+        *      </ul>
+        *      <br>Stream-based serializers can handle the following output 
class types:
+        *      <ul>
+        *              <li>{@link OutputStream}
+        *              <li>{@link File}
+        *      </ul>
+        * @return The new session.
+        */
+       protected SerializerSession createSession(Object output) {
+               return createSession(output, null, null);
+       }
+
+       /**
+        * Converts the contents of the specified object array to a list.
+        * <p>
+        *      Works on both object and primitive arrays.
+        * <p>
+        *      In the case of multi-dimensional arrays, the outgoing list will
+        *      contain elements of type n-1 dimension.  i.e. if {@code type} 
is <code><jk>int</jk>[][]</code>
+        *      then {@code list} will have entries of type 
<code><jk>int</jk>[]</code>.
+        *
+        * @param type The type of array.
+        * @param array The array being converted.
+        * @return The array as a list.
+        */
+       protected final List<Object> toList(Class<?> type, Object array) {
+               Class<?> componentType = type.getComponentType();
+               if (componentType.isPrimitive()) {
+                       int l = Array.getLength(array);
+                       List<Object> list = new ArrayList<Object>(l);
+                       for (int i = 0; i < l; i++)
+                               list.add(Array.get(array, i));
+                       return list;
+               }
+               return Arrays.asList((Object[])array);
+       }
+
+       /**
+        * Returns the media types handled based on the value of the {@link 
Produces} annotation on the serializer class.
+        * <p>
+        * This method can be overridden by subclasses to determine the media 
types programatically.
+        *
+        * @return The list of media types.  Never <jk>null</jk>.
+        */
+       public String[] getMediaTypes() {
+               return mediaTypes;
+       }
+
+       /**
+        * Returns the results from {@link #getMediaTypes()} parsed as {@link 
MediaRange MediaRanges}.
+        *
+        * @return The list of media types parsed as ranges.  Never 
<jk>null</jk>.
+        */
+       public MediaRange[] getMediaRanges() {
+               return mediaRanges;
+       }
+
+       /**
+        * Optional method that specifies HTTP request headers for this 
serializer.
+        * <p>
+        *      For example, {@link SoapXmlSerializer} needs to set a 
<code>SOAPAction</code> header.
+        * <p>
+        *      This method is typically meaningless if the serializer is being 
used standalone (i.e. outside of a REST server or client).
+        *
+        * @param properties Optional run-time properties (the same that are 
passed to {@link WriterSerializer#doSerialize(SerializerSession, Object)}.
+        *      Can be <jk>null</jk>.
+        * @return The HTTP headers to set on HTTP requests.
+        *      Can be <jk>null</jk>.
+        */
+       public ObjectMap getResponseHeaders(ObjectMap properties) {
+               return new ObjectMap(getBeanContext());
+       }
+
+       /**
+        * Optional method that returns the response <code>Content-Type</code> 
for this serializer if it is different from the matched media type.
+        * <p>
+        *      This method is specified to override the content type for this 
serializer.
+        *      For example, the {@link 
org.apache.juneau.json.JsonSerializer.Simple} class returns that it handles 
media type <js>"text/json+simple"</js>, but returns
+        *      <js>"text/json"</js> as the actual content type.
+        *      This allows clients to request specific 'flavors' of content 
using specialized <code>Accept</code> header values.
+        * <p>
+        *      This method is typically meaningless if the serializer is being 
used standalone (i.e. outside of a REST server or client).
+        *
+        * @return The response content type.  If <jk>null</jk>, then the 
matched media type is used.
+        */
+       public String getResponseContentType() {
+               return contentType;
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       @Override /* CoreApi */
+       public Serializer setProperty(String property, Object value) throws 
LockedException {
+               super.setProperty(property, value);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public Serializer addNotBeanClasses(Class<?>...classes) throws 
LockedException {
+               super.addNotBeanClasses(classes);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public Serializer addTransforms(Class<?>...classes) throws 
LockedException {
+               super.addTransforms(classes);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public <T> Serializer addImplClass(Class<T> interfaceClass, Class<? 
extends T> implClass) throws LockedException {
+               super.addImplClass(interfaceClass, implClass);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public Serializer setClassLoader(ClassLoader classLoader) throws 
LockedException {
+               super.setClassLoader(classLoader);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public Serializer lock() {
+               super.lock();
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public Serializer clone() throws CloneNotSupportedException {
+               Serializer c = (Serializer)super.clone();
+               return c;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java 
b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java
new file mode 100644
index 0000000..bc4ed6b
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerContext.java
@@ -0,0 +1,291 @@
+/***************************************************************************************************************************
+ * 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.serializer;
+
+import org.apache.juneau.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Parent class for all serializer contexts.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class SerializerContext extends Context {
+
+       /**
+        * Max serialization depth ({@link Integer}, default=<code>100</code>).
+        * <p>
+        * Abort serialization if specified depth is reached in the POJO tree.
+        * If this depth is exceeded, an exception is thrown.
+        * This prevents stack overflows from occurring when trying to 
serialize models with recursive references.
+        */
+       public static final String SERIALIZER_maxDepth = "Serializer.maxDepth";
+
+       /**
+        * Initial depth ({@link Integer}, default=<code>0</code>).
+        * <p>
+        * The initial indentation level at the root.
+        * Useful when constructing document fragments that need to be indented 
at a certain level.
+        */
+       public static final String SERIALIZER_initialDepth = 
"Serializer.initialDepth";
+
+       /**
+        * Automatically detect POJO recursions ({@link Boolean}, 
default=<jk>false</jk>).
+        * <p>
+        * Specifies that recursions should be checked for during serialization.
+        * <p>
+        * Recursions can occur when serializing models that aren't true trees, 
but rather contain loops.
+        * <p>
+        * The behavior when recursions are detected depends on the value for 
{@link #SERIALIZER_ignoreRecursions}.
+        * <p>
+        * For example, if a model contains the links A-&gt;B-&gt;C-&gt;A, then 
the JSON generated will look like
+        *      the following when <jsf>SERIALIZER_ignoreRecursions</jsf> is 
<jk>true</jk>...
+        * <code>{A:{B:{C:null}}}</code><br>
+        * <p>
+        * Note:  Checking for recursion can cause a small performance penalty.
+        */
+       public static final String SERIALIZER_detectRecursions = 
"Serializer.detectRecursions";
+
+       /**
+        * Ignore recursion errors ({@link Boolean}, default=<jk>false</jk>).
+        * <p>
+        * Used in conjunction with {@link #SERIALIZER_detectRecursions}.
+        * Setting is ignored if <jsf>SERIALIZER_detectRecursions</jsf> is 
<jk>false</jk>.
+        * <p>
+        * If <jk>true</jk>, when we encounter the same object when serializing 
a tree,
+        *      we set the value to <jk>null</jk>.
+        * Otherwise, an exception is thrown.
+        */
+       public static final String SERIALIZER_ignoreRecursions = 
"Serializer.ignoreRecursions";
+
+       /**
+        * Debug mode ({@link Boolean}, default=<jk>false</jk>).
+        * <p>
+        * Enables the following additional information during serialization:
+        * <ul class='spaced-list'>
+        *      <li>When bean getters throws exceptions, the exception includes 
the object stack information
+        *              in order to determine how that method was invoked.
+        *      <li>Enables {@link #SERIALIZER_detectRecursions}.
+        * </ul>
+        */
+       public static final String SERIALIZER_debug = "Serializer.debug";
+
+       /**
+        * Use indentation in output ({@link Boolean}, default=<jk>false</jk>).
+        * <p>
+        * If <jk>true</jk>, newlines and indentation is added to the output to 
improve readability.
+        */
+       public static final String SERIALIZER_useIndentation = 
"Serializer.useIndentation";
+
+       /**
+        * Add class attributes to output ({@link Boolean}, 
default=<jk>false</jk>).
+        * <p>
+        * If <jk>true</jk>, then <js>"_class"</js> attributes will be added to 
beans if their type cannot be inferred through reflection.
+        * This is used to recreate the correct objects during parsing if the 
object types cannot be inferred.
+        * For example, when serializing a {@code Map<String,Object>} field, 
where the bean class cannot be determined from the value type.
+        */
+       public static final String SERIALIZER_addClassAttrs = 
"Serializer.addClassAttrs";
+
+       /**
+        * Quote character ({@link Character}, default=<js>'"'</js>).
+        * <p>
+        * This is the character used for quoting attributes and values.
+        */
+       public static final String SERIALIZER_quoteChar = 
"Serializer.quoteChar";
+
+       /**
+        * Trim null bean property values from output ({@link Boolean}, 
default=<jk>true</jk>).
+        * <p>
+        * If <jk>true</jk>, null bean values will not be serialized to the 
output.
+        * <p>
+        *      Note that enabling this setting has the following effects on 
parsing:
+        * <ul class='spaced-list'>
+        *      <li>Map entries with <jk>null</jk> values will be lost.
+        * </ul>
+        */
+       public static final String SERIALIZER_trimNullProperties = 
"Serializer.trimNullProperties";
+
+       /**
+        * Trim empty lists and arrays from output ({@link Boolean}, 
default=<jk>false</jk>).
+        * <p>
+        * If <jk>true</jk>, empty list values will not be serialized to the 
output.
+        * <p>
+        * Note that enabling this setting has the following effects on parsing:
+        * <ul class='spaced-list'>
+        *      <li>Map entries with empty list values will be lost.
+        *      <li>Bean properties with empty list values will not be set.
+        * </ul>
+        */
+       public static final String SERIALIZER_trimEmptyLists = 
"Serializer.trimEmptyLists";
+
+       /**
+        * Trim empty maps from output ({@link Boolean}, 
default=<jk>false</jk>).
+        * <p>
+        * If <jk>true</jk>, empty map values will not be serialized to the 
output.
+        * <p>
+        * Note that enabling this setting has the following effects on parsing:
+        *      <ul class='spaced-list'>
+        *      <li>Bean properties with empty map values will not be set.
+        * </ul>
+        */
+       public static final String SERIALIZER_trimEmptyMaps = 
"Serializer.trimEmptyMaps";
+
+       /**
+        * Trim strings in output ({@link Boolean}, default=<jk>false</jk>).
+        * <p>
+        * If <jk>true</jk>, string values will be trimmed of whitespace using 
{@link String#trim()} before being serialized.
+        */
+       public static final String SERIALIZER_trimStrings = 
"Serializer.trimStrings";
+
+       /**
+        * URI base for relative URIs ({@link String}, default=<js>""</js>).
+        * <p>
+        * Prepended to relative URIs during serialization (along with the 
{@link #SERIALIZER_absolutePathUriBase} if specified.
+        * (i.e. URIs not containing a schema and not starting with 
<js>'/'</js>).
+        * (e.g. <js>"foo/bar"</js>)
+        *
+        * <dl>
+        *      <dt>Examples:</dt>
+        *      <dd>
+        *              <table class='styled'>
+        *                      
<tr><th>SERIALIZER_relativeUriBase</th><th>URI</th><th>Serialized URI</th></tr>
+        *                      <tr>
+        *                              
<td><code>http://foo:9080/bar/baz</code></td>
+        *                              <td><code>mywebapp</code></td>
+        *                              
<td><code>http://foo:9080/bar/baz/mywebapp</code></td>
+        *                      </tr>
+        *                      <tr>
+        *                              
<td><code>http://foo:9080/bar/baz</code></td>
+        *                              <td><code>/mywebapp</code></td>
+        *                              <td><code>/mywebapp</code></td>
+        *                      </tr>
+        *                      <tr>
+        *                              
<td><code>http://foo:9080/bar/baz</code></td>
+        *                              <td><code>http://mywebapp</code></td>
+        *                              <td><code>http://mywebapp</code></td>
+        *                      </tr>
+        *              </table>
+        *      </dd>
+        * </dl>
+        */
+       public static final String SERIALIZER_relativeUriBase = 
"Serializer.relativeUriBase";
+
+       /**
+        * Sort arrays and collections alphabetically before serializing 
({@link Boolean}, default=<jk>false</jk>).
+        * <p>
+        * Note that this introduces a performance penalty.
+        */
+       public static final String SERIALIZER_sortCollections = 
"Serializer.sortCollections";
+
+       /**
+        * Sort maps alphabetically before serializing ({@link Boolean}, 
default=<jk>false</jk>).
+        * <p>
+        * Note that this introduces a performance penalty.
+        */
+       public static final String SERIALIZER_sortMaps = "Serializer.sortMaps";
+
+       /**
+        * URI base for relative URIs with absolute paths ({@link String}, 
default=<js>""</js>).
+        * <p>
+        * Prepended to relative absolute-path URIs during serialization.
+        * (i.e. URIs starting with <js>'/'</js>).
+        * (e.g. <js>"/foo/bar"</js>)
+        *
+        * <dl>
+        *      <dt>Examples:</dt>
+        *      <dd>
+        *              <table class='styled'>
+        *                      
<tr><th>SERIALIZER_absolutePathUriBase</th><th>URI</th><th>Serialized 
URI</th></tr>
+        *                      <tr>
+        *                              
<td><code>http://foo:9080/bar/baz</code></td>
+        *                              <td><code>mywebapp</code></td>
+        *                              <td><code>mywebapp</code></td>
+        *                      </tr>
+        *                      <tr>
+        *                              
<td><code>http://foo:9080/bar/baz</code></td>
+        *                              <td><code>/mywebapp</code></td>
+        *                              
<td><code>http://foo:9080/bar/baz/mywebapp</code></td>
+        *                      </tr>
+        *                      <tr>
+        *                              
<td><code>http://foo:9080/bar/baz</code></td>
+        *                              <td><code>http://mywebapp</code></td>
+        *                              <td><code>http://mywebapp</code></td>
+        *                      </tr>
+        *              </table>
+        *      </dd>
+        * </dl>
+        */
+       public static final String SERIALIZER_absolutePathUriBase = 
"Serializer.absolutePathUriBase";
+
+
+       final int maxDepth, initialDepth;
+       final boolean
+               debug,
+               detectRecursions,
+               ignoreRecursions,
+               useIndentation,
+               addClassAttrs,
+               trimNulls,
+               trimEmptyLists,
+               trimEmptyMaps,
+               trimStrings,
+               sortCollections,
+               sortMaps;
+       final char quoteChar;
+       final String relativeUriBase, absolutePathUriBase;
+
+       /**
+        * Constructor.
+        * <p>
+        * Typically only called from {@link ContextFactory#getContext(Class)}.
+        *
+        * @param cf The factory that created this context.
+        */
+       public SerializerContext(ContextFactory cf) {
+               super(cf);
+               maxDepth = cf.getProperty(SERIALIZER_maxDepth, int.class, 100);
+               initialDepth = cf.getProperty(SERIALIZER_initialDepth, 
int.class, 0);
+               debug = cf.getProperty(SERIALIZER_debug, boolean.class, false);
+               detectRecursions = cf.getProperty(SERIALIZER_detectRecursions, 
boolean.class, false);
+               ignoreRecursions = cf.getProperty(SERIALIZER_ignoreRecursions, 
boolean.class, false);
+               useIndentation = cf.getProperty(SERIALIZER_useIndentation, 
boolean.class, false);
+               addClassAttrs = cf.getProperty(SERIALIZER_addClassAttrs, 
boolean.class, false);
+               trimNulls = cf.getProperty(SERIALIZER_trimNullProperties, 
boolean.class, true);
+               trimEmptyLists = cf.getProperty(SERIALIZER_trimEmptyLists, 
boolean.class, false);
+               trimEmptyMaps = cf.getProperty(SERIALIZER_trimEmptyMaps, 
boolean.class, false);
+               trimStrings = cf.getProperty(SERIALIZER_trimStrings, 
boolean.class, false);
+               sortCollections = cf.getProperty(SERIALIZER_sortCollections, 
boolean.class, false);
+               sortMaps = cf.getProperty(SERIALIZER_sortMaps, boolean.class, 
false);
+               quoteChar = cf.getProperty(SERIALIZER_quoteChar, String.class, 
"\"").charAt(0);
+               relativeUriBase = 
resolveRelativeUriBase(cf.getProperty(SERIALIZER_relativeUriBase, String.class, 
""));
+               absolutePathUriBase = 
resolveAbsolutePathUriBase(cf.getProperty(SERIALIZER_absolutePathUriBase, 
String.class, ""));
+       }
+
+       private String resolveRelativeUriBase(String s) {
+               if (StringUtils.isEmpty(s))
+                       return null;
+               if (s.equals("/"))
+                       return s;
+               else if (StringUtils.endsWith(s, '/'))
+                       s = s.substring(0, s.length()-1);
+               return s;
+       }
+
+       private String resolveAbsolutePathUriBase(String s) {
+               if (StringUtils.isEmpty(s))
+                       return null;
+               if (StringUtils.endsWith(s, '/'))
+                       s = s.substring(0, s.length()-1);
+               return s;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java 
b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java
new file mode 100644
index 0000000..926075e
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroup.java
@@ -0,0 +1,338 @@
+/***************************************************************************************************************************
+ * 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.serializer;
+
+import static org.apache.juneau.internal.ArrayUtils.*;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.*;
+
+import org.apache.juneau.*;
+
+/**
+ * Represents a group of {@link Serializer Serializers} that can be looked up 
by media type.
+ *
+ *
+ * <h6 class='topic'>Description</h6>
+ * <p>
+ *     Provides the following features:
+ * <ul class='spaced-list'>
+ *     <li>Finds serializers based on HTTP <code>Accept</code> header values.
+ *     <li>Sets common properties on all serializers in a single method call.
+ *     <li>Locks all serializers in a single method call.
+ *     <li>Clones existing groups and all serializers within the group in a 
single method call.
+ * </ul>
+ *
+ *
+ * <h6 class='topic'>Match ordering</h6>
+ * <p>
+ *     Serializers are matched against <code>Accept</code> strings in the 
order they exist in this group.
+ * <p>
+ *     Adding new entries will cause the entries to be prepended to the group.
+ *     This allows for previous serializers to be overridden through 
subsequent calls.
+ * <p>
+ *     For example, calling 
<code>g.append(S1.<jk>class</jk>,S2.<jk>class</jk>).append(S3.<jk>class</jk>,S4.<jk>class</jk>)</code>
+ *     will result in the order <code>S3, S4, S1, S2</code>.
+ *
+ *
+ * <h6 class='topic'>Examples</h6>
+ * <p class='bcode'>
+ *     <jc>// Construct a new serializer group</jc>
+ *     SerializerGroup g = <jk>new</jk> SerializerGroup();
+ *
+ *     <jc>// Add some serializers to it</jc>
+ *     g.append(JsonSerializer.<jk>class</jk>, XmlSerializer.<jk>class</jk>);
+ *
+ *     <jc>// Change settings for all serializers in the group and lock 
it.</jc>
+ *     g.setProperty(SerializerContext.<jsf>SERIALIZER_useIndentation</jsf>, 
<jk>true</jk>)
+ *             .addTransforms(CalendarTransform.ISO8601DT.<jk>class</jk>)
+ *             .lock();
+ *
+ *     <jc>// Find the appropriate serializer by Accept type</jc>
+ *     String mediaTypeMatch = g.findMatch(<js>"text/foo, text/json;q=0.8, 
text/*;q:0.6, *\/*;q=0.0"</js>);
+ *     WriterSerializer s = (WriterSerializer)g.getSerializer(mediaTypeMatch);
+ *
+ *     <jc>// Serialize a bean to JSON text </jc>
+ *     AddressBook addressBook = <jk>new</jk> AddressBook();  <jc>// Bean to 
serialize.</jc>
+ *     String json = s.serialize(addressBook);
+ * </p>
+ *
+ * @author James Bognar ([email protected])
+ */
+public final class SerializerGroup extends Lockable {
+
+       // Maps media-types to serializers.
+       private final Map<String,Serializer> serializerMap = new 
ConcurrentHashMap<String,Serializer>();
+
+       // Maps Accept headers to matching media types.
+       private final Map<String,String> mediaTypeMappings = new 
ConcurrentHashMap<String,String>();
+
+       private final CopyOnWriteArrayList<Serializer> serializers = new 
CopyOnWriteArrayList<Serializer>();
+
+       private final ReadWriteLock lock = new ReentrantReadWriteLock();
+       private final Lock rl = lock.readLock(), wl = lock.writeLock();
+
+
+       /**
+        * Registers the specified serializers with this group.
+        *
+        * @param s The serializers to append to this group.
+        * @return This object (for method chaining).
+        * @throws Exception Thrown if {@link Serializer} could not be 
constructed.
+        */
+       public SerializerGroup append(Class<? extends Serializer>...s) throws 
Exception {
+               checkLock();
+               wl.lock();
+               try {
+                       serializerMap.clear();
+                       mediaTypeMappings.clear();
+                       for (Class<? extends Serializer> ss : reverse(s)) {
+                               try {
+                                       append(ss);
+                               } catch (NoClassDefFoundError e) {
+                                       // Ignore if dependent library not 
found (e.g. Jena).
+                                       System.err.println(e);
+                               }
+                       }
+               } finally {
+                       wl.unlock();
+               }
+               return this;
+       }
+
+       /**
+        * Same as {@link #append(Class[])}, except specify a single class to 
avoid unchecked compile warnings.
+        *
+        * @param c The serializer to append to this group.
+        * @return This object (for method chaining).
+        * @throws Exception Thrown if {@link Serializer} could not be 
constructed.
+        */
+       public SerializerGroup append(Class<? extends Serializer> c) throws 
Exception {
+               checkLock();
+               wl.lock();
+               try {
+                       serializerMap.clear();
+                       mediaTypeMappings.clear();
+                       serializers.add(0, c.newInstance());
+               } catch (NoClassDefFoundError e) {
+                       // Ignore if dependent library not found (e.g. Jena).
+                       System.err.println(e);
+               } finally {
+                       wl.unlock();
+               }
+               return this;
+       }
+
+       /**
+        * Returns the serializer registered to handle the specified media type.
+        * <p>
+        * The media-type string must not contain any parameters or q-values.
+        *
+        * @param mediaType The media-type string (e.g. <js>"text/json"</js>
+        * @return The serializer that handles the specified accept content 
type, or <jk>null</jk> if
+        *              no serializer is registered to handle it.
+        */
+       public Serializer getSerializer(String mediaType) {
+               Serializer s = serializerMap.get(mediaType);
+               if (s == null)
+                       s = serializerMap.get(findMatch(mediaType));
+               return s;
+       }
+
+       /**
+        * Searches the group for a serializer that can handle the specified 
<code>Accept</code> value.
+        * <p>
+        *      The <code>accept</code> value complies with the syntax 
described in RFC2616, Section 14.1, as described below:
+        * <p class='bcode'>
+        *      Accept         = "Accept" ":"
+        *                        #( media-range [ accept-params ] )
+        *
+        *      media-range    = ( "*\/*"
+        *                        | ( type "/" "*" )
+        *                        | ( type "/" subtype )
+        *                        ) *( ";" parameter )
+        *      accept-params  = ";" "q" "=" qvalue *( accept-extension )
+        *      accept-extension = ";" token [ "=" ( token | quoted-string ) ]
+        * </p>
+        * <p>
+        *      The general idea behind having the serializer resolution be a 
two-step process is so that
+        *      the matched media type can be passed in to the {@link 
WriterSerializer#doSerialize(SerializerSession, Object)} method.
+        *      For example...
+        * <p class='bcode'>
+        *      String acceptHeaderValue = request.getHeader(<js>"Accept"</js>);
+        *      String matchingMediaType = group.findMatch(acceptHeaderValue);
+        *      if (matchingMediaType == <jk>null</jk>)
+        *              <jk>throw new</jk> 
RestException(<jsf>SC_NOT_ACCEPTABLE</jsf>);
+        *      WriterSerializer s = 
(WriterSerializer)group.getSerializer(matchingMediaType);
+        *  s.serialize(getPojo(), response.getWriter(), 
response.getProperties(), matchingMediaType);
+        * </p>
+        *
+        * @param acceptHeader The HTTP <l>Accept</l> header string.
+        * @return The media type registered by one of the parsers that matches 
the <code>accept</code> string,
+        *      or <jk>null</jk> if no media types matched.
+        */
+       public String findMatch(String acceptHeader) {
+               rl.lock();
+               try {
+                       String mt = mediaTypeMappings.get(acceptHeader);
+                       if (mt != null)
+                               return mt;
+
+                       MediaRange[] mr = MediaRange.parse(acceptHeader);
+                       if (mr.length == 0)
+                               mr = MediaRange.parse("*/*");
+
+                       for (MediaRange a : mr) {
+                               for (Serializer s : serializers) {
+                                       for (MediaRange a2 : 
s.getMediaRanges()) {
+                                               if (a.matches(a2)) {
+                                                       mt = a2.getMediaType();
+                                                       
mediaTypeMappings.put(acceptHeader, mt);
+                                                       serializerMap.put(mt, 
s);
+                                                       return mt;
+                                               }
+                                       }
+                               }
+                       }
+                       return null;
+               } finally {
+                       rl.unlock();
+               }
+       }
+
+       /**
+        * Returns the media types that all parsers in this group can handle
+        * <p>
+        * Entries are ordered in the same order as the parsers in the group.
+        *
+        * @return The list of media types.
+        */
+       public List<String> getSupportedMediaTypes() {
+               List<String> l = new ArrayList<String>();
+               for (Serializer s : serializers)
+                       for (String mt : s.getMediaTypes())
+                               if (! l.contains(mt))
+                                       l.add(mt);
+               return l;
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Convenience methods for setting properties on all serializers.
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Shortcut for calling {@link Serializer#setProperty(String, Object)} 
on all serializers in this group.
+        *
+        * @param property The property name.
+        * @param value The property value.
+        * @throws LockedException If {@link #lock()} was called on this object.
+        * @return This object (for method chaining).
+        */
+       public SerializerGroup setProperty(String property, Object value) 
throws LockedException {
+               checkLock();
+               for (Serializer s : serializers)
+                       s.setProperty(property, value);
+               return this;
+       }
+
+       /**
+        * Shortcut for calling {@link Serializer#setProperties(ObjectMap)} on 
all serializers in this group.
+        *
+        * @param properties The properties to set.  Ignored if <jk>null</jk>.
+        * @throws LockedException If {@link #lock()} was called on this object.
+        * @return This object (for method chaining).
+        */
+       public SerializerGroup setProperties(ObjectMap properties) {
+               checkLock();
+               for (Serializer s : serializers)
+                       s.setProperties(properties);
+               return this;
+       }
+
+       /**
+        * Shortcut for calling {@link Serializer#addNotBeanClasses(Class[])} 
on all serializers in this group.
+        *
+        * @param classes The classes to specify as not-beans to the underlying 
bean context of all serializers in this group.
+        * @throws LockedException If {@link #lock()} was called on this object.
+        * @return This object (for method chaining).
+        */
+       public SerializerGroup addNotBeanClasses(Class<?>...classes) throws 
LockedException {
+               checkLock();
+               for (Serializer s : serializers)
+                       s.addNotBeanClasses(classes);
+               return this;
+       }
+
+       /**
+        * Shortcut for calling {@link Serializer#addTransforms(Class[])} on 
all serializers in this group.
+        *
+        * @param classes The classes to add bean transforms for to the 
underlying bean context of all serializers in this group.
+        * @throws LockedException If {@link #lock()} was called on this object.
+        * @return This object (for method chaining).
+        */
+       public SerializerGroup addTransforms(Class<?>...classes) throws 
LockedException {
+               checkLock();
+               for (Serializer s : serializers)
+                       s.addTransforms(classes);
+               return this;
+       }
+
+       /**
+        * Shortcut for calling {@link Serializer#addImplClass(Class, Class)} 
on all serializers in this group.
+        *
+        * @param <T> The interface or abstract class type.
+        * @param interfaceClass The interface or abstract class.
+        * @param implClass The implementation class.
+        * @throws LockedException If {@link #lock()} was called on this object.
+        * @return This object (for method chaining).
+        */
+       public <T> SerializerGroup addImplClass(Class<T> interfaceClass, 
Class<? extends T> implClass) throws LockedException {
+               checkLock();
+               for (Serializer s : serializers)
+                       s.addImplClass(interfaceClass, implClass);
+               return this;
+       }
+
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Locks this group and all serializers in this group.
+        */
+       @Override /* Lockable */
+       public SerializerGroup lock() {
+               super.lock();
+               for (Serializer s : serializers)
+                       s.lock();
+               return this;
+       }
+
+       /**
+        * Clones this group and all serializers in this group.
+        */
+       @Override /* Lockable */
+       public SerializerGroup clone() throws CloneNotSupportedException {
+               SerializerGroup g = new SerializerGroup();
+
+               List<Serializer> l = new 
ArrayList<Serializer>(serializers.size());
+               for (Serializer s : serializers)
+                       l.add(s.clone());
+
+               g.serializers.addAll(l);
+
+               return g;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java 
b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java
new file mode 100644
index 0000000..eb5dd94
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java
@@ -0,0 +1,743 @@
+/***************************************************************************************************************************
+ * 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.serializer;
+
+import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.serializer.SerializerContext.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.text.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.transform.*;
+
+/**
+ * Context object that lives for the duration of a single use of {@link 
Serializer}.
+ * <p>
+ * Used by serializers for the following purposes:
+ * <ul class='spaced-list'>
+ *     <li>Keeping track of how deep it is in a model for indentation purposes.
+ *     <li>Ensuring infinite loops don't occur by setting a limit on how deep 
to traverse a model.
+ *     <li>Ensuring infinite loops don't occur from loops in the model (when 
detectRecursions is enabled.
+ *     <li>Allowing serializer properties to be overridden on method calls.
+ * </ul>
+ * <p>
+ * This class is NOT thread safe.  It is meant to be discarded after one-time 
use.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class SerializerSession extends Session {
+
+       private static JuneauLogger logger = 
JuneauLogger.getLogger(SerializerSession.class);
+
+       private final int maxDepth, initialDepth;
+       private final boolean
+               debug,
+               detectRecursions,
+               ignoreRecursions,
+               useIndentation,
+               addClassAttrs,
+               trimNulls,
+               trimEmptyLists,
+               trimEmptyMaps,
+               trimStrings,
+               sortCollections,
+               sortMaps;
+       private final char quoteChar;
+       private final String relativeUriBase, absolutePathUriBase;
+
+       private final ObjectMap overrideProperties;
+
+       /** The current indentation depth into the model. */
+       public int indent;
+
+       private boolean closed;
+       private final Map<Object,Object> set;                                   
        // Contains the current objects in the current branch of the model.
+       private final LinkedList<StackElement> stack = new 
LinkedList<StackElement>();  // Contains the current objects in the current 
branch of the model.
+       private boolean isBottom;                                               
        // If 'true', then we're at a leaf in the model (i.e. a String, Number, 
Boolean, or null).
+       private final List<String> warnings = new LinkedList<String>();         
        // Any warnings encountered.
+       private final BeanContext beanContext;                                  
        // The bean context being used for this session.
+       private final Method javaMethod;                                        
        // Java method that invoked this serializer.
+       private final Object output;
+       private OutputStream outputStream;
+       private Writer writer, flushOnlyWriter;
+       private BeanPropertyMeta<?> currentProperty;
+       private ClassMeta<?> currentClass;
+
+
+       /**
+        * Create a new session using properties specified in the context.
+        *
+        * @param ctx The context creating this session object.
+        *      The context contains all the configuration settings for this 
object.
+        * @param beanContext The bean context being used.
+        * @param output The output object.
+        *      <br>Character-based serializers can handle the following output 
class types:
+        *      <ul>
+        *              <li>{@link Writer}
+        *              <li>{@link OutputStream} - Output will be written as 
UTF-8 encoded stream.
+        *              <li>{@link File} - Output will be written as 
system-default encoded stream.
+        *      </ul>
+        *      <br>Stream-based serializers can handle the following output 
class types:
+        *      <ul>
+        *              <li>{@link OutputStream}
+        *              <li>{@link File}
+        *      </ul>
+        * @param op The override properties.
+        *      These override any context properties defined in the context.
+        * @param javaMethod The java method that called this parser, usually 
the method in a REST servlet.
+        */
+       public SerializerSession(SerializerContext ctx, BeanContext 
beanContext, Object output, ObjectMap op, Method javaMethod) {
+               super(ctx);
+               this.beanContext = beanContext;
+               this.javaMethod = javaMethod;
+               this.output = output;
+               if (op == null || op.isEmpty()) {
+                       overrideProperties = new ObjectMap();
+                       maxDepth = ctx.maxDepth;
+                       initialDepth = ctx.initialDepth;
+                       debug = ctx.debug;
+                       detectRecursions = ctx.detectRecursions;
+                       ignoreRecursions = ctx.ignoreRecursions;
+                       useIndentation = ctx.useIndentation;
+                       addClassAttrs = ctx.addClassAttrs;
+                       trimNulls = ctx.trimNulls;
+                       trimEmptyLists = ctx.trimEmptyLists;
+                       trimEmptyMaps = ctx.trimEmptyMaps;
+                       trimStrings = ctx.trimStrings;
+                       quoteChar = ctx.quoteChar;
+                       relativeUriBase = ctx.relativeUriBase;
+                       absolutePathUriBase = ctx.absolutePathUriBase;
+                       sortCollections = ctx.sortCollections;
+                       sortMaps = ctx.sortMaps;
+               } else {
+                       overrideProperties = op;
+                       maxDepth = op.getInt(SERIALIZER_maxDepth, ctx.maxDepth);
+                       initialDepth = op.getInt(SERIALIZER_initialDepth, 
ctx.initialDepth);
+                       debug = op.getBoolean(SERIALIZER_debug, ctx.debug);
+                       detectRecursions = 
op.getBoolean(SERIALIZER_detectRecursions, ctx.detectRecursions);
+                       ignoreRecursions = 
op.getBoolean(SERIALIZER_ignoreRecursions, ctx.ignoreRecursions);
+                       useIndentation = 
op.getBoolean(SERIALIZER_useIndentation, ctx.useIndentation);
+                       addClassAttrs = op.getBoolean(SERIALIZER_addClassAttrs, 
ctx.addClassAttrs);
+                       trimNulls = 
op.getBoolean(SERIALIZER_trimNullProperties, ctx.trimNulls);
+                       trimEmptyLists = 
op.getBoolean(SERIALIZER_trimEmptyLists, ctx.trimEmptyLists);
+                       trimEmptyMaps = op.getBoolean(SERIALIZER_trimEmptyMaps, 
ctx.trimEmptyMaps);
+                       trimStrings = op.getBoolean(SERIALIZER_trimStrings, 
ctx.trimStrings);
+                       quoteChar = op.getString(SERIALIZER_quoteChar, 
""+ctx.quoteChar).charAt(0);
+                       relativeUriBase = 
op.getString(SERIALIZER_relativeUriBase, ctx.relativeUriBase);
+                       absolutePathUriBase = 
op.getString(SERIALIZER_absolutePathUriBase, ctx.absolutePathUriBase);
+                       sortCollections = 
op.getBoolean(SERIALIZER_sortCollections, ctx.sortMaps);
+                       sortMaps = op.getBoolean(SERIALIZER_sortMaps, 
ctx.sortMaps);
+               }
+
+               this.indent = initialDepth;
+               if (detectRecursions || debug) {
+                       set = new IdentityHashMap<Object,Object>();
+               } else {
+                       set = Collections.emptyMap();
+               }
+       }
+
+       /**
+        * Wraps the specified output object inside an output stream.
+        * Subclasses can override this method to implement their own 
specialized output streams.
+        * <p>
+        * This method can be used if the output object is any of the following 
class types:
+        * <ul>
+        *      <li>{@link OutputStream}
+        *      <li>{@link File}
+        * </ul>
+        *
+        * @return The output object wrapped in an output stream.
+        * @throws Exception If object could not be converted to an output 
stream.
+        */
+       public OutputStream getOutputStream() throws Exception {
+               if (output == null)
+                       throw new SerializeException("Output cannot be null.");
+               if (output instanceof OutputStream)
+                       return (OutputStream)output;
+               if (output instanceof File) {
+                       if (outputStream == null)
+                               outputStream = new BufferedOutputStream(new 
FileOutputStream((File)output));
+                       return outputStream;
+               }
+               throw new SerializeException("Cannot convert object of type {0} 
to an OutputStream.", output.getClass().getName());
+       }
+
+
+       /**
+        * Wraps the specified output object inside a writer.
+        * Subclasses can override this method to implement their own 
specialized writers.
+        * <p>
+        * This method can be used if the output object is any of the following 
class types:
+        * <ul>
+        *      <li>{@link Writer}
+        *      <li>{@link OutputStream} - Output will be written as UTF-8 
encoded stream.
+        *      <li>{@link File} - Output will be written as system-default 
encoded stream.
+        * </ul>
+        *
+        * @return The output object wrapped in a Writer.
+        * @throws Exception If object could not be converted to a writer.
+        */
+       public Writer getWriter() throws Exception {
+               if (output == null)
+                       throw new SerializeException("Output cannot be null.");
+               if (output instanceof Writer)
+                       return (Writer)output;
+               if (output instanceof OutputStream) {
+                       if (flushOnlyWriter == null)
+                               flushOnlyWriter = new 
OutputStreamWriter((OutputStream)output, IOUtils.UTF8);
+                       return flushOnlyWriter;
+               }
+               if (output instanceof File) {
+                       if (writer == null)
+                               writer = new OutputStreamWriter(new 
BufferedOutputStream(new FileOutputStream((File)output)));
+                       return writer;
+               }
+               throw new SerializeException("Cannot convert object of type {0} 
to a Writer.", output.getClass().getName());
+       }
+
+       /**
+        * Returns the raw output object passed into this session.
+        *
+        * @return The raw output object passed into this session.
+        */
+       protected Object getOutput() {
+               return output;
+       }
+
+       /**
+        * Sets the current bean property being serialized for proper error 
messages.
+        * @param currentProperty The current property being serialized.
+        */
+       public void setCurrentProperty(BeanPropertyMeta<?> currentProperty) {
+               this.currentProperty = currentProperty;
+       }
+
+       /**
+        * Sets the current class being serialized for proper error messages.
+        * @param currentClass The current class being serialized.
+        */
+       public void setCurrentClass(ClassMeta<?> currentClass) {
+               this.currentClass = currentClass;
+       }
+
+       /**
+        * Returns the bean context in use for this session.
+        *
+        * @return The bean context in use for this session.
+        */
+       public final BeanContext getBeanContext() {
+               return beanContext;
+       }
+
+       /**
+        * Returns the Java method that invoked this serializer.
+        * <p>
+        * When using the REST API, this is the Java method invoked by the REST 
call.
+        * Can be used to access annotations defined on the method or class.
+        *
+        * @return The Java method that invoked this serializer.
+       */
+       public final Method getJavaMethod() {
+               return javaMethod;
+       }
+
+       /**
+        * Returns the override properties passed in to the constructor.
+        *
+        * @return The override properties pass in to the constructor.
+        */
+       public final ObjectMap getProperties() {
+               return overrideProperties;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_maxDepth} setting 
value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_maxDepth} setting 
value for this session.
+        */
+       public final int getMaxDepth() {
+               return maxDepth;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_initialDepth} 
setting value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_initialDepth} 
setting value for this session.
+        */
+       public final int getInitialDepth() {
+               return initialDepth;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_debug} setting value 
for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_debug} setting value 
for this session.
+        */
+       public final boolean isDebug() {
+               return debug;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_detectRecursions} 
setting value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_detectRecursions} 
setting value for this session.
+        */
+       public final boolean isDetectRecursions() {
+               return detectRecursions;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_ignoreRecursions} 
setting value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_ignoreRecursions} 
setting value for this session.
+        */
+       public final boolean isIgnoreRecursions() {
+               return ignoreRecursions;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_useIndentation} 
setting value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_useIndentation} 
setting value for this session.
+        */
+       public final boolean isUseIndentation() {
+               return useIndentation;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_addClassAttrs} 
setting value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_addClassAttrs} 
setting value for this session.
+        */
+       public final boolean isAddClassAttrs() {
+               return addClassAttrs;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_quoteChar} setting 
value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_quoteChar} setting 
value for this session.
+        */
+       public final char getQuoteChar() {
+               return quoteChar;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_trimNullProperties} 
setting value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_trimNullProperties} 
setting value for this session.
+        */
+       public final boolean isTrimNulls() {
+               return trimNulls;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_trimEmptyLists} 
setting value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_trimEmptyLists} 
setting value for this session.
+        */
+       public final boolean isTrimEmptyLists() {
+               return trimEmptyLists;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_trimEmptyMaps} 
setting value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_trimEmptyMaps} 
setting value for this session.
+        */
+       public final boolean isTrimEmptyMaps() {
+               return trimEmptyMaps;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_trimStrings} setting 
value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_trimStrings} setting 
value for this session.
+        */
+       public final boolean isTrimStrings() {
+               return trimStrings;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_sortCollections} 
setting value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_sortCollections} 
setting value for this session.
+        */
+       public final boolean isSortCollections() {
+               return sortCollections;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_sortMaps} setting 
value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_sortMaps} setting 
value for this session.
+        */
+       public final boolean isSortMaps() {
+               return sortMaps;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_relativeUriBase} 
setting value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_relativeUriBase} 
setting value for this session.
+        */
+       public final String getRelativeUriBase() {
+               return relativeUriBase;
+       }
+
+       /**
+        * Returns the {@link SerializerContext#SERIALIZER_absolutePathUriBase} 
setting value for this session.
+        *
+        * @return The {@link SerializerContext#SERIALIZER_absolutePathUriBase} 
setting value for this session.
+        */
+       public final String getAbsolutePathUriBase() {
+               return absolutePathUriBase;
+       }
+
+       /**
+        * Push the specified object onto the stack.
+        *
+        * @param attrName The attribute name.
+        * @param o The current object being serialized.
+        * @param eType The expected class type.
+        * @return The {@link ClassMeta} of the object so that 
<code>instanceof</code> operations
+        *      only need to be performed once (since they can be 
expensive).<br>
+        * @throws SerializeException If recursion occurred.
+        */
+       public ClassMeta<?> push(String attrName, Object o, ClassMeta<?> eType) 
throws SerializeException {
+               indent++;
+               isBottom = true;
+               if (o == null)
+                       return null;
+               Class<?> c = o.getClass();
+               ClassMeta<?> cm = (eType != null && c == eType.getInnerClass()) 
? eType : beanContext.getClassMeta(c);
+               if (cm.isCharSequence() || cm.isNumber() || cm.isBoolean())
+                       return cm;
+               if (detectRecursions || debug) {
+                       if (stack.size() > maxDepth)
+                               return null;
+                       if (willRecurse(attrName, o, cm))
+                               return null;
+                       isBottom = false;
+                       stack.add(new StackElement(stack.size(), attrName, o, 
cm));
+                       if (debug)
+                               logger.info(getStack(false));
+                       set.put(o, o);
+               }
+               return cm;
+       }
+
+       /**
+        * Returns <jk>true</jk> if {@link 
SerializerContext#SERIALIZER_detectRecursions} is enabled, and the specified
+        *      object is already higher up in the serialization chain.
+        *
+        * @param attrName The bean property attribute name, or some other 
identifier.
+        * @param o The object to check for recursion.
+        * @param cm The metadata on the object class.
+        * @return <jk>true</jk> if recursion detected.
+        * @throws SerializeException If recursion occurred.
+        */
+       public boolean willRecurse(String attrName, Object o, ClassMeta<?> cm) 
throws SerializeException {
+               if (! (detectRecursions || debug))
+                       return false;
+               if (! set.containsKey(o))
+                       return false;
+               if (ignoreRecursions && ! debug)
+                       return true;
+
+               stack.add(new StackElement(stack.size(), attrName, o, cm));
+               throw new SerializeException("Recursion occurred, stack={0}", 
getStack(true));
+       }
+
+       /**
+        * Pop an object off the stack.
+        */
+       public void pop() {
+               indent--;
+               if ((detectRecursions || debug) && ! isBottom)  {
+                       Object o = stack.removeLast().o;
+                       Object o2 = set.remove(o);
+                       if (o2 == null)
+                               addWarning("Couldn't remove object of type 
''{0}'' on attribute ''{1}'' from object stack.", o.getClass().getName(), 
stack);
+               }
+               isBottom = false;
+       }
+
+       /**
+        * The current indentation depth.
+        *
+        * @return The current indentation depth.
+        */
+       public int getIndent() {
+               return indent;
+       }
+
+       /**
+        * Logs a warning message.
+        *
+        * @param msg The warning message.
+        * @param args Optional printf arguments to replace in the error 
message.
+        */
+       public void addWarning(String msg, Object... args) {
+               logger.warning(msg, args);
+               msg = args.length == 0 ? msg : MessageFormat.format(msg, args);
+               warnings.add((warnings.size() + 1) + ": " + msg);
+       }
+
+       /**
+        * Specialized warning when an exception is thrown while executing a 
bean getter.
+        *
+        * @param p The bean map entry representing the bean property.
+        * @param t The throwable that the bean getter threw.
+        */
+       public void addBeanGetterWarning(BeanPropertyMeta<?> p, Throwable t) {
+               String prefix = (debug ? getStack(false) + ": " : "");
+               addWarning("{0}Could not call getValue() on property ''{1}'' of 
class ''{2}'', exception = {3}", prefix, p.getName(), 
p.getBeanMeta().getClassMeta(), t.getLocalizedMessage());
+       }
+
+       /**
+        * Trims the specified string if {@link 
SerializerSession#isTrimStrings()} returns <jk>true</jk>.
+        *
+        * @param o The input string to trim.
+        * @return The trimmed string, or <jk>null</jk> if the input was 
<jk>null</jk>.
+        */
+       public final String trim(Object o) {
+               if (o == null)
+                       return null;
+               String s = o.toString();
+               if (trimStrings)
+                       s = s.trim();
+               return s;
+       }
+
+       /**
+        * Generalize the specified object if a transform is associated with it.
+        *
+        * @param o The object to generalize.
+        * @param type The type of object.
+        * @return The generalized object, or <jk>null</jk> if the object is 
<jk>null</jk>.
+        * @throws SerializeException If a problem occurred trying to convert 
the output.
+        */
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       public final Object generalize(Object o, ClassMeta<?> type) throws 
SerializeException {
+               if (o == null)
+                       return null;
+               PojoTransform f = (type == null || type.isObject() ? 
getBeanContext().getClassMeta(o.getClass()).getPojoTransform() : 
type.getPojoTransform());
+               if (f == null)
+                       return o;
+               return f.transform(o);
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified value should not be 
serialized.
+        *
+        * @param cm The class type of the object being serialized.
+        * @param attrName The bean attribute name, or <jk>null</jk> if this 
isn't a bean attribute.
+        * @param value The object being serialized.
+        * @return <jk>true</jk> if the specified value should not be 
serialized.
+        * @throws SerializeException If recursion occurred.
+        */
+       public final boolean canIgnoreValue(ClassMeta<?> cm, String attrName, 
Object value) throws SerializeException {
+
+               if (trimNulls && value == null)
+                       return true;
+
+               if (value == null)
+                       return false;
+
+               if (cm == null)
+                       cm = getBeanContext().object();
+
+               if (trimEmptyLists) {
+                       if (cm.isArray() || (cm.isObject() && 
value.getClass().isArray())) {
+                               if (((Object[])value).length == 0)
+                                       return true;
+                       }
+                       if (cm.isCollection() || (cm.isObject() && 
isParentClass(Collection.class, value.getClass()))) {
+                               if (((Collection<?>)value).isEmpty())
+                                       return true;
+                       }
+               }
+
+               if (trimEmptyMaps) {
+                       if (cm.isMap() || (cm.isObject() && 
isParentClass(Map.class, value.getClass()))) {
+                               if (((Map<?,?>)value).isEmpty())
+                                       return true;
+                       }
+               }
+
+               if (trimNulls && willRecurse(attrName, value, cm))
+                       return true;
+
+               return false;
+       }
+
+       /**
+        * Sorts the specified map if {@link SerializerSession#isSortMaps()} 
returns <jk>true</jk>.
+        *
+        * @param m The map being sorted.
+        * @return A new sorted {@link TreeMap}.
+        */
+       public final <K,V> Map<K,V> sort(Map<K,V> m) {
+               if (sortMaps && m != null && (! m.isEmpty()) && 
m.keySet().iterator().next() instanceof Comparable<?>)
+                       return new TreeMap<K,V>(m);
+               return m;
+       }
+
+       /**
+        * Sorts the specified collection if {@link 
SerializerSession#isSortCollections()} returns <jk>true</jk>.
+        *
+        * @param c The collection being sorted.
+        * @return A new sorted {@link TreeSet}.
+        */
+       public final <E> Collection<E> sort(Collection<E> c) {
+               if (sortCollections && c != null && (! c.isEmpty()) && 
c.iterator().next() instanceof Comparable<?>)
+                       return new TreeSet<E>(c);
+               return c;
+       }
+
+       /**
+        * Converts a String to an absolute URI based on the {@link 
SerializerContext#SERIALIZER_absolutePathUriBase} and
+        *      {@link SerializerContext#SERIALIZER_relativeUriBase} settings 
on this context.
+        *
+        * @param uri The input URI.
+        * @return The resolved URI.
+        */
+       public String resolveUri(String uri) {
+               if (uri.indexOf("://") != -1 || (absolutePathUriBase == null && 
relativeUriBase == null))
+                       return uri;
+               StringBuilder sb = new StringBuilder(uri.length() + 
absolutePathUriBase.length() + 1 + relativeUriBase.length());
+               if (StringUtils.startsWith(uri, '/')) {
+                       if (absolutePathUriBase != null)
+                               sb.append(absolutePathUriBase);
+               } else {
+                       if (relativeUriBase != null) {
+                               sb.append(relativeUriBase);
+                               if (! uri.equals("/"))
+                                       sb.append("/");
+                       }
+               }
+               sb.append(uri);
+               return sb.toString();
+       }
+
+       /**
+        * Converts the specified object to a <code>String</code>.
+        *
+        * @param o The object to convert to a <code>String</code>.
+        * @return The
+        */
+       public String toString(Object o) {
+               if (o == null)
+                       return null;
+               if (o.getClass() == Class.class)
+                       return ClassUtils.getReadableClassName((Class<?>)o);
+               String s = o.toString();
+               if (trimStrings)
+                       s = s.trim();
+               return s;
+       }
+
+       /**
+        * Perform cleanup on this context object if necessary.
+        *
+        * @throws SerializeException If we're in debug mode and one or more 
warnings occurred.
+        */
+       public void close() throws SerializeException {
+               if (closed)
+                       throw new SerializeException("Attempt to close 
SerializerSession more than once.");
+
+               try {
+                       if (outputStream != null)
+                               outputStream.close();
+                       if (flushOnlyWriter != null)
+                               flushOnlyWriter.flush();
+                       if (writer != null)
+                               writer.close();
+               } catch (IOException e) {
+                       throw new SerializeException(e);
+               }
+
+               if (debug && warnings.size() > 0)
+                       throw new SerializeException("Warnings occurred during 
serialization: \n" + StringUtils.join(warnings, "\n"));
+
+               closed = true;
+       }
+
+       @Override /* Object */
+       protected void finalize() throws Throwable {
+               if (! closed)
+                       throw new RuntimeException("SerializerSession was not 
closed.");
+       }
+
+       private static class StackElement {
+               private int depth;
+               private String name;
+               private Object o;
+               private ClassMeta<?> aType;
+
+               private StackElement(int depth, String name, Object o, 
ClassMeta<?> aType) {
+                       this.depth = depth;
+                       this.name = name;
+                       this.o = o;
+                       this.aType = aType;
+               }
+
+               private String toString(boolean simple) {
+                       StringBuilder sb = new 
StringBuilder().append('[').append(depth).append(']');
+                       sb.append(StringUtils.isEmpty(name) ? "<noname>" : 
name).append(':');
+                       sb.append(aType.toString(simple));
+                       if (aType != aType.getTransformedClassMeta())
+                               
sb.append('/').append(aType.getTransformedClassMeta().toString(simple));
+                       return sb.toString();
+               }
+       }
+
+       private String getStack(boolean full) {
+               StringBuilder sb = new StringBuilder();
+               for (StackElement e : stack) {
+                       if (full) {
+                               sb.append("\n\t");
+                               for (int i = 1; i < e.depth; i++)
+                                       sb.append("  ");
+                               if (e.depth > 0)
+                                       sb.append("->");
+                               sb.append(e.toString(false));
+                       } else {
+                               sb.append(" > ").append(e.toString(true));
+                       }
+               }
+               return sb.toString();
+       }
+
+       /**
+        * Returns information used to determine at what location in the parse 
a failure occurred.
+        *
+        * @return A map, typically containing something like 
<code>{line:123,column:456,currentProperty:"foobar"}</code>
+        */
+       public Map<String,Object> getLastLocation() {
+               Map<String,Object> m = new LinkedHashMap<String,Object>();
+               if (currentClass != null)
+                       m.put("currentClass", currentClass);
+               if (currentProperty != null)
+                       m.put("currentProperty", currentProperty);
+               if (stack != null && ! stack.isEmpty())
+                       m.put("stack", stack);
+               return m;
+       }
+}

Reply via email to