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