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