blob: 502189afa97280d87a1a01d938e52b67827bcbe6 [file] [log] [blame]
// ***************************************************************************************************************************
// * 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 java.io.*;
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'>
* <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>"&lt;object&gt;&lt;a type='string'&gt;A&lt;/a&gt;&lt;b type='object'&gt;&lt;c type='string'&gt;C&lt;/c&gt;&lt;d type='number'&gt;123&lt;/d&gt;&lt;/b&gt;&lt;/object&gt;"</js>;
* 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'&amp;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.
* </p>
*/
public class ObjectMap extends LinkedHashMap<String,Object> {
private static final long serialVersionUID = 1L;
private transient BeanSession session;
private ObjectMap 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 */
@SuppressWarnings("unchecked")
public Set<Map.Entry<String,Object>> entrySet() {
return Collections.EMPTY_MAP.entrySet();
}
@Override /* Map */
@SuppressWarnings("unchecked")
public Set<String> 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<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 If the input contains a syntax error or is malformed.
*/
public ObjectMap(CharSequence s, Parser p) throws ParseException {
this(p == null ? BeanContext.DEFAULT.createSession() : p.getBeanContext().createSession());
if (p == null)
p = JsonParser.DEFAULT;
if (s != null)
p.parseIntoMap(s, this, session.string(), session.object());
}
/**
* Shortcut for <code><jk>new</jk> ObjectMap(string,JsonParser.<jsf>DEFAULT</jsf>);</code>
*
* @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 {
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 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, session.string(), session.object());
}
/**
* Construct an empty JSON object (i.e. an empty {@link LinkedHashMap}).
*/
public ObjectMap() {
this(BeanContext.DEFAULT.createSession());
}
/**
* Construct an empty JSON object (i.e. an empty {@link LinkedHashMap}) with the specified bean context.
*
* @param session The bean session to use for creating beans.
*/
public ObjectMap(BeanSession session) {
super();
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) {
super();
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'>
* 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(ObjectMap 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 preferrable 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;
}
/**
* Returns the inner map if one was set through {@link #setInner(ObjectMap)}.
*
* @return The inner map if one was set through {@link #setInner(ObjectMap)}, or <jk>null</jk> if no inner map is present.
*/
public ObjectMap getInner() {
return inner;
}
/**
* 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 multiple objects 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;
}
/**
* 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) {
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 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 get(String key, Object def) {
Object o = get(key);
return (o == null ? def : o);
}
/**
* Same as {@link Map#get(Object) get()}, but 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 <T> The class type.
* @param type The class type.
* @param key The key.
* @return The value, or <jk>null</jk> if the entry doesn't exist.
*/
public <T> T get(Class<T> type, String key) {
return get(type, key, null);
}
/**
* Same as {@link Map#get(Object) get()}, but converts the raw value to the specified class type using the specified beanFilter.
*
* @param <T> The transformed class type.
* @param pojoSwap The swap class used to convert the raw type to a transformed type.
* @param key The key.
* @return The value, or <jk>null</jk> 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> T get(PojoSwap<T,?> pojoSwap, String key) throws ParseException {
Object o = super.get(key);
if (o == null)
return null;
PojoSwap swap = pojoSwap;
return (T)swap.unswap(session, o, null);
}
/**
* Same as {@link Map#get(Object) get()}, but 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 <T> The class type.
* @param type The class type.
* @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 <T> T get(Class<T> type, String key, T def) {
Object o = get(key);
if (o == null)
return def;
T t = session.convertToType(o, type);
if (t == null)
return def;
return t;
}
/**
* Same as {@link Map#get(Object) get()}, but 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 <T> The class type.
* @param type The class type.
* @param key The key.
* @return The value, or the default value if the entry doesn't exist.
*/
public <T> T get(ClassMeta<T> type, String key) {
return get(type, key, null);
}
/**
* Same as {@link Map#get(Object) get()}, but 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 <T> The class type.
* @param type The class type.
* @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 <T> T get(ClassMeta<T> type, String key, T def) {
Object o = get(key);
if (o == null)
return def;
return session.convertToType(o, type);
}
/**
* 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(type, key);
return null;
}
/**
* Same as {@link #get(Class,String) get(Class,String)}, 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'>
* 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(<jk>long</jk>.<jk>class</jk>, <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).
* </p>
*
* @param <T> The class type.
* @param type The class type.
* @param path The path to the entry.
* @return The value, or <jk>null</jk> if the entry doesn't exist.
*/
public <T> T getAt(Class<T> type, String path) {
return getPojoRest().get(type, path);
}
/**
* Same as <code>put(String,Object)</code>, 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'>
* 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).
* </p>
*
* @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'>
* 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).
* </p>
*
* @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'>
* ObjectMap m = getObjectMap();
*
* <jc>// Long way</jc>
* m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(1).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).
* </p>
*
* @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 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}.
* <p>
* Shortcut for <code>get(String.<jk>class</jk>, key)</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(String.class, key);
}
/**
* Specialized method that calls {@link #getString(String)} and splits the
* results as a simple comma-delimited list.
*
* @param key the key.
* @return A list of tokens, trimmed of whitespace. An empty list if entry not found. Never <jk>null</jk>.
*/
public String[] getStringArray(String key) {
String s = get(String.class, key);
return (s == null ? new String[0] : StringUtils.split(s, ','));
}
/**
* 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) {
String s = get(String.class, key);
String[] r = (s == null ? new String[0] : StringUtils.split(s, ','));
return (r.length == 0 ? def : r);
}
/**
* Returns the specified entry value converted to a {@link String}.
* <p>
* Shortcut for <code>get(String.<jk>class</jk>, key, defVal)</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 get(String.class, key, defVal);
}
/**
* Returns the specified entry value converted to an {@link Integer}.
* <p>
* Shortcut for <code>get(Integer.<jk>class</jk>, key)</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(Integer.class, key);
}
/**
* Returns the specified entry value converted to an {@link Integer}.
* <p>
* Shortcut for <code>get(Integer.<jk>class</jk>, key, defVal)</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 get(Integer.class, key, defVal);
}
/**
* Returns the specified entry value converted to a {@link Long}.
* <p>
* Shortcut for <code>get(Long.<jk>class</jk>, key)</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(Long.class, key);
}
/**
* Returns the specified entry value converted to a {@link Long}.
* <p>
* Shortcut for <code>get(Long.<jk>class</jk>, key, defVal)</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 get(Long.class, key, defVal);
}
/**
* Returns the specified entry value converted to a {@link Boolean}.
* <p>
* Shortcut for <code>get(Boolean.<jk>class</jk>, key)</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(Boolean.class, key);
}
/**
* Returns the specified entry value converted to a {@link Boolean}.
* <p>
* Shortcut for <code>get(Boolean.<jk>class</jk>, key, defVal)</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 get(Boolean.class, key, defVal);
}
/**
* Returns the specified entry value converted to a {@link Map}.
* <p>
* Shortcut for <code>get(Map.<jk>class</jk>, key)</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(Map.class, key);
}
/**
* Returns the specified entry value converted to a {@link Map}.
* <p>
* Shortcut for <code>get(Map.<jk>class</jk>, key, defVal)</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 get(Map.class, key, defVal);
}
/**
* Returns the specified entry value converted to a {@link List}.
* <p>
* Shortcut for <code>get(List.<jk>class</jk>, key)</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(List.class, key);
}
/**
* Returns the specified entry value converted to a {@link List}.
* <p>
* Shortcut for <code>get(List.<jk>class</jk>, key, defVal)</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 get(List.class, key, defVal);
}
/**
* Returns the specified entry value converted to a {@link Map}.
* <p>
* Shortcut for <code>get(ObjectMap.<jk>class</jk>, key)</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(ObjectMap.class, key);
}
/**
* Returns the specified entry value converted to a {@link ObjectMap}.
* <p>
* Shortcut for <code>get(ObjectMap.<jk>class</jk>, key, defVal)</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 get(ObjectMap.class, key, defVal);
}
/**
* Returns the specified entry value converted to a {@link ObjectList}.
* <p>
* Shortcut for <code>get(ObjectList.<jk>class</jk>, key)</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(ObjectList.class, key);
}
/**
* Returns the specified entry value converted to a {@link ObjectList}.
* <p>
* Shortcut for <code>get(ObjectList.<jk>class</jk>, key, defVal)</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 get(ObjectList.class, key, defVal);
}
/**
* 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 session.getClassMetaForObject(get(key));
}
/**
* Equivalent to calling <code>get(class,key,def)</code> followed by <code>remove(key);</code>
*
* @param <T> The class type.
* @param type The class type.
* @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 <T> T remove(Class<T> type, String key, T defVal) {
T t = get(type, key, defVal);
remove(key);
return t;
}
/**
* 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);
}
@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 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 <code>ObjectMap</code> 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 <code>ObjectMap</code> 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;
}
/**
* Converts this map into the class type specified by the <js>"_type"</js> entry value.
* <p>
* TODO - Needs better description.
*
* @return This object map cast as another object.
*/
public Object cast() {
return cast(session.getBeanRegistry());
}
/**
* Same as {@link #cast()}, but first do a lookup for the name in the specified dictionary.
*
* @param beanRegistry
* The class lexicon to resolve the name. Can be <jk>null</jk>.
* @return The new Java object of type specified by the <js>"_type"</js> entry value, or this
* same object if entry does not exist.
*/
public Object cast(BeanRegistry beanRegistry) {
String c = (String)get(session.getBeanTypePropertyName());
if (c == null || beanRegistry == null)
return this;
ClassMeta<?> cm = beanRegistry.getClassMeta(c);
if (cm == null)
return this;
return cast2(cm);
}
/**
* Converts this map into an object of the specified type.
* <p>
* The rules are the same as those specified in {@link #cast()}.
* <p>
* If this map contains a <js>"_type"</js> entry, it must be the same as or a subclass
* of the <code>type</code>.
*
* @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 <code>type</code>
*/
@SuppressWarnings("unchecked")
public <T> T cast(Class<T> type) {
ClassMeta<?> c1 = session.getBeanRegistry().getClassMeta((String)get(session.getBeanTypePropertyName()));
ClassMeta<?> c2 = session.getClassMeta(type);
ClassMeta<?> c = narrowClassMeta(c1, c2);
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 <code>type</code>
*/
@SuppressWarnings({"unchecked"})
public <T> T cast(ClassMeta<T> cm) {
ClassMeta<?> c1 = session.getBeanRegistry().getClassMeta((String)get(session.getBeanTypePropertyName()));
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 superceed parent classes, and c2 superceeds 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 session.getClassMeta(c.getInnerClass(), k, v);
}
if (c1.isCollection()) {
ClassMeta<?> e = getNarrowedClassMeta(c1.getElementType(), c2.getElementType());
return session.getClassMeta(c.getInnerClass(), e);
}
return c;
}
/*
* If c1 is a child of c2 or the same as c2, returns c1.
* Otherwise, returns c2.
*/
private ClassMeta<?> getNarrowedClassMeta(ClassMeta<?> c1, ClassMeta<?> c2) {
if (isParentClass(c2.getInnerClass(), c1.getInnerClass()))
return c1;
return c2;
}
/*
* Converts this map to the specified class type.
*/
@SuppressWarnings({"unchecked","rawtypes"})
private <T> T cast2(ClassMeta<T> cm) {
try {
Object value = get("value");
if (cm.isMap()) {
Map m2 = (cm.canCreateNewInstance() ? (Map)cm.newInstance() : new ObjectMap(session));
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(session.getBeanTypePropertyName())) {
// Attempt to recursively cast child maps.
if (v instanceof ObjectMap)
v = ((ObjectMap)v).cast(session.getBeanRegistry());
k = (kType.isString() ? k : session.convertToType(k, kType));
v = (vType.isObject() ? v : session.convertToType(v, vType));
m2.put(k, v);
}
}
return (T)m2;
} else if (cm.isBean()) {
BeanMap<? extends T> bm = session.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(session.getBeanTypePropertyName())) {
// Attempt to recursively cast child maps.
if (v instanceof ObjectMap)
v = ((ObjectMap)v).cast(session.getBeanRegistry());
bm.put(k, v);
}
}
return bm.getBean();
} else if (cm.isCollectionOrArray()) {
List items = (List)get("items");
return session.convertToType(items, cm);
} else if (value != null) {
return session.convertToType(value, cm);
}
} catch (Exception e) {
throw new BeanRuntimeException(cm.innerClass, "Error occurred attempting to cast to an object of type ''{0}''", cm.innerClass.getName()).initCause(e);
}
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(JsonSerializer.DEFAULT_LAX);
} catch (SerializeException e) {
return e.getLocalizedMessage();
}
}
/**
* Convenience method for serializing this map to the specified <code>Writer</code> 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;
}
@Override /* Map */
public Set<String> keySet() {
if (inner == null)
return super.keySet();
LinkedHashSet<String> s = new LinkedHashSet<String>();
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();
}
};
}
}