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