All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.ObjectMap Maven / Gradle / Ivy

There is a newer version: 9.0.1
Show newest version
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
// * with the License.  You may obtain a copy of the License at                                                              *
// *                                                                                                                         *
// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
// *                                                                                                                         *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
// * specific language governing permissions and limitations under the License.                                              *
// ***************************************************************************************************************************
package org.apache.juneau;

import static org.apache.juneau.internal.ClassUtils.*;
import static org.apache.juneau.internal.StringUtils.*;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;

import org.apache.juneau.internal.*;
import org.apache.juneau.json.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.transform.*;
import org.apache.juneau.utils.*;

/**
 * Java implementation of a JSON object.
 *
 * 

* An extension of {@link LinkedHashMap}, so all methods available in that class are also available to this class. *

* Note that the use of this class is optional. * The serializers will accept any objects that implement the {@link java.util.Map} interface. * But this class provides some useful additional functionality when working with JSON models constructed from Java * Collections Framework objects. * For example, a constructor is provided for converting a JSON object string directly into a {@link Map}. * It also contains accessor methods for to avoid common typecasting when accessing elements in a list. * *

Example:
*

* // Construct an empty Map * Map m = new ObjectMap(); * * // Construct a Map from JSON * String json = "{a:'A',b:{c:'C',d:123}}"; * m = new ObjectMap(json); * * // Construct a Map using the append method * m = new ObjectMap().append("foo","x").append("bar",123) * .append("baz",true); * * // Construct a Map from XML generated by XmlSerializer * String xml = "<object><a type='string'>A</a><b type='object'><c type='string'>C</c><d type='number'>123</d></b></object>"; * m = new ObjectMap(xml, DataFormat.XML); * m = (Map)XmlParser.DEFAULT.parse(xml); // Equivalent * m = (Map)XmlParser.DEFAULT.parse(Object.class, xml); // Equivalent * m = XmlParser.DEFAULT.parse(Map.class, xml); // Equivalent * m = XmlParser.DEFAULT.parse(ObjectMap.class, xml); // Equivalent * * // Construct a Map from a URL GET parameter string generated by UrlEncodingParser * String urlParams = "?a='A'&b={c:'C',d:123}"; * m = new ObjectMap(urlParams, DataFormat.URLPARAM); * m = (Map)UrlEncodingParser.DEFAULT.parse(Object.class, xml); // Equivalent * m = UrlEncodingParser.DEFAULT.parse(Map.class, xml); // Equivalent * m = UrlEncodingParser.DEFAULT.parse(ObjectMap.class, xml); // Equivalent * * // Construct JSON from ObjectMap * m = new ObjectMap("{foo:'bar'},{baz:[123,true]}"); * json = m.toString(); // Produces "{foo:'bar'},{baz:[123,true]}" * json = m.toString(JsonSerializer.DEFAULT_CONDENSED); // Equivalent * json = JsonSerializer.DEFAULT_CONDENSED.serialize(m); // Equivalent * * // Get a map entry as an Integer * m = new ObjectMap("{foo:123}"); * Integer i = m.getInt("foo"); * i = m.get(Integer.class, "foo"); // Equivalent * * // Get a map entry as a Float * m = new ObjectMap("{foo:123}"); * Float f = m.getFloat("foo"); * f = m.get(Float.class, "foo"); // Equivalent * * // Same as above, except converted to a String * m = new ObjectMap("{foo:123}"); * String s = m.getString("foo"); // Returns "123" * s = m.get(String.class, "foo"); // Equivalent * * // Get one of the entries in the list as a bean (converted to a bean if it isn't already one) * m = new ObjectMap("{person:{name:'John Smith',age:45}}"); * Person p = m.get(Person.class, "person"); * * // Add an inner map * ObjectMap m1 = new ObjectMap("{a:1}"); * ObjectMap m2 = new ObjectMap("{b:2}").setInner(m1); * int a = m2.getInt("a"); // a == 1 *

* *

* This class is not thread safe. */ public class ObjectMap extends LinkedHashMap { private static final long serialVersionUID = 1L; private transient BeanSession session; private Map inner; private transient PojoRest pojoRest; /** * An empty read-only ObjectMap. */ public static final ObjectMap EMPTY_MAP = new ObjectMap() { private static final long serialVersionUID = 1L; @Override /* Map */ public Set> entrySet() { return Collections.EMPTY_MAP.entrySet(); } @Override /* Map */ public Set keySet() { return Collections.EMPTY_MAP.keySet(); } @Override /* Map */ public Object put(String key, Object value) { throw new UnsupportedOperationException(); } @Override /* Map */ public Object remove(Object key) { throw new UnsupportedOperationException(); } @Override /* Map */ public Collection values() { return Collections.emptyMap().values(); } }; /** * Construct an ObjectMap directly from a string using the specified parser. * * @param s The string being parsed. * @param p The parser to use to parse the input. * @throws ParseException If the input contains a syntax error or is malformed. */ public ObjectMap(CharSequence s, Parser p) throws ParseException { this(p == null ? null : p.createBeanSession()); if (p == null) p = JsonParser.DEFAULT; if (! StringUtils.isEmpty(s)) p.parseIntoMap(s, this, bs().string(), bs().object()); } /** * Shortcut for new ObjectMap(string,JsonParser.DEFAULT); * * @param s The JSON text to parse. * @throws ParseException If the input contains a syntax error or is malformed. */ public ObjectMap(CharSequence s) throws ParseException { this(s, null); } /** * Construct an ObjectMap directly from a reader using the specified parser. * * @param r The reader to read from. The reader will be wrapped in a {@link BufferedReader} if it isn't already. * @param p The parser to use to parse the input. * @throws ParseException If the input contains a syntax error or is malformed. * @throws IOException If a problem occurred trying to read from the reader. */ public ObjectMap(Reader r, Parser p) throws ParseException, IOException { this(p == null ? null : p.createBeanSession()); parseReader(r, p); } /** * Shortcut for new ObjectMap(reader, JsonParser.DEFAULT). * * @param r The reader to read from. The reader will be wrapped in a {@link BufferedReader} if it isn't already. * @throws ParseException If the input contains a syntax error or is malformed. * @throws IOException If a problem occurred trying to read from the reader. */ public ObjectMap(Reader r) throws ParseException, IOException { parseReader(r, JsonParser.DEFAULT); } private void parseReader(Reader r, Parser p) throws ParseException { if (p == null) p = JsonParser.DEFAULT; p.parseIntoMap(r, this, bs().string(), bs().object()); } /** * Construct an empty JSON object (an empty {@link LinkedHashMap}). */ public ObjectMap() { } /** * Construct an empty JSON object (an empty {@link LinkedHashMap}) with the specified bean context. * * @param session The bean session to use for creating beans. */ public ObjectMap(BeanSession session) { this.session = session; } /** * Construct a JSON object and fill it with the contents from the specified {@link Map}. * * @param m The map whose entries will be copied into this map. */ public ObjectMap(Map m) { this(); for (Map.Entry e : m.entrySet()) put(e.getKey().toString(), e.getValue()); } /** * Set an inner map in this map to allow for chained get calls. * *

* If {@link #get(Object)} returns null, then {@link #get(Object)} will be called on the inner map. * *

* In addition to providing the ability to chain maps, this method also provides the ability to wrap an existing map * inside another map so that you can add entries to the outer map without affecting the values on the inner map. * *

* ObjectMap m1 = new ObjectMap("{foo:1}"); * ObjectMap m2 = new ObjectMap().setInner(m1); * m2.put("foo", 2); // Overwrite the entry * int foo1 = m1.getInt("foo"); // foo1 == 1 * int foo2 = m2.getInt("foo"); // foo2 == 2 *

* * @param inner * The inner map. * Can be null to remove the inner map from an existing map. * @return This object (for method chaining). */ public ObjectMap setInner(Map inner) { this.inner = inner; return this; } /** * Searches for the specified key in this map ignoring case. * * @param key * The key to search for. * For performance reasons, it's preferable that the key be all lowercase. * @return The key, or null if map does not contain this key. */ public String findKeyIgnoreCase(String key) { for (String k : keySet()) if (key.equalsIgnoreCase(k)) return k; return null; } /** * Override the default bean session used for converting POJOs. * *

* Default is {@link BeanContext#DEFAULT}, which is sufficient in most cases. * *

* Useful if you're serializing/parsing beans with transforms defined. * * @param session The new bean session. * @return This object (for method chaining). */ public ObjectMap setBeanSession(BeanSession session) { this.session = session; return this; } /** * Returns the {@link BeanSession} currently associated with this map. * * @return The {@link BeanSession} currently associated with this map. */ public BeanSession getBeanSession() { return session; } /** * Convenience method for adding an entry to this map. * *

* Equivalent to calling {@code put(key, value)}, but returns this map so that the method can be chained. * * @param key The key. * @param value The value. * @return This object (for method chaining). */ public ObjectMap append(String key, Object value) { put(key, value); return this; } /** * Conditionally appends a value to this map. * * @param overwrite Overwrite the previous value if there was one. * @param skipNullValue Skip adding the value if the value is null. * @param skipEmptyValue Skip adding the value if the value is an empty string. * @param key The key. * @param value The value. * @return This object (for method chaining). */ public ObjectMap appendIf(boolean overwrite, boolean skipNullValue, boolean skipEmptyValue, String key, Object value) { if (value == null && skipNullValue) return this; if (skipEmptyValue && ObjectUtils.isEmpty(value)) return this; Object current = get(key); if (current == null || overwrite) put(key, value); return this; } /** * Conditionally appends a value to this map. * * @param flag The boolean value that must be true in order to add this entry.. * @param key The key. * @param value The value. * @return This object (for method chaining). */ public ObjectMap appendIf(boolean flag, String key, Object value) { if (flag) put(key, value); return this; } /** * Convenience method for adding an entry to this map. * *

* A no-op if the value is null or an empty string/map/collection. * * @param key The key. * @param value The value. * @return This object (for method chaining). */ public ObjectMap appendSkipEmpty(String key, Object value) { return appendIf(true, true, true, key, value); } /** * Convenience method for adding an entry to this map. * *

* A no-op if the value is false. * * @param key The key. * @param value The value. * @return This object (for method chaining). */ public ObjectMap appendSkipFalse(String key, boolean value) { if (value) append(key, value); return this; } /** * Convenience method for adding an entry to this map. * *

* A no-op if the value is -1. * * @param key The key. * @param value The value. * @return This object (for method chaining). */ public ObjectMap appendSkipMinusOne(String key, Number value) { if (value != null && value.intValue() != -1) append(key, value); return this; } /** * Convenience method for adding an entry to this map. * *

* Equivalent to calling {@code put(key, value)}, but returns this map so that the method can be chained. * *

* null values are skipped. * * @param key The key. * @param value The value. * @return This object (for method chaining). */ public ObjectMap appendSkipNull(String key, Object value) { if (value != null) append(key, value); return this; } /** * Convenience method for adding a contents of another map to this map. * *

* Equivalent to calling {@code putAll(m)}, but returns this map so that the method can be chained. * * @param m The map whose contents should be added to this map. * @return This object (for method chaining). */ public ObjectMap appendAll(Map m) { if (m != null) putAll(m); return this; } @Override /* Map */ public Object get(Object key) { Object o = super.get(key); if (o == null && inner != null) o = inner.get(key); return o; } /** * Same as {@link Map#get(Object) get()}, but casts or converts the value to the specified class type. * *

* This is the preferred get method for simple types. * *

Examples:
*

* ObjectMap m = new ObjectMap("..."); * * // Value converted to a string. * String s = m.get("key1", String.class); * * // Value converted to a bean. * MyBean b = m.get("key2", MyBean.class); * * // Value converted to a bean array. * MyBean[] ba = m.get("key3", MyBean[].class); * * // Value converted to a linked-list of objects. * List l = m.get("key4", LinkedList.class); * * // Value converted to a map of object keys/values. * Map m2 = m.get("key5", TreeMap.class); *

* *

* See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. * * @param key The key. * @param The class type returned. * @param type The class type returned. * @return The value, or null if the entry doesn't exist. */ public T get(String key, Class type) { return getWithDefault(key, (T)null, type); } /** * Same as {@link #get(String,Class)}, but allows for complex data types consisting of collections or maps. * *

* The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). * *

Examples:
*

* ObjectMap m = new ObjectMap("..."); * * // Value converted to a linked-list of strings. * List<String> l1 = m.get("key1", LinkedList.class, String.class); * * // Value converted to a linked-list of beans. * List<MyBean> l2 = m.get("key2", LinkedList.class, MyBean.class); * * // Value converted to a linked-list of linked-lists of strings. * List<List<String>> l3 = m.get("key3", LinkedList.class, LinkedList.class, String.class); * * // Value converted to a map of string keys/values. * Map<String,String> m1 = m.get("key4", TreeMap.class, String.class, String.class); * * // Value converted to a map containing string keys and values of lists containing beans. * Map<String,List<MyBean>> m2 = m.get("key5", TreeMap.class, String.class, List.class, MyBean.class); *

* *

* Collection classes are assumed to be followed by zero or one objects indicating the element type. * *

* Map classes are assumed to be followed by zero or two meta objects indicating the key and value types. * *

* The array can be arbitrarily long to indicate arbitrarily complex data structures. * *

* See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. * *

Notes:
*
    *
  • * Use the {@link #get(String, Class)} method instead if you don't need a parameterized map/collection. *
* * @param key The key. * @param The class type returned. * @param type The class type returned. * @param args The class type parameters. * @return The value, or null if the entry doesn't exist. */ public T get(String key, Type type, Type...args) { return getWithDefault(key, null, type, args); } /** * Same as {@link Map#get(Object) get()}, but returns the default value if the key could not be found. * * @param key The key. * @param def The default value if the entry doesn't exist. * @return The value, or the default value if the entry doesn't exist. */ public Object getWithDefault(String key, Object def) { Object o = get(key); return (o == null ? def : o); } /** * Same as {@link #get(String,Class)} but returns a default value if the value does not exist. * * @param key The key. * @param def The default value. Can be null. * @param The class type returned. * @param type The class type returned. * @return The value, or null if the entry doesn't exist. */ public T getWithDefault(String key, T def, Class type) { return getWithDefault(key, def, type, new Type[0]); } /** * Same as {@link #get(String,Type,Type...)} but returns a default value if the value does not exist. * * @param key The key. * @param def The default value. Can be null. * @param The class type returned. * @param type The class type returned. * @param args The class type parameters. * @return The value, or null if the entry doesn't exist. */ public T getWithDefault(String key, T def, Type type, Type...args) { Object o = get(key); if (o == null) return def; T t = bs().convertToType(o, type, args); return t == null ? def : t; } /** * Same as {@link Map#get(Object) get()}, but converts the raw value to the specified class type using the specified * POJO swap. * * @param key The key. * @param pojoSwap The swap class used to convert the raw type to a transformed type. * @param The transformed class type. * @return The value, or null if the entry doesn't exist. * @throws ParseException Thrown by the POJO swap if a problem occurred trying to parse the value. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public T getSwapped(String key, PojoSwap pojoSwap) throws ParseException { try { Object o = super.get(key); if (o == null) return null; PojoSwap swap = pojoSwap; return (T)swap.unswap(bs(), o, null); } catch (ParseException e) { throw e; } catch (Exception e) { throw new ParseException(e); } } /** * Returns the value for the first key in the list that has an entry in this map. * * @param keys The keys to look up in order. * @return The value of the first entry whose key exists, or null if none of the keys exist in this map. */ public Object find(String...keys) { for (String key : keys) if (containsKey(key)) return get(key); return null; } /** * Returns the value for the first key in the list that has an entry in this map. * *

* Casts or converts the value to the specified class type. * *

* See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. * * @param type The class type to convert the value to. * @param The class type to convert the value to. * @param keys The keys to look up in order. * @return The value of the first entry whose key exists, or null if none of the keys exist in this map. */ public T find(Class type, String...keys) { for (String key : keys) if (containsKey(key)) return get(key, type); return null; } /** * Same as {@link #get(String,Class) get(String,Class)}, but the key is a slash-delimited path used to traverse * entries in this POJO. * *

* For example, the following code is equivalent: *

*

* ObjectMap m = getObjectMap(); * * // Long way * long l = m.getObjectMap("foo").getObjectList("bar").getObjectMap("0") * .getLong("baz"); * * // Using this method * long l = m.getAt("foo/bar/0/baz", long.class); *

* *

* This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). * * @param path The path to the entry. * @param type The class type. * * @param The class type. * @return The value, or null if the entry doesn't exist. */ public T getAt(String path, Class type) { return getPojoRest().get(path, type); } /** * Same as {@link #getAt(String,Class)}, but allows for conversion to complex maps and collections. * *

* This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). * * @param path The path to the entry. * @param type The class type. * @param args The class parameter types. * * @param The class type. * @return The value, or null if the entry doesn't exist. */ public T getAt(String path, Type type, Type...args) { return getPojoRest().get(path, type, args); } /** * Same as put(String,Object), but the key is a slash-delimited path used to traverse entries in this * POJO. * *

* For example, the following code is equivalent: *

*

* ObjectMap m = getObjectMap(); * * // Long way * m.getObjectMap("foo").getObjectList("bar").getObjectMap("0") * .put("baz", 123); * * // Using this method * m.putAt("foo/bar/0/baz", 123); *

* *

* This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). * * @param path The path to the entry. * @param o The new value. * @return The previous value, or null if the entry doesn't exist. */ public Object putAt(String path, Object o) { return getPojoRest().put(path, o); } /** * Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append to collections and arrays. * *

* For example, the following code is equivalent: *

*

* ObjectMap m = getObjectMap(); * * // Long way * m.getObjectMap("foo").getObjectList("bar").append(123); * * // Using this method * m.postAt("foo/bar", 123); *

* *

* This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). * * @param path The path to the entry. * @param o The new value. * @return The previous value, or null if the entry doesn't exist. */ public Object postAt(String path, Object o) { return getPojoRest().post(path, o); } /** * Similar to {@link #remove(Object) remove(Object)}, but the key is a slash-delimited path used to traverse entries * in this POJO. * *

* For example, the following code is equivalent: *

*

* ObjectMap m = getObjectMap(); * * // Long way * m.getObjectMap("foo").getObjectList("bar").getObjectMap(0).remove("baz"); * * // Using this method * m.deleteAt("foo/bar/0/baz"); *

* *

* This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). * * @param path The path to the entry. * @return The previous value, or null if the entry doesn't exist. */ public Object deleteAt(String path) { return getPojoRest().delete(path); } /** * Convenience method for inserting JSON directly into an attribute on this object. * *

* The JSON text can be an object (i.e. "{...}") or an array (i.e. "[...]"). * * @param key The key. * @param json The JSON text that will be parsed into an Object and then inserted into this map. * @throws ParseException If the input contains a syntax error or is malformed. */ public void putJson(String key, String json) throws ParseException { this.put(key, JsonParser.DEFAULT.parse(json, Object.class)); } /** * Returns the specified entry value converted to a {@link String}. * *

* Shortcut for get(key, String.class). * * @param key The key. * @return The converted value, or null if the map contains no mapping for this key. */ public String getString(String key) { return get(key, String.class); } /** * Returns the specified entry value converted to a {@link String}. * *

* Shortcut for get(key, String[].class). * * @param key The key. * @return The converted value, or null if the map contains no mapping for this key. */ public String[] getStringArray(String key) { return getStringArray(key, null); } /** * Same as {@link #getStringArray(String)} but returns a default value if the value cannot be found. * * @param key The map key. * @param def The default value if value is not found. * @return The value converted to a string array. */ public String[] getStringArray(String key, String[] def) { Object s = get(key, Object.class); if (s == null) return def; String[] r = null; if (s instanceof Collection) r = ArrayUtils.toStringArray((Collection)s); else if (s instanceof String[]) r = (String[])s; else if (s instanceof Object[]) r = ArrayUtils.toStringArray(Arrays.asList((Object[])s)); else r = split(asString(s)); return (r.length == 0 ? def : r); } /** * Returns the specified entry value converted to a {@link String}. * *

* Shortcut for getWithDefault(key, defVal, String.class). * * @param key The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. */ public String getString(String key, String defVal) { return getWithDefault(key, defVal, String.class); } /** * Returns the specified entry value converted to an {@link Integer}. * *

* Shortcut for get(key, Integer.class). * * @param key The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Integer getInt(String key) { return get(key, Integer.class); } /** * Returns the specified entry value converted to an {@link Integer}. * *

* Shortcut for getWithDefault(key, defVal, Integer.class). * * @param key The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Integer getInt(String key, Integer defVal) { return getWithDefault(key, defVal, Integer.class); } /** * Returns the specified entry value converted to a {@link Long}. * *

* Shortcut for get(key, Long.class). * * @param key The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Long getLong(String key) { return get(key, Long.class); } /** * Returns the specified entry value converted to a {@link Long}. * *

* Shortcut for getWithDefault(key, defVal, Long.class). * * @param key The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Long getLong(String key, Long defVal) { return getWithDefault(key, defVal, Long.class); } /** * Returns the specified entry value converted to a {@link Boolean}. * *

* Shortcut for get(key, Boolean.class). * * @param key The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Boolean getBoolean(String key) { return get(key, Boolean.class); } /** * Returns the specified entry value converted to a {@link Boolean}. * *

* Shortcut for getWithDefault(key, defVal, Boolean.class). * * @param key The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Boolean getBoolean(String key, Boolean defVal) { return getWithDefault(key, defVal, Boolean.class); } /** * Returns the specified entry value converted to a {@link Map}. * *

* Shortcut for get(key, Map.class). * * @param key The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Map getMap(String key) { return get(key, Map.class); } /** * Returns the specified entry value converted to a {@link Map}. * *

* Shortcut for getWithDefault(key, defVal, Map.class). * * @param key The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Map getMap(String key, Map defVal) { return getWithDefault(key, defVal, Map.class); } /** * Same as {@link #getMap(String, Map)} except converts the keys and values to the specified types. * * @param key The key. * @param keyType The key type class. * @param valType The value type class. * @param def The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public Map getMap(String key, Class keyType, Class valType, Map def) { Object o = get(key); if (o == null) return def; return bs().convertToType(o, Map.class, keyType, valType); } /** * Returns the specified entry value converted to a {@link List}. * *

* Shortcut for get(key, List.class). * * @param key The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public List getList(String key) { return get(key, List.class); } /** * Returns the specified entry value converted to a {@link List}. * *

* Shortcut for getWithDefault(key, defVal, List.class). * * @param key The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public List getList(String key, List defVal) { return getWithDefault(key, defVal, List.class); } /** * Same as {@link #getList(String, List)} except converts the elements to the specified types. * * @param key The key. * @param elementType The element type class. * @param def The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public List getList(String key, Class elementType, List def) { Object o = get(key); if (o == null) return def; return bs().convertToType(o, List.class, elementType); } /** * Returns the specified entry value converted to a {@link Map}. * *

* Shortcut for get(key, ObjectMap.class). * * @param key The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public ObjectMap getObjectMap(String key) { return get(key, ObjectMap.class); } /** * Returns the specified entry value converted to a {@link ObjectMap}. * *

* Shortcut for getWithDefault(key, defVal, ObjectMap.class). * * @param key The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public ObjectMap getObjectMap(String key, ObjectMap defVal) { return getWithDefault(key, defVal, ObjectMap.class); } /** * Same as {@link #getObjectMap(String)} but creates a new empty {@link ObjectMap} if it doesn't already exist. * * @param key The key. * @param createIfNotExists If mapping doesn't already exist, create one with an empty {@link ObjectMap}. * @return The converted value, or an empty value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public ObjectMap getObjectMap(String key, boolean createIfNotExists) { ObjectMap m = getWithDefault(key, null, ObjectMap.class); if (m == null && createIfNotExists) { m = new ObjectMap(); put(key, m); } return m; } /** * Returns the specified entry value converted to a {@link ObjectList}. * *

* Shortcut for get(key, ObjectList.class). * * @param key The key. * @return The converted value, or null if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public ObjectList getObjectList(String key) { return get(key, ObjectList.class); } /** * Returns the specified entry value converted to a {@link ObjectList}. * *

* Shortcut for getWithDefault(key, defVal, ObjectList.class). * * @param key The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public ObjectList getObjectList(String key, ObjectList defVal) { return getWithDefault(key, defVal, ObjectList.class); } /** * Same as {@link #getObjectList(String)} but creates a new empty {@link ObjectList} if it doesn't already exist. * * @param key The key. * @param createIfNotExists If mapping doesn't already exist, create one with an empty {@link ObjectList}. * @return The converted value, or an empty value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public ObjectList getObjectList(String key, boolean createIfNotExists) { ObjectList m = getWithDefault(key, null, ObjectList.class); if (m == null && createIfNotExists) { m = new ObjectList(); put(key, m); } return m; } /** * Returns the first entry that exists converted to a {@link String}. * *

* Shortcut for find(String.class, keys). * * @param keys The list of keys to look for. * @return * The converted value of the first key in the list that has an entry in this map, or null if the map * contains no mapping for any of the keys. */ public String findString(String... keys) { return find(String.class, keys); } /** * Returns the first entry that exists converted to an {@link Integer}. * *

* Shortcut for find(Integer.class, keys). * * @param keys The list of keys to look for. * @return * The converted value of the first key in the list that has an entry in this map, or null if the map * contains no mapping for any of the keys. * @throws InvalidDataConversionException If value cannot be converted. */ public Integer findInt(String... keys) { return find(Integer.class, keys); } /** * Returns the first entry that exists converted to a {@link Long}. * *

* Shortcut for find(Long.class, keys). * * @param keys The list of keys to look for. * @return * The converted value of the first key in the list that has an entry in this map, or null if the map * contains no mapping for any of the keys. * @throws InvalidDataConversionException If value cannot be converted. */ public Long findLong(String... keys) { return find(Long.class, keys); } /** * Returns the first entry that exists converted to a {@link Boolean}. * *

* Shortcut for find(Boolean.class, keys). * * @param keys The list of keys to look for. * @return * The converted value of the first key in the list that has an entry in this map, or null if the map * contains no mapping for any of the keys. * @throws InvalidDataConversionException If value cannot be converted. */ public Boolean findBoolean(String... keys) { return find(Boolean.class, keys); } /** * Returns the first entry that exists converted to a {@link Map}. * *

* Shortcut for find(Map.class, keys). * * @param keys The list of keys to look for. * @return * The converted value of the first key in the list that has an entry in this map, or null if the map * contains no mapping for any of the keys. * @throws InvalidDataConversionException If value cannot be converted. */ public Map findMap(String... keys) { return find(Map.class, keys); } /** * Returns the first entry that exists converted to a {@link List}. * *

* Shortcut for find(List.class, keys). * * @param keys The list of keys to look for. * @return * The converted value of the first key in the list that has an entry in this map, or null if the map * contains no mapping for any of the keys. * @throws InvalidDataConversionException If value cannot be converted. */ public List findList(String... keys) { return find(List.class, keys); } /** * Returns the first entry that exists converted to a {@link ObjectMap}. * *

* Shortcut for find(ObjectMap.class, keys). * * @param keys The list of keys to look for. * @return * The converted value of the first key in the list that has an entry in this map, or null if the map * contains no mapping for any of the keys. * @throws InvalidDataConversionException If value cannot be converted. */ public ObjectMap findObjectMap(String... keys) { return find(ObjectMap.class, keys); } /** * Returns the first entry that exists converted to a {@link ObjectList}. * *

* Shortcut for find(ObjectList.class, keys). * * @param keys The list of keys to look for. * @return * The converted value of the first key in the list that has an entry in this map, or null if the map * contains no mapping for any of the keys. * @throws InvalidDataConversionException If value cannot be converted. */ public ObjectList findObjectList(String... keys) { return find(ObjectList.class, keys); } /** * Returns the first key in the map. * * @return The first key in the map, or null if the map is empty. */ public String getFirstKey() { return isEmpty() ? null : keySet().iterator().next(); } /** * Returns the class type of the object at the specified index. * * @param key The key into this map. * @return * The data type of the object at the specified key, or null if the value is null or does not exist. */ public ClassMeta getClassMeta(String key) { return bs().getClassMetaForObject(get(key)); } /** * Equivalent to calling get(class,key,def) followed by remove(key); * @param key The key. * @param defVal The default value if the map doesn't contain the specified mapping. * @param type The class type. * * @param The class type. * @return The converted value, or the default value if the map contains no mapping for this key. * @throws InvalidDataConversionException If value cannot be converted. */ public T removeWithDefault(String key, T defVal, Class type) { T t = getWithDefault(key, defVal, type); remove(key); return t; } /** * Convenience method for removing several keys at once. * * @param keys The list of keys to remove. */ public void removeAll(Collection keys) { for (String k : keys) remove(k); } /** * Convenience method for removing several keys at once. * * @param keys The list of keys to remove. */ public void removeAll(String... keys) { for (String k : keys) remove(k); } /** * The opposite of {@link #removeAll(String...)}. * *

* Discards all keys from this map that aren't in the specified list. * * @param keys The keys to keep. * @return This map. */ public ObjectMap keepAll(String...keys) { for (Iterator i = keySet().iterator(); i.hasNext();) { boolean remove = true; String key = i.next(); for (String k : keys) { if (k.equals(key)) { remove = false; break; } } if (remove) i.remove(); } return this; } @Override /* Map */ public boolean containsKey(Object key) { if (super.containsKey(key)) return true; if (inner != null) return inner.containsKey(key); return false; } /** * Returns true if the map contains the specified entry and the value is not null nor an empty string. * *

* Always returns false if the value is not a {@link CharSequence}. * * @param key The key. * @return true if the map contains the specified entry and the value is not null nor an empty string. */ public boolean containsKeyNotEmpty(String key) { Object val = get(key); if (val == null) return false; if (val instanceof CharSequence) return ! StringUtils.isEmpty(val); return false; } /** * Returns true if this map contains the specified key, ignoring the inner map if it exists. * * @param key The key to look up. * @return true if this map contains the specified key. */ public boolean containsOuterKey(Object key) { return super.containsKey(key); } /** * Returns a copy of this ObjectMap with only the specified keys. * * @param keys The keys of the entries to copy. * @return A new map with just the keys and values from this map. */ public ObjectMap include(String...keys) { ObjectMap m2 = new ObjectMap(); for (Map.Entry e : this.entrySet()) for (String k : keys) if (k.equals(e.getKey())) m2.put(k, e.getValue()); return m2; } /** * Returns a copy of this ObjectMap without the specified keys. * * @param keys The keys of the entries not to copy. * @return A new map without the keys and values from this map. */ public ObjectMap exclude(String...keys) { ObjectMap m2 = new ObjectMap(); for (Map.Entry e : this.entrySet()) { boolean exclude = false; for (String k : keys) if (k.equals(e.getKey())) exclude = true; if (! exclude) m2.put(e.getKey(), e.getValue()); } return m2; } /** * Sets a value in this map if the entry does not exist or the value is null. * * @param key The map key. * @param val The value to set if the current value does not exist or is null. * @return This object (for method chaining). */ public ObjectMap putIfNull(String key, Object val) { Object o = get(key); if (o == null) put(key, val); return this; } /** * Sets a value in this map if the entry does not exist or the value is null or an empty string. * * @param key The map key. * @param val The value to set if the current value does not exist or is null or an empty string. * @return This object (for method chaining). */ public ObjectMap putIfEmpty(String key, Object val) { Object o = get(key); if (o == null || o.toString().isEmpty()) put(key, val); return this; } /** * Adds a mapping if the specified key doesn't exist. * * @param key The map key. * @param val The value to set if the current value does not exist or is null or an empty string. * @return This object (for method chaining). */ public ObjectMap putIfNotExists(String key, Object val) { if (! containsKey(key)) put(key, val); return this; } /** * Converts this map into an object of the specified type. * *

* If this map contains a "_type" entry, it must be the same as or a subclass of the type. * * @param The class type to convert this map object to. * @param type The class type to convert this map object to. * @return The new object. * @throws ClassCastException * If the "_type" entry is present and not assignable from type */ @SuppressWarnings("unchecked") public T cast(Class type) { BeanSession bs = bs(); ClassMeta c2 = bs.getClassMeta(type); String typePropertyName = bs.getBeanTypePropertyName(c2); ClassMeta c1 = bs.getBeanRegistry().getClassMeta((String)get(typePropertyName)); ClassMeta c = c1 == null ? c2 : narrowClassMeta(c1, c2); if (c.isObject()) return (T)this; return (T)cast2(c); } /** * Same as {@link #cast(Class)}, except allows you to specify a {@link ClassMeta} parameter. * * @param The class type to convert this map object to. * @param cm The class type to convert this map object to. * @return The new object. * @throws ClassCastException * If the "_type" entry is present and not assignable from type */ @SuppressWarnings({"unchecked"}) public T cast(ClassMeta cm) { BeanSession bs = bs(); ClassMeta c1 = bs.getBeanRegistry().getClassMeta((String)get(bs.getBeanTypePropertyName(cm))); ClassMeta c = narrowClassMeta(c1, cm); return (T)cast2(c); } /* * Combines the class specified by a "_type" attribute with the ClassMeta * passed in through the cast(ClassMeta) method. * The rule is that child classes supersede parent classes, and c2 supersedes c1 * if one isn't the parent of another. */ private ClassMeta narrowClassMeta(ClassMeta c1, ClassMeta c2) { if (c1 == null) return c2; ClassMeta c = getNarrowedClassMeta(c1, c2); if (c1.isMap()) { ClassMeta k = getNarrowedClassMeta(c1.getKeyType(), c2.getKeyType()); ClassMeta v = getNarrowedClassMeta(c1.getValueType(), c2.getValueType()); return bs().getClassMeta(c.getInnerClass(), k, v); } if (c1.isCollection()) { ClassMeta e = getNarrowedClassMeta(c1.getElementType(), c2.getElementType()); return bs().getClassMeta(c.getInnerClass(), e); } return c; } /* * If c1 is a child of c2 or the same as c2, returns c1. * Otherwise, returns c2. */ private static ClassMeta getNarrowedClassMeta(ClassMeta c1, ClassMeta c2) { if (c2 == null || isParentClass(c2.getInnerClass(), c1.getInnerClass())) return c1; return c2; } /* * Converts this map to the specified class type. */ @SuppressWarnings({"unchecked","rawtypes"}) private T cast2(ClassMeta cm) { BeanSession bs = bs(); try { Object value = get("value"); if (cm.isMap()) { Map m2 = (cm.canCreateNewInstance() ? (Map)cm.newInstance() : new ObjectMap(bs)); ClassMeta kType = cm.getKeyType(), vType = cm.getValueType(); for (Map.Entry e : entrySet()) { Object k = e.getKey(); Object v = e.getValue(); if (! k.equals(bs.getBeanTypePropertyName(cm))) { // Attempt to recursively cast child maps. if (v instanceof ObjectMap) v = ((ObjectMap)v).cast(vType); k = (kType.isString() ? k : bs.convertToType(k, kType)); v = (vType.isObject() ? v : bs.convertToType(v, vType)); m2.put(k, v); } } return (T)m2; } else if (cm.isBean()) { BeanMap bm = bs.newBeanMap(cm.getInnerClass()); // Iterate through all the entries in the map and set the individual field values. for (Map.Entry e : entrySet()) { String k = e.getKey(); Object v = e.getValue(); if (! k.equals(bs.getBeanTypePropertyName(cm))) { // Attempt to recursively cast child maps. if (v instanceof ObjectMap) v = ((ObjectMap)v).cast(bm.getProperty(k).getMeta().getClassMeta()); bm.put(k, v); } } return bm.getBean(); } else if (cm.isCollectionOrArray()) { List items = (List)get("items"); return bs.convertToType(items, cm); } else if (value != null) { return bs.convertToType(value, cm); } } catch (Exception e) { throw new BeanRuntimeException(e, cm.innerClass, "Error occurred attempting to cast to an object of type ''{0}''", cm.innerClass.getName()); } throw new BeanRuntimeException(cm.innerClass, "Cannot convert to class type ''{0}''. Only beans and maps can be converted using this method.", cm.innerClass.getName()); } private PojoRest getPojoRest() { if (pojoRest == null) pojoRest = new PojoRest(this); return pojoRest; } /** * Serialize this object into a string using the specified serializer. * * @param serializer The serializer to use to convert this object to a string. * @return This object serialized as a string. * @throws SerializeException If a problem occurred trying to convert the output. */ public String toString(WriterSerializer serializer) throws SerializeException { return serializer.serialize(this); } /** * Serialize this object into a JSON string using the {@link JsonSerializer#DEFAULT} serializer. */ @Override /* Object */ public String toString() { try { return this.toString(SimpleJsonSerializer.DEFAULT); } catch (SerializeException e) { return e.getLocalizedMessage(); } } /** * Convenience method for serializing this map to the specified Writer using the * {@link JsonSerializer#DEFAULT} serializer. * * @param w The writer to serialize this object to. * @return This object (for method chaining). * @throws IOException If a problem occurred trying to write to the writer. * @throws SerializeException If a problem occurred trying to convert the output. */ public ObjectMap serializeTo(Writer w) throws IOException, SerializeException { JsonSerializer.DEFAULT.serialize(this); return this; } /** * Returns true if this map is unmodifiable. * * @return true if this map is unmodifiable. */ public boolean isUnmodifiable() { return false; } /** * Returns a modifiable copy of this map if it's unmodifiable. * * @return A modifiable copy of this map if it's unmodifiable, or this map if it is already modifiable. */ public ObjectMap modifiable() { if (isUnmodifiable()) return new ObjectMap(this); return this; } /** * Returns an unmodifiable copy of this map if it's modifiable. * * @return An unmodifiable copy of this map if it's modifiable, or this map if it is already unmodifiable. */ public ObjectMap unmodifiable() { if (this instanceof UnmodifiableObjectMap) return this; return new UnmodifiableObjectMap(this); } @Override /* Map */ public Set keySet() { if (inner == null) return super.keySet(); LinkedHashSet s = new LinkedHashSet<>(); s.addAll(inner.keySet()); s.addAll(super.keySet()); return s; } @Override /* Map */ public Set> entrySet() { if (inner == null) return super.entrySet(); final Set keySet = keySet(); final Iterator keys = keySet.iterator(); return new AbstractSet>() { @Override /* Iterable */ public Iterator> iterator() { return new Iterator>() { @Override /* Iterator */ public boolean hasNext() { return keys.hasNext(); } @Override /* Iterator */ public Map.Entry next() { return new Map.Entry() { String key = keys.next(); @Override /* Map.Entry */ public String getKey() { return key; } @Override /* Map.Entry */ public Object getValue() { return get(key); } @Override /* Map.Entry */ public Object setValue(Object object) { return put(key, object); } }; } @Override /* Iterator */ public void remove() { throw new UnsupportedOperationException(); } }; } @Override /* Set */ public int size() { return keySet.size(); } }; } private static final class UnmodifiableObjectMap extends ObjectMap { private static final long serialVersionUID = 1L; UnmodifiableObjectMap(ObjectMap contents) { super(); if (contents != null) { for (Map.Entry e : contents.entrySet()) { super.put(e.getKey(), e.getValue()); } } } @Override public final Object put(String key, Object val) { throw new UnsupportedOperationException("ObjectMap is read-only."); } @Override public final Object remove(Object key) { throw new UnsupportedOperationException("ObjectMap is read-only."); } @Override public final boolean isUnmodifiable() { return true; } } private BeanSession bs() { if (session == null) session = BeanContext.DEFAULT.createBeanSession(); return session; } }