You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2016/08/09 19:54:14 UTC

[41/51] [partial] incubator-juneau git commit: Rename project directories.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/ObjectList.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/ObjectList.java b/juneau-core/src/main/java/org/apache/juneau/ObjectList.java
new file mode 100644
index 0000000..4c15d3f
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/ObjectList.java
@@ -0,0 +1,569 @@
+/***************************************************************************************************************************
+ * 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;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.utils.*;
+
+/**
+ * Java implementation of a JSON array.
+ * <p>
+ * 	An extension of {@link LinkedList}, so all methods available to in that class are also available
+ * 	to this class.
+ * <p>
+ * 	Note that the use of this class is optional.  The serializers will accept any objects that implement
+ * 	the {@link Collection} interface.  But this class provides some useful additional functionality
+ * 	when working with JSON models constructed from Java Collections Framework objects.  For example, a
+ * 	constructor is provided for converting a JSON array string directly into a {@link List}.  It also contains
+ * 	accessor methods for to avoid common typecasting when accessing elements in a list.
+ *
+ * <h6 class='topic'>Examples</h6>
+ * <p class='bcode'>
+ * 	<jc>// Construct an empty List</jc>
+ * 	List l = <jk>new</jk> ObjectList();
+ *
+ * 	<jc>// Construct a list of objects using various methods</jc>
+ * 	l = <jk>new</jk> ObjectList().append(<js>"foo"</js>).append(123).append(<jk>true</jk>);
+ * 	l = <jk>new</jk> ObjectList().append(<js>"foo"</js>, 123, <jk>true</jk>);  <jc>// Equivalent</jc>
+ * 	l = <jk>new</jk> ObjectList(<js>"foo"</js>, 123, <jk>true</jk>);  <jc>// Equivalent</jc>
+ *
+ * 	<jc>// Construct a list of integers from JSON</jc>
+ * 	l = <jk>new</jk> ObjectList(<js>"[1,2,3]"</js>);
+ *
+ * 	<jc>// Construct a list of generic ObjectMap objects from JSON</jc>
+ * 	l = <jk>new</jk> ObjectList(<js>"[{foo:'bar'},{baz:'bing'}]"</js>);
+ *
+ * 	<jc>// Construct a list of integers from XML</jc>
+ * 	String xml = <js>"&lt;array&gt;&lt;number&gt;1&lt;/number&gt;&lt;number&gt;2&lt;/number&gt;&lt;number&gt;3&lt;/number&gt;&lt;/array&gt;"</js>;
+ * 	l = <jk>new</jk> ObjectList(xml, DataFormat.<jsf>XML</jsf>);
+ * 	l = (List)XmlParser.<jsf>DEFAULT</jsf>.parse(xml);  <jc>// Equivalent</jc>
+ * 	l = (List)XmlParser.<jsf>DEFAULT</jsf>.parse(Object.<jk>class</jk>, xml);  <jc>// Equivalent</jc>
+ * 	l = XmlParser.<jsf>DEFAULT</jsf>.parse(List.<jk>class</jk>, xml);  <jc>// Equivalent</jc>
+ * 	l = XmlParser.<jsf>DEFAULT</jsf>.parse(ObjectList.<jk>class</jk>, xml);  <jc>// Equivalent</jc>
+ *
+ * 	<jc>// Construct JSON from ObjectList</jc>
+ * 	l = <jk>new</jk> ObjectList(<js>"[{foo:'bar'},{baz:'bing'}]"</js>);
+ * 	String json = l.toString();  <jc>// Produces "[{foo:'bar'},{baz:'bing'}]"</jc>
+ * 	json = l.toString(JsonSerializer.<jsf>DEFAULT_CONDENSED</jsf>);  <jc>// Equivalent</jc>
+ * 	json = JsonSerializer.<jsf>DEFAULT_CONDENSED</jsf>.serialize(l);  <jc>// Equivalent</jc>
+ *
+ * 	<jc>// Get one of the entries in the list as an Integer</jc>
+ * 	l = <jk>new</jk> ObjectList(<js>"[1,2,3]"</js>);
+ * 	Integer i = l.getInt(1);
+ * 	i = l.get(Integer.<jk>class</jk>, 1);  <jc>// Equivalent</jc>
+ *
+ * 	<jc>// Get one of the entries in the list as an Float</jc>
+ * 	l = <jk>new</jk> ObjectList(<js>"[1,2,3]"</js>);
+ * 	Float f = l.getFloat(1); <jc>// Returns 2f </jc>
+ * 	f = l.get(Float.<jk>class</jk>, 1);  <jc>// Equivalent</jc>
+ *
+ * 	<jc>// Same as above, except converted to a String</jc>
+ * 	l = <jk>new</jk> ObjectList(<js>"[1,2,3]"</js>);
+ * 	String s = l.getString(1); <jc>// Returns "2" </jc>
+ * 	s = l.get(String.<jk>class</jk>, 1);  <jc>// Equivalent</jc>
+ *
+ * 	<jc>// Get one of the entries in the list as a bean (converted to a bean if it isn't already one)</jc>
+ * 	l = <jk>new</jk> ObjectList(<js>"[{name:'John Smith',age:45}]"</js>);
+ * 	Person p = l.get(Person.<jk>class</jk>, 0);
+ *
+ * 	<jc>// Iterate over a list of beans using the elements() method</jc>
+ * 	ObjectList ObjectList = <jk>new</jk> ObjectList(<js>"[{name:'John Smith',age:45}]"</js>);
+ * 	<jk>for</jk> (Person p : ObjectList.elements(Person.<jk>class</jk>) {
+ * 		<jc>// Do something with p</jc>
+ * 	}
+ * </p>
+ * <p>
+ * 	This class is not thread safe.
+ * </p>
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public class ObjectList extends LinkedList<Object> {
+	private static final long serialVersionUID = 1L;
+
+	private transient BeanContext beanContext = BeanContext.DEFAULT;
+	private transient PojoRest pojoRest;
+
+	/**
+	 * An empty read-only ObjectList.
+	 */
+	public static final ObjectList EMPTY_LIST = new ObjectList() {
+		private static final long serialVersionUID = 1L;
+
+		@Override /* List */
+		public void add(int location, Object object) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override /* List */
+		public ListIterator<Object> listIterator(final int location) {
+			return Collections.emptyList().listIterator(location);
+		}
+
+		@Override /* List */
+		public Object remove(int location) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override /* List */
+		public Object set(int location, Object object) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override /* List */
+		public List<Object> subList(int start, int end) {
+			return Collections.emptyList().subList(start, end);
+		}
+	};
+
+	/**
+	 * Construct a JSON array directly from text using the specified parser.
+	 *
+	 * @param s The string being parsed.
+	 * @param p The parser to use to parse the input.
+	 * @throws ParseException If the input contains a syntax error or is malformed.
+	 */
+	public ObjectList(CharSequence s, Parser p) throws ParseException {
+		this(p == null ? BeanContext.DEFAULT : p.getBeanContext());
+		if (p == null)
+			p = JsonParser.DEFAULT;
+		if (s != null)
+			p.parseIntoCollection(s, this, beanContext.object());
+	}
+
+	/**
+	 * Shortcut for <code><jk>new</jk> ObjectList(String,JsonParser.<jsf>DEFAULT</jsf>);</code>
+	 *
+	 * @param s The string being parsed.
+	 * @throws ParseException If the input contains a syntax error or is malformed.
+	 */
+	public ObjectList(CharSequence s) throws ParseException {
+		this(s, null);
+	}
+
+	/**
+	 * Construct a JSON array directly from a reader using the specified parser.
+	 *
+	 * @param r The reader to read from.  Will automatically be wrapped in a {@link BufferedReader} if it isn't already a BufferedReader.
+	 * @param p The parser to use to parse the input.
+	 * @throws ParseException If the input contains a syntax error or is malformed.
+	 * @throws IOException If a problem occurred trying to read from the reader.
+	 */
+	public ObjectList(Reader r, Parser p) throws ParseException, IOException {
+		parseReader(r, p);
+	}
+
+	private void parseReader(Reader r, Parser p) throws ParseException {
+		if (p == null)
+			p = JsonParser.DEFAULT;
+		p.parseIntoCollection(r, this, beanContext.object());
+	}
+
+	/**
+	 * Construct an empty JSON array. (i.e. an empty {@link LinkedList}).
+	 */
+	public ObjectList() {
+		this(BeanContext.DEFAULT);
+	}
+
+	/**
+	 * Construct an empty JSON array with the specified bean context. (i.e. an empty {@link LinkedList}).
+	 *
+	 * @param beanContext The bean context to associate with this object list for creating beans.
+	 */
+	public ObjectList(BeanContext beanContext) {
+		super();
+		this.beanContext = beanContext;
+	}
+
+	/**
+	 * Construct a JSON array and fill it with the specified objects.
+	 *
+	 * @param o A list of objects to add to this list.
+	 */
+	public ObjectList(Object... o) {
+		super(Arrays.asList(o));
+	}
+
+	/**
+	 * Construct a JSON array and fill it with the specified collection of objects.
+	 *
+	 * @param c A list of objects to add to this list.
+	 */
+	public ObjectList(Collection<?> c) {
+		super(c);
+	}
+
+	/**
+	 * Override the default bean context used for converting POJOs.
+	 * <p>
+	 * Default is {@link BeanContext#DEFAULT}, which is sufficient in most cases.
+	 * <p>
+	 * Useful if you're serializing/parsing beans with transforms defined.
+	 *
+	 * @param beanContext The new bean context.
+	 * @return This object (for method chaining).
+	 */
+	public ObjectList setBeanContext(BeanContext beanContext) {
+		this.beanContext = beanContext;
+		return this;
+	}
+
+	/**
+	 * Convenience method for adding multiple objects to this list.
+	 * @param o The objects to add to the list.
+	 * @return This object (for method chaining).
+	 */
+	public ObjectList append(Object...o) {
+		for (Object o2 : o)
+			add(o2);
+		return this;
+	}
+
+	/**
+	 * Get the entry at the specified index, converted to the specified type (if possible).
+	 * <p>
+	 * 	See {@link BeanContext#convertToType(Object, ClassMeta)} for the list of valid data conversions.
+	 *
+	 * @param type The type of object to convert the entry to.
+	 * @param index The index into this list.
+	 * @param <T> The type of object to convert the entry to.
+	 * @return The converted entry.
+	 */
+	public <T> T get(Class<T> type, int index) {
+		return beanContext.convertToType(get(index), type);
+	}
+
+	/**
+	 * Shortcut for calling <code>get(String.<jk>class</jk>, index)</code>.
+	 *
+	 * @param index The index.
+	 * @return The converted value.
+	 */
+	public String getString(int index) {
+		return get(String.class, index);
+	}
+
+	/**
+	 * Shortcut for calling <code>get(Integer.<jk>class</jk>, index)</code>.
+	 *
+	 * @param index The index.
+	 * @return The converted value.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Integer getInt(int index) {
+		return get(Integer.class, index);
+	}
+
+	/**
+	 * Shortcut for calling <code>get(Boolean.<jk>class</jk>, index)</code>.
+	 *
+	 * @param index The index.
+	 * @return The converted value.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Boolean getBoolean(int index) {
+		return get(Boolean.class, index);
+	}
+
+	/**
+	 * Shortcut for calling <code>get(Long.<jk>class</jk>, index)</code>.
+	 *
+	 * @param index The index.
+	 * @return The converted value.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Long getLong(int index) {
+		return get(Long.class, index);
+	}
+
+	/**
+	 * Shortcut for calling <code>get(Map.<jk>class</jk>, index)</code>.
+	 *
+	 * @param index The index.
+	 * @return The converted value.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Map<?,?> getMap(int index) {
+		return get(Map.class, index);
+	}
+
+	/**
+	 * Shortcut for calling <code>get(List.<jk>class</jk>, index)</code>.
+	 *
+	 * @param index The index.
+	 * @return The converted value.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public List<?> getList(int index) {
+		return get(List.class, index);
+	}
+
+	/**
+	 * Shortcut for calling <code>get(ObjectMap.<jk>class</jk>, index)</code>.
+	 *
+	 * @param index The index.
+	 * @return The converted value.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public ObjectMap getObjectMap(int index) {
+		return get(ObjectMap.class, index);
+	}
+
+	/**
+	 * Shortcut for calling <code>get(ObjectList.<jk>class</jk>, index)</code>.
+	 *
+	 * @param index The index.
+	 * @return The converted value.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public ObjectList getObjectList(int index) {
+		return get(ObjectList.class, index);
+	}
+
+	/**
+	 * Same as {@link #get(Class,int) get(Class,int)}, but the key is a slash-delimited
+	 * 	path used to traverse entries in this POJO.
+	 * <p>
+	 * 	For example, the following code is equivalent:
+	 * </p>
+	 * <p class='bcode'>
+	 * 	ObjectMap m = getObjectMap();
+	 *
+	 * 	<jc>// Long way</jc>
+	 * 	<jk>long</jk> l = m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>).getLong(<js>"baz"</js>);
+	 *
+	 * 	<jc>// Using this method</jc>
+	 * 	<jk>long</jk> l = m.getAt(<jk>long</jk>.<jk>class</jk>, <js>"foo/bar/0/baz"</js>);
+	 * </p>
+	 * <p>
+	 * 	This method uses the {@link PojoRest} class to perform the lookup, so the map can contain
+	 * 		any of the various class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays).
+	 * </p>
+	 *
+	 * @param <T> The class type.
+	 * @param type The class type.
+	 * @param path The path to the entry.
+	 * @return The value, or <jk>null</jk> if the entry doesn't exist.
+	 */
+	public <T> T getAt(Class<T> type, String path) {
+		return getPojoRest().get(type, path);
+	}
+
+	/**
+	 * Same as {@link #set(int,Object) set(int,Object)}, but the key is a slash-delimited
+	 * 	path used to traverse entries in this POJO.
+	 * <p>
+	 * 	For example, the following code is equivalent:
+	 * </p>
+	 * <p class='bcode'>
+	 * 	ObjectMap m = getObjectMap();
+	 *
+	 * 	<jc>// Long way</jc>
+	 * 	m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>).put(<js>"baz"</js>, 123);
+	 *
+	 * 	<jc>// Using this method</jc>
+	 * 	m.putAt(<js>"foo/bar/0/baz"</js>, 123);
+	 * </p>
+	 * <p>
+	 * 	This method uses the {@link PojoRest} class to perform the lookup, so the map can contain
+	 * 		any of the various class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays).
+	 * </p>
+	 *
+	 * @param path The path to the entry.
+	 * @param o The new value.
+	 * @return The previous value, or <jk>null</jk> if the entry doesn't exist.
+	 */
+	public Object putAt(String path, Object o) {
+		return getPojoRest().put(path, o);
+	}
+
+	/**
+	 * Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append
+	 * 	to collections and arrays.
+	 * <p>
+	 * 	For example, the following code is equivalent:
+	 * </p>
+	 * <p class='bcode'>
+	 * 	ObjectMap m = getObjectMap();
+	 *
+	 * 	<jc>// Long way</jc>
+	 * 	m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).append(123);
+	 *
+	 * 	<jc>// Using this method</jc>
+	 * 	m.postAt(<js>"foo/bar"</js>, 123);
+	 * </p>
+	 * <p>
+	 * 	This method uses the {@link PojoRest} class to perform the lookup, so the map can contain
+	 * 		any of the various class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays).
+	 * </p>
+	 *
+	 * @param path The path to the entry.
+	 * @param o The new value.
+	 * @return The previous value, or <jk>null</jk> if the entry doesn't exist.
+	 */
+	public Object postAt(String path, Object o) {
+		return getPojoRest().post(path, o);
+	}
+
+	/**
+	 * Similar to {@link #remove(int) remove(int)},but the key is a slash-delimited
+	 * 	path used to traverse entries in this POJO.
+	 * <p>
+	 * 	For example, the following code is equivalent:
+	 * </p>
+	 * <p class='bcode'>
+	 * 	ObjectMap m = getObjectMap();
+	 *
+	 * 	<jc>// Long way</jc>
+	 * 	m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(1).remove(<js>"baz"</js>);
+	 *
+	 * 	<jc>// Using this method</jc>
+	 * 	m.deleteAt(<js>"foo/bar/0/baz"</js>);
+	 * </p>
+	 * <p>
+	 * 	This method uses the {@link PojoRest} class to perform the lookup, so the map can contain
+	 * 		any of the various class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays).
+	 * </p>
+	 *
+	 * @param path The path to the entry.
+	 * @return The previous value, or <jk>null</jk> if the entry doesn't exist.
+	 */
+	public Object deleteAt(String path) {
+		return getPojoRest().delete(path);
+	}
+
+	/**
+	 * Creates an {@link Iterable} with elements of the specified child type.
+	 * <p>
+	 * Attempts to convert the child objects to the correct type if they aren't already the correct type.
+	 * <p>
+	 * The <code>next()</code> method on the returned iterator may throw a {@link InvalidDataConversionException} if
+	 * 	the next element cannot be converted to the specified type.
+	 * <p>
+	 * See {@link BeanContext#convertToType(Object, ClassMeta)} for a description of valid conversions.
+	 *
+	 * <dl>
+	 * 	<dt>Example:</dt>
+	 * 	<dd>
+	 * 		<p class='bcode'>
+	 * 	<jc>// Iterate over a list of ObjectMaps.</jc>
+	 * 	ObjectList l = <jk>new</jk> ObjectList(<js>"[{foo:'bar'},{baz:123}]"</js>);
+	 * 	for (ObjectMap m : l.elements(ObjectMap.<jk>class</jk>)) {
+	 * 		<jc>// Do something with m.</jc>
+	 * 	}
+	 *
+	 * 	<jc>// Iterate over a list of ints.</jc>
+	 * 	ObjectList l = <jk>new</jk> ObjectList(<js>"[1,2,3]"</js>);
+	 * 	for (Integer i : l.elements(Integer.<jk>class</jk>)) {
+	 * 		<jc>// Do something with i.</jc>
+	 * 	}
+	 *
+	 * 	<jc>// Iterate over a list of beans.</jc>
+	 * 	<jc>// Automatically converts to beans.</jc>
+	 * 	ObjectList l = <jk>new</jk> ObjectList(<js>"[{name:'John Smith',age:45}]"</js>);
+	 * 	for (Person p : l.elements(Person.<jk>class</jk>)) {
+	 * 		<jc>// Do something with p.</jc>
+	 * 	}
+	 * 		</p>
+	 * 	</dd>
+	 * </dl>
+	 *
+	 * @param <E> The child object type.
+	 * @param childType The child object type.
+	 * @return A new <code>Iterable</code> object over this list.
+	 */
+	public <E> Iterable<E> elements(final Class<E> childType) {
+		final Iterator<?> i = iterator();
+		return new Iterable<E>() {
+
+			@Override /* Iterable */
+			public Iterator<E> iterator() {
+				return new Iterator<E>() {
+
+					@Override /* Iterator */
+					public boolean hasNext() {
+						return i.hasNext();
+					}
+
+					@Override /* Iterator */
+					public E next() {
+						return beanContext.convertToType(i.next(), childType);
+					}
+
+					@Override /* Iterator */
+					public void remove() {
+						i.remove();
+					}
+
+				};
+			}
+		};
+	}
+
+	/**
+	 * Returns the {@link ClassMeta} of the class of the object at the specified index.
+	 *
+	 * @param index An index into this list, zero-based.
+	 * @return The data type of the object at the specified index, or <jk>null</jk> if the value is null.
+	 */
+	public ClassMeta<?> getClassMeta(int index) {
+		return beanContext.getClassMetaForObject(get(index));
+	}
+
+	private PojoRest getPojoRest() {
+		if (pojoRest == null)
+			pojoRest = new PojoRest(this);
+		return pojoRest;
+	}
+
+	/**
+	 * Serialize this array to a string using the specified serializer.
+	 *
+	 * @param serializer The serializer to use to convert this object to a string.
+	 * @return This object as a serialized string.
+	 * @throws SerializeException If a problem occurred trying to convert the output.
+	 */
+	public String toString(WriterSerializer serializer) throws SerializeException {
+		return serializer.serialize(this);
+	}
+
+	/**
+	 * Serialize this array to JSON using the {@link JsonSerializer#DEFAULT} serializer.
+	 */
+	@Override /* Object */
+	public String toString() {
+		try {
+			return this.toString(JsonSerializer.DEFAULT_LAX);
+		} catch (SerializeException e) {
+			return e.getLocalizedMessage();
+		}
+	}
+
+	/**
+	 * Convenience method for serializing this ObjectList to the specified Writer using
+	 * the JsonSerializer.DEFAULT serializer.
+	 *
+	 * @param w The writer to send the serialized contents of this object.
+	 * @throws IOException If a problem occurred trying to write to the writer.
+	 * @throws SerializeException If a problem occurred trying to convert the output.
+	 */
+	public void serializeTo(Writer w) throws IOException, SerializeException {
+		JsonSerializer.DEFAULT.serialize(this);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java b/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java
new file mode 100644
index 0000000..7bbbc8c
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java
@@ -0,0 +1,1404 @@
+/***************************************************************************************************************************
+ * 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;
+
+import static org.apache.juneau.internal.ClassUtils.*;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.transform.*;
+import org.apache.juneau.transforms.*;
+import org.apache.juneau.utils.*;
+
+/**
+ * Java implementation of a JSON object.
+ * <p>
+ * 	An extension of {@link LinkedHashMap}, so all methods available in that class are also available
+ * 	to this class.
+ * <p>
+ * 	Note that the use of this class is optional.  The serializers will accept any objects that implement
+ * 	the {@link java.util.Map} interface.  But this class provides some useful additional functionality
+ * 	when working with JSON models constructed from Java Collections Framework objects.  For example, a
+ * 	constructor is provided for converting a JSON object string directly into a {@link Map}.  It also contains
+ * 	accessor methods for to avoid common typecasting when accessing elements in a list.
+ *
+ * <h6 class='topic'>Examples</h6>
+ * <p class='bcode'>
+ * 	<jc>// Construct an empty Map</jc>
+ * 	Map m = <jk>new</jk> ObjectMap();
+ *
+ * 	<jc>// Construct a Map from JSON</jc>
+ * 	String json = <js>"{a:'A',b:{c:'C',d:123}}"</js>;
+ * 	m = <jk>new</jk> ObjectMap(json);
+ *
+ * 	<jc>// Construct a Map using the append method</jc>
+ * 	m = <jk>new</jk> ObjectMap().append(<js>"foo"</js>,<js>"x"</js>).append(<js>"bar"</js>,123).append(<js>"baz"</js>,<jk>true</jk>);
+ *
+ * 	<jc>// Construct a Map from XML generated by XmlSerializer</jc>
+ * 	String xml = <js>"&lt;object&gt;&lt;a type='string'&gt;A&lt;/a&gt;&lt;b type='object'&gt;&lt;c type='string'&gt;C&lt;/c&gt;&lt;d type='number'&gt;123&lt;/d&gt;&lt;/b&gt;&lt;/object&gt;"</js>;
+ * 	m = <jk>new</jk> ObjectMap(xml, DataFormat.<jsf>XML</jsf>);
+ * 	m = (Map)XmlParser.<jsf>DEFAULT</jsf>.parse(xml); <jc>// Equivalent</jc>
+ * 	m = (Map)XmlParser.<jsf>DEFAULT</jsf>.parse(Object.<jk>class</jk>, xml); <jc>// Equivalent</jc>
+ * 	m = XmlParser.<jsf>DEFAULT</jsf>.parse(Map.<jk>class</jk>, xml); <jc>// Equivalent</jc>
+ * 	m = XmlParser.<jsf>DEFAULT</jsf>.parse(ObjectMap.<jk>class</jk>, xml); <jc>// Equivalent</jc>
+ *
+ * 	<jc>// Construct a Map from a URL GET parameter string generated by UrlEncodingParser</jc>
+ * 	String urlParams = <js>"?a='A'&amp;b={c:'C',d:123}"</js>;
+ * 	m = <jk>new</jk> ObjectMap(urlParams, DataFormat.<jsf>URLPARAM</jsf>);
+ * 	m = (Map)UrlEncodingParser.<jsf>DEFAULT</jsf>.parse(Object.<jk>class</jk>, xml); <jc>// Equivalent</jc>
+ * 	m = UrlEncodingParser.<jsf>DEFAULT</jsf>.parse(Map.<jk>class</jk>, xml); <jc>// Equivalent</jc>
+ * 	m = UrlEncodingParser.<jsf>DEFAULT</jsf>.parse(ObjectMap.<jk>class</jk>, xml); <jc>// Equivalent</jc>
+ *
+ * 	<jc>// Construct JSON from ObjectMap</jc>
+ * 	m = <jk>new</jk> ObjectMap(<js>"{foo:'bar'},{baz:[123,true]}"</js>);
+ * 	json = m.toString();  <jc>// Produces "{foo:'bar'},{baz:[123,true]}"</jc>
+ * 	json = m.toString(JsonSerializer.<jsf>DEFAULT_CONDENSED</jsf>);  <jc>// Equivalent</jc>
+ * 	json = JsonSerializer.<jsf>DEFAULT_CONDENSED</jsf>.serialize(m);  <jc>// Equivalent</jc>
+ *
+ * 	<jc>// Get a map entry as an Integer</jc>
+ * 	m = <jk>new</jk> ObjectMap(<js>"{foo:123}"</js>);
+ * 	Integer i = m.getInt(<js>"foo"</js>);
+ * 	i = m.get(Integer.<jk>class</jk>, <js>"foo"</js>);  <jc>// Equivalent</jc>
+ *
+ * 	<jc>// Get a map entry as a Float</jc>
+ * 	m = <jk>new</jk> ObjectMap(<js>"{foo:123}"</js>);
+ * 	Float f = m.getFloat(<js>"foo"</js>);
+ * 	f = m.get(Float.<jk>class</jk>, <js>"foo"</js>);  <jc>// Equivalent</jc>
+ *
+ * 	<jc>// Same as above, except converted to a String</jc>
+ * 	m = <jk>new</jk> ObjectMap(<js>"{foo:123}"</js>);
+ * 	String s = m.getString(<js>"foo"</js>); <jc>// Returns "123"</jc>
+ * 	s = m.get(String.<jk>class</jk>, <js>"foo"</js>);  <jc>// Equivalent</jc>
+ *
+ * 	<jc>// Get one of the entries in the list as a bean (converted to a bean if it isn't already one)</jc>
+ * 	m = <jk>new</jk> ObjectMap(<js>"{person:{name:'John Smith',age:45}}"</js>);
+ * 	Person p = m.get(Person.<jk>class</jk>, <js>"person"</js>);
+ *
+ * 	<jc>// Add an inner map</jc>
+ * 	ObjectMap m1 = <jk>new</jk> ObjectMap(<js>"{a:1}"</js>);
+ * 	ObjectMap m2 = <jk>new</jk> ObjectMap(<js>"{b:2}"</js>).setInner(m1);
+ * 	<jk>int</jk> a = m2.getInt(<js>"a"</js>);  <jc>// a == 1 </jc>
+ * </p>
+ * <p>
+ * 	This class is not thread safe.
+ * </p>
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public class ObjectMap extends LinkedHashMap<String,Object> {
+	private static final long serialVersionUID = 1L;
+
+	private transient BeanContext beanContext = BeanContext.DEFAULT;
+	private ObjectMap inner;
+	private transient PojoRest pojoRest;
+
+	/**
+	 * An empty read-only ObjectMap.
+	 */
+	public static final ObjectMap EMPTY_MAP = new ObjectMap() {
+
+		private static final long serialVersionUID = 1L;
+
+		@Override /* Map */
+		@SuppressWarnings("unchecked")
+		public Set<Map.Entry<String,Object>> entrySet() {
+			return Collections.EMPTY_MAP.entrySet();
+		}
+
+		@Override /* Map */
+		@SuppressWarnings("unchecked")
+		public Set<String> keySet() {
+			return Collections.EMPTY_MAP.keySet();
+		}
+
+		@Override /* Map */
+		public Object put(String key, Object value) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override /* Map */
+		public Object remove(Object key) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override /* Map */
+		public Collection<Object> values() {
+			return Collections.emptyMap().values();
+		}
+	};
+
+	/**
+	 * Construct an ObjectMap directly from a string using the specified parser.
+	 *
+	 * @param s The string being parsed.
+	 * @param p The parser to use to parse the input.
+	 * @throws ParseException If the input contains a syntax error or is malformed.
+	 */
+	public ObjectMap(CharSequence s, Parser p) throws ParseException {
+		this(p == null ? BeanContext.DEFAULT : p.getBeanContext());
+		if (p == null)
+			p = JsonParser.DEFAULT;
+		if (s != null)
+			p.parseIntoMap(s, this, beanContext.string(), beanContext.object());
+	}
+
+	/**
+	 * Shortcut for <code><jk>new</jk> ObjectMap(string,JsonParser.<jsf>DEFAULT</jsf>);</code>
+	 *
+	 * @param s The JSON text to parse.
+	 * @throws ParseException If the input contains a syntax error or is malformed.
+	 */
+	public ObjectMap(CharSequence s) throws ParseException {
+		this(s, null);
+	}
+
+	/**
+	 * Construct an ObjectMap directly from a reader using the specified parser.
+	 *
+	 * @param r The reader to read from.  The reader will be wrapped in a {@link BufferedReader} if it isn't already.
+	 * @param p The parser to use to parse the input.
+	 * @throws ParseException If the input contains a syntax error or is malformed.
+	 * @throws IOException If a problem occurred trying to read from the reader.
+	 */
+	public ObjectMap(Reader r, Parser p) throws ParseException, IOException {
+		parseReader(r, p);
+	}
+
+	/**
+	 * Shortcut for <code><jk>new</jk> ObjectMap(reader, JsonParser.<jsf>DEFAULT</jsf>)</code>.
+	 *
+	 * @param r The reader to read from.  The reader will be wrapped in a {@link BufferedReader} if it isn't already.
+	 * @throws ParseException If the input contains a syntax error or is malformed.
+	 * @throws IOException If a problem occurred trying to read from the reader.
+	 */
+	public ObjectMap(Reader r) throws ParseException, IOException {
+		parseReader(r, JsonParser.DEFAULT);
+	}
+
+	private void parseReader(Reader r, Parser p) throws ParseException {
+		if (p == null)
+			p = JsonParser.DEFAULT;
+		p.parseIntoMap(r, this, beanContext.string(), beanContext.object());
+	}
+
+	/**
+	 * Construct an empty JSON object (i.e. an empty {@link LinkedHashMap}).
+	 */
+	public ObjectMap() {
+		this(BeanContext.DEFAULT);
+	}
+
+	/**
+	 * Construct an empty JSON object (i.e. an empty {@link LinkedHashMap}) with the specified bean context.
+	 *
+	 * @param beanContext The bean context to use for creating beans.
+	 */
+	public ObjectMap(BeanContext beanContext) {
+		super();
+		this.beanContext = beanContext;
+	}
+
+	/**
+	 * Construct a JSON object and fill it with the contents from the specified {@link Map}.
+	 *
+	 * @param m The map whose entries will be copied into this map.
+	 */
+	public ObjectMap(Map<?,?> m) {
+		super();
+		for (Map.Entry<?,?> e : m.entrySet())
+			put(e.getKey().toString(), e.getValue());
+	}
+
+	/**
+	 * Set an inner map in this map to allow for chained get calls.
+	 * <p>
+	 * If {@link #get(Object)} returns <jk>null</jk>, then {@link #get(Object)} will be called on the inner map.
+	 * <p>
+	 * In addition to providing the ability to chain maps, this method also provides the ability
+	 * to wrap an existing map inside another map so that you can add entries to the outer
+	 * map without affecting the values on the inner map.
+	 * <p class='bcode'>
+	 * 	ObjectMap m1 = <jk>new</jk> ObjectMap(<js>"{foo:1}"</js>);
+	 * 	ObjectMap m2 = <jk>new</jk> ObjectMap().setInner(m1);
+	 * 	m2.put(<js>"foo"</js>, 2);                      <jc>// Overwrite the entry</jc>
+	 * 	<jk>int</jk> foo1 = m1.getInt(<js>"foo"</js>);           <jc>// foo1 == 1 </jc>
+	 * 	<jk>int</jk> foo2 = m2.getInt(<js>"foo"</js>);           <jc>// foo2 == 2 </jc>
+	 * </p>
+	 *
+	 * @param inner The inner map.
+	 * 	Can be <jk>null</jk> to remove the inner map from an existing map.
+	 * @return This object (for method chaining).
+	 */
+	public ObjectMap setInner(ObjectMap inner) {
+		this.inner = inner;
+		return this;
+	}
+
+	/**
+	 * Searches for the specified key in this map ignoring case.
+	 *
+	 * @param key The key to search for.  For performance reasons, it's preferrable that the key be all lowercase.
+	 * @return The key, or <jk>null</jk> if map does not contain this key.
+	 */
+	public String findKeyIgnoreCase(String key) {
+		for (String k : keySet())
+			if (key.equalsIgnoreCase(k))
+				return k;
+		return null;
+	}
+
+
+	/**
+	 * Returns the inner map if one was set through {@link #setInner(ObjectMap)}.
+	 *
+	 * @return The inner map if one was set through {@link #setInner(ObjectMap)}, or <jk>null</jk> if no inner map is present.
+	 */
+	public ObjectMap getInner() {
+		return inner;
+	}
+
+	/**
+	 * Override the default bean context used for converting POJOs.
+	 * <p>
+	 * Default is {@link BeanContext#DEFAULT}, which is sufficient in most cases.
+	 * <p>
+	 * Useful if you're serializing/parsing beans with transforms defined.
+	 *
+	 * @param beanContext The new bean context.
+	 * @return This object (for method chaining).
+	 */
+	public ObjectMap setBeanContext(BeanContext beanContext) {
+		this.beanContext = beanContext;
+		return this;
+	}
+
+	/**
+	 * Returns the {@link BeanContext} currently associated with this map.
+	 *
+	 * @return The {@link BeanContext} currently associated with this map.
+	 */
+	public BeanContext getBeanContext() {
+		return beanContext;
+	}
+
+	/**
+	 * Convenience method for adding multiple objects to this map.
+	 * <p>
+	 * 	Equivalent to calling {@code put(key, value)}, but returns
+	 * 	this map so that the method can be chained.
+	 *
+	 * @param key The key.
+	 * @param value The value.
+	 * @return This object (for method chaining).
+	 */
+	public ObjectMap append(String key, Object value) {
+		put(key, value);
+		return this;
+	}
+
+	@Override /* Map */
+	public Object get(Object key) {
+		Object o = super.get(key);
+		if (o == null && inner != null)
+			o = inner.get(key);
+		return o;
+	}
+
+	/**
+	 * Same as {@link Map#get(Object) get()}, but returns the default value if the key
+	 * could not be found.
+	 *
+	 * @param key The key.
+	 * @param def The default value if the entry doesn't exist.
+	 * @return The value, or the default value if the entry doesn't exist.
+	 */
+	public Object get(String key, Object def) {
+		Object o = get(key);
+		return (o == null ? def : o);
+	}
+
+	/**
+	 * Same as {@link Map#get(Object) get()}, but casts or converts the value to the specified class type.
+	 * <p>
+	 * 	See {@link BeanContext#convertToType(Object, ClassMeta)} for the list of valid data conversions.
+	 *
+	 * @param <T> The class type.
+	 * @param type The class type.
+	 * @param key The key.
+	 * @return The value, or <jk>null</jk> if the entry doesn't exist.
+	 */
+	public <T> T get(Class<T> type, String key) {
+		return get(type, key, null);
+	}
+
+	/**
+	 * Same as {@link Map#get(Object) get()}, but converts the raw value to the specified class type using the specified transform.
+	 *
+	 * @param <T> The transformed class type.
+	 * @param transform The transform class used to convert the raw type to a transformed type.
+	 * @param key The key.
+	 * @return The value, or <jk>null</jk> if the entry doesn't exist.
+	 * @throws ParseException Thrown by the transform if a problem occurred trying to parse the value.
+	 */
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	public <T> T get(PojoTransform<T,?> transform, String key) throws ParseException {
+		Object o = super.get(key);
+		if (o == null)
+			return null;
+		PojoTransform f = transform;
+		return (T)f.normalize(o, null);
+	}
+
+	/**
+	 * Same as {@link Map#get(Object) get()}, but casts or converts the value to the specified class type.
+	 * <p>
+	 * 	See {@link BeanContext#convertToType(Object, ClassMeta)} for the list of valid data conversions.
+	 *
+	 * @param <T> The class type.
+	 * @param type The class type.
+	 * @param key The key.
+	 * @param def The default value if the entry doesn't exist.
+	 * @return The value, or the default value if the entry doesn't exist.
+	 */
+	public <T> T get(Class<T> type, String key, T def) {
+		Object o = get(key);
+		if (o == null)
+			return def;
+		T t = beanContext.convertToType(o, type);
+		if (t == null)
+			return def;
+		return t;
+	}
+
+	/**
+	 * Same as {@link Map#get(Object) get()}, but casts or converts the value to the specified class type.
+	 * <p>
+	 * 	See {@link BeanContext#convertToType(Object, ClassMeta)} for the list of valid data conversions.
+	 *
+	 * @param <T> The class type.
+	 * @param type The class type.
+	 * @param key The key.
+	 * @return The value, or the default value if the entry doesn't exist.
+	 */
+	public <T> T get(ClassMeta<T> type, String key) {
+		return get(type, key, null);
+	}
+
+	/**
+	 * Same as {@link Map#get(Object) get()}, but casts or converts the value to the specified class type.
+	 * <p>
+	 * 	See {@link BeanContext#convertToType(Object, ClassMeta)} for the list of valid data conversions.
+	 *
+	 * @param <T> The class type.
+	 * @param type The class type.
+	 * @param key The key.
+	 * @param def The default value if the entry doesn't exist.
+	 * @return The value, or the default value if the entry doesn't exist.
+	 */
+	public <T> T get(ClassMeta<T> type, String key, T def) {
+		Object o = get(key);
+		if (o == null)
+			return def;
+		return beanContext.convertToType(o, type);
+	}
+
+	/**
+	 * Returns the value for the first key in the list that has an entry in this map.
+	 *
+	 * @param keys The keys to look up in order.
+	 * @return The value of the first entry whose key exists, or <jk>null</jk> if none of the keys exist in this map.
+	 */
+	public Object find(String...keys) {
+		for (String key : keys)
+			if (containsKey(key))
+				return get(key);
+		return null;
+	}
+
+	/**
+	 * Returns the value for the first key in the list that has an entry in this map.
+	 * <p>
+	 * 	Casts or converts the value to the specified class type.
+	 * <p>
+	 * 	See {@link BeanContext#convertToType(Object, ClassMeta)} for the list of valid data conversions.
+	 *
+	 * @param type The class type to convert the value to.
+	 * @param <T> The class type to convert the value to.
+	 * @param keys The keys to look up in order.
+	 * @return The value of the first entry whose key exists, or <jk>null</jk> if none of the keys exist in this map.
+	 */
+	public <T> T find(Class<T> type, String...keys) {
+		for (String key : keys)
+			if (containsKey(key))
+				return get(type, key);
+		return null;
+	}
+
+	/**
+	 * Same as {@link #get(Class,String) get(Class,String)}, but the key is a slash-delimited
+	 * 	path used to traverse entries in this POJO.
+	 * <p>
+	 * 	For example, the following code is equivalent:
+	 * </p>
+	 * <p class='bcode'>
+	 * 	ObjectMap m = getObjectMap();
+	 *
+	 * 	<jc>// Long way</jc>
+	 * 	<jk>long</jk> l = m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>).getLong(<js>"baz"</js>);
+	 *
+	 * 	<jc>// Using this method</jc>
+	 * 	<jk>long</jk> l = m.getAt(<jk>long</jk>.<jk>class</jk>, <js>"foo/bar/0/baz"</js>);
+	 * </p>
+	 * <p>
+	 * 	This method uses the {@link PojoRest} class to perform the lookup, so the map can contain
+	 * 		any of the various class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays).
+	 * </p>
+	 *
+	 * @param <T> The class type.
+	 * @param type The class type.
+	 * @param path The path to the entry.
+	 * @return The value, or <jk>null</jk> if the entry doesn't exist.
+	 */
+	public <T> T getAt(Class<T> type, String path) {
+		return getPojoRest().get(type, path);
+	}
+
+	/**
+	 * Same as <code>put(String,Object)</code>, but the key is a slash-delimited
+	 * 	path used to traverse entries in this POJO.
+	 * <p>
+	 * 	For example, the following code is equivalent:
+	 * </p>
+	 * <p class='bcode'>
+	 * 	ObjectMap m = getObjectMap();
+	 *
+	 * 	<jc>// Long way</jc>
+	 * 	m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>).put(<js>"baz"</js>, 123);
+	 *
+	 * 	<jc>// Using this method</jc>
+	 * 	m.putAt(<js>"foo/bar/0/baz"</js>, 123);
+	 * </p>
+	 * <p>
+	 * 	This method uses the {@link PojoRest} class to perform the lookup, so the map can contain
+	 * 		any of the various class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays).
+	 * </p>
+	 *
+	 * @param path The path to the entry.
+	 * @param o The new value.
+	 * @return The previous value, or <jk>null</jk> if the entry doesn't exist.
+	 */
+	public Object putAt(String path, Object o) {
+		return getPojoRest().put(path, o);
+	}
+
+	/**
+	 * Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append
+	 * 	to collections and arrays.
+	 * <p>
+	 * 	For example, the following code is equivalent:
+	 * </p>
+	 * <p class='bcode'>
+	 * 	ObjectMap m = getObjectMap();
+	 *
+	 * 	<jc>// Long way</jc>
+	 * 	m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).append(123);
+	 *
+	 * 	<jc>// Using this method</jc>
+	 * 	m.postAt(<js>"foo/bar"</js>, 123);
+	 * </p>
+	 * <p>
+	 * 	This method uses the {@link PojoRest} class to perform the lookup, so the map can contain
+	 * 		any of the various class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays).
+	 * </p>
+	 *
+	 * @param path The path to the entry.
+	 * @param o The new value.
+	 * @return The previous value, or <jk>null</jk> if the entry doesn't exist.
+	 */
+	public Object postAt(String path, Object o) {
+		return getPojoRest().post(path, o);
+	}
+
+	/**
+	 * Similar to {@link #remove(Object) remove(Object)},but the key is a slash-delimited
+	 * 	path used to traverse entries in this POJO.
+	 * <p>
+	 * 	For example, the following code is equivalent:
+	 * </p>
+	 * <p class='bcode'>
+	 * 	ObjectMap m = getObjectMap();
+	 *
+	 * 	<jc>// Long way</jc>
+	 * 	m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(1).remove(<js>"baz"</js>);
+	 *
+	 * 	<jc>// Using this method</jc>
+	 * 	m.deleteAt(<js>"foo/bar/0/baz"</js>);
+	 * </p>
+	 * <p>
+	 * 	This method uses the {@link PojoRest} class to perform the lookup, so the map can contain
+	 * 		any of the various class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays).
+	 * </p>
+	 *
+	 * @param path The path to the entry.
+	 * @return The previous value, or <jk>null</jk> if the entry doesn't exist.
+	 */
+	public Object deleteAt(String path) {
+		return getPojoRest().delete(path);
+	}
+
+	/**
+	 * Convenience method for inserting JSON directly into an attribute on this object.
+	 * <p>
+	 * 	The JSON text can be an object (i.e. <js>"{...}"</js>) or an array (i.e. <js>"[...]"</js>).
+	 *
+	 * @param key The key.
+	 * @param json The JSON text that will be parsed into an Object and then inserted into this map.
+	 * @throws ParseException If the input contains a syntax error or is malformed.
+	 */
+	public void putJson(String key, String json) throws ParseException {
+		this.put(key, JsonParser.DEFAULT.parse(json, Object.class));
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link String}.
+	 * <p>
+	 * 	Shortcut for <code>get(String.<jk>class</jk>, key)</code>.
+	 *
+	 * @param key The key.
+	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
+	 */
+	public String getString(String key) {
+		return get(String.class, key);
+	}
+
+	/**
+	 * Specialized method that calls {@link #getString(String)} and splits the
+	 * 	results as a simple comma-delimited list.
+	 *
+	 * @param key the key.
+	 * @return A list of tokens, trimmed of whitespace.  An empty list if entry not found.  Never <jk>null</jk>.
+	 */
+	public String[] getStringArray(String key) {
+		String s = get(String.class, key);
+		return (s == null ? new String[0] : StringUtils.split(s, ','));
+	}
+
+	/**
+	 * Same as {@link #getStringArray(String)} but returns a default value if the value cannot be found.
+	 *
+	 * @param key The map key.
+	 * @param def The default value if value is not found.
+	 * @return The value converted to a string array.
+	 */
+	public String[] getStringArray(String key, String[] def) {
+		String s = get(String.class, key);
+		String[] r = (s == null ? new String[0] : StringUtils.split(s, ','));
+		return (r.length == 0 ? def : r);
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link String}.
+	 * <p>
+	 * 	Shortcut for <code>get(String.<jk>class</jk>, key, defVal)</code>.
+	 *
+	 * @param key The key.
+	 * @param defVal The default value if the map doesn't contain the specified mapping.
+	 * @return The converted value, or the default value if the map contains no mapping for this key.
+	 */
+	public String getString(String key, String defVal) {
+		return get(String.class, key, defVal);
+	}
+
+	/**
+	 * Returns the specified entry value converted to an {@link Integer}.
+	 * <p>
+	 * 	Shortcut for <code>get(Integer.<jk>class</jk>, key)</code>.
+	 *
+	 * @param key The key.
+	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Integer getInt(String key) {
+		return get(Integer.class, key);
+	}
+
+	/**
+	 * Returns the specified entry value converted to an {@link Integer}.
+	 * <p>
+	 * 	Shortcut for <code>get(Integer.<jk>class</jk>, key, defVal)</code>.
+	 *
+	 * @param key The key.
+	 * @param defVal The default value if the map doesn't contain the specified mapping.
+	 * @return The converted value, or the default value if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Integer getInt(String key, Integer defVal) {
+		return get(Integer.class, key, defVal);
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link Long}.
+	 * <p>
+	 * 	Shortcut for <code>get(Long.<jk>class</jk>, key)</code>.
+	 *
+	 * @param key The key.
+	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Long getLong(String key) {
+		return get(Long.class, key);
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link Long}.
+	 * <p>
+	 * 	Shortcut for <code>get(Long.<jk>class</jk>, key, defVal)</code>.
+	 *
+	 * @param key The key.
+	 * @param defVal The default value if the map doesn't contain the specified mapping.
+	 * @return The converted value, or the default value if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Long getLong(String key, Long defVal) {
+		return get(Long.class, key, defVal);
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link Boolean}.
+	 * <p>
+	 * 	Shortcut for <code>get(Boolean.<jk>class</jk>, key)</code>.
+	 *
+	 * @param key The key.
+	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Boolean getBoolean(String key) {
+		return get(Boolean.class, key);
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link Boolean}.
+	 * <p>
+	 * 	Shortcut for <code>get(Boolean.<jk>class</jk>, key, defVal)</code>.
+	 *
+	 * @param key The key.
+	 * @param defVal The default value if the map doesn't contain the specified mapping.
+	 * @return The converted value, or the default value if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Boolean getBoolean(String key, Boolean defVal) {
+		return get(Boolean.class, key, defVal);
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link Map}.
+	 * <p>
+	 * 	Shortcut for <code>get(Map.<jk>class</jk>, key)</code>.
+	 *
+	 * @param key The key.
+	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Map<?,?> getMap(String key) {
+		return get(Map.class, key);
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link Map}.
+	 * <p>
+	 * 	Shortcut for <code>get(Map.<jk>class</jk>, key, defVal)</code>.
+	 *
+	 * @param key The key.
+	 * @param defVal The default value if the map doesn't contain the specified mapping.
+	 * @return The converted value, or the default value if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Map<?,?> getMap(String key, Map<?,?> defVal) {
+		return get(Map.class, key, defVal);
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link List}.
+	 * <p>
+	 * 	Shortcut for <code>get(List.<jk>class</jk>, key)</code>.
+	 *
+	 * @param key The key.
+	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public List<?> getList(String key) {
+		return get(List.class, key);
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link List}.
+	 * <p>
+	 * 	Shortcut for <code>get(List.<jk>class</jk>, key, defVal)</code>.
+	 *
+	 * @param key The key.
+	 * @param defVal The default value if the map doesn't contain the specified mapping.
+	 * @return The converted value, or the default value if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public List<?> getList(String key, List<?> defVal) {
+		return get(List.class, key, defVal);
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link Map}.
+	 * <p>
+	 * 	Shortcut for <code>get(ObjectMap.<jk>class</jk>, key)</code>.
+	 *
+	 * @param key The key.
+	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public ObjectMap getObjectMap(String key) {
+		return get(ObjectMap.class, key);
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link ObjectMap}.
+	 * <p>
+	 * 	Shortcut for <code>get(ObjectMap.<jk>class</jk>, key, defVal)</code>.
+	 *
+	 * @param key The key.
+	 * @param defVal The default value if the map doesn't contain the specified mapping.
+	 * @return The converted value, or the default value if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public ObjectMap getObjectMap(String key, ObjectMap defVal) {
+		return get(ObjectMap.class, key, defVal);
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link ObjectList}.
+	 * <p>
+	 * 	Shortcut for <code>get(ObjectList.<jk>class</jk>, key)</code>.
+	 *
+	 * @param key The key.
+	 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public ObjectList getObjectList(String key) {
+		return get(ObjectList.class, key);
+	}
+
+	/**
+	 * Returns the specified entry value converted to a {@link ObjectList}.
+	 * <p>
+	 * 	Shortcut for <code>get(ObjectList.<jk>class</jk>, key, defVal)</code>.
+	 *
+	 * @param key The key.
+	 * @param defVal The default value if the map doesn't contain the specified mapping.
+	 * @return The converted value, or the default value if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public ObjectList getObjectList(String key, ObjectList defVal) {
+		return get(ObjectList.class, key, defVal);
+	}
+
+	/**
+	 * Returns the first entry that exists converted to a {@link String}.
+	 * <p>
+	 * 	Shortcut for <code>find(String.<jk>class</jk>, keys)</code>.
+	 *
+	 * @param keys The list of keys to look for.
+	 * @return The converted value of the first key in the list that has an entry in this map,
+	 * 	or <jk>null</jk> if the map contains no mapping for any of the keys.
+	 */
+	public String findString(String... keys) {
+		return find(String.class, keys);
+	}
+
+	/**
+	 * Returns the first entry that exists converted to an {@link Integer}.
+	 * <p>
+	 * 	Shortcut for <code>find(Integer.<jk>class</jk>, keys)</code>.
+	 *
+	 * @param keys The list of keys to look for.
+	 * @return The converted value of the first key in the list that has an entry in this map,
+	 * 	or <jk>null</jk> if the map contains no mapping for any of the keys.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Integer findInt(String... keys) {
+		return find(Integer.class, keys);
+	}
+
+	/**
+	 * Returns the first entry that exists converted to a {@link Long}.
+	 * <p>
+	 * 	Shortcut for <code>find(Long.<jk>class</jk>, keys)</code>.
+	 *
+	 * @param keys The list of keys to look for.
+	 * @return The converted value of the first key in the list that has an entry in this map,
+	 * 	or <jk>null</jk> if the map contains no mapping for any of the keys.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Long findLong(String... keys) {
+		return find(Long.class, keys);
+	}
+
+	/**
+	 * Returns the first entry that exists converted to a {@link Boolean}.
+	 * <p>
+	 * 	Shortcut for <code>find(Boolean.<jk>class</jk>, keys)</code>.
+	 *
+	 * @param keys The list of keys to look for.
+	 * @return The converted value of the first key in the list that has an entry in this map,
+	 * 	or <jk>null</jk> if the map contains no mapping for any of the keys.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Boolean findBoolean(String... keys) {
+		return find(Boolean.class, keys);
+	}
+
+	/**
+	 * Returns the first entry that exists converted to a {@link Map}.
+	 * <p>
+	 * 	Shortcut for <code>find(Map.<jk>class</jk>, keys)</code>.
+	 *
+	 * @param keys The list of keys to look for.
+	 * @return The converted value of the first key in the list that has an entry in this map,
+	 * 	or <jk>null</jk> if the map contains no mapping for any of the keys.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public Map<?,?> findMap(String... keys) {
+		return find(Map.class, keys);
+	}
+
+	/**
+	 * Returns the first entry that exists converted to a {@link List}.
+	 * <p>
+	 * 	Shortcut for <code>find(List.<jk>class</jk>, keys)</code>.
+	 *
+	 * @param keys The list of keys to look for.
+	 * @return The converted value of the first key in the list that has an entry in this map,
+	 * 	or <jk>null</jk> if the map contains no mapping for any of the keys.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public List<?> findList(String... keys) {
+		return find(List.class, keys);
+	}
+
+	/**
+	 * Returns the first entry that exists converted to a {@link ObjectMap}.
+	 * <p>
+	 * 	Shortcut for <code>find(ObjectMap.<jk>class</jk>, keys)</code>.
+	 *
+	 * @param keys The list of keys to look for.
+	 * @return The converted value of the first key in the list that has an entry in this map,
+	 * 	or <jk>null</jk> if the map contains no mapping for any of the keys.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public ObjectMap findObjectMap(String... keys) {
+		return find(ObjectMap.class, keys);
+	}
+
+	/**
+	 * Returns the first entry that exists converted to a {@link ObjectList}.
+	 * <p>
+	 * 	Shortcut for <code>find(ObjectList.<jk>class</jk>, keys)</code>.
+	 *
+	 * @param keys The list of keys to look for.
+	 * @return The converted value of the first key in the list that has an entry in this map,
+	 * 	or <jk>null</jk> if the map contains no mapping for any of the keys.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public ObjectList findObjectList(String... keys) {
+		return find(ObjectList.class, keys);
+	}
+
+	/**
+	 * Returns the first key in the map.
+	 *
+	 * @return The first key in the map, or <jk>null</jk> if the map is empty.
+	 */
+	public String getFirstKey() {
+		return isEmpty() ? null : keySet().iterator().next();
+	}
+
+	/**
+	 * Returns the class type of the object at the specified index.
+	 *
+	 * @param key The key into this map.
+	 * @return The data type of the object at the specified key, or <jk>null</jk> if the value is null or does not exist.
+	 */
+	public ClassMeta<?> getClassMeta(String key) {
+		return beanContext.getClassMetaForObject(get(key));
+	}
+
+	/**
+	 * Equivalent to calling <code>get(class,key,def)</code> followed by <code>remove(key);</code>
+	 *
+	 * @param <T> The class type.
+	 * @param type The class type.
+	 * @param key The key.
+	 * @param defVal The default value if the map doesn't contain the specified mapping.
+	 * @return The converted value, or the default value if the map contains no mapping for this key.
+	 * @throws InvalidDataConversionException If value cannot be converted.
+	 */
+	public <T> T remove(Class<T> type, String key, T defVal) {
+		T t = get(type, key, defVal);
+		remove(key);
+		return t;
+	}
+
+
+	/**
+	 * Convenience method for removing several keys at once.
+	 *
+	 * @param keys The list of keys to remove.
+	 */
+	public void removeAll(Collection<String> keys) {
+		for (String k : keys)
+			remove(k);
+	}
+
+	/**
+	 * Convenience method for removing several keys at once.
+	 *
+	 * @param keys The list of keys to remove.
+	 */
+	public void removeAll(String... keys) {
+		for (String k : keys)
+			remove(k);
+	}
+
+	@Override /* Map */
+	public boolean containsKey(Object key) {
+		if (super.containsKey(key))
+			return true;
+		if (inner != null)
+			return inner.containsKey(key);
+		return false;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if this map contains the specified key, ignoring
+	 * 	the inner map if it exists.
+	 *
+	 * @param key The key to look up.
+	 * @return <jk>true</jk> if this map contains the specified key.
+	 */
+	public boolean containsOuterKey(Object key) {
+		return super.containsKey(key);
+	}
+
+	/**
+	 * Returns a copy of this <code>ObjectMap</code> with only the specified keys.
+	 *
+	 * @param keys The keys of the entries to copy.
+	 * @return A new map with just the keys and values from this map.
+	 */
+	public ObjectMap include(String...keys) {
+		ObjectMap m2 = new ObjectMap();
+		for (Map.Entry<String,Object> e : this.entrySet())
+			for (String k : keys)
+				if (k.equals(e.getKey()))
+					m2.put(k, e.getValue());
+		return m2;
+	}
+
+	/**
+	 * Returns a copy of this <code>ObjectMap</code> without the specified keys.
+	 *
+	 * @param keys The keys of the entries not to copy.
+	 * @return A new map without the keys and values from this map.
+	 */
+	public ObjectMap exclude(String...keys) {
+		ObjectMap m2 = new ObjectMap();
+		for (Map.Entry<String,Object> e : this.entrySet()) {
+			boolean exclude = false;
+			for (String k : keys)
+				if (k.equals(e.getKey()))
+					exclude = true;
+			if (! exclude)
+				m2.put(e.getKey(), e.getValue());
+		}
+		return m2;
+	}
+
+	/**
+	 * Sets a value in this map if the entry does not exist or the value is <jk>null</jk>.
+	 *
+	 * @param key The map key.
+	 * @param val The value to set if the current value does not exist or is <jk>null</jk>.
+	 * @return This object (for method chaining).
+	 */
+	public ObjectMap putIfNull(String key, Object val) {
+		Object o = get(key);
+		if (o == null)
+			put(key, val);
+		return this;
+	}
+
+	/**
+	 * Sets a value in this map if the entry does not exist or the value is <jk>null</jk> or an empty string.
+	 *
+	 * @param key The map key.
+	 * @param val The value to set if the current value does not exist or is <jk>null</jk> or an empty string.
+	 * @return This object (for method chaining).
+	 */
+	public ObjectMap putIfEmpty(String key, Object val) {
+		Object o = get(key);
+		if (o == null || o.toString().isEmpty())
+			put(key, val);
+		return this;
+	}
+
+	/**
+	 * Converts this map into the class type specified by the <js>"_class"</js> entry value.
+	 * <p>
+	 * 	This method can be used to convert <code>ObjectMap</code> objects to a variety of POJO types.
+	 *
+	 * <dl>
+	 * 	<dt>Example of valid class types:</dt>
+	 * 	<dd>
+	 * 		<p>
+	 * 			An object map can be converted to a bean.
+	 * 		</p>
+	 * 		<p class='bcode'>
+	 * 	{
+	 * 		_class: <js>'com.ibm.sample.addressBook.Person'</js>,
+	 * 		name: <js>'John Smith'</js>,
+	 * 		...
+	 * 	}
+	 * 		</p>
+	 * 		<p>
+	 * 			It can also be converted into another map type.
+	 * 		</p>
+	 * 		<p class='bcode'>
+	 * 	<jc>// Generic TreeMap (String keys, Object values)</jc>
+	 * 	{
+	 * 		_class: <js>'java.util.TreeMap'</js>,
+	 * 		name: <js>'John Smith'</js>,
+	 * 		...
+	 * 	}
+	 * 	<jc>// TreeMap where values are forced to be strings.</jc>
+	 * 	{
+	 * 		_class: <js>'java.util.TreeMap&lt;java.lang.String,java.lang.String&gt;'</js>,
+	 * 		name: <js>'John Smith'</js>,
+	 * 		...
+	 * 	}
+	 * 		</p>
+	 * 		<p>
+	 * 			It can also be converted to Collections objects if map defines an <code>items</code> entry of type array.
+	 * 		</p>
+	 * 		<p class='bcode'>
+	 * 	<jc>// LinkedList of strings</jc>
+	 * 	{
+	 * 		_class: <js>'java.util.LinkedList'</js>,
+	 * 		items: [ <js>'John Smith'</js>, ... ]
+	 * 	}
+	 * 	<jc>// LinkedList of beans</jc>
+	 * 	{
+	 * 		_class: <js>'java.util.LinkedList&lt;com.ibm.sample.addressBook.Person&gt;'</js>,
+	 * 		items: [ { name: <js>'John Smith'</js>, ... }, ... ]
+	 * 	}
+	 * 		</p>
+	 * 		<p>
+	 * 			It can also be converted to arrays.
+	 * 		</p>
+	 * 		<p class='bcode'>
+	 * 	<jc>// Array of strings</jc>
+	 * 	{
+	 * 		_class: <js>'java.lang.String[]'</js>,
+	 * 		items: [ <js>'John Smith'</js>, ... ]
+	 * 	}
+	 * 	<jc>// Array of beans</jc>
+	 * 	{
+	 * 		_class: <js>'com.ibm.sample.addressBook.Person[]'</js>,
+	 * 		items: [ { name: <js>'John Smith'</js>, ... }, ... ]
+	 * 	}
+	 * 		</p>
+	 * 		<p>
+	 * 			It can also be converted to any type that can be handled by the {@link BeanContext#convertToType(Object, Class)} method.
+	 * 			In this case, the value is specified by an <code>value</code> entry of any type.
+	 * 			For example, if the bean context has a {@link CalendarTransform} associated with it, it can convert a string value to a calendar.
+	 * 		<p class='bcode'>
+	 * 	{
+	 * 		_class: <js>'java.util.GregorianCalendar'</js>,
+	 * 		value: <js>'2001-07-04T15:30:45-05:00'</js>
+	 * 	}
+	 * 		</p>
+	 * 	</dd>
+	 * 	<dt>Notes:</dt>
+	 * 	<dd>
+	 * 		<ul>
+	 * 			<li>This method is recursive.  It will also recursively convert any descendant entries to POJOs.
+	 * 		</ul>
+	 * 	</dd>
+	 * </dl>
+	 *
+	 * @return The new Java object of type specified by the <js>"_class"</js> entry value, or this
+	 * 	same object if entry does not exist.
+	 */
+	public Object cast() {
+		String c = (String)get("_class");
+		if (c == null) {
+			if (containsKey("_value"))
+				return get("_value");
+			return this;
+		}
+		return cast2(beanContext.getClassMetaFromString(c));
+	}
+
+	/**
+	 * Converts this map into an object of the specified type.
+	 * <p>
+	 * The rules are the same as those specified in {@link #cast()}.
+	 * <p>
+	 * If this map contains a <js>"_class"</js> entry, it must be the same as or a subclass
+	 * 	of the <code>type</code>.
+	 *
+	 * @param <T> The class type to convert this map object to.
+	 * @param type The class type to convert this map object to.
+	 * @return The new object.
+	 * @throws ClassCastException If the <js>"_class"</js> entry is present and not assignable
+	 * 	from <code>type</code>
+	 */
+	@SuppressWarnings("unchecked")
+	public <T> T cast(Class<T> type) {
+		ClassMeta<?> c1 = beanContext.getClassMetaFromString((String)get("_class"));
+		ClassMeta<?> c2 = beanContext.getClassMeta(type);
+		ClassMeta<?> c = narrowClassMeta(c1, c2);
+		return (T)cast2(c);
+	}
+
+	/**
+	 * Same as {@link #cast(Class)}, except allows you to specify a {@link ClassMeta} parameter.
+	 *
+	 * @param <T> The class type to convert this map object to.
+	 * @param cm The class type to convert this map object to.
+	 * @return The new object.
+	 * @throws ClassCastException If the <js>"_class"</js> entry is present and not assignable
+	 * 	from <code>type</code>
+	 */
+	@SuppressWarnings({"unchecked"})
+	public <T> T cast(ClassMeta<T> cm) {
+		ClassMeta<?> c1 = beanContext.getClassMetaFromString((String)get("_class"));
+		ClassMeta<?> c = narrowClassMeta(c1, cm);
+		return (T)cast2(c);
+	}
+
+	/*
+	 * Combines the class specified by a "_class" attribute with the ClassMeta
+	 * passed in through the cast(ClassMeta) method.
+	 * The rule is that child classes superceed parent classes, and c2 superceeds c1
+	 * if one isn't the parent of another.
+	 */
+	@SuppressWarnings("unchecked")
+	private ClassMeta<?> narrowClassMeta(ClassMeta<?> c1, ClassMeta<?> c2) {
+		if (c1 == null)
+			return c2;
+		ClassMeta<?> c = getNarrowedClassMeta(c1, c2);
+		if (c1.isMap()) {
+			ClassMeta<?> k = getNarrowedClassMeta(c1.getKeyType(), c2.getKeyType());
+			ClassMeta<?> v = getNarrowedClassMeta(c1.getValueType(), c2.getValueType());
+			return beanContext.getMapClassMeta((Class<? extends Map<?,?>>)c.getInnerClass(), k, v);
+		}
+		if (c1.isCollection()) {
+			ClassMeta<?> e = getNarrowedClassMeta(c1.getElementType(), c2.getElementType());
+			return beanContext.getCollectionClassMeta((Class<? extends Collection<?>>)c.getInnerClass(), e);
+		}
+		return c;
+	}
+
+	/*
+	 * If c1 is a child of c2 or the same as c2, returns c1.
+	 * Otherwise, returns c2.
+	 */
+	private ClassMeta<?> getNarrowedClassMeta(ClassMeta<?> c1, ClassMeta<?> c2) {
+		if (isParentClass(c2.getInnerClass(), c1.getInnerClass()))
+			return c1;
+		return c2;
+	}
+
+	/*
+	 * Converts this map to the specified class type.
+	 */
+	@SuppressWarnings({"unchecked","rawtypes"})
+	private <T> T cast2(ClassMeta<T> cm) {
+
+		try {
+			Object value = get("value");
+
+			if (cm.isMap()) {
+				Map m2 = (cm.canCreateNewInstance() ? (Map)cm.newInstance() : new ObjectMap(beanContext));
+				ClassMeta<?> kType = cm.getKeyType(), vType = cm.getValueType();
+				for (Map.Entry<String,Object> e : entrySet()) {
+					Object k = e.getKey();
+					Object v = e.getValue();
+					if (! k.equals("_class")) {
+
+						// Attempt to recursively cast child maps.
+						if (v instanceof ObjectMap)
+							v = ((ObjectMap)v).cast();
+
+						k = (kType.isString() ? k : beanContext.convertToType(k, kType));
+						v = (vType.isObject() ? v : beanContext.convertToType(v, vType));
+
+						m2.put(k, v);
+					}
+				}
+				return (T)m2;
+
+			} else if (cm.isBean()) {
+				BeanMap<? extends T> bm = beanContext.newBeanMap(cm.getInnerClass());
+
+				// Iterate through all the entries in the map and set the individual field values.
+				for (Map.Entry<String,Object> e : entrySet()) {
+					String k = e.getKey();
+					Object v = e.getValue();
+					if (! k.equals("_class")) {
+
+						// Attempt to recursively cast child maps.
+						if (v instanceof ObjectMap)
+							v = ((ObjectMap)v).cast();
+
+						bm.put(k, v);
+					}
+				}
+
+				return bm.getBean();
+
+			} else if (cm.isArray() || cm.isCollection()) {
+				List items = (List)get("items");
+				return beanContext.convertToType(items, cm);
+
+			} else if (value != null) {
+				return beanContext.convertToType(value, cm);
+			}
+
+		} catch (Exception e) {
+			throw new BeanRuntimeException(cm.innerClass, "Error occurred attempting to cast to an object of type ''{0}''", cm.innerClass.getName()).initCause(e);
+		}
+
+		throw new BeanRuntimeException(cm.innerClass, "Cannot convert to class type ''{0}''.  Only beans and maps can be converted using this method.", cm.innerClass.getName());
+	}
+
+	private PojoRest getPojoRest() {
+		if (pojoRest == null)
+			pojoRest = new PojoRest(this);
+		return pojoRest;
+	}
+
+	/**
+	 * Serialize this object into a string using the specified serializer.
+	 *
+	 * @param serializer The serializer to use to convert this object to a string.
+	 * @return This object serialized as a string.
+	 * @throws SerializeException If a problem occurred trying to convert the output.
+	 */
+	public String toString(WriterSerializer serializer) throws SerializeException {
+		return serializer.serialize(this);
+	}
+
+	/**
+	 * Serialize this object into a JSON string using the {@link JsonSerializer#DEFAULT} serializer.
+	 */
+	@Override /* Object */
+	public String toString() {
+		try {
+			return this.toString(JsonSerializer.DEFAULT_LAX);
+		} catch (SerializeException e) {
+			return e.getLocalizedMessage();
+		}
+	}
+
+	/**
+	 * Convenience method for serializing this map to the specified <code>Writer</code> using
+	 * the {@link JsonSerializer#DEFAULT} serializer.
+	 *
+	 * @param w The writer to serialize this object to.
+	 * @return This object (for method chaining).
+	 * @throws IOException If a problem occurred trying to write to the writer.
+	 * @throws SerializeException If a problem occurred trying to convert the output.
+	 */
+	public ObjectMap serializeTo(Writer w) throws IOException, SerializeException {
+		JsonSerializer.DEFAULT.serialize(this);
+		return this;
+	}
+
+	@Override /* Map */
+	public Set<String> keySet() {
+		if (inner == null)
+			return super.keySet();
+		LinkedHashSet<String> s = new LinkedHashSet<String>();
+		s.addAll(inner.keySet());
+		s.addAll(super.keySet());
+		return s;
+	}
+
+	@Override /* Map */
+	public Set<Map.Entry<String,Object>> entrySet() {
+		if (inner == null)
+			return super.entrySet();
+
+		final Set<String> keySet = keySet();
+		final Iterator<String> keys = keySet.iterator();
+
+		return new AbstractSet<Map.Entry<String,Object>>() {
+
+			@Override /* Iterable */
+			public Iterator<Map.Entry<String,Object>> iterator() {
+
+				return new Iterator<Map.Entry<String,Object>>() {
+
+					@Override /* Iterator */
+					public boolean hasNext() {
+						return keys.hasNext();
+					}
+
+					@Override /* Iterator */
+					public Map.Entry<String,Object> next() {
+						return new Map.Entry<String,Object>() {
+							String key = keys.next();
+
+							@Override /* Map.Entry */
+							public String getKey() {
+								return key;
+							}
+
+							@Override /* Map.Entry */
+							public Object getValue() {
+								return get(key);
+							}
+
+							@Override /* Map.Entry */
+							public Object setValue(Object object) {
+								return put(key, object);
+							}
+						};
+					}
+
+					@Override /* Iterator */
+					public void remove() {
+						throw new UnsupportedOperationException();
+					}
+				};
+			}
+
+			@Override /* Set */
+			public int size() {
+				return keySet.size();
+			}
+		};
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/PropertyNamer.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/PropertyNamer.java b/juneau-core/src/main/java/org/apache/juneau/PropertyNamer.java
new file mode 100644
index 0000000..9aa2897
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/PropertyNamer.java
@@ -0,0 +1,35 @@
+/***************************************************************************************************************************
+ * 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;
+
+import org.apache.juneau.annotation.*;
+
+/**
+ * Defines an API for converting conventional bean property names to some other form.
+ * <p>
+ * For example, given the bean property <js>"fooBarURL"</js>, the {@link PropertyNamerDashedLC}
+ * 	property namer will convert this to <js>"foo-bar-url"</js>.
+ * <p>
+ * Property namers are associated with beans through the {@link Bean#propertyNamer} annotation.
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public interface PropertyNamer {
+
+	/**
+	 * Convert the specified default property name to some other value.
+	 * @param name The original bean property name.
+	 * @return The converted property name.
+	 */
+	public String getPropertyName(String name);
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/PropertyNamerDashedLC.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/PropertyNamerDashedLC.java b/juneau-core/src/main/java/org/apache/juneau/PropertyNamerDashedLC.java
new file mode 100644
index 0000000..d1b1e7e
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/PropertyNamerDashedLC.java
@@ -0,0 +1,66 @@
+/***************************************************************************************************************************
+ * 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;
+
+/**
+ * Converts property names to dashed-lower-case format.
+ * <p>
+ * 	Examples:
+ * <ul>
+ * 	<li><js>"fooBar"</js> -&gt; <js>"foo-bar"</js>
+ * 	<li><js>"fooBarURL"</js> -&gt; <js>"foo-bar-url"</js>
+ * 	<li><js>"FooBarURL"</js> -&gt; <js>"foo-bar-url"</js>
+ * </ul>
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public final class PropertyNamerDashedLC implements PropertyNamer {
+
+	@Override /* PropertyNamer */
+	public String getPropertyName(String name) {
+		if (name == null || name.isEmpty())
+			return name;
+
+		int numUCs = 0;
+		boolean isPrevUC = Character.isUpperCase(name.charAt(0));
+		for (int i = 1; i < name.length(); i++) {
+			char c = name.charAt(i);
+			if (Character.isUpperCase(c)) {
+				if (! isPrevUC)
+					numUCs++;
+				isPrevUC = true;
+			} else {
+				isPrevUC = false;
+			}
+		}
+
+		char[] name2 = new char[name.length() + numUCs];
+		isPrevUC = Character.isUpperCase(name.charAt(0));
+		name2[0] = Character.toLowerCase(name.charAt(0));
+		int ni = 0;
+		for (int i = 0; i < name.length(); i++) {
+			char c = name.charAt(i);
+			if (Character.isUpperCase(c)) {
+				if (! isPrevUC)
+					name2[ni++] = '-';
+				isPrevUC = true;
+				name2[ni++] = Character.toLowerCase(c);
+			} else {
+				isPrevUC = false;
+				name2[ni++] = c;
+			}
+		}
+
+		return new String(name2);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/PropertyNamerDefault.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/PropertyNamerDefault.java b/juneau-core/src/main/java/org/apache/juneau/PropertyNamerDefault.java
new file mode 100644
index 0000000..9193e1d
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/PropertyNamerDefault.java
@@ -0,0 +1,39 @@
+/***************************************************************************************************************************
+ * 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;
+
+import java.beans.*;
+
+/**
+ * Default property namer.
+ * <p>
+ * 	Examples:
+ * <ul>
+ * 	<li><js>"fooBar"</js> -&gt; <js>"fooBar"</js>
+ * 	<li><js>"fooBarURL"</js> -&gt; <js>"fooBarURL"</js>
+ * 	<li><js>"FooBarURL"</js> -&gt; <js>"fooBarURL"</js>
+ * 	<li><js>"URL"</js> -&gt; <js>"URL"</js>
+ * </ul>
+ * <p>
+ * 	See {@link Introspector#decapitalize(String)} for exact rules.
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public final class PropertyNamerDefault implements PropertyNamer {
+
+	@Override /* PropertyNamer */
+	public String getPropertyName(String name) {
+		return Introspector.decapitalize(name);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/Session.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/Session.java b/juneau-core/src/main/java/org/apache/juneau/Session.java
new file mode 100644
index 0000000..653194e
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/Session.java
@@ -0,0 +1,33 @@
+/***************************************************************************************************************************
+ * 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;
+
+/**
+ * A one-time-use non-thread-safe object that's meant to be used once and then thrown away.
+ * <p>
+ * Serializers and parsers use context objects to retrieve config properties and to use it
+ * 	as a scratchpad during serialize and parse actions.
+ *
+ * @see ContextFactory
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public abstract class Session {
+
+	/**
+	 * Default constructor.
+	 *
+	 * @param ctx The context creating this session object.
+	 * 	The context contains all the configuration settings for the session.
+	 */
+	protected Session(Context ctx) {}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/Streamable.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/Streamable.java b/juneau-core/src/main/java/org/apache/juneau/Streamable.java
new file mode 100644
index 0000000..0d99b52
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/Streamable.java
@@ -0,0 +1,42 @@
+/***************************************************************************************************************************
+ * 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;
+
+import java.io.*;
+
+/**
+ * Interface that identifies that an object can be serialized directly to an output stream.
+ * <p>
+ * 	Instances must identify the media type of the content by implementing the
+ * 	{@link #getMediaType()} method.
+ * </p>
+ *
+ * @author James Bognar (james.bognar@salesforce.com)
+ */
+public interface Streamable {
+
+	/**
+	 * Serialize this object to the specified output stream.
+	 *
+	 * @param os The output stream to stream to.
+	 * @throws IOException
+	 */
+	void streamTo(OutputStream os) throws IOException;
+
+	/**
+	 * Returns the serialized media type for this resource (e.g. <js>"text/html"</js>).
+	 *
+	 * @return The media type, or <jk>null</jk> if the media type is not known.
+	 */
+	String getMediaType();
+}