http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java 
b/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java
new file mode 100644
index 0000000..1a1bd79
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java
@@ -0,0 +1,728 @@
+/***************************************************************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.  See the License for the
+ * specific language governing permissions and limitations under the License.
+ 
***************************************************************************************************************************/
+package org.apache.juneau.parser;
+
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.text.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.transform.*;
+import org.apache.juneau.transforms.*;
+import org.apache.juneau.utils.*;
+
+/**
+ * Parent class for all Juneau parsers.
+ *
+ *
+ * <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.
+ *
+ *
+ * <a id='ValidDataConversions'></a><h6 class='topic'>Valid data 
conversions</h6>
+ *     Parsers can parse any parsable POJO types, as specified in the <a 
class='doclink' href='../package-summary.html#PojoCategories'>POJO 
Categories</a>.
+ * <p>
+ *     Some examples of conversions are shown below...
+ * </p>
+ *     <table class='styled'>
+ *             <tr>
+ *                     <th>Data type</th>
+ *                     <th>Class type</th>
+ *                     <th>JSON example</th>
+ *                     <th>XML example</th>
+ *                     <th>Class examples</th>
+ *             </tr>
+ *             <tr>
+ *                     <td>object</td>
+ *                     <td>Maps, Java beans</td>
+ *                     <td class='code'>{name:<js>'John 
Smith'</js>,age:21}</td>
+ *                     <td class='code'><xt>&lt;object&gt;
+ *     &lt;name</xt> <xa>type</xa>=<xs>'string'</xs><xt>&gt;</xt>John 
Smith<xt>&lt;/name&gt;
+ *     &lt;age</xt> 
<xa>type</xa>=<xs>'number'</xs><xt>&gt;</xt>21<xt>&lt;/age&gt;
+ * &lt;/object&gt;</xt></td>
+ *                     <td class='code'>HashMap, 
TreeMap&lt;String,Integer&gt;</td>
+ *             </tr>
+ *             <tr>
+ *                     <td>array</td>
+ *                     <td>Collections, Java arrays</td>
+ *                     <td class='code'>[1,2,3]</td>
+ *                     <td class='code'><xt>&lt;array&gt;
+ *     &lt;number&gt;</xt>1<xt>&lt;/number&gt;
+ *     &lt;number&gt;</xt>2<xt>&lt;/number&gt;
+ *     &lt;number&gt;</xt>3<xt>&lt;/number&gt;
+ * &lt;/array&gt;</xt></td>
+ *                     <td class='code'>List&lt;Integer&gt;, <jk>int</jk>[], 
Float[], Set&lt;Person&gt;</td>
+ *             </tr>
+ *             <tr>
+ *                     <td>number</td>
+ *                     <td>Numbers</td>
+ *                     <td class='code'>123</td>
+ *                     <td 
class='code'><xt>&lt;number&gt;</xt>123<xt>&lt;/number&gt;</xt></td>
+ *                     <td class='code'>Integer, Long, Float, <jk>int</jk></td>
+ *             </tr>
+ *             <tr>
+ *                     <td>boolean</td>
+ *                     <td>Booleans</td>
+ *                     <td class='code'><jk>true</jk></td>
+ *                     <td 
class='code'><xt>&lt;boolean&gt;</xt>true<xt>&lt;/boolean&gt;</xt></td>
+ *                     <td class='code'>Boolean</td>
+ *             </tr>
+ *             <tr>
+ *                     <td>string</td>
+ *                     <td>CharSequences</td>
+ *                     <td class='code'><js>'foobar'</js></td>
+ *                     <td 
class='code'><xt>&lt;string&gt;</xt>foobar<xt>&lt;/string&gt;</xt></td>
+ *                     <td class='code'>String, StringBuilder</td>
+ *             </tr>
+ *     </table>
+ * <p>
+ *     In addition, any class types with {@link PojoTransform PojoTransforms} 
associated with them on the registered
+ *             {@link #getBeanContext() beanContext} can also be passed in.
+ * <p>
+ *     For example, if the {@link CalendarTransform} transform is used to 
generalize {@code Calendar} objects to {@code String} objects.  When registered
+ *     with this parser, you can construct {@code Calendar} objects from 
{@code Strings} using the following syntax...
+ * <p class='bcode'>
+ *     Calendar c = parser.parse(<js>"'Sun Mar 03 04:05:06 EST 2001'"</js>, 
GregorianCalendar.<jk>class</jk>);
+ * <p>
+ *     If <code>Object.<jk>class</jk></code> is specified as the target type, 
then the parser
+ *     automatically determines the data types and generates the following 
object types...
+ * </p>
+ * <table class='styled'>
+ *     <tr><th>JSON type</th><th>Class type</th></tr>
+ *     <tr><td>object</td><td>{@link ObjectMap}</td></tr>
+ *     <tr><td>array</td><td>{@link ObjectList}</td></tr>
+ *     <tr><td>number</td><td>{@link Number} <br>(depending on length and 
format, could be {@link Integer}, {@link Double}, {@link Float}, 
etc...)</td></tr>
+ *     <tr><td>boolean</td><td>{@link Boolean}</td></tr>
+ *     <tr><td>string</td><td>{@link String}</td></tr>
+ * </table>
+ *
+ *
+ * <a id='SupportedTypes'></a><h6 class='topic'>Supported types</h6>
+ * <p>
+ *     Several of the methods below take {@link Type} parameters to identify 
the type of
+ *             object to create.  Any of the following types can be passed in 
to these methods...
+ * </p>
+ * <ul>
+ *     <li>{@link ClassMeta}
+ *     <li>{@link Class}
+ *     <li>{@link ParameterizedType}
+ *     <li>{@link GenericArrayType}
+ * </ul>
+ * <p>
+ *     However, {@code ParameterizedTypes} and {@code GenericArrayTypes} 
should not contain
+ *             {@link WildcardType WildcardTypes} or {@link TypeVariable 
TypeVariables}.
+ * <p>
+ *     Passing in <jk>null</jk> or <code>Object.<jk>class</jk></code> 
typically signifies that it's up to the parser
+ *     to determine what object type is being parsed parsed based on the rules 
above.
+
+ *
+ * @author James Bognar ([email protected])
+ */
+public abstract class Parser extends CoreApi {
+
+       /** General serializer properties currently set on this serializer. */
+       private final List<ParserListener> listeners = new 
LinkedList<ParserListener>();
+       private final String[] mediaTypes;
+       private final MediaRange[] mediaRanges;
+
+       // Hidden constructor to force subclass from InputStreamParser or 
ReaderParser.
+       Parser() {
+               Consumes c = ReflectionUtils.getAnnotation(Consumes.class, 
getClass());
+               if (c == null)
+                       throw new RuntimeException(MessageFormat.format("Class 
''{0}'' is missing the @Consumes annotation", getClass().getName()));
+               this.mediaTypes = c.value();
+               for (int i = 0; i < mediaTypes.length; i++) {
+                       mediaTypes[i] = 
mediaTypes[i].toLowerCase(Locale.ENGLISH);
+               }
+
+               List<MediaRange> l = new LinkedList<MediaRange>();
+               for (int i = 0; i < mediaTypes.length; i++)
+                       
l.addAll(Arrays.asList(MediaRange.parse(mediaTypes[i])));
+               mediaRanges = l.toArray(new MediaRange[l.size()]);
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Abstract methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Workhorse method.  Subclasses are expected to implement this method.
+        * @param session The runtime session object returned by {@link 
#createSession(Object, ObjectMap, Method, Object)}.
+        *      If <jk>null</jk>, one will be created using {@link 
#createSession(Object)}.
+        * @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.
+        *      For example, when parsing JSON text, it may return a 
<code>String</code>, <code>Number</code>, <code>ObjectMap</code>, etc...
+        *
+        * @param <T> The class type of the object to create.
+        * @return The parsed object.
+        * @throws Exception If thrown from underlying stream, or if the input 
contains a syntax error or is malformed.
+        */
+       protected abstract <T> T doParse(ParserSession session, ClassMeta<T> 
type) throws Exception;
+
+       /**
+        * Returns <jk>true</jk> if this parser subclasses from {@link 
ReaderParser}.
+        *
+        * @return <jk>true</jk> if this parser subclasses from {@link 
ReaderParser}.
+        */
+       public abstract boolean isReaderParser();
+
+       
//--------------------------------------------------------------------------------
+       // Other methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Parses the content of the reader and creates an object of the 
specified type.
+        * @param session The runtime session returned by {@link 
#createSession(Object, ObjectMap, Method, Object)}.
+        * @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.
+        *      For example, when parsing JSON text, it may return a 
<code>String</code>, <code>Number</code>, <code>ObjectMap</code>, etc...
+        *
+        * @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 final <T> T parse(ParserSession session, ClassMeta<T> type) 
throws ParseException {
+               try {
+                       return doParse(session, type);
+               } catch (ParseException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new ParseException(session, e);
+               } finally {
+                       session.close();
+               }
+       }
+
+       /**
+        * Parses the content of the reader and creates an object of the 
specified type.
+        * <p>
+        * Equivalent to calling <code>parser.parse(in, type, 
<jk>null</jk>);</code>
+        *
+        * @param input The input.
+        *      <br>Character-based parsers can handle the following input 
class types:
+        *      <ul>
+        *              <li><jk>null</jk>
+        *              <li>{@link Reader}
+        *              <li>{@link CharSequence}
+        *              <li>{@link InputStream} containing UTF-8 encoded text.
+        *              <li>{@link File} containing system encoded text.
+        *      </ul>
+        *      <br>Stream-based parsers can handle the following input class 
types:
+        *      <ul>
+        *              <li><jk>null</jk>
+        *              <li>{@link InputStream}
+        *              <li><code><jk>byte</jk>[]</code>
+        *              <li>{@link File}
+        *      </ul>
+        * @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.
+        *      For example, when parsing JSON text, it may return a 
<code>String</code>, <code>Number</code>, <code>ObjectMap</code>, etc...
+        * @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 final <T> T parse(Object input, ClassMeta<T> type) throws 
ParseException {
+               ParserSession session = createSession(input);
+               return parse(session, type);
+       }
+
+       /**
+        * Parses input into the specified object type.
+        *
+        * <dl>
+        *      <dt>Example:</dt>
+        *      <dd>
+        *              <p class='bcode'>
+        *      ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>;
+        *      MyBean b = p.parse(json, MyBean.<jk>class</jk>);
+        *              </p>
+        *              <p>
+        *              This method equivalent to the following code:
+        *              <p class='bcode'>
+        *      ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>;
+        *      ClassMeta&lt;MyBean&gt; cm = 
p.getBeanContext().getClassMeta(MyBean.<jk>class</jk>);
+        *      MyBean b = p.parse(json, cm, <jk>null</jk>);
+        *              </p>
+        *      </dd>
+        * </dl>
+        *
+        * @param <T> The class type of the object to create.
+        * @param input The input.  See {@link #parse(Object, ClassMeta)} for 
supported input types.
+        * @param type 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 final <T> T parse(Object input, Class<T> type) throws 
ParseException {
+               ClassMeta<T> cm = getBeanContext().getClassMeta(type);
+               return parse(input, cm);
+       }
+
+       /**
+        * Parses input into a map with specified key and value types.
+        *
+        * <dl>
+        *      <dt>Example:</dt>
+        *      <dd>
+        *              <p class='bcode'>
+        *      ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>;
+        *      Map&lt;String,MyBean&gt; m = p.parseMap(json, 
LinkedHashMap.<jk>class</jk>, String.<jk>class</jk>, MyBean.<jk>class</jk>);
+        *              </p>
+        *              <p>
+        *                      A simpler approach is often to just extend the 
map class you want and just use the normal {@link #parse(Object, Class)} method:
+        *              </p>
+        *              <p class='bcode'>
+        *      <jk>public static class</jk> MyMap <jk>extends</jk> 
LinkedHashMap&lt;String,MyBean&gt; {}
+        *
+        *      ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>;
+        *      Map&lt;String,MyBean&gt; m = p.parse(json, 
MyMap.<jk>class</jk>);
+        *              </p>
+        *              <p>
+        *                      This method equivalent to the following code:
+        *              </p>
+        *              <p class='bcode'>
+        *      ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>;
+        *      ClassMeta&lt;Map&lt;String,MyBean&gt;&gt; cm = 
p.getBeanContext().getMapClassMeta(LinkedList.<jk>class</jk>, 
String.<jk>class</jk>, MyBean.<jk>class</jk>);
+        *      Map&ltString,MyBean&gt; m = p.parse(json, cm, <jk>null</jk>);
+        *              </p>
+        *      </dd>
+        * </dl>
+        *
+        * @param <T> The class type of the object to create.
+        * @param input The input.  See {@link #parse(Object, ClassMeta)} for 
supported input types.
+        * @param mapClass The map class type.
+        * @param keyClass The key class type.
+        * @param valueClass The value class type.
+        * @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 final <K,V,T extends Map<K,V>> T parseMap(Object input, Class<T> 
mapClass, Class<K> keyClass, Class<V> valueClass) throws ParseException {
+               ClassMeta<T> cm = getBeanContext().getMapClassMeta(mapClass, 
keyClass, valueClass);
+               return parse(input, cm);
+       }
+
+       /**
+        * Parses input into a collection with a specified element type.
+        *
+        * <dl>
+        *      <dt>Example:</dt>
+        *      <dd>
+        *              <p class='bcode'>
+        *      ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>;
+        *      List&lt;MyBean&gt; l = p.parseCollection(json, 
LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>);
+        *              </p>
+        *              <p>
+        *                      A simpler approach is often to just extend the 
collection class you want and just use the normal {@link #parse(Object, Class)} 
method:
+        *              </p>
+        *              <p class='bcode'>
+        *      <jk>public static class</jk> MyBeanCollection <jk>extends</jk> 
LinkedList&lt;MyBean&gt; {}
+        *
+        *      ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>;
+        *      List&lt;MyBean&gt; l = p.parse(json, 
MyBeanCollection.<jk>class</jk>);
+        *              </p>
+        *              <p>
+        *                      This method equivalent to the following code:
+        *              </p>
+        *              <p class='bcode'>
+        *      ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>;
+        *      ClassMeta&lt;List&lt;MyBean&gt;&gt; cm = 
p.getBeanContext().getCollectionClassMeta(LinkedList.<jk>class</jk>, 
MyBean.<jk>class</jk>);
+        *      List&lt;MyBean&gt; l = p.parse(json, cm, <jk>null</jk>);
+        *              </p>
+        *      </dd>
+        * </dl>
+        *
+        * @param <T> The class type of the object to create.
+        * @param input The input.  See {@link #parse(Object, ClassMeta)} for 
supported input types.
+        * @param collectionClass The collection class type.
+        * @param entryClass The class type of entries in the collection.
+        * @return The parsed object.
+        * @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.
+        */
+       public final <E,T extends Collection<E>> T parseCollection(Object 
input, Class<T> collectionClass, Class<E> entryClass) throws ParseException, 
IOException {
+               ClassMeta<T> cm = 
getBeanContext().getCollectionClassMeta(collectionClass, entryClass);
+               return parse(input, cm);
+       }
+
+       /**
+        * Create the session object that will be passed in to the parse method.
+        * <p>
+        *      It's up to implementers to decide what the session object looks 
like, although typically
+        *      it's going to be a subclass of {@link ParserSession}.
+        *
+        * @param input The input.  See {@link #parse(Object, ClassMeta)} for 
supported input types.
+        * @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.
+        * @param outer The outer object for instantiating top-level non-static 
inner classes.
+        * @return The new context.
+        */
+       public ParserSession createSession(Object input, ObjectMap properties, 
Method javaMethod, Object outer) {
+               return new ParserSession(getContext(ParserContext.class), 
getBeanContext(), input, properties, javaMethod, outer);
+       }
+
+       /**
+        * Create a basic session object without overriding properties or 
specifying <code>javaMethod</code>.
+        * <p>
+        * Equivalent to calling <code>createSession(<jk>null</jk>, 
<jk>null</jk>)</code>.
+        *
+        * @param input The input.  See {@link #parse(Object, ClassMeta)} for 
supported input types.
+        * @return The new context.
+        */
+       protected final ParserSession createSession(Object input) {
+               return createSession(input, null, null, null);
+       }
+
+       
//--------------------------------------------------------------------------------
+       // 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 class='spaced-list'>
+        *      <li>The various character-based constructors in {@link 
ObjectMap} (e.g. {@link ObjectMap#ObjectMap(CharSequence,Parser)}).
+        * </ul>
+        *
+        * @param <K> The key class type.
+        * @param <V> The value class type.
+        * @param input The input.  See {@link #parse(Object, ClassMeta)} for 
supported input types.
+        * @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 UnsupportedOperationException If not implemented.
+        */
+       public final <K,V> Map<K,V> parseIntoMap(Object input, Map<K,V> m, Type 
keyType, Type valueType) throws ParseException {
+               ParserSession session = createSession(input);
+               try {
+                       return doParseIntoMap(session, m, keyType, valueType);
+               } catch (ParseException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new ParseException(session, e);
+               } finally {
+                       session.close();
+               }
+       }
+
+       /**
+        * Implementation method.
+        * Default implementation throws an {@link 
UnsupportedOperationException}.
+        * @param session The runtime session object returned by {@link 
#createSession(Object, ObjectMap, Method, Object)}.
+        *      If <jk>null</jk>, one will be created using {@link 
#createSession(Object)}.
+        * @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 Exception If thrown from underlying stream, or if the input 
contains a syntax error or is malformed.
+        */
+       protected <K,V> Map<K,V> doParseIntoMap(ParserSession session, Map<K,V> 
m, Type keyType, Type valueType) throws Exception {
+               throw new UnsupportedOperationException("Parser 
'"+getClass().getName()+"' does not support this method.");
+       }
+
+       /**
+        * Parses the contents of the specified reader and loads the results 
into the specified collection.
+        * <p>
+        *      Used in the following locations:
+        * <ul class='spaced-list'>
+        *      <li>The various character-based constructors in {@link 
ObjectList} (e.g. {@link ObjectList#ObjectList(CharSequence,Parser)}.
+        * </ul>
+        *
+        * @param <E> The element class type.
+        * @param input The input.  See {@link #parse(Object, ClassMeta)} for 
supported input types.
+        * @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 UnsupportedOperationException If not implemented.
+        */
+       public final <E> Collection<E> parseIntoCollection(Object input, 
Collection<E> c, Type elementType) throws ParseException {
+               ParserSession session = createSession(input);
+               try {
+                       return doParseIntoCollection(session, c, elementType);
+               } catch (ParseException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new ParseException(session, e);
+               } finally {
+                       session.close();
+               }
+       }
+
+       /**
+        * Implementation method.
+        * Default implementation throws an {@link 
UnsupportedOperationException}.
+        * @param session The runtime session object returned by {@link 
#createSession(Object, ObjectMap, Method, Object)}.
+        *      If <jk>null</jk>, one will be created using {@link 
#createSession(Object)}.
+        * @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 Exception If thrown from underlying stream, or if the input 
contains a syntax error or is malformed.
+        */
+       protected <E> Collection<E> doParseIntoCollection(ParserSession 
session, Collection<E> c, Type elementType) throws Exception {
+               throw new UnsupportedOperationException("Parser 
'"+getClass().getName()+"' does not support this method.");
+       }
+
+       /**
+        * 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 class='spaced-list'>
+        *      <li>Used to parse argument strings in the {@link 
PojoIntrospector#invokeMethod(Method, Reader)} method.
+        * </ul>
+        *
+        * @param input The input.  Subclasses can support different input 
types.
+        * @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 UnsupportedOperationException If not implemented.
+        */
+       public final Object[] parseArgs(Object input, ClassMeta<?>[] argTypes) 
throws ParseException {
+               if (argTypes == null || argTypes.length == 0)
+                       return new Object[0];
+               ParserSession session = createSession(input);
+               try {
+                       return doParseArgs(session, argTypes);
+               } catch (ParseException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new ParseException(session, e);
+               } finally {
+                       session.close();
+               }
+       }
+
+       /**
+        * Implementation method.
+        * Default implementation throws an {@link 
UnsupportedOperationException}.
+        * @param session The runtime session object returned by {@link 
#createSession(Object, ObjectMap, Method, Object)}.
+        *      If <jk>null</jk>, one will be created using {@link 
#createSession(Object)}.
+        * @param argTypes Specifies the type of objects to create for each 
entry in the array.
+        *
+        * @return An array of parsed objects.
+        * @throws Exception If thrown from underlying stream, or if the input 
contains a syntax error or is malformed.
+        */
+       protected Object[] doParseArgs(ParserSession session, ClassMeta<?>[] 
argTypes) throws Exception {
+               throw new UnsupportedOperationException("Parser 
'"+getClass().getName()+"' does not support this method.");
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Other methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Adds a {@link ParserListener} to this parser to listen for parse 
events.
+        *
+        * @param listener The listener to associate with this parser.
+        * @throws LockedException If {@link #lock()} was called on this object.
+        * @return This object (for method chaining).
+        */
+       public Parser addListener(ParserListener listener) throws 
LockedException {
+               checkLock();
+               this.listeners.add(listener);
+               return this;
+       }
+
+       /**
+        * Returns the current parser listeners associated with this parser.
+        *
+        * @return The current list of parser listeners.
+        */
+       public List<ParserListener> getListeners() {
+               return listeners;
+       }
+
+       /**
+        * Converts the specified string to the specified type.
+        *
+        * @param session The session object.
+        * @param outer The outer object if we're converting to an inner object 
that needs to be created within the context of an outer object.
+        * @param s The string to convert.
+        * @param type The class type to convert the string to.
+        * @return The string converted as an object of the specified type.
+        * @throws Exception If the input contains a syntax error or is 
malformed, or is not valid for the specified type.
+        * @param <T> The class type to convert the string to.
+        */
+       @SuppressWarnings({ "unchecked", "rawtypes" })
+       protected <T> T convertAttrToType(ParserSession session, Object outer, 
String s, ClassMeta<T> type) throws Exception {
+               if (s == null)
+                       return null;
+
+               if (type == null)
+                       type = (ClassMeta<T>)object();
+               PojoTransform transform = type.getPojoTransform();
+               ClassMeta<?> gType = type.getTransformedClassMeta();
+
+               Object o = s;
+               if (gType.isChar())
+                       o = s.charAt(0);
+               else if (gType.isNumber())
+                       if (type.canCreateNewInstanceFromNumber(outer))
+                               o = type.newInstanceFromNumber(outer, 
parseNumber(s, type.getNewInstanceFromNumberClass()));
+                       else
+                               o = parseNumber(s, (Class<? extends 
Number>)gType.getInnerClass());
+               else if (gType.isBoolean())
+                       o = Boolean.parseBoolean(s);
+               else if (! (gType.isCharSequence() || gType.isObject())) {
+                       if (gType.canCreateNewInstanceFromString(outer))
+                               o = gType.newInstanceFromString(outer, s);
+                       else
+                               throw new ParseException(session, "Invalid 
conversion from string to class ''{0}''", type);
+               }
+
+               if (transform != null)
+                       o = transform.normalize(o, type);
+
+               return (T)o;
+       }
+
+       /**
+        * Convenience method for calling the {@link ParentProperty 
@ParentProperty} method on
+        * the specified object if it exists.
+        *
+        * @param cm The class type of the object.
+        * @param o The object.
+        * @param parent The parent to set.
+        * @throws Exception
+        */
+       protected void setParent(ClassMeta<?> cm, Object o, Object parent) 
throws Exception {
+               Method m = cm.getParentProperty();
+               if (m != null)
+                       m.invoke(o, parent);
+       }
+
+       /**
+        * Convenience method for calling the {@link NameProperty 
@NameProperty} method on
+        * the specified object if it exists.
+        *
+        * @param cm The class type of the object.
+        * @param o The object.
+        * @param name The name to set.
+        * @throws Exception
+        */
+       protected void setName(ClassMeta<?> cm, Object o, Object name) throws 
Exception {
+               if (cm != null) {
+                       Method m = cm.getNameProperty();
+                       if (m != null)
+                               m.invoke(o, name);
+               }
+       }
+
+       /**
+        * Method that gets called when an unknown bean property name is 
encountered.
+        *
+        * @param session The parser session.
+        * @param propertyName The unknown bean property name.
+        * @param beanMap The bean that doesn't have the expected property.
+        * @param line The line number where the property was found.  
<code>-1</code> if line numbers are not available.
+        * @param col The column number where the property was found.  
<code>-1</code> if column numbers are not available.
+        * @throws ParseException Automatically thrown if {@link 
BeanContext#BEAN_ignoreUnknownBeanProperties} setting
+        *      on this parser is <jk>false</jk>
+        * @param <T> The class type of the bean map that doesn't have the 
expected property.
+        */
+       protected <T> void onUnknownProperty(ParserSession session, String 
propertyName, BeanMap<T> beanMap, int line, int col) throws ParseException {
+               if (propertyName.equals("uri") || propertyName.equals("type") 
|| propertyName.equals("_class"))
+                       return;
+               if (! session.getBeanContext().isIgnoreUnknownBeanProperties())
+                       throw new ParseException(session, "Unknown property 
''{0}'' encountered while trying to parse into class ''{1}''", propertyName, 
beanMap.getClassMeta());
+               if (listeners.size() > 0)
+                       for (ParserListener listener : listeners)
+                               listener.onUnknownProperty(propertyName, 
beanMap.getClassMeta().getInnerClass(), beanMap.getBean(), line, col);
+       }
+
+
+       /**
+        * Returns the media types handled based on the value of the {@link 
Consumes} annotation on the parser class.
+        * <p>
+        * This method can be overridden by subclasses to determine the media 
types programatically.
+        *
+        * @return The list of media types.  Never <jk>null</jk>.
+        */
+       public String[] getMediaTypes() {
+               return mediaTypes;
+       }
+
+       /**
+        * Returns the results from {@link #getMediaTypes()} parsed as {@link 
MediaRange MediaRanges}.
+        *
+        * @return The list of media types parsed as ranges.  Never 
<jk>null</jk>.
+        */
+       public MediaRange[] getMediaRanges() {
+               return mediaRanges;
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       @Override /* CoreApi */
+       public Parser setProperty(String property, Object value) throws 
LockedException {
+               super.setProperty(property, value);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public Parser addNotBeanClasses(Class<?>...classes) throws 
LockedException {
+               super.addNotBeanClasses(classes);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public Parser addTransforms(Class<?>...classes) throws LockedException {
+               super.addTransforms(classes);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public <T> Parser addImplClass(Class<T> interfaceClass, Class<? extends 
T> implClass) throws LockedException {
+               super.addImplClass(interfaceClass, implClass);
+               return this;
+       }
+
+       @Override /* CoreApi */
+       public Parser setClassLoader(ClassLoader classLoader) throws 
LockedException {
+               super.setClassLoader(classLoader);
+               return this;
+       }
+
+       @Override /* Lockable */
+       public Parser lock() {
+               super.lock();
+               return this;
+       }
+
+       @Override /* Lockable */
+       public Parser clone() throws CloneNotSupportedException {
+               Parser c = (Parser)super.clone();
+               return c;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/ParserContext.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/parser/ParserContext.java 
b/juneau-core/src/main/java/org/apache/juneau/parser/ParserContext.java
new file mode 100644
index 0000000..c806ffd
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserContext.java
@@ -0,0 +1,57 @@
+/***************************************************************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.  See the License for the
+ * specific language governing permissions and limitations under the License.
+ 
***************************************************************************************************************************/
+package org.apache.juneau.parser;
+
+import org.apache.juneau.*;
+
+/**
+ * Parent class for all parser contexts.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class ParserContext extends Context {
+
+       /**
+        * Debug mode ({@link Boolean}, default=<jk>false</jk>).
+        * <p>
+        * Enables the following additional information during parsing:
+        * <ul class='spaced-list'>
+        *      <li>When bean setters throws exceptions, the exception includes 
the object stack information
+        *              in order to determine how that method was invoked.
+        * </ul>
+        */
+       public static final String PARSER_debug = "Parser.debug";
+
+       /**
+        * Trim parsed strings ({@link Boolean}, default=<jk>false</jk>).
+        * <p>
+        * If <jk>true</jk>, string values will be trimmed of whitespace using 
{@link String#trim()} before being added to the POJO.
+        */
+       public static final String PARSER_trimStrings = "Parser.trimStrings";
+
+
+       final boolean debug, trimStrings;
+
+       /**
+        * Constructor.
+        * <p>
+        * Typically only called from {@link ContextFactory#getContext(Class)}.
+        *
+        * @param cf The factory that created this context.
+        */
+       public ParserContext(ContextFactory cf) {
+               super(cf);
+               this.debug = cf.getProperty(PARSER_debug, boolean.class, false);
+               this.trimStrings = cf.getProperty(PARSER_trimStrings, 
boolean.class, false);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroup.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroup.java 
b/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroup.java
new file mode 100644
index 0000000..0e810b8
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroup.java
@@ -0,0 +1,314 @@
+/***************************************************************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.  See the License for the
+ * specific language governing permissions and limitations under the License.
+ 
***************************************************************************************************************************/
+package org.apache.juneau.parser;
+
+import static org.apache.juneau.internal.ArrayUtils.*;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.*;
+
+import org.apache.juneau.*;
+
+/**
+ * Represents a group of {@link Parser Parsers} that can be looked up by media 
type.
+ *
+ *
+ * <h6 class='topic'>Description</h6>
+ * <p>
+ *     Provides the following features:
+ * <ul class='spaced-list'>
+ *     <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>
+ *
+ *
+ * <h6 class='topic'>Match ordering</h6>
+ * <p>
+ *     Parsers are matched against <code>Content-Type</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 parsers to be overridden through subsequent 
calls.
+ * <p>
+ *     For example, calling 
<code>g.append(P1.<jk>class</jk>,P2.<jk>class</jk>).append(P3.<jk>class</jk>,P4.<jk>class</jk>)</code>
+ *     will result in the order <code>P3, P4, P1, P2</code>.
+ *
+ *
+ * <h6 class='topic'>Examples</h6>
+ * <p class='bcode'>
+ *     <jc>// Construct a new parser group</jc>
+ *     ParserGroup g = <jk>new</jk> ParserGroup();
+ *
+ *     <jc>// Add some parsers to it</jc>
+ *     g.append(JsonParser.<jk>class</jk>, XmlParser.<jk>class</jk>);
+ *
+ *     <jc>// Change settings on parsers simultaneously</jc>
+ *     g.setProperty(BeanContext.<jsf>BEAN_beansRequireSerializable</jsf>, 
<jk>true</jk>)
+ *             .addTransforms(CalendarTransform.ISO8601DT.<jk>class</jk>)
+ *             .lock();
+ *
+ *     <jc>// Find the appropriate parser by Content-Type</jc>
+ *     ReaderParser p = (ReaderParser)g.getParser(<js>"text/json"</js>);
+ *
+ *     <jc>// Parse a bean from JSON</jc>
+ *     String json = <js>"{...}"</js>;
+ *     AddressBook addressBook = p.parse(json, AddressBook.<jk>class</jk>);
+ * </p>
+ *
+ * @author James Bognar ([email protected])
+ */
+public final class ParserGroup extends Lockable {
+
+       // Maps media-types to parsers.
+       private final Map<String,Parser> parserMap = new 
ConcurrentHashMap<String,Parser>();
+
+       // Maps Content-Type headers to matching media types.
+       private final Map<String,String> mediaTypeMappings = new 
ConcurrentHashMap<String,String>();
+
+       private final CopyOnWriteArrayList<Parser> parsers = new 
CopyOnWriteArrayList<Parser>();
+
+       private final ReadWriteLock lock = new ReentrantReadWriteLock();
+       private final Lock rl = lock.readLock(), wl = lock.writeLock();
+
+
+       /**
+        * Registers the specified parsers with this group.
+        *
+        * @param p The parsers to append to this group.
+        * @return This object (for method chaining).
+        * @throws Exception Thrown if {@link Parser} could not be constructed.
+        */
+       public ParserGroup append(Class<? extends Parser>...p) throws Exception 
{
+               checkLock();
+               wl.lock();
+               try {
+                       for (Class<? extends Parser> c : reverse(p)) {
+                               parserMap.clear();
+                               mediaTypeMappings.clear();
+                               try {
+                                       append(c);
+                               } catch (NoClassDefFoundError e) {
+                                       // Ignore if dependent library not 
found (e.g. Jena).
+                                       System.err.println(e);
+                               }
+                       }
+               } finally {
+                       wl.unlock();
+               }
+               return this;
+       }
+
+       /**
+        * Same as {@link #append(Class[])}, except specify a single class to 
avoid unchecked compile warnings.
+        *
+        * @param p The parser to append to this group.
+        * @return This object (for method chaining).
+        * @throws Exception Thrown if {@link Parser} could not be constructed.
+        */
+       public ParserGroup append(Class<? extends Parser> p) throws Exception {
+               checkLock();
+               wl.lock();
+               try {
+                       parserMap.clear();
+                       mediaTypeMappings.clear();
+                       parsers.add(0, p.newInstance());
+               } catch (NoClassDefFoundError e) {
+                       // Ignore if dependent library not found (e.g. Jena).
+                       System.err.println(e);
+               } finally {
+                       wl.unlock();
+               }
+               return this;
+       }
+
+       /**
+        * Returns the parser registered to handle the specified media type.
+        * <p>
+        * The media-type string must not contain any parameters such as 
<js>";charset=X"</js>.
+        *
+        * @param mediaType The media-type string (e.g. <js>"text/json"</js>).
+        * @return The REST parser that handles the specified request content 
type, or <jk>null</jk> if
+        *              no parser is registered to handle it.
+        */
+       public Parser getParser(String mediaType) {
+               Parser p = parserMap.get(mediaType);
+               if (p == null) {
+                       String mt = findMatch(mediaType);
+                       if (mt != null)
+                               p = parserMap.get(mt);
+               }
+               return p;
+       }
+
+       /**
+        * Searches the group for a parser that can handle the specified 
<l>Content-Type</l> header value.
+        *
+        * @param contentTypeHeader The HTTP <l>Content-Type</l> header value.
+        * @return The media type registered by one of the parsers that matches 
the <code>mediaType</code> string,
+        *      or <jk>null</jk> if no media types matched.
+        */
+       public String findMatch(String contentTypeHeader) {
+               rl.lock();
+               try {
+                       String mt = mediaTypeMappings.get(contentTypeHeader);
+                       if (mt != null)
+                               return mt;
+
+                       MediaRange[] mr = MediaRange.parse(contentTypeHeader);
+                       if (mr.length == 0)
+                               mr = MediaRange.parse("*/*");
+
+                       for (MediaRange a : mr) {
+                               for (Parser p : parsers) {
+                                       for (MediaRange a2 : 
p.getMediaRanges()) {
+                                               if (a.matches(a2)) {
+                                                       mt = a2.getMediaType();
+                                                       
mediaTypeMappings.put(contentTypeHeader, mt);
+                                                       parserMap.put(mt, p);
+                                                       return mt;
+                                               }
+                                       }
+                               }
+                       }
+                       return null;
+               } finally {
+                       rl.unlock();
+               }
+       }
+
+       /**
+        * Returns the media types that all parsers in this group can handle
+        * <p>
+        * Entries are ordered in the same order as the parsers in the group.
+        *
+        * @return The list of media types.
+        */
+       public List<String> getSupportedMediaTypes() {
+               List<String> l = new ArrayList<String>();
+               for (Parser p : parsers)
+                       for (String mt : p.getMediaTypes())
+                               if (! l.contains(mt))
+                                       l.add(mt);
+               return l;
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Convenience methods for setting properties on all parsers.
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Shortcut for calling {@link Parser#setProperty(String, Object)} on 
all parsers 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 ParserGroup setProperty(String property, Object value) throws 
LockedException {
+               checkLock();
+               for (Parser p : parsers)
+                       p.setProperty(property, value);
+               return this;
+       }
+
+       /**
+        * Shortcut for calling {@link Parser#setProperties(ObjectMap)} on all 
parsers 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 ParserGroup setProperties(ObjectMap properties) {
+               checkLock();
+               for (Parser p : parsers)
+                       p.setProperties(properties);
+               return this;
+       }
+
+       /**
+        * Shortcut for calling {@link Parser#addNotBeanClasses(Class[])} on 
all parsers in this group.
+        *
+        * @param classes The classes to specify as not-beans to the underlying 
bean context of all parsers in this group.
+        * @throws LockedException If {@link #lock()} was called on this object.
+        * @return This object (for method chaining).
+        */
+       public ParserGroup addNotBeanClasses(Class<?>...classes) throws 
LockedException {
+               checkLock();
+               for (Parser p : parsers)
+                       p.addNotBeanClasses(classes);
+               return this;
+       }
+
+       /**
+        * Shortcut for calling {@link Parser#addTransforms(Class[])} on all 
parsers in this group.
+        *
+        * @param classes The classes to add bean transforms for to the 
underlying bean context of all parsers in this group.
+        * @throws LockedException If {@link #lock()} was called on this object.
+        * @return This object (for method chaining).
+        */
+       public ParserGroup addTransforms(Class<?>...classes) throws 
LockedException {
+               checkLock();
+               for (Parser p : parsers)
+                       p.addTransforms(classes);
+               return this;
+       }
+
+       /**
+        * Shortcut for calling {@link Parser#addImplClass(Class, Class)} on 
all parsers 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> ParserGroup addImplClass(Class<T> interfaceClass, Class<? 
extends T> implClass) throws LockedException {
+               checkLock();
+               for (Parser p : parsers)
+                       p.addImplClass(interfaceClass, implClass);
+               return this;
+       }
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       /**
+        * Locks this group and all parsers in this group.
+        */
+       @Override /* Lockable */
+       public ParserGroup lock() {
+               super.lock();
+               for (Parser p : parsers)
+                       p.lock();
+               return this;
+       }
+
+       /**
+        * Clones this group and all parsers in this group.
+        */
+       @Override /* Lockable */
+       public ParserGroup clone() throws CloneNotSupportedException {
+               ParserGroup g = new ParserGroup();
+
+               List<Parser> l = new ArrayList<Parser>(parsers.size());
+               for (Parser p : parsers)
+                       l.add(p.clone());
+
+               g.parsers.addAll(l);
+
+               return g;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/ParserListener.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/parser/ParserListener.java 
b/juneau-core/src/main/java/org/apache/juneau/parser/ParserListener.java
new file mode 100644
index 0000000..b803166
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserListener.java
@@ -0,0 +1,44 @@
+/***************************************************************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.  See the License for the
+ * specific language governing permissions and limitations under the License.
+ 
***************************************************************************************************************************/
+package org.apache.juneau.parser;
+
+import org.apache.juneau.*;
+
+/**
+ * Class for listening for certain parse events during a document parse.
+ * <p>
+ *     Listeners can be registered with parsers through the {@link 
Parser#addListener(ParserListener)} method.
+ * </p>
+ *     It should be noted that listeners are not automatically copied over to 
new parsers when a parser is cloned.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class ParserListener {
+
+       /**
+        * Gets called when an unknown bean property is detected in a document.
+        * <p>
+        *      This method only gets called if {@link 
BeanContext#BEAN_ignoreUnknownBeanProperties} setting is <jk>true</jk>.
+        *      Otherwise, the parser will throw a {@link ParseException}.
+        *
+        * @param <T> The class type of the bean.
+        * @param propertyName The property name encountered in the document.
+        * @param beanClass The bean class.
+        * @param bean The bean.
+        * @param line The line number where the unknown property was found (-1 
if parser doesn't support line/column indicators).
+        * @param col The column number where the unknown property was found 
(-1 if parser doesn't support line/column indicators).
+        */
+       public <T> void onUnknownProperty(String propertyName, Class<T> 
beanClass, T bean, int line, int col) {
+               // Do something with information
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/ParserReader.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/parser/ParserReader.java 
b/juneau-core/src/main/java/org/apache/juneau/parser/ParserReader.java
new file mode 100644
index 0000000..df4dd2f
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserReader.java
@@ -0,0 +1,382 @@
+/***************************************************************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.  See the License for the
+ * specific language governing permissions and limitations under the License.
+ 
***************************************************************************************************************************/
+package org.apache.juneau.parser;
+
+import java.io.*;
+
+import org.apache.juneau.internal.*;
+
+/**
+ * Similar to a {@link java.io.PushbackReader} with a pushback buffer of 1 
character.
+ * <p>
+ *     Code is optimized to work with a 1 character buffer.
+ * <p>
+ *     Additionally keeps track of current line and column number, and 
provides the ability to set
+ *     mark points and capture characters from the previous mark point.
+ * <p>
+ *     <b>Warning:</b>  Not thread safe.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class ParserReader extends Reader {
+
+       /** Wrapped reader */
+       protected Reader r;
+
+       private char[] buff;       // Internal character buffer
+       private int line = 1;      // Current line number
+       private int column;        // Current column number
+       private int iCurrent = 0;  // Current pointer into character buffer
+       private int iMark = -1;    // Mark position in buffer
+       private int iEnd = 0;      // The last good character position in the 
buffer
+       private boolean endReached, holesExist;
+
+       ParserReader() {}
+
+       /**
+        * Constructor for input from a {@link CharSequence}.
+        *
+        * @param in The character sequence being read from.
+        */
+       public ParserReader(CharSequence in) {
+               this.r = new CharSequenceReader(in);
+               if (in == null)
+                       this.buff = new char[0];
+               else
+                       this.buff = new char[in.length() < 1024 ? in.length() : 
1024];
+       }
+
+       /**
+        * Constructor for input from a {@link Reader}).
+        *
+        * @param r The Reader being wrapped.
+        */
+       public ParserReader(Reader r) {
+               if (r instanceof ParserReader)
+                       this.r = ((ParserReader)r).r;
+               else
+                       this.r = r;
+               this.buff = new char[1024];
+       }
+
+       /**
+        * Returns the current line number position in this reader.
+        *
+        * @return The current line number.
+        */
+       public final int getLine() {
+               return line;
+       }
+
+       /**
+        * Returns the current column number position in this reader.
+        *
+        * @return The current column number.
+        */
+       public final int getColumn() {
+               return column;
+       }
+
+       /**
+        * Reads a single character.
+        * Note that this method does NOT process extended unicode characters 
(i.e. characters
+        *      above 0x10000), but rather returns them as two <jk>char</jk>s.
+        * Use {@link #readCodePoint()} to ensure proper handling of extended 
unicode.
+        *
+        * @return The character read, or -1 if the end of the stream has been 
reached.
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        */
+       @Override /* Reader */
+       public final int read() throws IOException {
+               int c = readFromBuff();
+               if (c == -1)
+                       return -1;
+               if (c == '\n') {
+                       line++;
+                       column = 0;
+               } else {
+                       column++;
+               }
+               return c;
+       }
+
+       /**
+        * Same as {@link #read()} but detects and combines extended unicode 
characters (i.e. characters
+        *      above 0x10000).
+        *
+        * @return The character read, or -1 if the end of the stream has been 
reached.
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        */
+       public final int readCodePoint() throws IOException {
+               int c = read();
+
+               // Characters that take up 2 chars.
+               if (c >= 0xd800 && c <= 0xdbff) {
+                       int low = read();
+                       if (low >= 0xdc00 && low <= 0xdfff)
+                               c = 0x10000 + ((c - 0xd800) << 10) + (low - 
0xdc00);
+               }
+
+               return c;
+       }
+
+       private final int readFromBuff() throws IOException {
+               while (iCurrent >= iEnd) {
+                       if (endReached)
+                               return -1;
+
+                       // If there's still space at the end of this buffer, 
fill it.
+                       // Make sure there's at least 2 character spaces free 
for extended unicode characters.
+                       //if (false) {
+                       if (iEnd+1 < buff.length) {
+                               int x = read(buff, iCurrent, buff.length-iEnd);
+                               if (x == -1) {
+                                       endReached = true;
+                                       return -1;
+                               }
+                               iEnd += x;
+
+                       } else {
+                               // If we're currently marking, then we want to 
copy from the current mark point
+                               // to the beginning of the buffer and then fill 
in the remainder of buffer.
+                               if (iMark >= 0) {
+
+                                       // If we're marking from the beginning 
of the array, we double the size of the
+                                       // buffer.  This isn't likely to occur 
often.
+                                       if (iMark == 0) {
+                                               char[] buff2 = new 
char[buff.length<<1];
+                                               System.arraycopy(buff, 0, 
buff2, 0, buff.length);
+                                               buff = buff2;
+
+                                       // Otherwise, we copy what's currently 
marked to the beginning of the buffer.
+                                       } else {
+                                               int copyBuff = iMark;
+                                               System.arraycopy(buff, 
copyBuff, buff, 0, buff.length - copyBuff);
+                                               iCurrent -= copyBuff;
+                                               iMark -= copyBuff;
+                                       }
+                                       int expected = buff.length - iCurrent;
+
+                                       int x = read(buff, iCurrent, expected);
+                                       if (x == -1) {
+                                               endReached = true;
+                                               iEnd = iCurrent;
+                                               return -1;
+                                       }
+                                       iEnd = iCurrent + x;
+                               } else {
+                                       // Copy the last 10 chars in the buffer 
to the beginning of the buffer.
+                                       int copyBuff = Math.min(iCurrent, 10);
+                                       System.arraycopy(buff, 
iCurrent-copyBuff, buff, 0, copyBuff);
+
+                                       // Number of characters we expect to 
copy on the next read.
+                                       int expected = buff.length - copyBuff;
+                                       int x = read(buff, copyBuff, expected);
+                                       iCurrent = copyBuff;
+                                       if (x == -1) {
+                                               endReached = true;
+                                               iEnd = iCurrent;
+                                               return -1;
+                                       }
+                                       iEnd = iCurrent + x;
+                               }
+                       }
+               }
+               return buff[iCurrent++];
+       }
+
+       /**
+        * Start buffering the calls to read() so that the text can be gathered 
from the mark
+        * point on calling {@code getFromMarked()}.
+        */
+       public final void mark() {
+               iMark = iCurrent;
+       }
+
+
+       /**
+        * Peeks the next character in the stream.
+        * <p>
+        *      This is equivalent to doing a {@code read()} followed by an 
{@code unread()}.
+        *
+        * @return The peeked character, or (char)-1 if the end of the stream 
has been reached.
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        */
+       public final int peek() throws IOException {
+               int c = read();
+               if (c != -1)
+                       unread();
+               return c;
+       }
+
+       /**
+        * Read the specified number of characters off the stream.
+        *
+        * @param num The number of characters to read.
+        * @return The characters packaged as a String.
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        */
+       public final String read(int num) throws IOException {
+               char[] c = new char[num];
+               for (int i = 0; i < num; i++) {
+                       int c2 = read();
+                       if (c2 == -1)
+                               return new String(c, 0, i);
+                       c[i] = (char)c2;
+               }
+               return new String(c);
+       }
+
+       /**
+        * Pushes the last read character back into the stream.
+        *
+        * @return This object (for method chaining).
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        */
+       public final ParserReader unread() throws IOException {
+               if (iCurrent <= 0)
+                       throw new IOException("Buffer underflow.");
+               iCurrent--;
+               column--;
+               return this;
+       }
+
+       /**
+        * Close this reader and the underlying reader.
+        *
+        * @throws IOException If a problem occurred trying to read from the 
reader.
+        */
+       @Override /* Reader */
+       public void close() throws IOException {
+               r.close();
+       }
+
+       /**
+        * Returns the contents of the reusable character buffer as a string, 
and
+        * resets the buffer for next usage.
+        *
+        * @return The contents of the reusable character buffer as a string.
+        */
+       public final String getMarked() {
+               return getMarked(0, 0);
+       }
+
+       /**
+        * Same as {@link #getMarked()} except allows you to specify offsets
+        *      into the buffer.
+        * <p>
+        * For example, to return the marked string, but trim the first and 
last characters,
+        *      call the following:
+        * <p class='bcode'>
+        *      getFromMarked(1, -1);
+        * </p>
+        *
+        * @param offsetStart The offset of the start position.
+        * @param offsetEnd The offset of the end position.
+        * @return The contents of the reusable character buffer as a string.
+        */
+       public final String getMarked(int offsetStart, int offsetEnd) {
+               int offset = 0;
+
+               // Holes are \u00FF 'delete' characters that we need to get rid 
of now.
+               if (holesExist) {
+                       for (int i = iMark; i < iCurrent; i++) {
+                               char c = buff[i];
+                               if (c == 127)
+                                       offset++;
+                               else
+                                       buff[i-offset] = c;
+                       }
+                       holesExist = false;
+               }
+               int start = iMark + offsetStart, len = iCurrent - iMark + 
offsetEnd - offsetStart - offset;
+               String s = new String(buff, start, len);
+               iMark = -1;
+               return s;
+       }
+
+       /**
+        * Trims off the last character in the marking buffer.
+        * Useful for removing escape characters from sequences.
+        *
+        * @return This object (for method chaining).
+        */
+       public final ParserReader delete() {
+               return delete(1);
+       }
+
+       /**
+        * Trims off the specified number of last characters in the marking 
buffer.
+        * Useful for removing escape characters from sequences.
+        *
+        * @param count The number of characters to delete.
+        * @return This object (for method chaining).
+        */
+       public final ParserReader delete(int count) {
+               for (int i = 0; i < count; i++)
+                       buff[iCurrent-i-1] = 127;
+               holesExist = true;
+               return this;
+       }
+
+       /**
+        * Replaces the last character in the marking buffer with the specified 
character.
+        * <code>offset</code> must be at least <code>1</code> for normal 
characters, and
+        * <code>2</code> for extended unicode characters in order for the 
replacement
+        * to fit into the buffer.
+        *
+        * @param c The new character.
+        * @param offset The offset.
+        * @return This object (for method chaining).
+        * @throws IOException
+        */
+       public final ParserReader replace(int c, int offset) throws IOException 
{
+               if (c < 0x10000) {
+                       if (offset < 1)
+                               throw new IOException("Buffer underflow.");
+                       buff[iCurrent-offset] = (char)c;
+               } else {
+                       if (offset < 2)
+                               throw new IOException("Buffer underflow.");
+                       c -= 0x10000;
+                       buff[iCurrent-offset] = (char)(0xd800 + (c >> 10));
+                       buff[iCurrent-offset+1] = (char)(0xdc00 + (c & 0x3ff));
+                       offset--;
+               }
+               // Fill in the gap with DEL characters.
+               for (int i = 1; i < offset; i++)
+                       buff[iCurrent-i] = 127;
+               holesExist |= (offset > 1);
+               return this;
+       }
+
+       /**
+        * Replace the last read character in the buffer with the specified 
character.
+        *
+        * @param c The new character.
+        * @return This object (for method chaining).
+        * @throws IOException
+        */
+       public final ParserReader replace(char c) throws IOException {
+               return replace(c, 1);
+       }
+
+       /**
+        * Subclasses can override this method to provide additional filtering.
+        * Default implementation simply calls the same method on the 
underlying reader.
+        */
+       @Override /* Reader */
+       public int read(char[] cbuf, int off, int len) throws IOException {
+               return r.read(cbuf, off, len);
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/ParserSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/parser/ParserSession.java 
b/juneau-core/src/main/java/org/apache/juneau/parser/ParserSession.java
new file mode 100644
index 0000000..948fad8
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserSession.java
@@ -0,0 +1,310 @@
+/***************************************************************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.  See the License for the
+ * specific language governing permissions and limitations under the License.
+ 
***************************************************************************************************************************/
+package org.apache.juneau.parser;
+
+import static org.apache.juneau.parser.ParserContext.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Session object that lives for the duration of a single use of {@link 
Parser}.
+ * <p>
+ * This class is NOT thread safe.  It is meant to be discarded after one-time 
use.
+ *
+ * @author James Bognar ([email protected])
+ */
+public class ParserSession extends Session {
+
+       private static JuneauLogger logger = 
JuneauLogger.getLogger(ParserSession.class);
+
+       private final boolean debug, trimStrings;
+       private boolean closed;
+       private final BeanContext beanContext;
+       private final List<String> warnings = new LinkedList<String>();
+
+       private final ObjectMap properties;
+       private final Method javaMethod;
+       private final Object outer;
+       private final Object input;
+       private InputStream inputStream;
+       private Reader reader, noCloseReader;
+       private BeanPropertyMeta<?> currentProperty;
+       private ClassMeta<?> currentClass;
+
+       /**
+        * Create a new session using properties specified in the context.
+        *
+        * @param ctx The context creating this session object.
+        *      The context contains all the configuration settings for this 
object.
+        * @param beanContext The bean context being used.
+        * @param input The input.
+        *      <br>For character-based parsers, this can be any of the 
following types:
+        *      <ul>
+        *              <li><jk>null</jk>
+        *              <li>{@link Reader}
+        *              <li>{@link CharSequence}
+        *              <li>{@link InputStream} containing UTF-8 encoded text.
+        *              <li>{@link File} containing system encoded text.
+        *      </ul>
+        *      <br>For byte-based parsers, this can be any of the following 
types:
+        *      <ul>
+        *              <li><jk>null</jk>
+        *              <li>{@link InputStream}
+        *              <li><code><jk>byte</jk>[]</code>
+        *              <li>{@link File}
+        *      </ul>
+        * @param op The override properties.
+        *      These override any context properties defined in the context.
+        * @param javaMethod The java method that called this parser, usually 
the method in a REST servlet.
+        * @param outer The outer object for instantiating top-level non-static 
inner classes.
+        */
+       public ParserSession(ParserContext ctx, BeanContext beanContext, Object 
input, ObjectMap op, Method javaMethod, Object outer) {
+               super(ctx);
+               if (op == null || op.isEmpty()) {
+                       debug = ctx.debug;
+                       trimStrings = ctx.trimStrings;
+               } else {
+                       debug = op.getBoolean(PARSER_debug, ctx.debug);
+                       trimStrings = op.getBoolean(PARSER_trimStrings, 
ctx.trimStrings);
+               }
+               this.beanContext = beanContext;
+               this.input = input;
+               this.properties = op;
+               this.javaMethod = javaMethod;
+               this.outer = outer;
+       }
+
+       /**
+        * Wraps the specified input object inside an input stream.
+        * Subclasses can override this method to implement their own input 
streams.
+        *
+        * @return The input object wrapped in an input stream, or 
<jk>null</jk> if the object is null.
+        * @throws ParseException If object could not be converted to an input 
stream.
+        */
+       public InputStream getInputStream() throws ParseException {
+               if (input == null)
+                       return null;
+               if (input instanceof InputStream)
+                       return (InputStream)input;
+               if (input instanceof byte[])
+                       return new ByteArrayInputStream((byte[])input);
+               if (input instanceof File)
+                       try {
+                               inputStream = new FileInputStream((File)input);
+                               return inputStream;
+                       } catch (FileNotFoundException e) {
+                               throw new ParseException(e);
+                       }
+               throw new ParseException("Cannot convert object of type {0} to 
an InputStream.", input.getClass().getName());
+       }
+
+
+       /**
+        * Wraps the specified input object inside a reader.
+        * Subclasses can override this method to implement their own readers.
+        *
+        * @return The input object wrapped in a Reader, or <jk>null</jk> if 
the object is null.
+        * @throws Exception If object could not be converted to a reader.
+        */
+       public Reader getReader() throws Exception {
+               if (input == null)
+                       return null;
+               if (input instanceof Reader)
+                       return (Reader)input;
+               if (input instanceof CharSequence) {
+                       if (reader == null)
+                               reader = new ParserReader((CharSequence)input);
+                       return reader;
+               }
+               if (input instanceof InputStream) {
+                       if (noCloseReader == null)
+                               noCloseReader = new 
InputStreamReader((InputStream)input, IOUtils.UTF8);
+                       return noCloseReader;
+               }
+               if (input instanceof File) {
+                       if (reader == null)
+                               reader = new FileReader((File)input);
+                       return reader;
+               }
+               throw new ParseException("Cannot convert object of type {0} to 
a Reader.", input.getClass().getName());
+       }
+
+       /**
+        * Returns information used to determine at what location in the parse 
a failure occurred.
+        *
+        * @return A map, typically containing something like 
<code>{line:123,column:456,currentProperty:"foobar"}</code>
+        */
+       public Map<String,Object> getLastLocation() {
+               Map<String,Object> m = new LinkedHashMap<String,Object>();
+               if (currentClass != null)
+                       m.put("currentClass", currentClass.toString(true));
+               if (currentProperty != null)
+                       m.put("currentProperty", currentProperty);
+               return m;
+       }
+
+       /**
+        * Returns the raw input object passed into this session.
+        *
+        * @return The raw input object passed into this session.
+        */
+       protected Object getInput() {
+               return input;
+       }
+
+       /**
+        * Returns the bean context in use for this session.
+        *
+        * @return The bean context in use for this session.
+        */
+       public final BeanContext getBeanContext() {
+               return beanContext;
+       }
+
+       /**
+        * Returns the Java method that invoked this parser.
+        * <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 parser.
+       */
+       public final Method getJavaMethod() {
+               return javaMethod;
+       }
+
+       /**
+        * Returns the outer object used for instantiating top-level non-static 
member classes.
+        * When using the REST API, this is the servlet object.
+        *
+        * @return The outer object.
+       */
+       public final Object getOuter() {
+               return outer;
+       }
+
+       /**
+        * Sets the current bean property being parsed for proper error 
messages.
+        * @param currentProperty The current property being parsed.
+        */
+       public void setCurrentProperty(BeanPropertyMeta<?> currentProperty) {
+               this.currentProperty = currentProperty;
+       }
+
+       /**
+        * Sets the current class being parsed for proper error messages.
+        * @param currentClass The current class being parsed.
+        */
+       public void setCurrentClass(ClassMeta<?> currentClass) {
+               this.currentClass = currentClass;
+       }
+
+       /**
+        * Returns the {@link ParserContext#PARSER_debug} setting value for 
this session.
+        *
+        * @return The {@link ParserContext#PARSER_debug} setting value for 
this session.
+        */
+       public final boolean isDebug() {
+               return debug;
+       }
+
+       /**
+        * Returns the {@link ParserContext#PARSER_trimStrings} setting value 
for this session.
+        *
+        * @return The {@link ParserContext#PARSER_trimStrings} setting value 
for this session.
+        */
+       public final boolean isTrimStrings() {
+               return trimStrings;
+       }
+
+       /**
+        * Returns the runtime properties associated with this context.
+        *
+        * @return The runtime properties associated with this context.
+        */
+       public final ObjectMap getProperties() {
+               return properties;
+       }
+
+       /**
+        * Logs a warning message.
+        *
+        * @param msg The warning message.
+        * @param args Optional printf arguments to replace in the error 
message.
+        */
+       public void addWarning(String msg, Object... args) {
+               logger.warning(msg, args);
+               msg = args.length == 0 ? msg : String.format(msg, args);
+               warnings.add((warnings.size() + 1) + ": " + msg);
+       }
+
+       /**
+        * Trims the specified object if it's a <code>String</code> and {@link 
#isTrimStrings()} returns <jk>true</jk>.
+        *
+        * @param o The object to trim.
+        * @return The trimmmed string if it's a string.
+        */
+       @SuppressWarnings("unchecked")
+       public final <K> K trim(K o) {
+               if (trimStrings && o instanceof String)
+                       return (K)o.toString().trim();
+               return o;
+
+       }
+
+       /**
+        * Trims the specified string if {@link ParserSession#isTrimStrings()} 
returns <jk>true</jk>.
+        *
+        * @param s The input string to trim.
+        * @return The trimmed string, or <jk>null</jk> if the input was 
<jk>null</jk>.
+        */
+       public final String trim(String s) {
+               if (trimStrings && s != null)
+                       return s.trim();
+               return s;
+       }
+
+       /**
+        * Perform cleanup on this context object if necessary.
+        *
+        * @throws ParseException If called more than once, or in debug mode 
and warnings occurred.
+        */
+       public void close() throws ParseException {
+               if (closed)
+                       throw new ParseException("Attempt to close 
ParserSession more than once.");
+
+               try {
+                       if (inputStream != null)
+                               inputStream.close();
+                       if (reader != null)
+                               reader.close();
+               } catch (IOException e) {
+                       throw new ParseException(e);
+               }
+
+               if (debug && warnings.size() > 0)
+                       throw new ParseException("Warnings occurred during 
parsing: \n" + StringUtils.join(warnings, "\n"));
+               closed = true;
+       }
+
+       @Override /* Object */
+       protected void finalize() throws Throwable {
+               if (! closed)
+                       throw new RuntimeException("ParserSession was not 
closed.");
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParser.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParser.java 
b/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParser.java
new file mode 100644
index 0000000..34af8ff
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParser.java
@@ -0,0 +1,45 @@
+/***************************************************************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.  See the License for the
+ * specific language governing permissions and limitations under the License.
+ 
***************************************************************************************************************************/
+package org.apache.juneau.parser;
+
+import org.apache.juneau.annotation.*;
+
+/**
+ * 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(ParserSession, ClassMeta)</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 {
+
+       @Override /* Parser */
+       public boolean isReaderParser() {
+               return true;
+       }
+}

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

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParser.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParser.java 
b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParser.java
new file mode 100644
index 0000000..36ab2b8
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParser.java
@@ -0,0 +1,70 @@
+/***************************************************************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.  See the License for the
+ * specific language governing permissions and limitations under the License.
+ 
***************************************************************************************************************************/
+package org.apache.juneau.plaintext;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.transform.*;
+
+/**
+ * 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 transform if the object class has an {@link 
PojoTransform PojoTransform&lt;?,String&gt;} transform defined on it.
+ *
+ *
+ * <h6 class='topic'>Configurable properties</h6>
+ * <p>
+ *     This class has the following properties associated with it:
+ * <ul>
+ *     <li>{@link ParserContext}
+ * </ul>
+ *
+ *
+ * @author James Bognar ([email protected])
+ */
+@Consumes("text/plain")
+public final class PlainTextParser extends ReaderParser {
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       @Override /* Parser */
+       protected <T> T doParse(ParserSession session, ClassMeta<T> type) 
throws Exception {
+               return 
session.getBeanContext().convertToType(IOUtils.read(session.getReader()), 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/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializer.java
 
b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializer.java
new file mode 100644
index 0000000..fd916f3
--- /dev/null
+++ 
b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializer.java
@@ -0,0 +1,69 @@
+/***************************************************************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.  See the License for the
+ * specific language governing permissions and limitations under the License.
+ 
***************************************************************************************************************************/
+package org.apache.juneau.plaintext;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.transform.*;
+
+/**
+ * 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 transform if the object class has an 
{@link PojoTransform PojoTransform&lt;?,String&gt;} transform defined on it.
+ *
+ *
+ * <h6 class='topic'>Configurable properties</h6>
+ * <p>
+ *     This class has the following properties associated with it:
+ * <ul>
+ *     <li>{@link SerializerContext}
+ *     <li>{@link BeanContext}
+ * </ul>
+ *
+ *
+ * @author James Bognar ([email protected])
+ */
+@Produces("text/plain")
+public final class PlainTextSerializer extends WriterSerializer {
+
+       
//--------------------------------------------------------------------------------
+       // Overridden methods
+       
//--------------------------------------------------------------------------------
+
+       @Override /* Serializer */
+       protected void doSerialize(SerializerSession session, Object o) throws 
Exception {
+               session.getWriter().write(o == null ? "null" : 
session.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.
+               }
+       }
+}

Reply via email to