http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/ReaderParser.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/ReaderParser.java 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/ReaderParser.java
new file mode 100755
index 0000000..865b95e
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/ReaderParser.java
@@ -0,0 +1,394 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.parser;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import com.ibm.juno.core.*;
+import com.ibm.juno.core.annotation.*;
+import com.ibm.juno.core.utils.*;
+
+/**
+ * Subclass of {@link Parser} for characters-based parsers.
+ *
+ *
+ * <h6 class='topic'>Description</h6>
+ * <p>
+ *     This class is typically the parent class of all character-based parsers.
+ *     It has 1 abstract method to implement...
+ * <ul>
+ *     <li><code>parse(Reader, ClassMeta, ParserContext)</code>
+ * </ul>
+ *
+ *
+ * <h6 class='topic'>@Consumes annotation</h6>
+ * <p>
+ *     The media types that this parser can handle is specified through the 
{@link Consumes @Consumes} annotation.
+ * <p>
+ *     However, the media types can also be specified programmatically by 
overriding the {@link #getMediaTypes()} method.
+ *
+ *
+ * @author James Bognar ([email protected])
+ */
+public abstract class ReaderParser extends Parser<Reader> {
+
+       
//--------------------------------------------------------------------------------
+       // Abstract methods
+       
//--------------------------------------------------------------------------------
+
+       @Override /* Parser */
+       protected abstract <T> T doParse(Reader in, int estimatedSize, 
ClassMeta<T> type, ParserContext ctx) throws ParseException, IOException;
+
+       
//--------------------------------------------------------------------------------
+       // Other methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Same as <code>parse(Reader, Class)</code> except parses from a 
<code>CharSequence</code>.
+        *
+        * @param in The string containing the input.
+        * @param type The class type of the object to create.
+        *      If <jk>null</jk> or <code>Object.<jk>class</jk></code>, object 
type is based on what's being parsed.
+        * @param <T> The class type of the object to create.
+        * @return The parsed object.
+        * @throws ParseException If the input contains a syntax error or is 
malformed, or is not valid for the specified type.
+        */
+       public <T> T parse(CharSequence in, Class<T> type) throws 
ParseException {
+               return parse(in, getBeanContext().getClassMeta(type));
+       }
+
+       /**
+        * Same as <code>parse(Reader, ClassMeta)</code> except parses from a 
<code>CharSequence</code>.
+        *
+        * @param in The string containing the input.
+        * @param type The class type of the object to create.
+        *      If <jk>null</jk> or <code>Object.<jk>class</jk></code>, object 
type is based on what's being parsed.
+        * @param <T> The class type of the object to create.
+        * @return The parsed object.
+        * @throws ParseException If the input contains a syntax error or is 
malformed, or is not valid for the specified type.
+        */
+       public <T> T parse(CharSequence in, ClassMeta<T> type) throws 
ParseException {
+               try {
+                       if (in == null)
+                               return null;
+                       return parse(wrapReader(in), in.length(), type, 
createContext());
+               } catch (IOException e) {
+                       throw new ParseException(e); // Won't happen since it's 
a StringReader.
+               }
+       }
+
+       /**
+        * Same as <code>parseMap(Reader, Class, Class, Class)</code> except 
parses from a <code>CharSequence</code>.
+        *
+        * @param <T> The map class type.
+        * @param <K> The class type of the map keys.
+        * @param <V> The class type of the map values.
+        * @param in The string containing the input.
+        * @param mapClass The map class type.
+        * @param keyClass The class type of the map keys.
+        * @param valueClass The class type of the map values.
+        * @return A new map instance.
+        * @throws ParseException
+        */
+       public <K,V,T extends Map<K,V>> T parseMap(CharSequence in, Class<T> 
mapClass, Class<K> keyClass, Class<V> valueClass) throws ParseException {
+               ClassMeta<T> cm = getBeanContext().getMapClassMeta(mapClass, 
keyClass, valueClass);
+               return parse(in, cm);
+       }
+
+       /**
+        * Same as <code>parseCollection(Reader, Class, Class)</code> except 
parses from a <code>CharSequence</code>.
+        *
+        * @param <T> The collection class type.
+        * @param <E> The class type of the collection entries.
+        * @param in The string containing the input.
+        * @param collectionClass The map class type.
+        * @param entryClass collectionClass
+        * @return A new collection instance.
+        * @throws ParseException
+        */
+       public <E,T extends Collection<E>> T parseCollection(CharSequence in, 
Class<T> collectionClass, Class<E> entryClass) throws ParseException {
+               ClassMeta<T> cm = 
getBeanContext().getCollectionClassMeta(collectionClass, entryClass);
+               return parse(in, cm);
+       }
+
+       /**
+        * Wraps the specified character sequence inside a reader.
+        * Subclasses can override this method to implement their own readers.
+        *
+        * @param in The string being wrapped.
+        * @return The string wrapped in a reader, or <jk>null</jk> if the 
<code>CharSequence</code> is null.
+        */
+       protected Reader wrapReader(CharSequence in) {
+               return new CharSequenceReader(in);
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Optional methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Parses the contents of the specified reader and loads the results 
into the specified map.
+        * <p>
+        *      Reader must contain something that serializes to a map (such as 
text containing a JSON object).
+        * <p>
+        *      Used in the following locations:
+        * <ul>
+        *      <li>The various character-based constructors in {@link 
ObjectMap} (e.g. {@link ObjectMap#ObjectMap(CharSequence, ReaderParser)}).
+        * </ul>
+        *
+        * @param <K> The key class type.
+        * @param <V> The value class type.
+        * @param in The reader containing the input.
+        * @param estimatedSize The estimated size of the input, or 
<code>-1</code> if unknown.
+        * @param m The map being loaded.
+        * @param keyType The class type of the keys, or <jk>null</jk> to 
default to <code>String.<jk>class</jk></code>.<br>
+        * @param valueType The class type of the values, or <jk>null</jk> to 
default to whatever is being parsed.<br>
+        * @return The same map that was passed in to allow this method to be 
chained.
+        * @throws ParseException If the input contains a syntax error or is 
malformed, or is not valid for the specified type.
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        * @throws UnsupportedOperationException If not implemented.
+        */
+       public final <K,V> Map<K,V> parseIntoMap(Reader in, int estimatedSize, 
Map<K,V> m, Type keyType, Type valueType) throws ParseException, IOException {
+               ParserContext ctx = createContext();
+               try {
+                       if (in == null)
+                               throw new IOException("Null input stream or 
reader passed to parser.");
+                       return doParseIntoMap(in, estimatedSize, m, keyType, 
valueType, ctx);
+               } finally {
+                       ctx.close();
+               }
+       }
+
+       /**
+        * Implementation method.
+        * Default implementation throws an {@link 
UnsupportedOperationException}.
+        *
+        * @param in The input.  Must represent an array.
+        * @param estimatedSize The estimated size of the input, or 
<code>-1</code> if unknown.
+        * @param m The map being loaded.
+        * @param keyType The class type of the keys, or <jk>null</jk> to 
default to <code>String.<jk>class</jk></code>.<br>
+        * @param valueType The class type of the values, or <jk>null</jk> to 
default to whatever is being parsed.<br>
+        * @param ctx The runtime context object returned by {@link 
#createContext(ObjectMap, Method, Object)}.
+        *      If <jk>null</jk>, one will be created using {@link 
#createContext()}.
+        * @return The same map that was passed in to allow this method to be 
chained.
+        * @throws ParseException Occurs if syntax error detected in input.
+        * @throws IOException Occurs if thrown from <code>Reader</code>
+        */
+       protected <K,V> Map<K,V> doParseIntoMap(Reader in, int estimatedSize, 
Map<K,V> m, Type keyType, Type valueType, ParserContext ctx) throws 
ParseException, IOException {
+               throw new UnsupportedOperationException("Parser 
'"+getClass().getName()+"' does not support this method.");
+       }
+
+       /**
+        * Same as {@link #parseIntoMap(Reader, int, Map, Type, Type)} except 
reads from a <code>CharSequence</code>.
+        *
+        * @param in The input.  Must represent an array.
+        * @param m The map being loaded.
+        * @param keyType The class type of the keys, or <jk>null</jk> to 
default to <code>String.<jk>class</jk></code>.<br>
+        * @param valueType The class type of the values, or <jk>null</jk> to 
default to whatever is being parsed.<br>
+        * @return The same map that was passed in to allow this method to be 
chained.
+        * @throws ParseException Occurs if syntax error detected in input.
+        */
+       public final <K,V> Map<K,V> parseIntoMap(CharSequence in, Map<K,V> m, 
Type keyType, Type valueType) throws ParseException {
+               try {
+                       if (in == null)
+                               return null;
+                       return parseIntoMap(wrapReader(in), in.length(), m, 
keyType, valueType);
+               } catch (IOException e) {
+                       throw new ParseException(e);  // Won't happen.
+               }
+       }
+
+       /**
+        * Parses the contents of the specified reader and loads the results 
into the specified collection.
+        * <p>
+        *      Used in the following locations:
+        * <ul>
+        *      <li>The various character-based constructors in {@link 
ObjectList} (e.g. {@link ObjectList#ObjectList(CharSequence, ReaderParser)}.
+        * </ul>
+        *
+        * @param <E> The element class type.
+        * @param in The reader containing the input.
+        * @param estimatedSize The estimated size of the input, or 
<code>-1</code> if unknown.
+        * @param c The collection being loaded.
+        * @param elementType The class type of the elements, or <jk>null</jk> 
to default to whatever is being parsed.
+        * @return The same collection that was passed in to allow this method 
to be chained.
+        * @throws ParseException If the input contains a syntax error or is 
malformed, or is not valid for the specified type.
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        * @throws UnsupportedOperationException If not implemented.
+        */
+       public final <E> Collection<E> parseIntoCollection(Reader in, int 
estimatedSize, Collection<E> c, Type elementType) throws ParseException, 
IOException {
+               ParserContext ctx = createContext();
+               try {
+                       if (in == null)
+                               throw new IOException("Null reader passed to 
parser.");
+                       return doParseIntoCollection(in, estimatedSize, c, 
elementType, ctx);
+               } finally {
+                       ctx.close();
+               }
+       }
+
+       /**
+        * Implementation method.
+        * Default implementation throws an {@link 
UnsupportedOperationException}.
+        *
+        * @param in The input.  Must represent an array.
+        * @param estimatedSize The estimated size of the input, or 
<code>-1</code> if unknown.
+        * @param c The collection being loaded.
+        * @param elementType The class type of the elements, or <jk>null</jk> 
to default to whatever is being parsed.
+        * @param ctx The runtime context object returned by {@link 
#createContext(ObjectMap, Method, Object)}.
+        *      If <jk>null</jk>, one will be created using {@link 
#createContext()}.
+        * @return The same collection that was passed in to allow this method 
to be chained.
+        * @throws ParseException Occurs if syntax error detected in input.
+        * @throws IOException Occurs if thrown from <code>Reader</code>
+        */
+       protected <E> Collection<E> doParseIntoCollection(Reader in, int 
estimatedSize, Collection<E> c, Type elementType, ParserContext ctx) throws 
ParseException, IOException {
+               throw new UnsupportedOperationException("Parser 
'"+getClass().getName()+"' does not support this method.");
+       }
+
+       /**
+        * Same as {@link #parseIntoCollection(Reader, int, Collection, Type)} 
except reads from a <code>CharSequence</code>.
+        *
+        * @param in The input.  Must represent an array.
+        * @param c The collection being loaded.
+        * @param elementType The class type of the elements, or <jk>null</jk> 
to default to whatever is being parsed.
+        * @return The same collection that was passed in to allow this method 
to be chained.
+        * @throws ParseException Occurs if syntax error detected in input.
+        */
+       public final <E> Collection<E> parseIntoCollection(CharSequence in, 
Collection<E> c, Type elementType) throws ParseException {
+               try {
+                       return parseIntoCollection(wrapReader(in), in.length(), 
c, elementType);
+               } catch (IOException e) {
+                       throw new ParseException(e);  // Won't happen.
+               }
+       }
+
+       /**
+        * Parses the specified array input with each entry in the object 
defined by the {@code argTypes}
+        * argument.
+        * <p>
+        *      Used for converting arrays (e.g. <js>"[arg1,arg2,...]"</js>) 
into an {@code Object[]} that can be passed
+        *      to the {@code Method.invoke(target, args)} method.
+        * <p>
+        *      Used in the following locations:
+        * <ul>
+        *      <li>Used to parse argument strings in the {@link 
PojoIntrospector#invokeMethod(Method, Reader)} method.
+        * </ul>
+        *
+        * @param in The input.  Must represent an array.
+        * @param estimatedSize The estimated size of the input, or 
<code>-1</code> if unknown.
+        * @param argTypes Specifies the type of objects to create for each 
entry in the array.
+        * @return An array of parsed objects.
+        * @throws ParseException If the input contains a syntax error or is 
malformed, or is not valid for the specified type.
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        * @throws UnsupportedOperationException If not implemented.
+        */
+       public final Object[] parseArgs(Reader in, int estimatedSize, 
ClassMeta<?>[] argTypes) throws ParseException, IOException {
+               if (in == null)
+                       throw new IOException("Null reader passed to parser.");
+               if (argTypes == null || argTypes.length == 0)
+                       return new Object[0];
+               ParserContext ctx = createContext();
+               try {
+                       return doParseArgs(in, estimatedSize, argTypes, ctx);
+               } finally {
+                       ctx.close();
+               }
+       }
+
+       /**
+        * Implementation method.
+        * Default implementation throws an {@link 
UnsupportedOperationException}.
+        *
+        * @param in The input.  Must represent an array.
+        * @param estimatedSize The estimated size of the input, or 
<code>-1</code> if unknown.
+        * @param argTypes Specifies the type of objects to create for each 
entry in the array.
+        * @param ctx The runtime context object returned by {@link 
#createContext(ObjectMap, Method, Object)}.
+        *      If <jk>null</jk>, one will be created using {@link 
#createContext()}.
+        * @return An array of parsed objects.
+        * @throws ParseException Occurs if syntax error detected in input.
+        * @throws IOException Occurs if thrown from <code>Reader</code>
+        */
+       protected Object[] doParseArgs(Reader in, int estimatedSize, 
ClassMeta<?>[] argTypes, ParserContext ctx) throws ParseException, IOException {
+               throw new UnsupportedOperationException("Parser 
'"+getClass().getName()+"' does not support this method.");
+       }
+
+       /**
+        * Same as {@link #parseArgs(Reader, int, ClassMeta[])} except reads 
from a <code>CharSequence</code>.
+        *
+        * @param in The input.  Must represent an array.
+        * @param argTypes Specifies the type of objects to create for each 
entry in the array.
+        * @return An array of parsed objects.
+        * @throws ParseException Occurs if syntax error detected in input.
+        */
+       public Object[] parseArgs(CharSequence in, ClassMeta<?>[] argTypes) 
throws ParseException {
+               try {
+                       return parseArgs(wrapReader(in), in.length(), argTypes);
+               } catch (IOException e) {
+                       throw new ParseException(e);  // Won't happen.
+               }
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       @Override /* Parser */
+       public boolean isReaderParser() {
+               return true;
+       }
+
+       @Override /* Parser */
+       public ReaderParser setProperty(String property, Object value) throws 
LockedException {
+               super.setProperty(property, value);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public ReaderParser setProperties(ObjectMap properties) throws 
LockedException {
+               super.setProperties(properties);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public ReaderParser addNotBeanClasses(Class<?>...classes) throws 
LockedException {
+               super.addNotBeanClasses(classes);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public ReaderParser addFilters(Class<?>...classes) throws 
LockedException {
+               super.addFilters(classes);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public <T> ReaderParser addImplClass(Class<T> interfaceClass, Class<? 
extends T> implClass) throws LockedException {
+               super.addImplClass(interfaceClass, implClass);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public ReaderParser setClassLoader(ClassLoader classLoader) throws 
LockedException {
+               super.setClassLoader(classLoader);
+               return this;
+       }
+
+       @Override /* Lockable */
+       public ReaderParser lock() {
+               super.lock();
+               return this;
+       }
+
+       @Override /* Lockable */
+       public ReaderParser clone() throws CloneNotSupportedException {
+               return (ReaderParser)super.clone();
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/package.html
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/package.html 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/package.html
new file mode 100755
index 0000000..ccee463
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/package.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+<!--
+    Licensed Materials - Property of IBM
+    (c) Copyright IBM Corporation 2014. All Rights Reserved.
+   
+    Note to U.S. Government Users Restricted Rights:  
+    Use, duplication or disclosure restricted by GSA ADP Schedule 
+    Contract with IBM Corp. 
+ -->
+<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>Parser API</p>
+
+<script>
+       function toggle(x) {
+               var div = x.nextSibling;
+               while (div != null && div.nodeType != 1)
+                       div = div.nextSibling;
+               if (div != null) {
+                       var d = div.style.display;
+                       if (d == 'block' || d == '') {
+                               div.style.display = 'none';
+                               x.className += " closed";
+                       } else {
+                               div.style.display = 'block';
+                               x.className = 
x.className.replace(/(?:^|\s)closed(?!\S)/g , '' );
+                       }
+               }
+       }
+</script>
+
+<a id='TOC'></a><h5 class='toc'>Table of Contents</h5>
+<ol class='toc'>
+       <li><p><a class='doclink' href='#Parser'>Parser API</a></p>
+       <ol>
+               <li><p><a class='doclink' href='#ParserGroup'>The ParserGroup 
class</a></p>
+       </ol>
+       <li><p><a class='doclink' href='#DefiningParser'>Defining a new 
Parser</a></p>
+</ol>
+
+<!-- 
========================================================================================================
 -->
+<a id="Parser"></a>
+<h2 class='topic' onclick='toggle(this)'>1 - Parser API</h2>
+<div class='topic'>
+       <p>
+               The parser API is designed to be easily extensible by 
developers. <br>
+               If you are writing your own parser, you will typically subclass 
directly from either {@link com.ibm.juno.core.parser.ReaderParser}
+                       or {@link 
com.ibm.juno.core.parser.InputStreamParser}.<br>
+       </p>
+
+       <!-- 
========================================================================================================
 -->
+       <a id="ParserGroup"></a>
+       <h3 class='topic' onclick='toggle(this)'>1.1 - The ParserGroup 
class</h3>
+       <div class='topic'>
+               <p>
+                       The {@link com.ibm.juno.core.parser.ParserGroup} class 
represents a group of parser registered with the media types they handle.
+               </p>
+               
+               <h6 class='topic'>Features</h6>         
+               <p>
+                       The <code>ParserGroup</code> class provides the 
following features:
+               <ul>
+                       <li>Finds parsers based on HTTP 
<code>Content-Type</code> header values.
+                       <li>Sets common properties on all parsers in a single 
method call.
+                       <li>Locks all parsers in a single method call.
+                       <li>Clones existing groups and all parsers within the 
group in a single method call.
+               </ul>
+               
+               <p>
+                       Refer to {@link com.ibm.juno.core.parser.ParserGroup} 
for additional information.
+               </p>
+       </div> 
+</div>
+
+
+<!-- 
========================================================================================================
 -->
+<a id="DefiningParser"></a>
+<h2 class='topic' onclick='toggle(this)'>2 - Defining a new Parser</h2>
+<div class='topic'>
+       <p>
+               Defining a new parser is quite simple if you subclass directly 
from {@link com.ibm.juno.core.parser.ReaderParser} 
+                       or {@link com.ibm.juno.core.parser.InputStreamParser}.  
In each case, you simply need to implement a single
+                       method and specify a {@link 
com.ibm.juno.core.annotation.Consumes} annotation.
+       </p>
+       <p>
+               The following example shows a simple parser that converts input 
streams to images using standard JRE classes.
+       </p>
+       <p class='bcode'>
+       <ja>@Consumes</ja>({<js>"image/png"</js>,<js>"image/jpeg"</js>})
+       <jk>public static class</jk> ImageParser <jk>extends</jk> 
InputStreamParser {
+               <ja>@Override</ja>
+               <jk>public</jk> &lt;T&gt; T parse(InputStream in, 
ClassMeta&lt;T&gt; type, ParserContext ctx) <jk>throws</jk> ParseException, 
IOException {
+                       BufferedImage image = ImageIO.<jsm>read</jsm>(in);
+                       <jk>return</jk> (T)image;
+               }
+       }
+       </p>
+       <p>
+               Parsers that take advantage of the entire {@link 
com.ibm.juno.core.CoreApi} interface to be able to parse arbitrary beans and 
POJOs is
+                       considerably more complex and outside the scope of this 
document.  
+               If developing such a parser, the best course of action would be 
to replicate what occurs in the {@link com.ibm.juno.core.json.JsonParser} class.
+       </p>
+</div>
+
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.class
new file mode 100755
index 0000000..319f116
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.java
new file mode 100755
index 0000000..4aa7ad1
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.plaintext;
+
+import java.io.*;
+
+import com.ibm.juno.core.*;
+import com.ibm.juno.core.annotation.*;
+import com.ibm.juno.core.filter.*;
+import com.ibm.juno.core.parser.*;
+import com.ibm.juno.core.utils.*;
+
+/**
+ * Parsers HTTP plain text request bodies into <a 
href='../package-summary.html#PojoCategories'>Group 5</a> POJOs.
+ *
+ *
+ * <h6 class='topic'>Media types</h6>
+ * <p>
+ *     Handles <code>Accept</code> types: <code>text/plain</code>
+ * <p>
+ *     Produces <code>Content-Type</code> types: <code>text/plain</code>
+ *
+ *
+ * <h6 class='topic'>Description</h6>
+ * <p>
+ *     Essentially just converts plain text to POJOs via static 
<code>fromString()</code> or <code>valueOf()</code>, or
+ *     through constructors that take a single string argument.
+ * <p>
+ *     Also parses objects using a filter if the object class has an {@link 
PojoFilter PojoFilter&lt;?,String&gt;} filter defined on it.
+ *
+ *
+ * <h6 class='topic'>Configurable properties</h6>
+ * <p>
+ *     This class has the following properties associated with it:
+ * <ul>
+ *     <li>{@link BeanContextProperties}
+ * </ul>
+ *
+ *
+ * @author James Bognar ([email protected])
+ */
+@Consumes("text/plain")
+public final class PlainTextParser extends ReaderParser {
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       @Override /* Parser */
+       protected <T> T doParse(Reader in, int estimatedSize, ClassMeta<T> 
type, ParserContext ctx) throws IOException, ParseException {
+               return ctx.getBeanContext().convertToType(IOUtils.read(in), 
type);
+       }
+
+       @Override /* Lockable */
+       public PlainTextParser clone() {
+               try {
+                       return (PlainTextParser)super.clone();
+               } catch (CloneNotSupportedException e) {
+                       throw new RuntimeException(e); // Shouldn't happen.
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.class
new file mode 100755
index 0000000..63a5321
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.java
new file mode 100755
index 0000000..9466161
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.plaintext;
+
+import java.io.*;
+
+import com.ibm.juno.core.*;
+import com.ibm.juno.core.annotation.*;
+import com.ibm.juno.core.filter.*;
+import com.ibm.juno.core.serializer.*;
+
+/**
+ * Serializes POJOs to plain text using just the <code>toString()</code> 
method on the serialized object.
+ *
+ *
+ * <h6 class='topic'>Media types</h6>
+ * <p>
+ *     Handles <code>Accept</code> types: <code>text/plain</code>
+ * <p>
+ *     Produces <code>Content-Type</code> types: <code>text/plain</code>
+ *
+ *
+ * <h6 class='topic'>Description</h6>
+ * <p>
+ *     Essentially converts POJOs to plain text using the 
<code>toString()</code> method.
+ * <p>
+ *     Also serializes objects using a filter if the object class has an 
{@link PojoFilter PojoFilter&lt;?,String&gt;} filter defined on it.
+ *
+ *
+ * <h6 class='topic'>Configurable properties</h6>
+ * <p>
+ *     This class has the following properties associated with it:
+ * <ul>
+ *     <li>{@link BeanContextProperties}
+ * </ul>
+ *
+ *
+ * @author James Bognar ([email protected])
+ */
+@Produces("text/plain")
+public final class PlainTextSerializer extends WriterSerializer {
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       @Override /* Serializer */
+       protected void doSerialize(Object o, Writer out, SerializerContext ctx) 
throws IOException, SerializeException {
+               out.write(o == null ? "null" : 
ctx.getBeanContext().convertToType(o, String.class));
+       }
+
+       @Override /* Serializer */
+       public PlainTextSerializer clone() {
+               try {
+                       return (PlainTextSerializer)super.clone();
+               } catch (CloneNotSupportedException e) {
+                       throw new RuntimeException(e); // Shouldn't happen.
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/package.html
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/package.html 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/package.html
new file mode 100755
index 0000000..a0b213f
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/package.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<!--
+    Licensed Materials - Property of IBM
+    (c) Copyright IBM Corporation 2014. All Rights Reserved.
+   
+    Note to U.S. Government Users Restricted Rights:  
+    Use, duplication or disclosure restricted by GSA ADP Schedule 
+    Contract with IBM Corp. 
+ -->
+<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/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.class
new file mode 100755
index 0000000..0428518
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.java
new file mode 100755
index 0000000..a609751
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * © Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.serializer;
+
+import java.io.*;
+
+import com.ibm.juno.core.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(Object, OutputStream, SerializerContext)}
+ * </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<OutputStream> {
+
+       @Override /* Serializer */
+       public boolean isWriterSerializer() {
+               return false;
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Abstract methods
+       
//--------------------------------------------------------------------------------
+
+       @Override /* Serializer */
+       protected abstract void doSerialize(Object o, OutputStream out, 
SerializerContext ctx) throws IOException, SerializeException;
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.class
new file mode 100755
index 0000000..54d57e1
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.java
new file mode 100755
index 0000000..9b0e2da
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.serializer;
+
+import java.text.*;
+
+/**
+ * General exception thrown whenever an error occurs during serialization.
+ *
+ * @author James Bognar ([email protected])
+ */
+public final class SerializeException extends Exception {
+
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * Constructor.
+        *
+        * @param msg The error message.
+        * @param args Optional printf arguments to replace in the error 
message.
+        */
+       public SerializeException(String msg, Object... args) {
+               super(args.length == 0 ? msg : MessageFormat.format(msg, args));
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param cause The cause.
+        */
+       public SerializeException(Throwable cause) {
+               super(cause == null ? null : cause.getLocalizedMessage());
+               initCause(cause);
+       }
+
+       /**
+        * 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;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.class
new file mode 100755
index 0000000..0a67c62
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.java
new file mode 100755
index 0000000..1d3cf51
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.java
@@ -0,0 +1,377 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.serializer;
+
+import static com.ibm.juno.core.utils.ClassUtils.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.text.*;
+import java.util.*;
+
+import com.ibm.juno.core.*;
+import com.ibm.juno.core.annotation.*;
+import com.ibm.juno.core.filter.*;
+import com.ibm.juno.core.soap.*;
+import com.ibm.juno.core.utils.*;
+
+/**
+ * Parent class for all Juno 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 SerializerProperties} for a list of configurable properties 
that can be set on this class
+ *     using the {@link #setProperty(String, Object)} method.
+ *
+ * @param <W> The output stream or writer class type.
+ * @author James Bognar ([email protected])
+ */
+public abstract class Serializer<W> extends CoreApi {
+
+       /** General serializer properties currently set on this serializer. */
+       protected transient SerializerProperties sp = new 
SerializerProperties();
+       private String[] mediaTypes;
+       private String contentType;
+
+       // Hidden constructor to force subclass from OuputStreamSerializer or 
WriterSerializer.
+       Serializer() {}
+
+       /**
+        * 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 o The object to serialize.
+        * @param out The writer or output stream to write to.
+        * @param ctx The serializer context object return by {@link 
#createContext(ObjectMap, Method)}.<br>
+        *      If <jk>null</jk>, context is created using {@link 
#createContext()}.
+        *
+        * @throws IOException If a problem occurred trying to write to the 
writer.
+        * @throws SerializeException If a problem occurred trying to convert 
the output.
+        */
+       protected abstract void doSerialize(Object o, W out, SerializerContext 
ctx) throws IOException, SerializeException;
+
+       
//--------------------------------------------------------------------------------
+       // Other methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Calls {@link #serialize(Object, Object, SerializerContext)} but 
intercepts {@link StackOverflowError} exceptions
+        *      and wraps them in a useful message.
+        * @param o The object to serialize.
+        * @param out The writer or output stream to write to.
+        * @param ctx The serializer context object return by {@link 
#createContext(ObjectMap, Method)}.<br>
+        *      If <jk>null</jk>, context is created using {@link 
#createContext()}.
+        *
+        * @throws IOException If a problem occurred trying to write to the 
writer.
+        * @throws SerializeException If a problem occurred trying to convert 
the output.
+        */
+       public final void serialize(Object o, W out, SerializerContext ctx) 
throws IOException, SerializeException {
+               try {
+                       doSerialize(o, out, ctx);
+               } catch (StackOverflowError e) {
+                       throw new SerializeException("Stack overflow occurred.  
This can occur when trying to serialize models containing loops.  It's 
recommended you use the SerializerProperties.SERIALIZER_detectRecursions 
setting to help locate the loop.").initCause(e);
+               } finally {
+                       ctx.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 out The writer or output stream to write to.
+        *
+        * @throws IOException If a problem occurred trying to write to the 
writer.
+        * @throws SerializeException If a problem occurred trying to convert 
the output.
+        */
+       public final void serialize(Object o, W out) throws IOException, 
SerializeException {
+               SerializerContext ctx = createContext();
+               serialize(o, out, ctx);
+       }
+
+       /**
+        * Create the context object that will be passed in to the serialize 
method.
+        * <p>
+        *      It's up to implementers to decide what the context object looks 
like, although typically
+        *      it's going to be a subclass of {@link SerializerContext}.
+        *
+        * @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 context.
+        */
+       public SerializerContext createContext(ObjectMap properties, Method 
javaMethod) {
+               return new SerializerContext(getBeanContext(), sp, properties, 
javaMethod);
+       }
+
+       /**
+        * Create a basic context object without overriding properties or 
specifying <code>javaMethod</code>.
+        * <p>
+        * Equivalent to calling <code>createContext(<jk>null</jk>, 
<jk>null</jk>)</code>.
+        *
+        * @return The new context.
+        */
+       protected SerializerContext createContext() {
+               return createContext(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);
+       }
+
+       /**
+        * Generalize the specified object if a filter is associated with it.
+        *
+        * @param ctx The context that exists for the duration of a single 
serialize.
+        * @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" })
+       protected final Object generalize(SerializerContext ctx, Object o, 
ClassMeta<?> type) throws SerializeException {
+               if (o == null)
+                       return null;
+               PojoFilter f = (type == null || type.isObject() ? 
ctx.getBeanContext().getClassMeta(o.getClass()).getPojoFilter() : 
type.getPojoFilter());
+               if (f == null)
+                       return o;
+               return f.filter(o);
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified value should not be 
serialized.
+        *
+        * @param ctx The context that exists for the duration of a single 
serialize.
+        * @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
+        */
+       protected final boolean canIgnoreValue(SerializerContext ctx, 
ClassMeta<?> cm, String attrName, Object value) throws SerializeException {
+
+               if (ctx.isTrimNulls() && value == null)
+                       return true;
+
+               if (value == null)
+                       return false;
+
+               if (cm == null)
+                       cm = object();
+
+               if (ctx.isTrimEmptyLists()) {
+                       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 (ctx.isTrimEmptyMaps()) {
+                       if (cm.isMap() || (cm.isObject() && 
isParentClass(Map.class, value.getClass()))) {
+                               if (((Map<?,?>)value).isEmpty())
+                                       return true;
+                       }
+               }
+
+               if (ctx.isTrimNulls() && ctx.willRecurse(attrName, value, cm))
+                       return true;
+
+               return false;
+       }
+
+       /**
+        * Sorts the specified map if {@link SerializerContext#isSortMaps()} 
returns <jk>true</jk>.
+        *
+        * @param ctx The context that exists for the duration of a single 
serialize.
+        * @param m The map being sorted.
+        * @return A new sorted {@link TreeMap}.
+        */
+       protected final <K,V> Map<K,V> sort(SerializerContext ctx, Map<K,V> m) {
+               if (ctx.isSortMaps() && m != null && (! m.isEmpty()) && 
m.keySet().iterator().next() instanceof Comparable<?>)
+                       return new TreeMap<K,V>(m);
+               return m;
+       }
+
+       /**
+        * Sorts the specified collection if {@link 
SerializerContext#isSortCollections()} returns <jk>true</jk>.
+        *
+        * @param ctx The context that exists for the duration of a single 
serialize.
+        * @param c The collection being sorted.
+        * @return A new sorted {@link TreeSet}.
+        */
+       protected final <E> Collection<E> sort(SerializerContext ctx, 
Collection<E> c) {
+               if (ctx.isSortCollections() && c != null && (! c.isEmpty()) && 
c.iterator().next() instanceof Comparable<?>)
+                       return new TreeSet<E>(c);
+               return c;
+       }
+
+       /**
+        * 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() {
+               if (mediaTypes == null) {
+                       Produces p = 
ReflectionUtils.getAnnotation(Produces.class, getClass());
+                       if (p == null)
+                               throw new 
RuntimeException(MessageFormat.format("Class ''{0}'' is missing the @Produces 
annotation", getClass().getName()));
+                       mediaTypes = p.value();
+               }
+               return mediaTypes;
+       }
+
+       /**
+        * 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(Object, Writer, 
SerializerContext)}.
+        *      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 
com.ibm.juno.core.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() {
+               if (contentType == null) {
+                       Produces p = getClass().getAnnotation(Produces.class);
+                       if (p == null)
+                               contentType = "";
+                       else {
+                               contentType = p.contentType();
+                               if (contentType.isEmpty())
+                                       contentType = p.value()[0];
+                       }
+               }
+               return (contentType.isEmpty() ? null : contentType);
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       @Override /* CoreApi */
+       public Serializer<W> setProperty(String property, Object value) throws 
LockedException {
+               checkLock();
+               if (! sp.setProperty(property, value))
+                       super.setProperty(property, value);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public Serializer<W> addNotBeanClasses(Class<?>...classes) throws 
LockedException {
+               super.addNotBeanClasses(classes);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public Serializer<W> addFilters(Class<?>...classes) throws 
LockedException {
+               super.addFilters(classes);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public <T> Serializer<W> addImplClass(Class<T> interfaceClass, Class<? 
extends T> implClass) throws LockedException {
+               super.addImplClass(interfaceClass, implClass);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public Serializer<W> setClassLoader(ClassLoader classLoader) throws 
LockedException {
+               super.setClassLoader(classLoader);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public Serializer<W> lock() {
+               super.lock();
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public Serializer<W> clone() throws CloneNotSupportedException {
+               @SuppressWarnings("unchecked")
+               Serializer<W> c = (Serializer<W>)super.clone();
+               c.sp = sp.clone();
+               return c;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$1.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$1.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$1.class
new file mode 100755
index 0000000..81d75cb
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$1.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$StackElement.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$StackElement.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$StackElement.class
new file mode 100755
index 0000000..f298463
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$StackElement.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.class
new file mode 100755
index 0000000..1d86e46
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.java
new file mode 100755
index 0000000..f646b0a
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.java
@@ -0,0 +1,464 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.serializer;
+
+import static com.ibm.juno.core.serializer.SerializerProperties.*;
+
+import java.lang.reflect.*;
+import java.text.*;
+import java.util.*;
+import java.util.logging.*;
+
+import com.ibm.juno.core.*;
+import com.ibm.juno.core.utils.*;
+
+/**
+ * Context object that lives for the duration of a single serialization of 
{@link Serializer} and its subclasses.
+ * <p>
+ *     Used by serializers for the following purposes:
+ *     <ul>
+ *             <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>
+ *
+ * @author James Bognar ([email protected])
+ */
+public class SerializerContext {
+
+       private static Logger logger = 
Logger.getLogger(SerializerContext.class.getName());
+
+       private final int maxDepth, initialDepth;
+       private final boolean
+               debug,
+               detectRecursions,
+               ignoreRecursions,
+               useIndentation,
+               addClassAttrs,
+               trimNulls,
+               trimEmptyLists,
+               trimEmptyMaps,
+               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;
+
+       /** Contains the current objects in the current branch of the model. */
+       private Map<Object,Object> set;
+
+       /** Contains the current objects in the current branch of the model. */
+       private LinkedList<StackElement> stack;
+
+       /** If 'true', then we're at a leaf in the model (i.e. a String, 
Number, Boolean, or null). */
+       private boolean isBottom;
+
+       /** Any warnings encountered. */
+       private final List<String> warnings = new LinkedList<String>();
+
+       /** The bean context being used in this context. */
+       private final BeanContext beanContext;
+
+       /** Java method that invoked this serializer. */
+       private final Method javaMethod;
+
+       /**
+        * Create a new HashStack with the specified options.
+        *
+        * @param beanContext The bean context being used by the serializer.
+        * @param sp The default serializer properties.
+        * @param op The override 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.
+        */
+       public SerializerContext(BeanContext beanContext, SerializerProperties 
sp, ObjectMap op, Method javaMethod) {
+               this.beanContext = beanContext;
+               this.javaMethod = javaMethod;
+               if (op == null || op.isEmpty()) {
+                       overrideProperties = new ObjectMap();
+                       maxDepth = sp.maxDepth;
+                       initialDepth = sp.initialDepth;
+                       debug = sp.debug;
+                       detectRecursions = sp.detectRecursions;
+                       ignoreRecursions = sp.ignoreRecursions;
+                       useIndentation = sp.useIndentation;
+                       addClassAttrs = sp.addClassAttrs;
+                       trimNulls = sp.trimNulls;
+                       trimEmptyLists = sp.trimEmptyLists;
+                       trimEmptyMaps = sp.trimEmptyMaps;
+                       quoteChar = sp.quoteChar;
+                       relativeUriBase = 
resolveRelativeUriBase(sp.relativeUriBase);
+                       absolutePathUriBase = 
resolveAbsolutePathUriBase(sp.absolutePathUriBase);
+                       sortCollections = sp.sortCollections;
+                       sortMaps = sp.sortMaps;
+               } else {
+                       overrideProperties = op;
+                       maxDepth = op.getInt(SERIALIZER_maxDepth, sp.maxDepth);
+                       initialDepth = op.getInt(SERIALIZER_initialDepth, 
sp.initialDepth);
+                       debug = op.getBoolean(SERIALIZER_debug, sp.debug);
+                       detectRecursions = 
op.getBoolean(SERIALIZER_detectRecursions, sp.detectRecursions);
+                       ignoreRecursions = 
op.getBoolean(SERIALIZER_ignoreRecursions, sp.ignoreRecursions);
+                       useIndentation = 
op.getBoolean(SERIALIZER_useIndentation, sp.useIndentation);
+                       addClassAttrs = op.getBoolean(SERIALIZER_addClassAttrs, 
sp.addClassAttrs);
+                       trimNulls = 
op.getBoolean(SERIALIZER_trimNullProperties, sp.trimNulls);
+                       trimEmptyLists = 
op.getBoolean(SERIALIZER_trimEmptyLists, sp.trimEmptyLists);
+                       trimEmptyMaps = op.getBoolean(SERIALIZER_trimEmptyMaps, 
sp.trimEmptyMaps);
+                       quoteChar = op.getString(SERIALIZER_quoteChar, 
""+sp.quoteChar).charAt(0);
+                       relativeUriBase = 
resolveRelativeUriBase(op.getString(SERIALIZER_relativeUriBase, 
sp.relativeUriBase));
+                       absolutePathUriBase = 
resolveAbsolutePathUriBase(op.getString(SERIALIZER_absolutePathUriBase, 
sp.absolutePathUriBase));
+                       sortCollections = 
op.getBoolean(SERIALIZER_sortCollections, sp.sortMaps);
+                       sortMaps = op.getBoolean(SERIALIZER_sortMaps, 
sp.sortMaps);
+               }
+
+               this.indent = initialDepth;
+               if (detectRecursions || debug) {
+                       set = new IdentityHashMap<Object,Object>();
+                       stack = new LinkedList<StackElement>();
+               }
+       }
+
+       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;
+       }
+
+       /**
+        * Returns the bean context associated with this context.
+        *
+        * @return The bean context associated with this context.
+        */
+       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 runtime properties associated with this context.
+        *
+        * @return The runtime properties associated with this context.
+        */
+       public final ObjectMap getProperties() {
+               return overrideProperties;
+       }
+
+       /**
+        * Returns the {@link SerializerProperties#SERIALIZER_maxDepth} setting 
value in this context.
+        *
+        * @return The {@link SerializerProperties#SERIALIZER_maxDepth} setting 
value in this context.
+        */
+       public final int getMaxDepth() {
+               return maxDepth;
+       }
+
+       /**
+        * Returns the {@link SerializerProperties#SERIALIZER_initialDepth} 
setting value in this context.
+        *
+        * @return The {@link SerializerProperties#SERIALIZER_initialDepth} 
setting value in this context.
+        */
+       public final int getInitialDepth() {
+               return initialDepth;
+       }
+
+       /**
+        * Returns the {@link SerializerProperties#SERIALIZER_debug} setting 
value in this context.
+        *
+        * @return The {@link SerializerProperties#SERIALIZER_debug} setting 
value in this context.
+        */
+       public final boolean isDebug() {
+               return debug;
+       }
+
+       /**
+        * Returns the {@link SerializerProperties#SERIALIZER_detectRecursions} 
setting value in this context.
+        *
+        * @return The {@link SerializerProperties#SERIALIZER_detectRecursions} 
setting value in this context.
+        */
+       public final boolean isDetectRecursions() {
+               return detectRecursions;
+       }
+
+       /**
+        * Returns the {@link SerializerProperties#SERIALIZER_ignoreRecursions} 
setting value in this context.
+        *
+        * @return The {@link SerializerProperties#SERIALIZER_ignoreRecursions} 
setting value in this context.
+        */
+       public final boolean isIgnoreRecursions() {
+               return ignoreRecursions;
+       }
+
+       /**
+        * Returns the {@link SerializerProperties#SERIALIZER_useIndentation} 
setting value in this context.
+        *
+        * @return The {@link SerializerProperties#SERIALIZER_useIndentation} 
setting value in this context.
+        */
+       public final boolean isUseIndentation() {
+               return useIndentation;
+       }
+
+       /**
+        * Returns the {@link SerializerProperties#SERIALIZER_addClassAttrs} 
setting value in this context.
+        *
+        * @return The {@link SerializerProperties#SERIALIZER_addClassAttrs} 
setting value in this context.
+        */
+       public final boolean isAddClassAttrs() {
+               return addClassAttrs;
+       }
+
+       /**
+        * Returns the {@link SerializerProperties#SERIALIZER_quoteChar} 
setting value in this context.
+        *
+        * @return The {@link SerializerProperties#SERIALIZER_quoteChar} 
setting value in this context.
+        */
+       public final char getQuoteChar() {
+               return quoteChar;
+       }
+
+       /**
+        * Returns the {@link 
SerializerProperties#SERIALIZER_trimNullProperties} setting value in this 
context.
+        *
+        * @return The {@link 
SerializerProperties#SERIALIZER_trimNullProperties} setting value in this 
context.
+        */
+       public final boolean isTrimNulls() {
+               return trimNulls;
+       }
+
+       /**
+        * Returns the {@link SerializerProperties#SERIALIZER_trimEmptyLists} 
setting value in this context.
+        *
+        * @return The {@link SerializerProperties#SERIALIZER_trimEmptyLists} 
setting value in this context.
+        */
+       public final boolean isTrimEmptyLists() {
+               return trimEmptyLists;
+       }
+
+       /**
+        * Returns the {@link SerializerProperties#SERIALIZER_trimEmptyMaps} 
setting value in this context.
+        *
+        * @return The {@link SerializerProperties#SERIALIZER_trimEmptyMaps} 
setting value in this context.
+        */
+       public final boolean isTrimEmptyMaps() {
+               return trimEmptyMaps;
+       }
+
+       /**
+        * Returns the {@link SerializerProperties#SERIALIZER_sortCollections} 
setting value in this context.
+        *
+        * @return The {@link SerializerProperties#SERIALIZER_sortCollections} 
setting value in this context.
+        */
+       public final boolean isSortCollections() {
+               return sortCollections;
+       }
+
+       /**
+        * Returns the {@link SerializerProperties#SERIALIZER_sortMaps} setting 
value in this context.
+        *
+        * @return The {@link SerializerProperties#SERIALIZER_sortMaps} setting 
value in this context.
+        */
+       public final boolean isSortMaps() {
+               return sortMaps;
+       }
+
+       /**
+        * Returns the {@link SerializerProperties#SERIALIZER_relativeUriBase} 
setting value in this context.
+        *
+        * @return The {@link SerializerProperties#SERIALIZER_relativeUriBase} 
setting value in this context.
+        */
+       public final String getRelativeUriBase() {
+               return relativeUriBase;
+       }
+
+       /**
+        * Returns the {@link 
SerializerProperties#SERIALIZER_absolutePathUriBase} setting value in this 
context.
+        *
+        * @return The {@link 
SerializerProperties#SERIALIZER_absolutePathUriBase} setting value in this 
context.
+        */
+       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
+        */
+       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 
SerializerProperties#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
+        */
+       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) {
+               msg = args.length == 0 ? msg : MessageFormat.format(msg, args);
+               logger.warning(msg);
+               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());
+       }
+
+       /**
+        * Perform cleanup on this context object if necessary.
+        *
+        * @throws SerializeException
+        */
+       public void close() throws SerializeException {
+               if (debug && warnings.size() > 0)
+                       throw new SerializeException("Warnings occurred during 
serialization: \n" + StringUtils.join(warnings, "\n"));
+       }
+
+       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.getFilteredClassMeta())
+                               
sb.append("/").append(aType.getFilteredClassMeta().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();
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup$SerializerEntry.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup$SerializerEntry.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup$SerializerEntry.class
new file mode 100755
index 0000000..ad993fd
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup$SerializerEntry.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.class
new file mode 100755
index 0000000..b2c10d7
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.class
 differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.java
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.java
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.java
new file mode 100755
index 0000000..509ed58
--- /dev/null
+++ 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.java
@@ -0,0 +1,345 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ 
*******************************************************************************/
+package com.ibm.juno.core.serializer;
+
+import static com.ibm.juno.core.utils.ArrayUtils.*;
+
+import java.io.*;
+import java.util.*;
+
+import com.ibm.juno.core.*;
+
+/**
+ * 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>
+ *     <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(SerializerProperties.<jsf>SERIALIZER_useIndentation</jsf>, 
<jk>true</jk>)
+ *             .addFilters(CalendarFilter.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 transient Map<String,SerializerEntry> entryMap = new 
HashMap<String,SerializerEntry>();
+       private transient LinkedList<SerializerEntry> tempEntries = new 
LinkedList<SerializerEntry>();
+       private transient SerializerEntry[] entries;
+
+
+       /**
+        * Registers the specified REST serializers with this serializer group.
+        *
+        * @param s The serializers to append to this group.
+        * @return This object (for method chaining).
+        */
+       public SerializerGroup append(Serializer<?>...s) {
+               checkLock();
+               entries = null;
+               for (Serializer<?> ss : reverse(s))  {
+                       SerializerEntry e = new SerializerEntry(ss);
+                       tempEntries.addFirst(e);
+                       for (String mediaType : e.mediaTypes)
+                               entryMap.put(mediaType, e);
+               }
+               return this;
+       }
+
+       /**
+        * Same as {@link #append(Serializer[])}, except specify classes 
instead of class instances
+        *       of {@link Serializer}.
+        * <p>
+        * Note that this can only be used on {@link Serializer Serializers} 
with public no-arg constructors.
+        *
+        * @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();
+               for (Class<? extends Serializer<?>> ss : reverse(s))
+                       try {
+                       append(ss.newInstance());
+                       } catch (NoClassDefFoundError e) {
+                               // Ignore if dependent library not found (e.g. 
Jena).
+                               System.err.println(e);
+                       }
+               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();
+               try {
+               append(c.newInstance());
+               } catch (NoClassDefFoundError e) {
+                       // Ignore if dependent library not found (e.g. Jena).
+                       System.err.println(e);
+               }
+               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) {
+               SerializerEntry e = entryMap.get(mediaType);
+               return (e == null ? null : e.serializer);
+       }
+
+       /**
+        * 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(Object, Writer, SerializerContext)} 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 accept The accept 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 accept) {
+               MediaRange[] mr = MediaRange.parse(accept);
+               if (mr.length == 0)
+                       mr = MediaRange.parse("*/*");
+
+               for (MediaRange a : mr)
+                       for (SerializerEntry e : getEntries())
+                               for (MediaRange a2 : e.mediaRanges)
+                                       if (a.matches(a2))
+                                               return a2.getMediaType();
+
+               return null;
+       }
+
+       /**
+        * Returns the media types that all serializers in this group can handle
+        * <p>
+        * Entries are ordered in the same order as the serializers in the 
group.
+        *
+        * @return The list of media types.
+        */
+       public List<String> getSupportedMediaTypes() {
+               List<String> l = new ArrayList<String>();
+               for (SerializerEntry e : getEntries())
+                       for (String mt : e.mediaTypes)
+                               if (! l.contains(mt))
+                                       l.add(mt);
+               return l;
+       }
+
+       private SerializerEntry[] getEntries() {
+               if (entries == null)
+                       entries = tempEntries.toArray(new 
SerializerEntry[tempEntries.size()]);
+               return entries;
+       }
+
+       static class SerializerEntry {
+               Serializer<?> serializer;
+               MediaRange[] mediaRanges;
+               String[] mediaTypes;
+
+               SerializerEntry(Serializer<?> s) {
+                       serializer = s;
+
+                       mediaTypes = new String[s.getMediaTypes().length];
+                       int i = 0;
+                       for (String mt : s.getMediaTypes())
+                               mediaTypes[i++] = 
mt.toLowerCase(Locale.ENGLISH);
+
+                       List<MediaRange> l = new LinkedList<MediaRange>();
+                       for (i = 0; i < mediaTypes.length; i++)
+                               
l.addAll(Arrays.asList(MediaRange.parse(mediaTypes[i])));
+                       mediaRanges = l.toArray(new MediaRange[l.size()]);
+               }
+       }
+
+
+       
//--------------------------------------------------------------------------------
+       // 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 (SerializerEntry e : getEntries())
+                       e.serializer.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();
+               if (properties != null)
+                       for (Map.Entry<String,Object> e : properties.entrySet())
+                               setProperty(e.getKey(), e.getValue());
+               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 (SerializerEntry e : getEntries())
+                       e.serializer.addNotBeanClasses(classes);
+               return this;
+       }
+
+       /**
+        * Shortcut for calling {@link Serializer#addFilters(Class[])} on all 
serializers in this group.
+        *
+        * @param classes The classes to add bean filters 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 addFilters(Class<?>...classes) throws 
LockedException {
+               checkLock();
+               for (SerializerEntry e : getEntries())
+                       e.serializer.addFilters(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 (SerializerEntry e : getEntries())
+                       e.serializer.addImplClass(interfaceClass, implClass);
+               return this;
+       }
+
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Locks this group and all serializers in this group.
+        */
+       @Override /* Lockable */
+       public SerializerGroup lock() {
+               super.lock();
+               for (SerializerEntry e : getEntries())
+                       e.serializer.lock();
+               return this;
+       }
+
+       /**
+        * Clones this group and all serializers in this group.
+        */
+       @Override /* Lockable */
+       public SerializerGroup clone() throws CloneNotSupportedException {
+               SerializerGroup c = (SerializerGroup)super.clone();
+               c.entryMap = new HashMap<String,SerializerEntry>();
+               c.tempEntries = new LinkedList<SerializerEntry>();
+               c.entries = null;
+               SerializerEntry[] e = getEntries();
+               for (int i = e.length-1; i >= 0; i--)
+                       c.append(e[i].serializer.clone());
+               return c;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerProperties.class
----------------------------------------------------------------------
diff --git 
a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerProperties.class
 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerProperties.class
new file mode 100755
index 0000000..ace6ce9
Binary files /dev/null and 
b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerProperties.class
 differ

Reply via email to