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