001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau;
014
015import static org.apache.juneau.internal.ClassUtils.*;
016import static org.apache.juneau.internal.StringUtils.*;
017
018import java.io.*;
019import java.lang.reflect.*;
020import java.util.*;
021
022import org.apache.juneau.internal.*;
023import org.apache.juneau.json.*;
024import org.apache.juneau.parser.*;
025import org.apache.juneau.serializer.*;
026import org.apache.juneau.transform.*;
027import org.apache.juneau.utils.*;
028
029/**
030 * Java implementation of a JSON object.
031 * 
032 * <p>
033 * An extension of {@link LinkedHashMap}, so all methods available in that class are also available to this class.
034 * <p>
035 * Note that the use of this class is optional.
036 * The serializers will accept any objects that implement the {@link java.util.Map} interface.
037 * But this class provides some useful additional functionality when working with JSON models constructed from Java
038 * Collections Framework objects.
039 * For example, a constructor is provided for converting a JSON object string directly into a {@link Map}.
040 * It also contains accessor methods for to avoid common typecasting when accessing elements in a list.
041 * 
042 * <h5 class='section'>Example:</h5>
043 * <p class='bcode'>
044 *    <jc>// Construct an empty Map</jc>
045 *    Map m = <jk>new</jk> ObjectMap();
046 * 
047 *    <jc>// Construct a Map from JSON</jc>
048 *    String json = <js>"{a:'A',b:{c:'C',d:123}}"</js>;
049 *    m = <jk>new</jk> ObjectMap(json);
050 * 
051 *    <jc>// Construct a Map using the append method</jc>
052 *    m = <jk>new</jk> ObjectMap().append(<js>"foo"</js>,<js>"x"</js>).append(<js>"bar"</js>,123)
053 *       .append(<js>"baz"</js>,<jk>true</jk>);
054 * 
055 *    <jc>// Construct a Map from XML generated by XmlSerializer</jc>
056 *    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>;
057 *    m = <jk>new</jk> ObjectMap(xml, DataFormat.<jsf>XML</jsf>);
058 *    m = (Map)XmlParser.<jsf>DEFAULT</jsf>.parse(xml); <jc>// Equivalent</jc>
059 *    m = (Map)XmlParser.<jsf>DEFAULT</jsf>.parse(Object.<jk>class</jk>, xml); <jc>// Equivalent</jc>
060 *    m = XmlParser.<jsf>DEFAULT</jsf>.parse(Map.<jk>class</jk>, xml); <jc>// Equivalent</jc>
061 *    m = XmlParser.<jsf>DEFAULT</jsf>.parse(ObjectMap.<jk>class</jk>, xml); <jc>// Equivalent</jc>
062 * 
063 *    <jc>// Construct a Map from a URL GET parameter string generated by UrlEncodingParser</jc>
064 *    String urlParams = <js>"?a='A'&amp;b={c:'C',d:123}"</js>;
065 *    m = <jk>new</jk> ObjectMap(urlParams, DataFormat.<jsf>URLPARAM</jsf>);
066 *    m = (Map)UrlEncodingParser.<jsf>DEFAULT</jsf>.parse(Object.<jk>class</jk>, xml); <jc>// Equivalent</jc>
067 *    m = UrlEncodingParser.<jsf>DEFAULT</jsf>.parse(Map.<jk>class</jk>, xml); <jc>// Equivalent</jc>
068 *    m = UrlEncodingParser.<jsf>DEFAULT</jsf>.parse(ObjectMap.<jk>class</jk>, xml); <jc>// Equivalent</jc>
069 * 
070 *    <jc>// Construct JSON from ObjectMap</jc>
071 *    m = <jk>new</jk> ObjectMap(<js>"{foo:'bar'},{baz:[123,true]}"</js>);
072 *    json = m.toString();  <jc>// Produces "{foo:'bar'},{baz:[123,true]}"</jc>
073 *    json = m.toString(JsonSerializer.<jsf>DEFAULT_CONDENSED</jsf>);  <jc>// Equivalent</jc>
074 *    json = JsonSerializer.<jsf>DEFAULT_CONDENSED</jsf>.serialize(m);  <jc>// Equivalent</jc>
075 * 
076 *    <jc>// Get a map entry as an Integer</jc>
077 *    m = <jk>new</jk> ObjectMap(<js>"{foo:123}"</js>);
078 *    Integer i = m.getInt(<js>"foo"</js>);
079 *    i = m.get(Integer.<jk>class</jk>, <js>"foo"</js>);  <jc>// Equivalent</jc>
080 * 
081 *    <jc>// Get a map entry as a Float</jc>
082 *    m = <jk>new</jk> ObjectMap(<js>"{foo:123}"</js>);
083 *    Float f = m.getFloat(<js>"foo"</js>);
084 *    f = m.get(Float.<jk>class</jk>, <js>"foo"</js>);  <jc>// Equivalent</jc>
085 * 
086 *    <jc>// Same as above, except converted to a String</jc>
087 *    m = <jk>new</jk> ObjectMap(<js>"{foo:123}"</js>);
088 *    String s = m.getString(<js>"foo"</js>); <jc>// Returns "123"</jc>
089 *    s = m.get(String.<jk>class</jk>, <js>"foo"</js>);  <jc>// Equivalent</jc>
090 * 
091 *    <jc>// Get one of the entries in the list as a bean (converted to a bean if it isn't already one)</jc>
092 *    m = <jk>new</jk> ObjectMap(<js>"{person:{name:'John Smith',age:45}}"</js>);
093 *    Person p = m.get(Person.<jk>class</jk>, <js>"person"</js>);
094 * 
095 *    <jc>// Add an inner map</jc>
096 *    ObjectMap m1 = <jk>new</jk> ObjectMap(<js>"{a:1}"</js>);
097 *    ObjectMap m2 = <jk>new</jk> ObjectMap(<js>"{b:2}"</js>).setInner(m1);
098 *    <jk>int</jk> a = m2.getInt(<js>"a"</js>);  <jc>// a == 1 </jc>
099 * </p>
100 * 
101 * <p>
102 * This class is not thread safe.
103 */
104public class ObjectMap extends LinkedHashMap<String,Object> {
105   private static final long serialVersionUID = 1L;
106
107   private transient BeanSession session;
108   private Map<String,Object> inner;
109   private transient PojoRest pojoRest;
110
111   /**
112    * An empty read-only ObjectMap.
113    */
114   public static final ObjectMap EMPTY_MAP = new ObjectMap() {
115
116      private static final long serialVersionUID = 1L;
117
118      @Override /* Map */
119      public Set<Map.Entry<String,Object>> entrySet() {
120         return Collections.EMPTY_MAP.entrySet();
121      }
122
123      @Override /* Map */
124      public Set<String> keySet() {
125         return Collections.EMPTY_MAP.keySet();
126      }
127
128      @Override /* Map */
129      public Object put(String key, Object value) {
130         throw new UnsupportedOperationException();
131      }
132
133      @Override /* Map */
134      public Object remove(Object key) {
135         throw new UnsupportedOperationException();
136      }
137
138      @Override /* Map */
139      public Collection<Object> values() {
140         return Collections.emptyMap().values();
141      }
142   };
143
144   /**
145    * Construct an ObjectMap directly from a string using the specified parser.
146    * 
147    * @param s The string being parsed.
148    * @param p The parser to use to parse the input.
149    * @throws ParseException If the input contains a syntax error or is malformed.
150    */
151   public ObjectMap(CharSequence s, Parser p) throws ParseException {
152      this(p == null ? BeanContext.DEFAULT.createSession() : p.createBeanSession());
153      if (p == null)
154         p = JsonParser.DEFAULT;
155      try {
156         if (! StringUtils.isEmpty(s))
157            p.parseIntoMap(s, this, session.string(), session.object());
158      } catch (ParseException e) {
159         throw new ParseException("Invalid input for {0} parser.\n---start---\n{1}\n---end---",
160            p.getClass().getSimpleName(), s).initCause(e);
161      }
162   }
163
164   /**
165    * Shortcut for <code><jk>new</jk> ObjectMap(string,JsonParser.<jsf>DEFAULT</jsf>);</code>
166    * 
167    * @param s The JSON text to parse.
168    * @throws ParseException If the input contains a syntax error or is malformed.
169    */
170   public ObjectMap(CharSequence s) throws ParseException {
171      this(s, null);
172   }
173
174   /**
175    * Construct an ObjectMap directly from a reader using the specified parser.
176    * 
177    * @param r The reader to read from.  The reader will be wrapped in a {@link BufferedReader} if it isn't already.
178    * @param p The parser to use to parse the input.
179    * @throws ParseException If the input contains a syntax error or is malformed.
180    * @throws IOException If a problem occurred trying to read from the reader.
181    */
182   public ObjectMap(Reader r, Parser p) throws ParseException, IOException {
183      this(p == null ? BeanContext.DEFAULT.createSession() : p.createBeanSession());
184      parseReader(r, p);
185   }
186
187   /**
188    * Shortcut for <code><jk>new</jk> ObjectMap(reader, JsonParser.<jsf>DEFAULT</jsf>)</code>.
189    * 
190    * @param r The reader to read from.  The reader will be wrapped in a {@link BufferedReader} if it isn't already.
191    * @throws ParseException If the input contains a syntax error or is malformed.
192    * @throws IOException If a problem occurred trying to read from the reader.
193    */
194   public ObjectMap(Reader r) throws ParseException, IOException {
195      this(BeanContext.DEFAULT.createSession());
196      parseReader(r, JsonParser.DEFAULT);
197   }
198
199   private void parseReader(Reader r, Parser p) throws ParseException {
200      if (p == null)
201         p = JsonParser.DEFAULT;
202      p.parseIntoMap(r, this, session.string(), session.object());
203   }
204
205   /**
206    * Construct an empty JSON object (i.e. an empty {@link LinkedHashMap}).
207    */
208   public ObjectMap() {
209      this(BeanContext.DEFAULT.createSession());
210   }
211
212   /**
213    * Construct an empty JSON object (i.e. an empty {@link LinkedHashMap}) with the specified bean context.
214    * 
215    * @param session The bean session to use for creating beans.
216    */
217   public ObjectMap(BeanSession session) {
218      this.session = session;
219   }
220
221   /**
222    * Construct a JSON object and fill it with the contents from the specified {@link Map}.
223    * 
224    * @param m The map whose entries will be copied into this map.
225    */
226   public ObjectMap(Map<?,?> m) {
227      super();
228      for (Map.Entry<?,?> e : m.entrySet())
229         put(e.getKey().toString(), e.getValue());
230   }
231
232   /**
233    * Set an inner map in this map to allow for chained get calls.
234    * 
235    * <p>
236    * If {@link #get(Object)} returns <jk>null</jk>, then {@link #get(Object)} will be called on the inner map.
237    * 
238    * <p>
239    * In addition to providing the ability to chain maps, this method also provides the ability to wrap an existing map
240    * inside another map so that you can add entries to the outer map without affecting the values on the inner map.
241    * 
242    * <p class='bcode'>
243    *    ObjectMap m1 = <jk>new</jk> ObjectMap(<js>"{foo:1}"</js>);
244    *    ObjectMap m2 = <jk>new</jk> ObjectMap().setInner(m1);
245    *    m2.put(<js>"foo"</js>, 2);                      <jc>// Overwrite the entry</jc>
246    *    <jk>int</jk> foo1 = m1.getInt(<js>"foo"</js>);           <jc>// foo1 == 1 </jc>
247    *    <jk>int</jk> foo2 = m2.getInt(<js>"foo"</js>);           <jc>// foo2 == 2 </jc>
248    * </p>
249    * 
250    * @param inner
251    *    The inner map.
252    *    Can be <jk>null</jk> to remove the inner map from an existing map.
253    * @return This object (for method chaining).
254    */
255   public ObjectMap setInner(Map<String,Object> inner) {
256      this.inner = inner;
257      return this;
258   }
259
260   /**
261    * Searches for the specified key in this map ignoring case.
262    * 
263    * @param key
264    *    The key to search for.
265    *    For performance reasons, it's preferable that the key be all lowercase.
266    * @return The key, or <jk>null</jk> if map does not contain this key.
267    */
268   public String findKeyIgnoreCase(String key) {
269      for (String k : keySet())
270         if (key.equalsIgnoreCase(k))
271            return k;
272      return null;
273   }
274
275   /**
276    * Override the default bean session used for converting POJOs.
277    * 
278    * <p>
279    * Default is {@link BeanContext#DEFAULT}, which is sufficient in most cases.
280    * 
281    * <p>
282    * Useful if you're serializing/parsing beans with transforms defined.
283    * 
284    * @param session The new bean session.
285    * @return This object (for method chaining).
286    */
287   public ObjectMap setBeanSession(BeanSession session) {
288      this.session = session;
289      return this;
290   }
291
292   /**
293    * Returns the {@link BeanSession} currently associated with this map.
294    * 
295    * @return The {@link BeanSession} currently associated with this map.
296    */
297   public BeanSession getBeanSession() {
298      return session;
299   }
300
301   /**
302    * Convenience method for adding multiple objects to this map.
303    * 
304    * <p>
305    * Equivalent to calling {@code put(key, value)}, but returns this map so that the method can be chained.
306    * 
307    * @param key The key.
308    * @param value The value.
309    * @return This object (for method chaining).
310    */
311   public ObjectMap append(String key, Object value) {
312      put(key, value);
313      return this;
314   }
315
316   /**
317    * Convenience method for adding a contents of another map to this map.
318    * 
319    * <p>
320    * Equivalent to calling {@code putAll(m)}, but returns this map so that the method can be chained.
321    * 
322    * @param m The map whose contents should be added to this map.
323    * @return This object (for method chaining).
324    */
325   public ObjectMap appendAll(Map<String,Object> m) {
326      putAll(m);
327      return this;
328   }
329
330   @Override /* Map */
331   public Object get(Object key) {
332      Object o = super.get(key);
333      if (o == null && inner != null)
334         o = inner.get(key);
335      return o;
336   }
337
338   /**
339    * Same as {@link Map#get(Object) get()}, but casts or converts the value to the specified class type.
340    * 
341    * <p>
342    * This is the preferred get method for simple types.
343    * 
344    * <h5 class='section'>Examples:</h5>
345    * <p class='bcode'>
346    *    ObjectMap m = <jk>new</jk> ObjectMap(<js>"..."</js>);
347    * 
348    *    <jc>// Value converted to a string.</jc>
349    *    String s = m.get(<js>"key1"</js>, String.<jk>class</jk>);
350    * 
351    *    <jc>// Value converted to a bean.</jc>
352    *    MyBean b = m.get(<js>"key2"</js>, MyBean.<jk>class</jk>);
353    * 
354    *    <jc>// Value converted to a bean array.</jc>
355    *    MyBean[] ba = m.get(<js>"key3"</js>, MyBean[].<jk>class</jk>);
356    * 
357    *    <jc>// Value converted to a linked-list of objects.</jc>
358    *    List l = m.get(<js>"key4"</js>, LinkedList.<jk>class</jk>);
359    * 
360    *    <jc>// Value converted to a map of object keys/values.</jc>
361    *    Map m2 = m.get(<js>"key5"</js>, TreeMap.<jk>class</jk>);
362    * </p>
363    * 
364    * <p>
365    * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions.
366    * 
367    * @param key The key.
368    * @param <T> The class type returned.
369    * @param type The class type returned.
370    * @return The value, or <jk>null</jk> if the entry doesn't exist.
371    */
372   public <T> T get(String key, Class<T> type) {
373      return getWithDefault(key, (T)null, type);
374   }
375
376   /**
377    * Same as {@link #get(String,Class)}, but allows for complex data types consisting of collections or maps.
378    * 
379    * <p>
380    * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps).
381    * 
382    * <h5 class='section'>Examples:</h5>
383    * <p class='bcode'>
384    *    ObjectMap m = <jk>new</jk> ObjectMap(<js>"..."</js>);
385    * 
386    *    <jc>// Value converted to a linked-list of strings.</jc>
387    *    List&lt;String&gt; l1 = m.get(<js>"key1"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>);
388    * 
389    *    <jc>// Value converted to a linked-list of beans.</jc>
390    *    List&lt;MyBean&gt; l2 = m.get(<js>"key2"</js>, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>);
391    * 
392    *    <jc>// Value converted to a linked-list of linked-lists of strings.</jc>
393    *    List&lt;List&lt;String&gt;&gt; l3 = m.get(<js>"key3"</js>, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>);
394    * 
395    *    <jc>// Value converted to a map of string keys/values.</jc>
396    *    Map&lt;String,String&gt; m1 = m.get(<js>"key4"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>);
397    * 
398    *    <jc>// Value converted to a map containing string keys and values of lists containing beans.</jc>
399    *    Map&lt;String,List&lt;MyBean&gt;&gt; m2 = m.get(<js>"key5"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>);
400    * </p>
401    * 
402    * <p>
403    * <code>Collection</code> classes are assumed to be followed by zero or one objects indicating the element type.
404    * 
405    * <p>
406    * <code>Map</code> classes are assumed to be followed by zero or two meta objects indicating the key and value types.
407    * 
408    * <p>
409    * The array can be arbitrarily long to indicate arbitrarily complex data structures.
410    * 
411    * <p>
412    * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions.
413    * 
414    * <h5 class='section'>Notes:</h5>
415    * <ul class='spaced-list'>
416    *    <li>
417    *       Use the {@link #get(String, Class)} method instead if you don't need a parameterized map/collection.
418    * </ul>
419    * 
420    * @param key The key.
421    * @param <T> The class type returned.
422    * @param type The class type returned.
423    * @param args The class type parameters.
424    * @return The value, or <jk>null</jk> if the entry doesn't exist.
425    */
426   public <T> T get(String key, Type type, Type...args) {
427      return getWithDefault(key, null, type, args);
428   }
429
430   /**
431    * Same as {@link Map#get(Object) get()}, but returns the default value if the key could not be found.
432    * 
433    * @param key The key.
434    * @param def The default value if the entry doesn't exist.
435    * @return The value, or the default value if the entry doesn't exist.
436    */
437   public Object getWithDefault(String key, Object def) {
438      Object o = get(key);
439      return (o == null ? def : o);
440   }
441
442   /**
443    * Same as {@link #get(String,Class)} but returns a default value if the value does not exist.
444    * 
445    * @param key The key.
446    * @param def The default value.  Can be <jk>null</jk>.
447    * @param <T> The class type returned.
448    * @param type The class type returned.
449    * @return The value, or <jk>null</jk> if the entry doesn't exist.
450    */
451   public <T> T getWithDefault(String key, T def, Class<T> type) {
452      return getWithDefault(key, def, type, new Type[0]);
453   }
454
455   /**
456    * Same as {@link #get(String,Type,Type...)} but returns a default value if the value does not exist.
457    * 
458    * @param key The key.
459    * @param def The default value.  Can be <jk>null</jk>.
460    * @param <T> The class type returned.
461    * @param type The class type returned.
462    * @param args The class type parameters.
463    * @return The value, or <jk>null</jk> if the entry doesn't exist.
464    */
465   public <T> T getWithDefault(String key, T def, Type type, Type...args) {
466      T t = session.convertToType(get(key), type, args);
467      return t == null ? def : t;
468   }
469
470
471   /**
472    * Same as {@link Map#get(Object) get()}, but converts the raw value to the specified class type using the specified
473    * POJO swap.
474    * 
475    * @param key The key.
476    * @param pojoSwap The swap class used to convert the raw type to a transformed type.
477    * @param <T> The transformed class type.
478    * @return The value, or <jk>null</jk> if the entry doesn't exist.
479    * @throws ParseException Thrown by the POJO swap if a problem occurred trying to parse the value.
480    */
481   @SuppressWarnings({ "rawtypes", "unchecked" })
482   public <T> T getSwapped(String key, PojoSwap<T,?> pojoSwap) throws ParseException {
483      try {
484         Object o = super.get(key);
485         if (o == null)
486            return null;
487         PojoSwap swap = pojoSwap;
488         return (T)swap.unswap(session, o, null);
489      } catch (ParseException e) {
490         throw e;
491      } catch (Exception e) {
492         throw new ParseException(e);
493      }
494   }
495
496   /**
497    * Returns the value for the first key in the list that has an entry in this map.
498    * 
499    * @param keys The keys to look up in order.
500    * @return The value of the first entry whose key exists, or <jk>null</jk> if none of the keys exist in this map.
501    */
502   public Object find(String...keys) {
503      for (String key : keys)
504         if (containsKey(key))
505            return get(key);
506      return null;
507   }
508
509   /**
510    * Returns the value for the first key in the list that has an entry in this map.
511    * 
512    * <p>
513    * Casts or converts the value to the specified class type.
514    * 
515    * <p>
516    * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions.
517    * 
518    * @param type The class type to convert the value to.
519    * @param <T> The class type to convert the value to.
520    * @param keys The keys to look up in order.
521    * @return The value of the first entry whose key exists, or <jk>null</jk> if none of the keys exist in this map.
522    */
523   public <T> T find(Class<T> type, String...keys) {
524      for (String key : keys)
525         if (containsKey(key))
526            return get(key, type);
527      return null;
528   }
529
530   /**
531    * Same as {@link #get(String,Class) get(String,Class)}, but the key is a slash-delimited path used to traverse
532    * entries in this POJO.
533    * 
534    * <p>
535    * For example, the following code is equivalent:
536    * </p>
537    * <p class='bcode'>
538    *    ObjectMap m = getObjectMap();
539    * 
540    *    <jc>// Long way</jc>
541    *    <jk>long</jk> l = m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>)
542    *       .getLong(<js>"baz"</js>);
543    * 
544    *    <jc>// Using this method</jc>
545    *    <jk>long</jk> l = m.getAt(<js>"foo/bar/0/baz"</js>, <jk>long</jk>.<jk>class</jk>);
546    * </p>
547    * 
548    * <p>
549    * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various
550    * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays).
551    * 
552    * @param path The path to the entry.
553    * @param type The class type.
554    * 
555    * @param <T> The class type.
556    * @return The value, or <jk>null</jk> if the entry doesn't exist.
557    */
558   public <T> T getAt(String path, Class<T> type) {
559      return getPojoRest().get(path, type);
560   }
561
562   /**
563    * Same as {@link #getAt(String,Class)}, but allows for conversion to complex maps and collections.
564    * 
565    * <p>
566    * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various
567    * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays).
568    * 
569    * @param path The path to the entry.
570    * @param type The class type.
571    * @param args The class parameter types.
572    * 
573    * @param <T> The class type.
574    * @return The value, or <jk>null</jk> if the entry doesn't exist.
575    */
576   public <T> T getAt(String path, Type type, Type...args) {
577      return getPojoRest().get(path, type, args);
578   }
579
580   /**
581    * Same as <code>put(String,Object)</code>, but the key is a slash-delimited path used to traverse entries in this
582    * POJO.
583    * 
584    * <p>
585    * For example, the following code is equivalent:
586    * </p>
587    * <p class='bcode'>
588    *    ObjectMap m = getObjectMap();
589    * 
590    *    <jc>// Long way</jc>
591    *    m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>)
592    *       .put(<js>"baz"</js>, 123);
593    * 
594    *    <jc>// Using this method</jc>
595    *    m.putAt(<js>"foo/bar/0/baz"</js>, 123);
596    * </p>
597    * 
598    * <p>
599    * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various
600    * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays).
601    * 
602    * @param path The path to the entry.
603    * @param o The new value.
604    * @return The previous value, or <jk>null</jk> if the entry doesn't exist.
605    */
606   public Object putAt(String path, Object o) {
607      return getPojoRest().put(path, o);
608   }
609
610   /**
611    * Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append to collections and arrays.
612    * 
613    * <p>
614    * For example, the following code is equivalent:
615    * </p>
616    * <p class='bcode'>
617    *    ObjectMap m = getObjectMap();
618    * 
619    *    <jc>// Long way</jc>
620    *    m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).append(123);
621    * 
622    *    <jc>// Using this method</jc>
623    *    m.postAt(<js>"foo/bar"</js>, 123);
624    * </p>
625    * 
626    * <p>
627    * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various
628    * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays).
629    * 
630    * @param path The path to the entry.
631    * @param o The new value.
632    * @return The previous value, or <jk>null</jk> if the entry doesn't exist.
633    */
634   public Object postAt(String path, Object o) {
635      return getPojoRest().post(path, o);
636   }
637
638   /**
639    * Similar to {@link #remove(Object) remove(Object)}, but the key is a slash-delimited path used to traverse entries
640    * in this POJO.
641    * 
642    * <p>
643    * For example, the following code is equivalent:
644    * </p>
645    * <p class='bcode'>
646    *    ObjectMap m = getObjectMap();
647    * 
648    *    <jc>// Long way</jc>
649    *    m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(0).remove(<js>"baz"</js>);
650    * 
651    *    <jc>// Using this method</jc>
652    *    m.deleteAt(<js>"foo/bar/0/baz"</js>);
653    * </p>
654    * 
655    * <p>
656    * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various
657    * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays).
658    * 
659    * @param path The path to the entry.
660    * @return The previous value, or <jk>null</jk> if the entry doesn't exist.
661    */
662   public Object deleteAt(String path) {
663      return getPojoRest().delete(path);
664   }
665
666   /**
667    * Convenience method for inserting JSON directly into an attribute on this object.
668    * 
669    * <p>
670    * The JSON text can be an object (i.e. <js>"{...}"</js>) or an array (i.e. <js>"[...]"</js>).
671    * 
672    * @param key The key.
673    * @param json The JSON text that will be parsed into an Object and then inserted into this map.
674    * @throws ParseException If the input contains a syntax error or is malformed.
675    */
676   public void putJson(String key, String json) throws ParseException {
677      this.put(key, JsonParser.DEFAULT.parse(json, Object.class));
678   }
679
680   /**
681    * Returns the specified entry value converted to a {@link String}.
682    * 
683    * <p>
684    * Shortcut for <code>get(key, String.<jk>class</jk>)</code>.
685    * 
686    * @param key The key.
687    * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
688    */
689   public String getString(String key) {
690      return get(key, String.class);
691   }
692
693   /**
694    * Returns the specified entry value converted to a {@link String}.
695    * 
696    * <p>
697    * Shortcut for <code>get(key, String[].<jk>class</jk>)</code>.
698    * 
699    * @param key The key.
700    * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
701    */
702   public String[] getStringArray(String key) {
703      return getStringArray(key, null);
704   }
705
706   /**
707    * Same as {@link #getStringArray(String)} but returns a default value if the value cannot be found.
708    * 
709    * @param key The map key.
710    * @param def The default value if value is not found.
711    * @return The value converted to a string array.
712    */
713   public String[] getStringArray(String key, String[] def) {
714      Object s = get(key, Object.class);
715      if (s == null)
716         return def;
717      String[] r = null;
718      if (s instanceof Collection)
719         r = ArrayUtils.toStringArray((Collection<?>)s);
720      else if (s instanceof String[])
721         r = (String[])s;
722      else if (s instanceof Object[])
723         r = ArrayUtils.toStringArray(Arrays.asList((Object[])s));
724      else
725         r = split(asString(s));
726      return (r.length == 0 ? def : r);
727   }
728
729   /**
730    * Returns the specified entry value converted to a {@link String}.
731    * 
732    * <p>
733    * Shortcut for <code>getWithDefault(key, defVal, String.<jk>class</jk>)</code>.
734    * 
735    * @param key The key.
736    * @param defVal The default value if the map doesn't contain the specified mapping.
737    * @return The converted value, or the default value if the map contains no mapping for this key.
738    */
739   public String getString(String key, String defVal) {
740      return getWithDefault(key, defVal, String.class);
741   }
742
743   /**
744    * Returns the specified entry value converted to an {@link Integer}.
745    * 
746    * <p>
747    * Shortcut for <code>get(key, Integer.<jk>class</jk>)</code>.
748    * 
749    * @param key The key.
750    * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
751    * @throws InvalidDataConversionException If value cannot be converted.
752    */
753   public Integer getInt(String key) {
754      return get(key, Integer.class);
755   }
756
757   /**
758    * Returns the specified entry value converted to an {@link Integer}.
759    * 
760    * <p>
761    * Shortcut for <code>getWithDefault(key, defVal, Integer.<jk>class</jk>)</code>.
762    * 
763    * @param key The key.
764    * @param defVal The default value if the map doesn't contain the specified mapping.
765    * @return The converted value, or the default value if the map contains no mapping for this key.
766    * @throws InvalidDataConversionException If value cannot be converted.
767    */
768   public Integer getInt(String key, Integer defVal) {
769      return getWithDefault(key, defVal, Integer.class);
770   }
771
772   /**
773    * Returns the specified entry value converted to a {@link Long}.
774    * 
775    * <p>
776    * Shortcut for <code>get(key, Long.<jk>class</jk>)</code>.
777    * 
778    * @param key The key.
779    * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
780    * @throws InvalidDataConversionException If value cannot be converted.
781    */
782   public Long getLong(String key) {
783      return get(key, Long.class);
784   }
785
786   /**
787    * Returns the specified entry value converted to a {@link Long}.
788    * 
789    * <p>
790    * Shortcut for <code>getWithDefault(key, defVal, Long.<jk>class</jk>)</code>.
791    * 
792    * @param key The key.
793    * @param defVal The default value if the map doesn't contain the specified mapping.
794    * @return The converted value, or the default value if the map contains no mapping for this key.
795    * @throws InvalidDataConversionException If value cannot be converted.
796    */
797   public Long getLong(String key, Long defVal) {
798      return getWithDefault(key, defVal, Long.class);
799   }
800
801   /**
802    * Returns the specified entry value converted to a {@link Boolean}.
803    * 
804    * <p>
805    * Shortcut for <code>get(key, Boolean.<jk>class</jk>)</code>.
806    * 
807    * @param key The key.
808    * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
809    * @throws InvalidDataConversionException If value cannot be converted.
810    */
811   public Boolean getBoolean(String key) {
812      return get(key, Boolean.class);
813   }
814
815   /**
816    * Returns the specified entry value converted to a {@link Boolean}.
817    * 
818    * <p>
819    * Shortcut for <code>getWithDefault(key, defVal, Boolean.<jk>class</jk>)</code>.
820    * 
821    * @param key The key.
822    * @param defVal The default value if the map doesn't contain the specified mapping.
823    * @return The converted value, or the default value if the map contains no mapping for this key.
824    * @throws InvalidDataConversionException If value cannot be converted.
825    */
826   public Boolean getBoolean(String key, Boolean defVal) {
827      return getWithDefault(key, defVal, Boolean.class);
828   }
829
830   /**
831    * Returns the specified entry value converted to a {@link Map}.
832    * 
833    * <p>
834    * Shortcut for <code>get(key, Map.<jk>class</jk>)</code>.
835    * 
836    * @param key The key.
837    * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
838    * @throws InvalidDataConversionException If value cannot be converted.
839    */
840   public Map<?,?> getMap(String key) {
841      return get(key, Map.class);
842   }
843
844   /**
845    * Returns the specified entry value converted to a {@link Map}.
846    * 
847    * <p>
848    * Shortcut for <code>getWithDefault(key, defVal, Map.<jk>class</jk>)</code>.
849    * 
850    * @param key The key.
851    * @param defVal The default value if the map doesn't contain the specified mapping.
852    * @return The converted value, or the default value if the map contains no mapping for this key.
853    * @throws InvalidDataConversionException If value cannot be converted.
854    */
855   public Map<?,?> getMap(String key, Map<?,?> defVal) {
856      return getWithDefault(key, defVal, Map.class);
857   }
858
859   /**
860    * Same as {@link #getMap(String, Map)} except converts the keys and values to the specified types.
861    * 
862    * @param key The key.
863    * @param keyType The key type class.
864    * @param valType The value type class.
865    * @param def The default value if the map doesn't contain the specified mapping.
866    * @return The converted value, or the default value if the map contains no mapping for this key.
867    * @throws InvalidDataConversionException If value cannot be converted.
868    */
869   public <K,V> Map<K,V> getMap(String key, Class<K> keyType, Class<V> valType, Map<K,V> def) {
870      Object o = get(key);
871      if (o == null)
872         return def;
873      return session.convertToType(o, Map.class, keyType, valType);
874   }
875
876   /**
877    * Returns the specified entry value converted to a {@link List}.
878    * 
879    * <p>
880    * Shortcut for <code>get(key, List.<jk>class</jk>)</code>.
881    * 
882    * @param key The key.
883    * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
884    * @throws InvalidDataConversionException If value cannot be converted.
885    */
886   public List<?> getList(String key) {
887      return get(key, List.class);
888   }
889
890   /**
891    * Returns the specified entry value converted to a {@link List}.
892    * 
893    * <p>
894    * Shortcut for <code>getWithDefault(key, defVal, List.<jk>class</jk>)</code>.
895    * 
896    * @param key The key.
897    * @param defVal The default value if the map doesn't contain the specified mapping.
898    * @return The converted value, or the default value if the map contains no mapping for this key.
899    * @throws InvalidDataConversionException If value cannot be converted.
900    */
901   public List<?> getList(String key, List<?> defVal) {
902      return getWithDefault(key, defVal, List.class);
903   }
904
905   /**
906    * Same as {@link #getList(String, List)} except converts the elements to the specified types.
907    * 
908    * @param key The key.
909    * @param elementType The element type class.
910    * @param def The default value if the map doesn't contain the specified mapping.
911    * @return The converted value, or the default value if the map contains no mapping for this key.
912    * @throws InvalidDataConversionException If value cannot be converted.
913    */
914   public <E> List<E> getList(String key, Class<E> elementType, List<E> def) {
915      Object o = get(key);
916      if (o == null)
917         return def;
918      return session.convertToType(o, List.class, elementType);
919   }
920
921   /**
922    * Returns the specified entry value converted to a {@link Map}.
923    * 
924    * <p>
925    * Shortcut for <code>get(key, ObjectMap.<jk>class</jk>)</code>.
926    * 
927    * @param key The key.
928    * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
929    * @throws InvalidDataConversionException If value cannot be converted.
930    */
931   public ObjectMap getObjectMap(String key) {
932      return get(key, ObjectMap.class);
933   }
934
935   /**
936    * Returns the specified entry value converted to a {@link ObjectMap}.
937    * 
938    * <p>
939    * Shortcut for <code>getWithDefault(key, defVal, ObjectMap.<jk>class</jk>)</code>.
940    * 
941    * @param key The key.
942    * @param defVal The default value if the map doesn't contain the specified mapping.
943    * @return The converted value, or the default value if the map contains no mapping for this key.
944    * @throws InvalidDataConversionException If value cannot be converted.
945    */
946   public ObjectMap getObjectMap(String key, ObjectMap defVal) {
947      return getWithDefault(key, defVal, ObjectMap.class);
948   }
949
950   /**
951    * Returns the specified entry value converted to a {@link ObjectList}.
952    * 
953    * <p>
954    * Shortcut for <code>get(key, ObjectList.<jk>class</jk>)</code>.
955    * 
956    * @param key The key.
957    * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key.
958    * @throws InvalidDataConversionException If value cannot be converted.
959    */
960   public ObjectList getObjectList(String key) {
961      return get(key, ObjectList.class);
962   }
963
964   /**
965    * Returns the specified entry value converted to a {@link ObjectList}.
966    * 
967    * <p>
968    * Shortcut for <code>getWithDefault(key, defVal, ObjectList.<jk>class</jk>)</code>.
969    * 
970    * @param key The key.
971    * @param defVal The default value if the map doesn't contain the specified mapping.
972    * @return The converted value, or the default value if the map contains no mapping for this key.
973    * @throws InvalidDataConversionException If value cannot be converted.
974    */
975   public ObjectList getObjectList(String key, ObjectList defVal) {
976      return getWithDefault(key, defVal, ObjectList.class);
977   }
978
979   /**
980    * Returns the first entry that exists converted to a {@link String}.
981    * 
982    * <p>
983    * Shortcut for <code>find(String.<jk>class</jk>, keys)</code>.
984    * 
985    * @param keys The list of keys to look for.
986    * @return
987    *    The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
988    *    contains no mapping for any of the keys.
989    */
990   public String findString(String... keys) {
991      return find(String.class, keys);
992   }
993
994   /**
995    * Returns the first entry that exists converted to an {@link Integer}.
996    * 
997    * <p>
998    * Shortcut for <code>find(Integer.<jk>class</jk>, keys)</code>.
999    * 
1000    * @param keys The list of keys to look for.
1001    * @return
1002    *    The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
1003    *    contains no mapping for any of the keys.
1004    * @throws InvalidDataConversionException If value cannot be converted.
1005    */
1006   public Integer findInt(String... keys) {
1007      return find(Integer.class, keys);
1008   }
1009
1010   /**
1011    * Returns the first entry that exists converted to a {@link Long}.
1012    * 
1013    * <p>
1014    * Shortcut for <code>find(Long.<jk>class</jk>, keys)</code>.
1015    * 
1016    * @param keys The list of keys to look for.
1017    * @return
1018    *    The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
1019    *    contains no mapping for any of the keys.
1020    * @throws InvalidDataConversionException If value cannot be converted.
1021    */
1022   public Long findLong(String... keys) {
1023      return find(Long.class, keys);
1024   }
1025
1026   /**
1027    * Returns the first entry that exists converted to a {@link Boolean}.
1028    * 
1029    * <p>
1030    * Shortcut for <code>find(Boolean.<jk>class</jk>, keys)</code>.
1031    * 
1032    * @param keys The list of keys to look for.
1033    * @return
1034    *    The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
1035    *    contains no mapping for any of the keys.
1036    * @throws InvalidDataConversionException If value cannot be converted.
1037    */
1038   public Boolean findBoolean(String... keys) {
1039      return find(Boolean.class, keys);
1040   }
1041
1042   /**
1043    * Returns the first entry that exists converted to a {@link Map}.
1044    * 
1045    * <p>
1046    * Shortcut for <code>find(Map.<jk>class</jk>, keys)</code>.
1047    * 
1048    * @param keys The list of keys to look for.
1049    * @return
1050    *    The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
1051    *    contains no mapping for any of the keys.
1052    * @throws InvalidDataConversionException If value cannot be converted.
1053    */
1054   public Map<?,?> findMap(String... keys) {
1055      return find(Map.class, keys);
1056   }
1057
1058   /**
1059    * Returns the first entry that exists converted to a {@link List}.
1060    * 
1061    * <p>
1062    * Shortcut for <code>find(List.<jk>class</jk>, keys)</code>.
1063    * 
1064    * @param keys The list of keys to look for.
1065    * @return
1066    *    The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
1067    *    contains no mapping for any of the keys.
1068    * @throws InvalidDataConversionException If value cannot be converted.
1069    */
1070   public List<?> findList(String... keys) {
1071      return find(List.class, keys);
1072   }
1073
1074   /**
1075    * Returns the first entry that exists converted to a {@link ObjectMap}.
1076    * 
1077    * <p>
1078    * Shortcut for <code>find(ObjectMap.<jk>class</jk>, keys)</code>.
1079    * 
1080    * @param keys The list of keys to look for.
1081    * @return
1082    *    The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
1083    *    contains no mapping for any of the keys.
1084    * @throws InvalidDataConversionException If value cannot be converted.
1085    */
1086   public ObjectMap findObjectMap(String... keys) {
1087      return find(ObjectMap.class, keys);
1088   }
1089
1090   /**
1091    * Returns the first entry that exists converted to a {@link ObjectList}.
1092    * 
1093    * <p>
1094    * Shortcut for <code>find(ObjectList.<jk>class</jk>, keys)</code>.
1095    * 
1096    * @param keys The list of keys to look for.
1097    * @return
1098    *    The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map
1099    *    contains no mapping for any of the keys.
1100    * @throws InvalidDataConversionException If value cannot be converted.
1101    */
1102   public ObjectList findObjectList(String... keys) {
1103      return find(ObjectList.class, keys);
1104   }
1105
1106   /**
1107    * Returns the first key in the map.
1108    * 
1109    * @return The first key in the map, or <jk>null</jk> if the map is empty.
1110    */
1111   public String getFirstKey() {
1112      return isEmpty() ? null : keySet().iterator().next();
1113   }
1114
1115   /**
1116    * Returns the class type of the object at the specified index.
1117    * 
1118    * @param key The key into this map.
1119    * @return
1120    *    The data type of the object at the specified key, or <jk>null</jk> if the value is null or does not exist.
1121    */
1122   public ClassMeta<?> getClassMeta(String key) {
1123      return session.getClassMetaForObject(get(key));
1124   }
1125
1126   /**
1127    * Equivalent to calling <code>get(class,key,def)</code> followed by <code>remove(key);</code>
1128    * @param key The key.
1129    * @param defVal The default value if the map doesn't contain the specified mapping.
1130    * @param type The class type.
1131    * 
1132    * @param <T> The class type.
1133    * @return The converted value, or the default value if the map contains no mapping for this key.
1134    * @throws InvalidDataConversionException If value cannot be converted.
1135    */
1136   public <T> T removeWithDefault(String key, T defVal, Class<T> type) {
1137      T t = getWithDefault(key, defVal, type);
1138      remove(key);
1139      return t;
1140   }
1141
1142
1143   /**
1144    * Convenience method for removing several keys at once.
1145    * 
1146    * @param keys The list of keys to remove.
1147    */
1148   public void removeAll(Collection<String> keys) {
1149      for (String k : keys)
1150         remove(k);
1151   }
1152
1153   /**
1154    * Convenience method for removing several keys at once.
1155    * 
1156    * @param keys The list of keys to remove.
1157    */
1158   public void removeAll(String... keys) {
1159      for (String k : keys)
1160         remove(k);
1161   }
1162
1163   @Override /* Map */
1164   public boolean containsKey(Object key) {
1165      if (super.containsKey(key))
1166         return true;
1167      if (inner != null)
1168         return inner.containsKey(key);
1169      return false;
1170   }
1171
1172   /**
1173    * Returns <jk>true</jk> if this map contains the specified key, ignoring the inner map if it exists.
1174    * 
1175    * @param key The key to look up.
1176    * @return <jk>true</jk> if this map contains the specified key.
1177    */
1178   public boolean containsOuterKey(Object key) {
1179      return super.containsKey(key);
1180   }
1181
1182   /**
1183    * Returns a copy of this <code>ObjectMap</code> with only the specified keys.
1184    * 
1185    * @param keys The keys of the entries to copy.
1186    * @return A new map with just the keys and values from this map.
1187    */
1188   public ObjectMap include(String...keys) {
1189      ObjectMap m2 = new ObjectMap();
1190      for (Map.Entry<String,Object> e : this.entrySet())
1191         for (String k : keys)
1192            if (k.equals(e.getKey()))
1193               m2.put(k, e.getValue());
1194      return m2;
1195   }
1196
1197   /**
1198    * Returns a copy of this <code>ObjectMap</code> without the specified keys.
1199    * 
1200    * @param keys The keys of the entries not to copy.
1201    * @return A new map without the keys and values from this map.
1202    */
1203   public ObjectMap exclude(String...keys) {
1204      ObjectMap m2 = new ObjectMap();
1205      for (Map.Entry<String,Object> e : this.entrySet()) {
1206         boolean exclude = false;
1207         for (String k : keys)
1208            if (k.equals(e.getKey()))
1209               exclude = true;
1210         if (! exclude)
1211            m2.put(e.getKey(), e.getValue());
1212      }
1213      return m2;
1214   }
1215
1216   /**
1217    * Sets a value in this map if the entry does not exist or the value is <jk>null</jk>.
1218    * 
1219    * @param key The map key.
1220    * @param val The value to set if the current value does not exist or is <jk>null</jk>.
1221    * @return This object (for method chaining).
1222    */
1223   public ObjectMap putIfNull(String key, Object val) {
1224      Object o = get(key);
1225      if (o == null)
1226         put(key, val);
1227      return this;
1228   }
1229
1230   /**
1231    * Sets a value in this map if the entry does not exist or the value is <jk>null</jk> or an empty string.
1232    * 
1233    * @param key The map key.
1234    * @param val The value to set if the current value does not exist or is <jk>null</jk> or an empty string.
1235    * @return This object (for method chaining).
1236    */
1237   public ObjectMap putIfEmpty(String key, Object val) {
1238      Object o = get(key);
1239      if (o == null || o.toString().isEmpty())
1240         put(key, val);
1241      return this;
1242   }
1243
1244   /**
1245    * Converts this map into an object of the specified type.
1246    * 
1247    * <p>
1248    * If this map contains a <js>"_type"</js> entry, it must be the same as or a subclass of the <code>type</code>.
1249    * 
1250    * @param <T> The class type to convert this map object to.
1251    * @param type The class type to convert this map object to.
1252    * @return The new object.
1253    * @throws ClassCastException
1254    *    If the <js>"_type"</js> entry is present and not assignable from <code>type</code>
1255    */
1256   @SuppressWarnings("unchecked")
1257   public <T> T cast(Class<T> type) {
1258      ClassMeta<?> c2 = session.getClassMeta(type);
1259      String typePropertyName = session.getBeanTypePropertyName(c2);
1260      ClassMeta<?> c1 = session.getBeanRegistry().getClassMeta((String)get(typePropertyName));
1261      ClassMeta<?> c = c1 == null ? c2 : narrowClassMeta(c1, c2);
1262      if (c.isObject())
1263         return (T)this;
1264      return (T)cast2(c);
1265   }
1266
1267   /**
1268    * Same as {@link #cast(Class)}, except allows you to specify a {@link ClassMeta} parameter.
1269    * 
1270    * @param <T> The class type to convert this map object to.
1271    * @param cm The class type to convert this map object to.
1272    * @return The new object.
1273    * @throws ClassCastException
1274    *    If the <js>"_type"</js> entry is present and not assignable from <code>type</code>
1275    */
1276   @SuppressWarnings({"unchecked"})
1277   public <T> T cast(ClassMeta<T> cm) {
1278      ClassMeta<?> c1 = session.getBeanRegistry().getClassMeta((String)get(session.getBeanTypePropertyName(cm)));
1279      ClassMeta<?> c = narrowClassMeta(c1, cm);
1280      return (T)cast2(c);
1281   }
1282
1283   /*
1284    * Combines the class specified by a "_type" attribute with the ClassMeta
1285    * passed in through the cast(ClassMeta) method.
1286    * The rule is that child classes supersede parent classes, and c2 supersedes c1
1287    * if one isn't the parent of another.
1288    */
1289   private ClassMeta<?> narrowClassMeta(ClassMeta<?> c1, ClassMeta<?> c2) {
1290      if (c1 == null)
1291         return c2;
1292      ClassMeta<?> c = getNarrowedClassMeta(c1, c2);
1293      if (c1.isMap()) {
1294         ClassMeta<?> k = getNarrowedClassMeta(c1.getKeyType(), c2.getKeyType());
1295         ClassMeta<?> v = getNarrowedClassMeta(c1.getValueType(), c2.getValueType());
1296         return session.getClassMeta(c.getInnerClass(), k, v);
1297      }
1298      if (c1.isCollection()) {
1299         ClassMeta<?> e = getNarrowedClassMeta(c1.getElementType(), c2.getElementType());
1300         return session.getClassMeta(c.getInnerClass(), e);
1301      }
1302      return c;
1303   }
1304
1305   /*
1306    * If c1 is a child of c2 or the same as c2, returns c1.
1307    * Otherwise, returns c2.
1308    */
1309   private static ClassMeta<?> getNarrowedClassMeta(ClassMeta<?> c1, ClassMeta<?> c2) {
1310      if (c2 == null || isParentClass(c2.getInnerClass(), c1.getInnerClass()))
1311         return c1;
1312      return c2;
1313   }
1314
1315   /*
1316    * Converts this map to the specified class type.
1317    */
1318   @SuppressWarnings({"unchecked","rawtypes"})
1319   private <T> T cast2(ClassMeta<T> cm) {
1320
1321      try {
1322         Object value = get("value");
1323
1324         if (cm.isMap()) {
1325            Map m2 = (cm.canCreateNewInstance() ? (Map)cm.newInstance() : new ObjectMap(session));
1326            ClassMeta<?> kType = cm.getKeyType(), vType = cm.getValueType();
1327            for (Map.Entry<String,Object> e : entrySet()) {
1328               Object k = e.getKey();
1329               Object v = e.getValue();
1330               if (! k.equals(session.getBeanTypePropertyName(cm))) {
1331
1332                  // Attempt to recursively cast child maps.
1333                  if (v instanceof ObjectMap)
1334                     v = ((ObjectMap)v).cast(vType);
1335
1336                  k = (kType.isString() ? k : session.convertToType(k, kType));
1337                  v = (vType.isObject() ? v : session.convertToType(v, vType));
1338
1339                  m2.put(k, v);
1340               }
1341            }
1342            return (T)m2;
1343
1344         } else if (cm.isBean()) {
1345            BeanMap<? extends T> bm = session.newBeanMap(cm.getInnerClass());
1346
1347            // Iterate through all the entries in the map and set the individual field values.
1348            for (Map.Entry<String,Object> e : entrySet()) {
1349               String k = e.getKey();
1350               Object v = e.getValue();
1351               if (! k.equals(session.getBeanTypePropertyName(cm))) {
1352
1353                  // Attempt to recursively cast child maps.
1354                  if (v instanceof ObjectMap)
1355                     v = ((ObjectMap)v).cast(bm.getProperty(k).getMeta().getClassMeta());
1356
1357                  bm.put(k, v);
1358               }
1359            }
1360
1361            return bm.getBean();
1362
1363         } else if (cm.isCollectionOrArray()) {
1364            List items = (List)get("items");
1365            return session.convertToType(items, cm);
1366
1367         } else if (value != null) {
1368            return session.convertToType(value, cm);
1369         }
1370
1371      } catch (Exception e) {
1372         throw new BeanRuntimeException(cm.innerClass,
1373            "Error occurred attempting to cast to an object of type ''{0}''", cm.innerClass.getName()).initCause(e);
1374      }
1375
1376      throw new BeanRuntimeException(cm.innerClass,
1377         "Cannot convert to class type ''{0}''.  Only beans and maps can be converted using this method.",
1378         cm.innerClass.getName());
1379   }
1380
1381   private PojoRest getPojoRest() {
1382      if (pojoRest == null)
1383         pojoRest = new PojoRest(this);
1384      return pojoRest;
1385   }
1386
1387   /**
1388    * Serialize this object into a string using the specified serializer.
1389    * 
1390    * @param serializer The serializer to use to convert this object to a string.
1391    * @return This object serialized as a string.
1392    * @throws SerializeException If a problem occurred trying to convert the output.
1393    */
1394   public String toString(WriterSerializer serializer) throws SerializeException {
1395      return serializer.serialize(this);
1396   }
1397
1398   /**
1399    * Serialize this object into a JSON string using the {@link JsonSerializer#DEFAULT} serializer.
1400    */
1401   @Override /* Object */
1402   public String toString() {
1403      try {
1404         return this.toString(JsonSerializer.DEFAULT_LAX);
1405      } catch (SerializeException e) {
1406         return e.getLocalizedMessage();
1407      }
1408   }
1409
1410   /**
1411    * Convenience method for serializing this map to the specified <code>Writer</code> using the
1412    * {@link JsonSerializer#DEFAULT} serializer.
1413    * 
1414    * @param w The writer to serialize this object to.
1415    * @return This object (for method chaining).
1416    * @throws IOException If a problem occurred trying to write to the writer.
1417    * @throws SerializeException If a problem occurred trying to convert the output.
1418    */
1419   public ObjectMap serializeTo(Writer w) throws IOException, SerializeException {
1420      JsonSerializer.DEFAULT.serialize(this);
1421      return this;
1422   }
1423
1424   @Override /* Map */
1425   public Set<String> keySet() {
1426      if (inner == null)
1427         return super.keySet();
1428      LinkedHashSet<String> s = new LinkedHashSet<>();
1429      s.addAll(inner.keySet());
1430      s.addAll(super.keySet());
1431      return s;
1432   }
1433
1434   @Override /* Map */
1435   public Set<Map.Entry<String,Object>> entrySet() {
1436      if (inner == null)
1437         return super.entrySet();
1438
1439      final Set<String> keySet = keySet();
1440      final Iterator<String> keys = keySet.iterator();
1441
1442      return new AbstractSet<Map.Entry<String,Object>>() {
1443
1444         @Override /* Iterable */
1445         public Iterator<Map.Entry<String,Object>> iterator() {
1446
1447            return new Iterator<Map.Entry<String,Object>>() {
1448
1449               @Override /* Iterator */
1450               public boolean hasNext() {
1451                  return keys.hasNext();
1452               }
1453
1454               @Override /* Iterator */
1455               public Map.Entry<String,Object> next() {
1456                  return new Map.Entry<String,Object>() {
1457                     String key = keys.next();
1458
1459                     @Override /* Map.Entry */
1460                     public String getKey() {
1461                        return key;
1462                     }
1463
1464                     @Override /* Map.Entry */
1465                     public Object getValue() {
1466                        return get(key);
1467                     }
1468
1469                     @Override /* Map.Entry */
1470                     public Object setValue(Object object) {
1471                        return put(key, object);
1472                     }
1473                  };
1474               }
1475
1476               @Override /* Iterator */
1477               public void remove() {
1478                  throw new UnsupportedOperationException();
1479               }
1480            };
1481         }
1482
1483         @Override /* Set */
1484         public int size() {
1485            return keySet.size();
1486         }
1487      };
1488   }
1489}