// *************************************************************************************************************************** | |
// * 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.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. | |
* | |
* <p> | |
* An extension of {@link LinkedHashMap}, so all methods available in that class are also available to this class. | |
* <p> | |
* 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. | |
* | |
* <h5 class='section'>Example:</h5> | |
* <p class='bcode w800'> | |
* <jc>// Construct an empty Map</jc> | |
* Map m = <jk>new</jk> ObjectMap(); | |
* | |
* <jc>// Construct a Map from JSON</jc> | |
* String json = <js>"{a:'A',b:{c:'C',d:123}}"</js>; | |
* m = <jk>new</jk> ObjectMap(json); | |
* | |
* <jc>// Construct a Map using the append method</jc> | |
* m = <jk>new</jk> ObjectMap().append(<js>"foo"</js>,<js>"x"</js>).append(<js>"bar"</js>,123) | |
* .append(<js>"baz"</js>,<jk>true</jk>); | |
* | |
* <jc>// Construct a Map from XML generated by XmlSerializer</jc> | |
* String xml = <js>"<object><a type='string'>A</a><b type='object'><c type='string'>C</c><d type='number'>123</d></b></object>"</js>; | |
* m = <jk>new</jk> ObjectMap(xml, DataFormat.<jsf>XML</jsf>); | |
* m = (Map)XmlParser.<jsf>DEFAULT</jsf>.parse(xml); <jc>// Equivalent</jc> | |
* m = (Map)XmlParser.<jsf>DEFAULT</jsf>.parse(Object.<jk>class</jk>, xml); <jc>// Equivalent</jc> | |
* m = XmlParser.<jsf>DEFAULT</jsf>.parse(Map.<jk>class</jk>, xml); <jc>// Equivalent</jc> | |
* m = XmlParser.<jsf>DEFAULT</jsf>.parse(ObjectMap.<jk>class</jk>, xml); <jc>// Equivalent</jc> | |
* | |
* <jc>// Construct a Map from a URL GET parameter string generated by UrlEncodingParser</jc> | |
* String urlParams = <js>"?a='A'&b={c:'C',d:123}"</js>; | |
* m = <jk>new</jk> ObjectMap(urlParams, DataFormat.<jsf>URLPARAM</jsf>); | |
* m = (Map)UrlEncodingParser.<jsf>DEFAULT</jsf>.parse(Object.<jk>class</jk>, xml); <jc>// Equivalent</jc> | |
* m = UrlEncodingParser.<jsf>DEFAULT</jsf>.parse(Map.<jk>class</jk>, xml); <jc>// Equivalent</jc> | |
* m = UrlEncodingParser.<jsf>DEFAULT</jsf>.parse(ObjectMap.<jk>class</jk>, xml); <jc>// Equivalent</jc> | |
* | |
* <jc>// Construct JSON from ObjectMap</jc> | |
* m = <jk>new</jk> ObjectMap(<js>"{foo:'bar'},{baz:[123,true]}"</js>); | |
* json = m.toString(); <jc>// Produces "{foo:'bar'},{baz:[123,true]}"</jc> | |
* json = m.toString(JsonSerializer.<jsf>DEFAULT_CONDENSED</jsf>); <jc>// Equivalent</jc> | |
* json = JsonSerializer.<jsf>DEFAULT_CONDENSED</jsf>.serialize(m); <jc>// Equivalent</jc> | |
* | |
* <jc>// Get a map entry as an Integer</jc> | |
* m = <jk>new</jk> ObjectMap(<js>"{foo:123}"</js>); | |
* Integer i = m.getInt(<js>"foo"</js>); | |
* i = m.get(Integer.<jk>class</jk>, <js>"foo"</js>); <jc>// Equivalent</jc> | |
* | |
* <jc>// Get a map entry as a Float</jc> | |
* m = <jk>new</jk> ObjectMap(<js>"{foo:123}"</js>); | |
* Float f = m.getFloat(<js>"foo"</js>); | |
* f = m.get(Float.<jk>class</jk>, <js>"foo"</js>); <jc>// Equivalent</jc> | |
* | |
* <jc>// Same as above, except converted to a String</jc> | |
* m = <jk>new</jk> ObjectMap(<js>"{foo:123}"</js>); | |
* String s = m.getString(<js>"foo"</js>); <jc>// Returns "123"</jc> | |
* s = m.get(String.<jk>class</jk>, <js>"foo"</js>); <jc>// Equivalent</jc> | |
* | |
* <jc>// Get one of the entries in the list as a bean (converted to a bean if it isn't already one)</jc> | |
* m = <jk>new</jk> ObjectMap(<js>"{person:{name:'John Smith',age:45}}"</js>); | |
* Person p = m.get(Person.<jk>class</jk>, <js>"person"</js>); | |
* | |
* <jc>// Add an inner map</jc> | |
* ObjectMap m1 = <jk>new</jk> ObjectMap(<js>"{a:1}"</js>); | |
* ObjectMap m2 = <jk>new</jk> ObjectMap(<js>"{b:2}"</js>).setInner(m1); | |
* <jk>int</jk> a = m2.getInt(<js>"a"</js>); <jc>// a == 1 </jc> | |
* </p> | |
* | |
* <p> | |
* This class is not thread safe. | |
*/ | |
public class ObjectMap extends LinkedHashMap<String,Object> { | |
private static final long serialVersionUID = 1L; | |
private transient BeanSession session; | |
private Map<String,Object> 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<Map.Entry<String,Object>> entrySet() { | |
return Collections.<String,Object>emptyMap().entrySet(); | |
} | |
@Override /* Map */ | |
public Set<String> keySet() { | |
return Collections.<String,Object>emptyMap().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<Object> 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 Malformed input encountered. | |
*/ | |
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()); | |
} | |
/** | |
* Static constructor from JSON string. | |
* | |
* @param s JSON initialization string. Can be <jk>null</jk>. | |
* @return A new {@link ObjectMap} object, or <jk>null</jk> if the input is <jk>null</jk>. | |
* @throws ParseException Invalid JSON string. | |
*/ | |
public static ObjectMap parse(CharSequence s) throws ParseException { | |
return s == null ? null : new ObjectMap(s); | |
} | |
/** | |
* Shortcut for <code><jk>new</jk> ObjectMap(string,JsonParser.<jsf>DEFAULT</jsf>);</code> | |
* | |
* @param s The JSON text to parse. | |
* @throws ParseException Malformed input encountered. | |
*/ | |
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 <code><jk>new</jk> ObjectMap(reader, JsonParser.<jsf>DEFAULT</jsf>)</code>. | |
* | |
* @param r The reader to read from. The reader will be wrapped in a {@link BufferedReader} if it isn't already. | |
* @throws ParseException Malformed input encountered. | |
* @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(); | |
if (m != null) | |
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. | |
* | |
* <p> | |
* If {@link #get(Object)} returns <jk>null</jk>, then {@link #get(Object)} will be called on the inner map. | |
* | |
* <p> | |
* 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. | |
* | |
* <p class='bcode w800'> | |
* ObjectMap m1 = <jk>new</jk> ObjectMap(<js>"{foo:1}"</js>); | |
* ObjectMap m2 = <jk>new</jk> ObjectMap().setInner(m1); | |
* m2.put(<js>"foo"</js>, 2); <jc>// Overwrite the entry</jc> | |
* <jk>int</jk> foo1 = m1.getInt(<js>"foo"</js>); <jc>// foo1 == 1 </jc> | |
* <jk>int</jk> foo2 = m2.getInt(<js>"foo"</js>); <jc>// foo2 == 2 </jc> | |
* </p> | |
* | |
* @param inner | |
* The inner map. | |
* Can be <jk>null</jk> to remove the inner map from an existing map. | |
* @return This object (for method chaining). | |
*/ | |
public ObjectMap setInner(Map<String,Object> 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 <jk>null</jk> 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. | |
* | |
* <p> | |
* Default is {@link BeanContext#DEFAULT}, which is sufficient in most cases. | |
* | |
* <p> | |
* 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. | |
* | |
* <p> | |
* 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 <jk>null</jk>. | |
* @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 <jk>true</jk> 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. | |
* | |
* <p> | |
* A no-op if the value is <jk>null</jk> 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. | |
* | |
* <p> | |
* A no-op if the value is <jk>false</jk>. | |
* | |
* @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. | |
* | |
* <p> | |
* A no-op if the value is <c>-1</c>. | |
* | |
* @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. | |
* | |
* <p> | |
* Equivalent to calling {@code put(key, value)}, but returns this map so that the method can be chained. | |
* | |
* <p> | |
* <jk>null</jk> 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. | |
* | |
* <p> | |
* 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<String,Object> 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. | |
* | |
* <p> | |
* This is the preferred get method for simple types. | |
* | |
* <h5 class='section'>Examples:</h5> | |
* <p class='bcode w800'> | |
* ObjectMap m = <jk>new</jk> ObjectMap(<js>"..."</js>); | |
* | |
* <jc>// Value converted to a string.</jc> | |
* String s = m.get(<js>"key1"</js>, String.<jk>class</jk>); | |
* | |
* <jc>// Value converted to a bean.</jc> | |
* MyBean b = m.get(<js>"key2"</js>, MyBean.<jk>class</jk>); | |
* | |
* <jc>// Value converted to a bean array.</jc> | |
* MyBean[] ba = m.get(<js>"key3"</js>, MyBean[].<jk>class</jk>); | |
* | |
* <jc>// Value converted to a linked-list of objects.</jc> | |
* List l = m.get(<js>"key4"</js>, LinkedList.<jk>class</jk>); | |
* | |
* <jc>// Value converted to a map of object keys/values.</jc> | |
* Map m2 = m.get(<js>"key5"</js>, TreeMap.<jk>class</jk>); | |
* </p> | |
* | |
* <p> | |
* See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. | |
* | |
* @param key The key. | |
* @param <T> The class type returned. | |
* @param type The class type returned. | |
* @return The value, or <jk>null</jk> if the entry doesn't exist. | |
*/ | |
public <T> T get(String key, Class<T> type) { | |
return getWithDefault(key, (T)null, type); | |
} | |
/** | |
* Same as {@link #get(String,Class)}, but allows for complex data types consisting of collections or maps. | |
* | |
* <p> | |
* The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). | |
* | |
* <h5 class='section'>Examples:</h5> | |
* <p class='bcode w800'> | |
* ObjectMap m = <jk>new</jk> ObjectMap(<js>"..."</js>); | |
* | |
* <jc>// Value converted to a linked-list of strings.</jc> | |
* List<String> l1 = m.get(<js>"key1"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); | |
* | |
* <jc>// Value converted to a linked-list of beans.</jc> | |
* List<MyBean> l2 = m.get(<js>"key2"</js>, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); | |
* | |
* <jc>// Value converted to a linked-list of linked-lists of strings.</jc> | |
* List<List<String>> l3 = m.get(<js>"key3"</js>, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); | |
* | |
* <jc>// Value converted to a map of string keys/values.</jc> | |
* Map<String,String> m1 = m.get(<js>"key4"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); | |
* | |
* <jc>// Value converted to a map containing string keys and values of lists containing beans.</jc> | |
* Map<String,List<MyBean>> m2 = m.get(<js>"key5"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); | |
* </p> | |
* | |
* <p> | |
* <c>Collection</c> classes are assumed to be followed by zero or one objects indicating the element type. | |
* | |
* <p> | |
* <c>Map</c> classes are assumed to be followed by zero or two meta objects indicating the key and value types. | |
* | |
* <p> | |
* The array can be arbitrarily long to indicate arbitrarily complex data structures. | |
* | |
* <p> | |
* See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. | |
* | |
* <ul class='notes'> | |
* <li> | |
* Use the {@link #get(String, Class)} method instead if you don't need a parameterized map/collection. | |
* </ul> | |
* | |
* @param key The key. | |
* @param <T> The class type returned. | |
* @param type The class type returned. | |
* @param args The class type parameters. | |
* @return The value, or <jk>null</jk> if the entry doesn't exist. | |
*/ | |
public <T> 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 <jk>null</jk>. | |
* @param <T> The class type returned. | |
* @param type The class type returned. | |
* @return The value, or <jk>null</jk> if the entry doesn't exist. | |
*/ | |
public <T> T getWithDefault(String key, T def, Class<T> 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 <jk>null</jk>. | |
* @param <T> The class type returned. | |
* @param type The class type returned. | |
* @param args The class type parameters. | |
* @return The value, or <jk>null</jk> if the entry doesn't exist. | |
*/ | |
public <T> 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 <T> The transformed class type. | |
* @return The value, or <jk>null</jk> if the entry doesn't exist. | |
* @throws ParseException Malformed input encountered. | |
*/ | |
@SuppressWarnings({ "rawtypes", "unchecked" }) | |
public <T> T getSwapped(String key, PojoSwap<T,?> 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 <jk>null</jk> 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. | |
* | |
* <p> | |
* Casts or converts the value to the specified class type. | |
* | |
* <p> | |
* See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. | |
* | |
* @param type The class type to convert the value to. | |
* @param <T> 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 <jk>null</jk> if none of the keys exist in this map. | |
*/ | |
public <T> T find(Class<T> 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. | |
* | |
* <p> | |
* For example, the following code is equivalent: | |
* </p> | |
* <p class='bcode w800'> | |
* ObjectMap m = getObjectMap(); | |
* | |
* <jc>// Long way</jc> | |
* <jk>long</jk> l = m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>) | |
* .getLong(<js>"baz"</js>); | |
* | |
* <jc>// Using this method</jc> | |
* <jk>long</jk> l = m.getAt(<js>"foo/bar/0/baz"</js>, <jk>long</jk>.<jk>class</jk>); | |
* </p> | |
* | |
* <p> | |
* 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 <T> The class type. | |
* @return The value, or <jk>null</jk> if the entry doesn't exist. | |
*/ | |
public <T> T getAt(String path, Class<T> type) { | |
return getPojoRest().get(path, type); | |
} | |
/** | |
* Same as {@link #getAt(String,Class)}, but allows for conversion to complex maps and collections. | |
* | |
* <p> | |
* 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 <T> The class type. | |
* @return The value, or <jk>null</jk> if the entry doesn't exist. | |
*/ | |
public <T> T getAt(String path, Type type, Type...args) { | |
return getPojoRest().get(path, type, args); | |
} | |
/** | |
* Same as <c>put(String,Object)</c>, but the key is a slash-delimited path used to traverse entries in this | |
* POJO. | |
* | |
* <p> | |
* For example, the following code is equivalent: | |
* </p> | |
* <p class='bcode w800'> | |
* ObjectMap m = getObjectMap(); | |
* | |
* <jc>// Long way</jc> | |
* m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>) | |
* .put(<js>"baz"</js>, 123); | |
* | |
* <jc>// Using this method</jc> | |
* m.putAt(<js>"foo/bar/0/baz"</js>, 123); | |
* </p> | |
* | |
* <p> | |
* 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 <jk>null</jk> 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. | |
* | |
* <p> | |
* For example, the following code is equivalent: | |
* </p> | |
* <p class='bcode w800'> | |
* ObjectMap m = getObjectMap(); | |
* | |
* <jc>// Long way</jc> | |
* m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).append(123); | |
* | |
* <jc>// Using this method</jc> | |
* m.postAt(<js>"foo/bar"</js>, 123); | |
* </p> | |
* | |
* <p> | |
* 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 <jk>null</jk> 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. | |
* | |
* <p> | |
* For example, the following code is equivalent: | |
* </p> | |
* <p class='bcode w800'> | |
* ObjectMap m = getObjectMap(); | |
* | |
* <jc>// Long way</jc> | |
* m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(0).remove(<js>"baz"</js>); | |
* | |
* <jc>// Using this method</jc> | |
* m.deleteAt(<js>"foo/bar/0/baz"</js>); | |
* </p> | |
* | |
* <p> | |
* 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 <jk>null</jk> 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. | |
* | |
* <p> | |
* The JSON text can be an object (i.e. <js>"{...}"</js>) or an array (i.e. <js>"[...]"</js>). | |
* | |
* @param key The key. | |
* @param json The JSON text that will be parsed into an Object and then inserted into this map. | |
* @throws ParseException Malformed input encountered. | |
*/ | |
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}. | |
* | |
* <p> | |
* Shortcut for <code>get(key, String.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @return The converted value, or <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>get(key, String[].<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @return The converted value, or <jk>null</jk> 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(stringify(s)); | |
return (r.length == 0 ? def : r); | |
} | |
/** | |
* Returns the specified entry value converted to a {@link String}. | |
* | |
* <p> | |
* Shortcut for <code>getWithDefault(key, defVal, String.<jk>class</jk>)</code>. | |
* | |
* @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}. | |
* | |
* <p> | |
* Shortcut for <code>get(key, Integer.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @return The converted value, or <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>getWithDefault(key, defVal, Integer.<jk>class</jk>)</code>. | |
* | |
* @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}. | |
* | |
* <p> | |
* Shortcut for <code>get(key, Long.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @return The converted value, or <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>getWithDefault(key, defVal, Long.<jk>class</jk>)</code>. | |
* | |
* @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}. | |
* | |
* <p> | |
* Shortcut for <code>get(key, Boolean.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @return The converted value, or <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>getWithDefault(key, defVal, Boolean.<jk>class</jk>)</code>. | |
* | |
* @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}. | |
* | |
* <p> | |
* Shortcut for <code>get(key, Map.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @return The converted value, or <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>getWithDefault(key, defVal, Map.<jk>class</jk>)</code>. | |
* | |
* @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 <K,V> Map<K,V> getMap(String key, Class<K> keyType, Class<V> valType, Map<K,V> 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}. | |
* | |
* <p> | |
* Shortcut for <code>get(key, List.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @return The converted value, or <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>getWithDefault(key, defVal, List.<jk>class</jk>)</code>. | |
* | |
* @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 <E> List<E> getList(String key, Class<E> elementType, List<E> 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}. | |
* | |
* <p> | |
* Shortcut for <code>get(key, ObjectMap.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @return The converted value, or <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>getWithDefault(key, defVal, ObjectMap.<jk>class</jk>)</code>. | |
* | |
* @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}. | |
* | |
* <p> | |
* Shortcut for <code>get(key, ObjectList.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @return The converted value, or <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>getWithDefault(key, defVal, ObjectList.<jk>class</jk>)</code>. | |
* | |
* @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}. | |
* | |
* <p> | |
* Shortcut for <code>find(String.<jk>class</jk>, keys)</code>. | |
* | |
* @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 <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>find(Integer.<jk>class</jk>, keys)</code>. | |
* | |
* @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 <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>find(Long.<jk>class</jk>, keys)</code>. | |
* | |
* @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 <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>find(Boolean.<jk>class</jk>, keys)</code>. | |
* | |
* @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 <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>find(Map.<jk>class</jk>, keys)</code>. | |
* | |
* @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 <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>find(List.<jk>class</jk>, keys)</code>. | |
* | |
* @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 <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>find(ObjectMap.<jk>class</jk>, keys)</code>. | |
* | |
* @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 <jk>null</jk> 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}. | |
* | |
* <p> | |
* Shortcut for <code>find(ObjectList.<jk>class</jk>, keys)</code>. | |
* | |
* @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 <jk>null</jk> 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 <jk>null</jk> 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 <jk>null</jk> if the value is null or does not exist. | |
*/ | |
public ClassMeta<?> getClassMeta(String key) { | |
return bs().getClassMetaForObject(get(key)); | |
} | |
/** | |
* Equivalent to calling <c>get(class,key,def)</c> followed by <c>remove(key);</c> | |
* @param key The key. | |
* @param defVal The default value if the map doesn't contain the specified mapping. | |
* @param type The class type. | |
* | |
* @param <T> 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> T removeWithDefault(String key, T defVal, Class<T> type) { | |
T t = getWithDefault(key, defVal, type); | |
remove(key); | |
return t; | |
} | |
/** | |
* Equivalent to calling <code>removeWithDefault(key,<jk>null</jk>,String.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. | |
* @throws InvalidDataConversionException If value cannot be converted. | |
*/ | |
public String removeString(String key) { | |
return removeString(key, null); | |
} | |
/** | |
* Equivalent to calling <code>removeWithDefault(key,def,String.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @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 String removeString(String key, String def) { | |
return removeWithDefault(key, def, String.class); | |
} | |
/** | |
* Equivalent to calling <code>removeWithDefault(key,<jk>null</jk>,Integer.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. | |
* @throws InvalidDataConversionException If value cannot be converted. | |
*/ | |
public Integer removeInt(String key) { | |
return removeInt(key, null); | |
} | |
/** | |
* Equivalent to calling <code>removeWithDefault(key,def,Integer.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @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 Integer removeInt(String key, Integer def) { | |
return removeWithDefault(key, def, Integer.class); | |
} | |
/** | |
* Equivalent to calling <code>removeWithDefault(key,<jk>null</jk>,Boolean.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. | |
* @throws InvalidDataConversionException If value cannot be converted. | |
*/ | |
public Boolean removeBoolean(String key) { | |
return removeBoolean(key, null); | |
} | |
/** | |
* Equivalent to calling <code>removeWithDefault(key,def,Boolean.<jk>class</jk>)</code>. | |
* | |
* @param key The key. | |
* @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 Boolean removeBoolean(String key, Boolean def) { | |
return removeWithDefault(key, def, Boolean.class); | |
} | |
/** | |
* Convenience method for removing several keys at once. | |
* | |
* @param keys The list of keys to remove. | |
*/ | |
public void removeAll(Collection<String> 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...)}. | |
* | |
* <p> | |
* 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<String> 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 <jk>true</jk> if the map contains the specified entry and the value is not null nor an empty string. | |
* | |
* <p> | |
* Always returns <jk>false</jk> if the value is not a {@link CharSequence}. | |
* | |
* @param key The key. | |
* @return <jk>true</jk> 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 <jk>true</jk> if this map contains the specified key, ignoring the inner map if it exists. | |
* | |
* @param key The key to look up. | |
* @return <jk>true</jk> if this map contains the specified key. | |
*/ | |
public boolean containsOuterKey(Object key) { | |
return super.containsKey(key); | |
} | |
/** | |
* Returns a copy of this <c>ObjectMap</c> 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<String,Object> e : this.entrySet()) | |
for (String k : keys) | |
if (k.equals(e.getKey())) | |
m2.put(k, e.getValue()); | |
return m2; | |
} | |
/** | |
* Returns a copy of this <c>ObjectMap</c> 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<String,Object> 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 <jk>null</jk>. | |
* | |
* @param key The map key. | |
* @param val The value to set if the current value does not exist or is <jk>null</jk>. | |
* @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 <jk>null</jk> or an empty string. | |
* | |
* @param key The map key. | |
* @param val The value to set if the current value does not exist or is <jk>null</jk> 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 <jk>null</jk> 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. | |
* | |
* <p> | |
* If this map contains a <js>"_type"</js> entry, it must be the same as or a subclass of the <c>type</c>. | |
* | |
* @param <T> 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 <js>"_type"</js> entry is present and not assignable from <c>type</c> | |
*/ | |
@SuppressWarnings("unchecked") | |
public <T> T cast(Class<T> 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 <T> 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 <js>"_type"</js> entry is present and not assignable from <c>type</c> | |
*/ | |
@SuppressWarnings({"unchecked"}) | |
public <T> T cast(ClassMeta<T> 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 || c2.getInfo().isParentOf(c1.getInnerClass())) | |
return c1; | |
return c2; | |
} | |
/* | |
* Converts this map to the specified class type. | |
*/ | |
@SuppressWarnings({"unchecked","rawtypes"}) | |
private <T> T cast2(ClassMeta<T> 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<String,Object> 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<? extends T> bm = bs.newBeanMap(cm.getInnerClass()); | |
// Iterate through all the entries in the map and set the individual field values. | |
for (Map.Entry<String,Object> 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 <c>Writer</c> 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 <jk>true</jk> if this map is unmodifiable. | |
* | |
* @return <jk>true</jk> 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<String> keySet() { | |
if (inner == null) | |
return super.keySet(); | |
LinkedHashSet<String> s = new LinkedHashSet<>(); | |
s.addAll(inner.keySet()); | |
s.addAll(super.keySet()); | |
return s; | |
} | |
@Override /* Map */ | |
public Set<Map.Entry<String,Object>> entrySet() { | |
if (inner == null) | |
return super.entrySet(); | |
final Set<String> keySet = keySet(); | |
final Iterator<String> keys = keySet.iterator(); | |
return new AbstractSet<Map.Entry<String,Object>>() { | |
@Override /* Iterable */ | |
public Iterator<Map.Entry<String,Object>> iterator() { | |
return new Iterator<Map.Entry<String,Object>>() { | |
@Override /* Iterator */ | |
public boolean hasNext() { | |
return keys.hasNext(); | |
} | |
@Override /* Iterator */ | |
public Map.Entry<String,Object> next() { | |
return new Map.Entry<String,Object>() { | |
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<String,Object> 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; | |
} | |
} |