Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.apache.juneau.collections.JsonMap Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * 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.collections;
import static org.apache.juneau.common.internal.StringUtils.*;
import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.ConsumerUtils.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.*;
import org.apache.juneau.*;
import org.apache.juneau.common.internal.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.json.*;
import org.apache.juneau.marshaller.*;
import org.apache.juneau.objecttools.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.swap.*;
/**
* Java implementation of a JSON object.
*
*
* An extension of {@link LinkedHashMap}, so all methods available in that class are also available to this class.
*
*
* Note that the use of this class is optional for generating JSON. The serializers will accept any objects that implement the
* {@link java.util.Map} interface. But this class provides some useful additional functionality when working with
* JSON models constructed from Java Collections Framework objects. For example, a constructor is provided for
* converting a JSON object string directly into a {@link Map}. It also contains accessor methods for to avoid common
* typecasting when accessing elements in a list.
*
*
Example:
*
* // Construct an empty Map
* JsonMap map = JsonMap.of ();
*
* // Construct a Map from JSON
* map = JsonMap.ofJson ("{a:'A',b:{c:'C',d:123}}" );
*
* // Construct a Map using the append method
* map = JsonMap.of ().a("foo" ,"x" ).a("bar" ,123).a("baz" ,true );
*
* // Construct a Map from XML generated by XmlSerializer
* String xml = "<object><a type='string'>A</a><b type='object'><c type='string'>C</c><d type='number'>123</d></b></object>" ;
* map = JsonMap.of (xml , XmlParser.DEFAULT );
*
* // Construct a Map from a URL GET parameter string generated by UrlEncodingParser
* String urlParams = "?a='A'&b={c:'C',d:123}" ;
* map = JsonMap.of (urlParams , UrlEncodingParser.DEFAULT );
*
* // Construct JSON from JsonMap
* map = JsonMap.ofJson ("{foo:'bar'},{baz:[123,true]}" );
* String json = map .toString(); // Produces "{foo:'bar'},{baz:[123,true]}"
* json = map .toString(JsonSerializer.DEFAULT ); // Equivalent
* json = JsonSerializer.DEFAULT .serialize(map ); // Equivalent
*
* // Get a map entry as an Integer
* map = JsonMap.ofJson ("{foo:123}" );
* Integer integer = map .getInt("foo" );
* integer = map .get(Integer.class , "foo" ); // Equivalent
*
* // Get a map entry as a Float
* map = JsonMap.ofJson ("{foo:123}" );
* Float _float = map .getFloat("foo" );
* _float = map .get(Float.class , "foo" ); // Equivalent
*
* // Same as above, except converted to a String
* map = JsonMap.ofJson ("{foo:123}" );
* String string = map .getString("foo" ); // Returns "123"
* string = map .get(String.class , "foo" ); // Equivalent
*
* // Get one of the entries in the list as a bean (converted to a bean if it isn't already one)
* map = JsonMap.ofJson ("{person:{name:'John Smith',age:45}}" );
* Person person = map .get(Person.class , "person" );
*
* // Add an inner map
* JsonMap map1 = JsonMap.ofJson ("{a:1}" );
* JsonMap map2 = JsonMap.ofJson ("{b:2}" ).setInner(map1 );
* int _int = map2 .getInt("a" ); // a == 1
*
*
* Notes:
* This class is not thread safe.
*
*/
public class JsonMap extends LinkedHashMap {
//------------------------------------------------------------------------------------------------------------------
// Static
//------------------------------------------------------------------------------------------------------------------
private static final long serialVersionUID = 1L;
/**
* An empty read-only JsonMap.
*
* @serial exclude
*/
public static final JsonMap EMPTY_MAP = new JsonMap() {
private static final long serialVersionUID = 1L;
@Override /* Map */
public Set> entrySet() {
return Collections.emptyMap().entrySet();
}
@Override /* Map */
public Set keySet() {
return Collections.emptyMap().keySet();
}
@Override /* Map */
public Object put(String key, Object value) {
throw new UnsupportedOperationException("Not supported on read-only object.");
}
@Override /* Map */
public Object remove(Object key) {
throw new UnsupportedOperationException("Not supported on read-only object.");
}
@Override /* Map */
public Collection values() {
return Collections.emptyMap().values();
}
};
/**
* Construct an empty map.
*
* @return An empty map.
*/
public static JsonMap create() {
return new JsonMap();
}
/**
* Construct an empty map.
*
* @return An empty map.
*/
public static JsonMap filteredMap() {
return create().filtered();
}
/**
* Construct a map initialized with the specified map.
*
* @param values
* The map to copy.
* Can be null .
* Keys will be converted to strings using {@link Object#toString()}.
* @return A new map or null if the map was null .
*/
public static JsonMap of(Map,?> values) {
return values == null ? null : new JsonMap(values);
}
/**
* Construct a map initialized with the specified JSON string.
*
* @param json
* The JSON text to parse.
* Can be normal or simplified JSON.
* @return A new map or null if the string was null.
* @throws ParseException Malformed input encountered.
*/
public static JsonMap ofJson(CharSequence json) throws ParseException {
return json == null ? null : new JsonMap(json);
}
/**
* Construct a map initialized with the specified string.
*
* @param in
* The input being parsed.
* Can be null .
* @param p
* The parser to use to parse the input.
* If null , uses {@link JsonParser}.
* @return A new map or null if the input was null .
* @throws ParseException Malformed input encountered.
*/
public static JsonMap ofText(CharSequence in, Parser p) throws ParseException {
return in == null ? null : new JsonMap(in, p);
}
/**
* Construct a map initialized with the specified reader containing JSON.
*
* @param json
* The reader containing JSON text to parse.
* Can contain normal or simplified JSON.
* @return A new map or null if the input was null .
* @throws ParseException Malformed input encountered.
*/
public static JsonMap ofJson(Reader json) throws ParseException {
return json == null ? null : new JsonMap(json);
}
/**
* Construct a map initialized with the specified string.
*
* @param in
* The reader containing the input being parsed.
* Can contain normal or simplified JSON.
* @param p
* The parser to use to parse the input.
* If null , uses {@link JsonParser}.
* @return A new map or null if the input was null .
* @throws ParseException Malformed input encountered.
*/
public static JsonMap ofText(Reader in, Parser p) throws ParseException {
return in == null ? null : new JsonMap(in);
}
/**
* Construct a map initialized with the specified key/value pairs.
*
* Examples:
*
* JsonMap map = new JsonMap("key1" ,"val1" ,"key2" ,"val2" );
*
*
* @param keyValuePairs A list of key/value pairs to add to this map.
* @return A new map, never null .
*/
public static JsonMap of(Object... keyValuePairs) {
return new JsonMap(keyValuePairs);
}
/**
* Construct a map initialized with the specified key/value pairs.
*
*
* Same as {@link #of(Object...)} but calls {@link #filtered()} on the created map.
*
*
Examples:
*
* JsonMap map = new JsonMap("key1" ,"val1" ,"key2" ,"val2" );
*
*
* @param keyValuePairs A list of key/value pairs to add to this map.
* @return A new map, never null .
*/
public static JsonMap filteredMap(Object... keyValuePairs) {
return new JsonMap(keyValuePairs).filtered();
}
//------------------------------------------------------------------------------------------------------------------
// Instance
//------------------------------------------------------------------------------------------------------------------
private transient BeanSession session;
private Map inner;
private transient ObjectRest objectRest;
private transient Predicate valueFilter = x -> true;
/**
* Construct an empty map.
*/
public JsonMap() {}
/**
* Construct an empty map with the specified bean context.
*
* @param session The bean session to use for creating beans.
*/
public JsonMap(BeanSession session) {
this.session = session;
}
/**
* Construct a map initialized with the specified map.
*
* @param in
* The map to copy.
* Can be null .
* Keys will be converted to strings using {@link Object#toString()}.
*/
public JsonMap(Map,?> in) {
this();
if (in != null)
in.forEach((k,v) -> put(k.toString(), v));
}
/**
* Construct a map initialized with the specified JSON.
*
* @param json
* The JSON text to parse.
* Can be normal or simplified JSON.
* @throws ParseException Malformed input encountered.
*/
public JsonMap(CharSequence json) throws ParseException {
this(json, JsonParser.DEFAULT);
}
/**
* Construct a map initialized with the specified string.
*
* @param in
* The input being parsed.
* Can be null .
* @param p
* The parser to use to parse the input.
* If null , uses {@link JsonParser}.
* @throws ParseException Malformed input encountered.
*/
public JsonMap(CharSequence in, Parser p) throws ParseException {
this(p == null ? BeanContext.DEFAULT_SESSION : p.getBeanContext().getSession());
if (p == null)
p = JsonParser.DEFAULT;
if (! StringUtils.isEmpty(in))
p.parseIntoMap(in, this, bs().string(), bs().object());
}
/**
* Construct a map initialized with the specified reader containing JSON.
*
* @param json
* The reader containing JSON text to parse.
* Can contain normal or simplified JSON.
* @throws ParseException Malformed input encountered.
*/
public JsonMap(Reader json) throws ParseException {
parse(json, JsonParser.DEFAULT);
}
/**
* Construct a map initialized with the specified string.
*
* @param in
* The reader containing the input being parsed.
* Can contain normal or simplified JSON.
* @param p
* The parser to use to parse the input.
* If null , uses {@link JsonParser}.
* @throws ParseException Malformed input encountered.
*/
public JsonMap(Reader in, Parser p) throws ParseException {
this(p == null ? BeanContext.DEFAULT_SESSION : p.getBeanContext().getSession());
parse(in, p);
}
/**
* Construct a map initialized with the specified key/value pairs.
*
* Examples:
*
* JsonMap map = new JsonMap("key1" ,"val1" ,"key2" ,"val2" );
*
*
* @param keyValuePairs A list of key/value pairs to add to this map.
*/
public JsonMap(Object... keyValuePairs) {
if (keyValuePairs.length % 2 != 0)
throw new IllegalArgumentException("Odd number of parameters passed into JsonMap(Object...)");
for (int i = 0; i < keyValuePairs.length; i+=2)
put(stringify(keyValuePairs[i]), keyValuePairs[i+1]);
}
//------------------------------------------------------------------------------------------------------------------
// Initializers
//------------------------------------------------------------------------------------------------------------------
/**
* Set an inner map in this map to allow for chained get calls.
*
*
* If {@link #get(Object)} returns null , then {@link #get(Object)} will be called on the inner map.
*
*
* In addition to providing the ability to chain maps, this method also provides the ability to wrap an existing map
* inside another map so that you can add entries to the outer map without affecting the values on the inner map.
*
*
* JsonMap map1 = JsonMap.ofJson ("{foo:1}" );
* JsonMap map2 = JsonMap.of ().setInner(map1 );
* map2 .put("foo" , 2); // Overwrite the entry
* int foo1 = map1 .getInt("foo" ); // foo1 == 1
* int foo2 = map2 .getInt("foo" ); // foo2 == 2
*
*
* @param inner
* The inner map.
* Can be null to remove the inner map from an existing map.
* @return This object.
*/
public JsonMap inner(Map inner) {
this.inner = inner;
return this;
}
/**
* Override the default bean session used for converting POJOs.
*
*
* Default is {@link BeanContext#DEFAULT}, which is sufficient in most cases.
*
*
* Useful if you're serializing/parsing beans with transforms defined.
*
* @param session The new bean session.
* @return This object.
*/
public JsonMap session(BeanSession session) {
this.session = session;
return this;
}
//------------------------------------------------------------------------------------------------------------------
// Appenders
//------------------------------------------------------------------------------------------------------------------
/**
* Adds an entry to this map.
*
* @param key The key.
* @param value The value.
* @return This object.
*/
public JsonMap append(String key, Object value) {
put(key, value);
return this;
}
/**
* Appends all the entries in the specified map to this map.
*
* @param values The map to copy. Can be null .
* @return This object.
*/
public JsonMap append(Map values) {
if (values != null)
super.putAll(values);
return this;
}
/**
* Add if flag is true .
*
* @param flag The flag to check.
* @param key The key.
* @param value The value.
* @return This object.
*/
public JsonMap appendIf(boolean flag, String key, Object value) {
if (flag)
append(key, value);
return this;
}
/**
* Add if predicate matches value.
*
* @param The value type.
* @param test The predicate to match against.
* @param key The key.
* @param value The value.
* @return This object.
*/
public JsonMap appendIf(Predicate test, String key, T value) {
return appendIf(test(test, value), key, value);
}
/**
* Adds the first value that matches the specified predicate.
*
* @param The value types.
* @param test The predicate to match against.
* @param key The key.
* @param values The values to test.
* @return This object.
*/
@SafeVarargs
public final JsonMap appendFirst(Predicate test, String key, T...values) {
for (T v : values)
if (test(test, v))
return append(key, v);
return this;
}
/**
* Adds a value in this map if the entry does not exist or the current value is null .
*
* @param key The map key.
* @param value The value to set if the current value does not exist or is null .
* @return This object.
*/
public JsonMap appendIfAbsent(String key, Object value) {
return appendIfAbsentIf(x -> true, key, value);
}
/**
* Adds a value in this map if the entry does not exist or the current value is null and the value matches the specified predicate.
*
* @param The value type.
* @param predicate The predicate to test the value with.
* @param key The map key.
* @param value The value to set if the current value does not exist or is null .
* @return This object.
*/
public JsonMap appendIfAbsentIf(Predicate predicate, String key, T value) {
Object o = get(key);
if (o == null && predicate.test(value))
put(key, value);
return this;
}
/**
* Enables filtering based on default values.
*
*
* Any of the following types will be ignored when set as values in this map:
*
* null
* false
* -1 (any Number type)
* Empty arrays/collections/maps.
*
* @return This object.
*/
public JsonMap filtered() {
return filtered(x -> ! (
x == null
|| (x instanceof Boolean && x.equals(false))
|| (x instanceof Number && ((Number)x).intValue() == -1)
|| (x.getClass().isArray() && Array.getLength(x) == 0)
|| (x instanceof Map && ((Map,?>)x).isEmpty())
|| (x instanceof Collection && ((Collection>)x).isEmpty())
));
}
/**
* Enables filtering based on a predicate test.
*
*
* If the predicate evaluates to false on values added to this map, the entry will be skipped.
*
* @param value The value tester predicate.
* @return This object.
*/
public JsonMap filtered(Predicate value) {
valueFilter = value;
return this;
}
//------------------------------------------------------------------------------------------------------------------
// Retrievers
//------------------------------------------------------------------------------------------------------------------
/**
* Same as {@link Map#get(Object) get()}, but casts or converts the value to the specified class type.
*
*
* This is the preferred get method for simple types.
*
*
Examples:
*
* JsonMap map = JsonMap.ofJson ("..." );
*
* // Value converted to a string.
* String string = map .get("key1" , String.class );
*
* // Value converted to a bean.
* MyBean bean = map .get("key2" , MyBean.class );
*
* // Value converted to a bean array.
* MyBean[] beanArray = map .get("key3" , MyBean[].class );
*
* // Value converted to a linked-list of objects.
* List list = map .get("key4" , LinkedList.class );
*
* // Value converted to a map of object keys/values.
* Map map2 = map .get("key5" , TreeMap.class );
*
*
*
* See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions.
*
* @param key The key.
* @param The class type returned.
* @param type The class type returned.
* @return The value, or null if the entry doesn't exist.
*/
public T get(String key, Class type) {
return getWithDefault(key, (T)null, type);
}
/**
* Same as {@link #get(String,Class)}, but allows for complex data types consisting of collections or maps.
*
*
* The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps).
*
*
Examples:
*
* JsonMap map = JsonMap.ofJson ("..." );
*
* // Value converted to a linked-list of strings.
* List<String> list1 = map .get("key1" , LinkedList.class , String.class );
*
* // Value converted to a linked-list of beans.
* List<MyBean> list2 = map .get("key2" , LinkedList.class , MyBean.class );
*
* // Value converted to a linked-list of linked-lists of strings.
* List<List<String>> list3 = map .get("key3" , LinkedList.class , LinkedList.class , String.class );
*
* // Value converted to a map of string keys/values.
* Map<String,String> map1 = map .get("key4" , TreeMap.class , String.class , String.class );
*
* // Value converted to a map containing string keys and values of lists containing beans.
* Map<String,List<MyBean>> map2 = map .get("key5" , TreeMap.class , String.class , List.class , MyBean.class );
*
*
*
* Collection classes are assumed to be followed by zero or one objects indicating the element type.
*
*
* Map classes are assumed to be followed by zero or two meta objects indicating the key and value types.
*
*
* The array can be arbitrarily long to indicate arbitrarily complex data structures.
*
*
* See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions.
*
*
Notes:
*
* Use the {@link #get(String, Class)} method instead if you don't need a parameterized map/collection.
*
*
* @param key The key.
* @param The class type returned.
* @param type The class type returned.
* @param args The class type parameters.
* @return The value, or null if the entry doesn't exist.
*/
public T get(String key, Type type, Type...args) {
return getWithDefault(key, null, type, args);
}
/**
* Same as {@link Map#get(Object) get()}, but returns the default value if the key could not be found.
*
* @param key The key.
* @param def The default value if the entry doesn't exist.
* @return The value, or the default value if the entry doesn't exist.
*/
public Object getWithDefault(String key, Object def) {
Object o = get(key);
return (o == null ? def : o);
}
/**
* Same as {@link #get(String,Class)} but returns a default value if the value does not exist.
*
* @param key The key.
* @param def The default value. Can be null .
* @param The class type returned.
* @param type The class type returned.
* @return The value, or null if the entry doesn't exist.
*/
public T getWithDefault(String key, T def, Class type) {
return getWithDefault(key, def, type, new Type[0]);
}
/**
* Same as {@link #get(String,Type,Type...)} but returns a default value if the value does not exist.
*
* @param key The key.
* @param def The default value. Can be null .
* @param The class type returned.
* @param type The class type returned.
* @param args The class type parameters.
* @return The value, or null if the entry doesn't exist.
*/
public T getWithDefault(String key, T def, Type type, Type...args) {
Object o = get(key);
if (o == null)
return def;
T t = bs().convertToType(o, type, args);
return t == null ? def : t;
}
/**
* Searches for the specified key in this map ignoring case.
*
* @param key
* The key to search for.
* For performance reasons, it's preferable that the key be all lowercase.
* @return The key, or null if map does not contain this key.
*/
public String findKeyIgnoreCase(String key) {
for (String k : keySet())
if (key.equalsIgnoreCase(k))
return k;
return null;
}
/**
* 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 objectSwap The swap class used to convert the raw type to a transformed type.
* @param The transformed class type.
* @return The value, or null if the entry doesn't exist.
* @throws ParseException Malformed input encountered.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public T getSwapped(String key, ObjectSwap objectSwap) throws ParseException {
try {
Object o = super.get(key);
if (o == null)
return null;
ObjectSwap swap = objectSwap;
return (T) swap.unswap(bs(), o, null);
} catch (ParseException e) {
throw e;
} catch (Exception e) {
throw new ParseException(e);
}
}
/**
* Returns the value for the first key in the list that has an entry in this map.
*
* @param keys The keys to look up in order.
* @return The value of the first entry whose key exists, or null if none of the keys exist in this map.
*/
public Object find(String...keys) {
for (String key : keys)
if (containsKey(key))
return get(key);
return null;
}
/**
* Returns the value for the first key in the list that has an entry in this map.
*
*
* Casts or converts the value to the specified class type.
*
*
* See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions.
*
* @param type The class type to convert the value to.
* @param The class type to convert the value to.
* @param keys The keys to look up in order.
* @return The value of the first entry whose key exists, or null if none of the keys exist in this map.
*/
public T find(Class type, String...keys) {
for (String key : keys)
if (containsKey(key))
return get(key, type);
return null;
}
/**
* Returns the specified entry value converted to a {@link String}.
*
*
* Shortcut for get(key, String.class )
.
*
* @param key The key.
* @return The converted value, or null if the map contains no mapping for this key.
*/
public String getString(String key) {
return get(key, String.class);
}
/**
* Returns the specified entry value converted to a {@link String}.
*
*
* Shortcut for get(key, String[].class )
.
*
* @param key The key.
* @return The converted value, or null if the map contains no mapping for this key.
*/
public String[] getStringArray(String key) {
return getStringArray(key, null);
}
/**
* Same as {@link #getStringArray(String)} but returns a default value if the value cannot be found.
*
* @param key The map key.
* @param def The default value if value is not found.
* @return The value converted to a string array.
*/
public String[] getStringArray(String key, String[] def) {
Object s = get(key, Object.class);
if (s == null)
return def;
String[] r = null;
if (s instanceof Collection)
r = ArrayUtils.toStringArray((Collection>)s);
else if (s instanceof String[])
r = (String[])s;
else if (s instanceof Object[])
r = ArrayUtils.toStringArray(alist((Object[])s));
else
r = split(stringify(s));
return (r.length == 0 ? def : r);
}
/**
* Returns the specified entry value converted to a {@link String}.
*
*
* Shortcut for getWithDefault(key, defVal, String.class )
.
*
* @param key The key.
* @param defVal The default value if the map doesn't contain the specified mapping.
* @return The converted value, or the default value if the map contains no mapping for this key.
*/
public String getString(String key, String defVal) {
return getWithDefault(key, defVal, String.class);
}
/**
* Returns the specified entry value converted to an {@link Integer}.
*
*
* Shortcut for get(key, Integer.class )
.
*
* @param key The key.
* @return The converted value, or null if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public Integer getInt(String key) {
return get(key, Integer.class);
}
/**
* Returns the specified entry value converted to an {@link Integer}.
*
*
* Shortcut for getWithDefault(key, defVal, Integer.class )
.
*
* @param key The key.
* @param defVal The default value if the map doesn't contain the specified mapping.
* @return The converted value, or the default value if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public Integer getInt(String key, Integer defVal) {
return getWithDefault(key, defVal, Integer.class);
}
/**
* Returns the specified entry value converted to a {@link Long}.
*
*
* Shortcut for get(key, Long.class )
.
*
* @param key The key.
* @return The converted value, or null if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public Long getLong(String key) {
return get(key, Long.class);
}
/**
* Returns the specified entry value converted to a {@link Long}.
*
*
* Shortcut for getWithDefault(key, defVal, Long.class )
.
*
* @param key The key.
* @param defVal The default value if the map doesn't contain the specified mapping.
* @return The converted value, or the default value if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public Long getLong(String key, Long defVal) {
return getWithDefault(key, defVal, Long.class);
}
/**
* Returns the specified entry value converted to a {@link Boolean}.
*
*
* Shortcut for get(key, Boolean.class )
.
*
* @param key The key.
* @return The converted value, or null if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public Boolean getBoolean(String key) {
return get(key, Boolean.class);
}
/**
* Returns the specified entry value converted to a {@link Boolean}.
*
*
* Shortcut for getWithDefault(key, defVal, Boolean.class )
.
*
* @param key The key.
* @param defVal The default value if the map doesn't contain the specified mapping.
* @return The converted value, or the default value if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public Boolean getBoolean(String key, Boolean defVal) {
return getWithDefault(key, defVal, Boolean.class);
}
/**
* Returns the specified entry value converted to a {@link Map}.
*
*
* Shortcut for get(key, JsonMap.class )
.
*
* @param key The key.
* @return The converted value, or null if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public JsonMap getMap(String key) {
return get(key, JsonMap.class);
}
/**
* Returns the specified entry value converted to a {@link JsonMap}.
*
*
* Shortcut for getWithDefault(key, defVal, JsonMap.class )
.
*
* @param key The key.
* @param defVal The default value if the map doesn't contain the specified mapping.
* @return The converted value, or the default value if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public JsonMap getMap(String key, JsonMap defVal) {
return getWithDefault(key, defVal, JsonMap.class);
}
/**
* Same as {@link #getMap(String)} but creates a new empty {@link JsonMap} if it doesn't already exist.
*
* @param key The key.
* @param createIfNotExists If mapping doesn't already exist, create one with an empty {@link JsonMap}.
* @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 JsonMap getMap(String key, boolean createIfNotExists) {
JsonMap m = getWithDefault(key, null, JsonMap.class);
if (m == null && createIfNotExists) {
m = new JsonMap();
put(key, m);
}
return m;
}
/**
* Same as {@link #getMap(String, JsonMap)} except converts the keys and values to the specified types.
*
* @param The key type.
* @param The value type.
* @param key The key.
* @param keyType The key type class.
* @param valType The value type class.
* @param def The default value if the map doesn't contain the specified mapping.
* @return The converted value, or the default value if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public Map getMap(String key, Class keyType, Class valType, Map def) {
Object o = get(key);
if (o == null)
return def;
return bs().convertToType(o, Map.class, keyType, valType);
}
/**
* Returns the specified entry value converted to a {@link JsonList}.
*
*
* Shortcut for get(key, JsonList.class )
.
*
* @param key The key.
* @return The converted value, or null if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public JsonList getList(String key) {
return get(key, JsonList.class);
}
/**
* Returns the specified entry value converted to a {@link JsonList}.
*
*
* Shortcut for getWithDefault(key, defVal, JsonList.class )
.
*
* @param key The key.
* @param defVal The default value if the map doesn't contain the specified mapping.
* @return The converted value, or the default value if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public JsonList getList(String key, JsonList defVal) {
return getWithDefault(key, defVal, JsonList.class);
}
/**
* Same as {@link #getList(String)} but creates a new empty {@link JsonList} if it doesn't already exist.
*
* @param key The key.
* @param createIfNotExists If mapping doesn't already exist, create one with an empty {@link JsonList}.
* @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 JsonList getList(String key, boolean createIfNotExists) {
JsonList m = getWithDefault(key, null, JsonList.class);
if (m == null && createIfNotExists) {
m = new JsonList();
put(key, m);
}
return m;
}
/**
* Same as {@link #getList(String, JsonList)} except converts the elements to the specified types.
*
* @param The element type.
* @param key The key.
* @param elementType The element type class.
* @param def The default value if the map doesn't contain the specified mapping.
* @return The converted value, or the default value if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public List getList(String key, Class elementType, List def) {
Object o = get(key);
if (o == null)
return def;
return bs().convertToType(o, List.class, elementType);
}
/**
* Returns the first entry that exists converted to a {@link String}.
*
*
* Shortcut for find(String.class , keys)
.
*
* @param keys The list of keys to look for.
* @return
* The converted value of the first key in the list that has an entry in this map, or null if the map
* contains no mapping for any of the keys.
*/
public String findString(String... keys) {
return find(String.class, keys);
}
/**
* Returns the first entry that exists converted to an {@link Integer}.
*
*
* Shortcut for find(Integer.class , keys)
.
*
* @param keys The list of keys to look for.
* @return
* The converted value of the first key in the list that has an entry in this map, or null if the map
* contains no mapping for any of the keys.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public Integer findInt(String... keys) {
return find(Integer.class, keys);
}
/**
* Returns the first entry that exists converted to a {@link Long}.
*
*
* Shortcut for find(Long.class , keys)
.
*
* @param keys The list of keys to look for.
* @return
* The converted value of the first key in the list that has an entry in this map, or null if the map
* contains no mapping for any of the keys.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public Long findLong(String... keys) {
return find(Long.class, keys);
}
/**
* Returns the first entry that exists converted to a {@link Boolean}.
*
*
* Shortcut for find(Boolean.class , keys)
.
*
* @param keys The list of keys to look for.
* @return
* The converted value of the first key in the list that has an entry in this map, or null if the map
* contains no mapping for any of the keys.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public Boolean findBoolean(String... keys) {
return find(Boolean.class, keys);
}
/**
* Returns the first entry that exists converted to a {@link JsonMap}.
*
*
* Shortcut for find(JsonMap.class , keys)
.
*
* @param keys The list of keys to look for.
* @return
* The converted value of the first key in the list that has an entry in this map, or null if the map
* contains no mapping for any of the keys.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public JsonMap findMap(String... keys) {
return find(JsonMap.class, keys);
}
/**
* Returns the first entry that exists converted to a {@link JsonList}.
*
*
* Shortcut for find(JsonList.class , keys)
.
*
* @param keys The list of keys to look for.
* @return
* The converted value of the first key in the list that has an entry in this map, or null if the map
* contains no mapping for any of the keys.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public JsonList findList(String... keys) {
return find(JsonList.class, keys);
}
/**
* Returns the first key in the map.
*
* @return The first key in the map, or null if the map is empty.
*/
public String getFirstKey() {
return isEmpty() ? null : keySet().iterator().next();
}
/**
* Returns the class type of the object at the specified index.
*
* @param key The key into this map.
* @return
* The data type of the object at the specified key, or null if the value is null or does not exist.
*/
public ClassMeta> getClassMeta(String key) {
return bs().getClassMetaForObject(get(key));
}
/**
* Equivalent to calling get(class,key,def) followed by remove(key);
* @param key The key.
* @param defVal The default value if the map doesn't contain the specified mapping.
* @param type The class type.
*
* @param The class type.
* @return The converted value, or the default value if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public T removeWithDefault(String key, T defVal, Class type) {
T t = getWithDefault(key, defVal, type);
remove(key);
return t;
}
/**
* Equivalent to calling removeWithDefault(key,null ,String.class )
.
*
* @param key The key.
* @return The converted value, or null if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public String removeString(String key) {
return removeString(key, null);
}
/**
* Equivalent to calling removeWithDefault(key,def,String.class )
.
*
* @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 removeWithDefault(key,null ,Integer.class )
.
*
* @param key The key.
* @return The converted value, or null if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public Integer removeInt(String key) {
return removeInt(key, null);
}
/**
* Equivalent to calling removeWithDefault(key,def,Integer.class )
.
*
* @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 removeWithDefault(key,null ,Boolean.class )
.
*
* @param key The key.
* @return The converted value, or null if the map contains no mapping for this key.
* @throws InvalidDataConversionException If value cannot be converted.
*/
public Boolean removeBoolean(String key) {
return removeBoolean(key, null);
}
/**
* Equivalent to calling removeWithDefault(key,def,Boolean.class )
.
*
* @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 keys) {
keys.forEach(this::remove);
}
/**
* Convenience method for removing several keys at once.
*
* @param keys The list of keys to remove.
*/
public void removeAll(String... keys) {
for (String k : keys)
remove(k);
}
/**
* The opposite of {@link #removeAll(String...)}.
*
*
* Discards all keys from this map that aren't in the specified list.
*
* @param keys The keys to keep.
* @return This map.
*/
public JsonMap keepAll(String...keys) {
for (Iterator i = keySet().iterator(); i.hasNext();) {
boolean remove = true;
String key = i.next();
for (String k : keys) {
if (k.equals(key)) {
remove = false;
break;
}
}
if (remove)
i.remove();
}
return this;
}
/**
* Returns true if the map contains the specified entry and the value is not null nor an empty string.
*
*
* Always returns false if the value is not a {@link CharSequence}.
*
* @param key The key.
* @return true if the map contains the specified entry and the value is not null nor an empty string.
*/
public boolean containsKeyNotEmpty(String key) {
Object val = get(key);
if (val == null)
return false;
if (val instanceof CharSequence)
return ! StringUtils.isEmpty((CharSequence)val);
return false;
}
/**
* Returns true if this map contains the specified key, ignoring the inner map if it exists.
*
* @param key The key to look up.
* @return true if this map contains the specified key.
*/
public boolean containsOuterKey(Object key) {
return super.containsKey(key);
}
/**
* Returns a copy of this JsonMap 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 JsonMap include(String...keys) {
JsonMap m2 = new JsonMap();
this.forEach((k,v) -> {
for (String kk : keys)
if (kk.equals(k))
m2.put(kk, v);
});
return m2;
}
/**
* Returns a copy of this JsonMap 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 JsonMap exclude(String...keys) {
JsonMap m2 = new JsonMap();
this.forEach((k,v) -> {
boolean exclude = false;
for (String kk : keys)
if (kk.equals(k))
exclude = true;
if (! exclude)
m2.put(k, v);
});
return m2;
}
/**
* Converts this map into an object of the specified type.
*
*
* If this map contains a "_type" entry, it must be the same as or a subclass of the type .
*
* @param The class type to convert this map object to.
* @param type The class type to convert this map object to.
* @return The new object.
* @throws ClassCastException
* If the "_type" entry is present and not assignable from type
*/
@SuppressWarnings("unchecked")
public T cast(Class type) {
BeanSession bs = bs();
ClassMeta> c2 = bs.getClassMeta(type);
String typePropertyName = bs.getBeanTypePropertyName(c2);
ClassMeta> c1 = bs.getBeanRegistry().getClassMeta((String)get(typePropertyName));
ClassMeta> c = c1 == null ? c2 : narrowClassMeta(c1, c2);
if (c.isObject())
return (T)this;
return (T)cast2(c);
}
/**
* Same as {@link #cast(Class)}, except allows you to specify a {@link ClassMeta} parameter.
*
* @param The class type to convert this map object to.
* @param cm The class type to convert this map object to.
* @return The new object.
* @throws ClassCastException
* If the "_type" entry is present and not assignable from type
*/
@SuppressWarnings({"unchecked"})
public T cast(ClassMeta cm) {
BeanSession bs = bs();
ClassMeta> c1 = bs.getBeanRegistry().getClassMeta((String)get(bs.getBeanTypePropertyName(cm)));
ClassMeta> c = narrowClassMeta(c1, cm);
return (T)cast2(c);
}
//------------------------------------------------------------------------------------------------------------------
// POJO REST methods.
//------------------------------------------------------------------------------------------------------------------
/**
* Same as {@link #get(String,Class) get(String,Class)}, but the key is a slash-delimited path used to traverse
* entries in this POJO.
*
*
* For example, the following code is equivalent:
*
*
* JsonMap map = JsonMap.ofJson ("..." );
*
* // Long way
* long _long = map .getMap("foo" ).getList("bar" ).getMap("0" ).getLong("baz" );
*
* // Using this method
* long _long = map .getAt("foo/bar/0/baz" , long .class );
*
*
*
* This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various
* class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays).
*
* @param path The path to the entry.
* @param type The class type.
*
* @param The class type.
* @return The value, or null if the entry doesn't exist.
*/
public T getAt(String path, Class type) {
return getObjectRest().get(path, type);
}
/**
* Same as {@link #getAt(String,Class)}, but allows for conversion to complex maps and collections.
*
*
* This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various
* class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays).
*
* @param path The path to the entry.
* @param type The class type.
* @param args The class parameter types.
*
* @param The class type.
* @return The value, or null if the entry doesn't exist.
*/
public T getAt(String path, Type type, Type...args) {
return getObjectRest().get(path, type, args);
}
/**
* Same as put(String,Object) , but the key is a slash-delimited path used to traverse entries in this
* POJO.
*
*
* For example, the following code is equivalent:
*
*
* JsonMap map = JsonMap.ofJson ("..." );
*
* // Long way
* map .getMap("foo" ).getList("bar" ).getMap("0" ).put("baz" , 123);
*
* // Using this method
* map .putAt("foo/bar/0/baz" , 123);
*
*
*
* This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various
* class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays).
*
* @param path The path to the entry.
* @param o The new value.
* @return The previous value, or null if the entry doesn't exist.
*/
public Object putAt(String path, Object o) {
return getObjectRest().put(path, o);
}
/**
* Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append to collections and arrays.
*
*
* For example, the following code is equivalent:
*
*
* JsonMap map = JsonMap.ofJson ("..." );
*
* // Long way
* map .getMap("foo" ).getList("bar" ).append(123);
*
* // Using this method
* map .postAt("foo/bar" , 123);
*
*
*
* This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various
* class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays).
*
* @param path The path to the entry.
* @param o The new value.
* @return The previous value, or null if the entry doesn't exist.
*/
public Object postAt(String path, Object o) {
return getObjectRest().post(path, o);
}
/**
* Similar to {@link #remove(Object) remove(Object)}, but the key is a slash-delimited path used to traverse entries
* in this POJO.
*
*
* For example, the following code is equivalent:
*
*
* JsonMap map = JsonMap.ofJson ("..." );
*
* // Long way
* map .getMap("foo" ).getList("bar" ).getMap(0).remove("baz" );
*
* // Using this method
* map .deleteAt("foo/bar/0/baz" );
*
*
*
* This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various
* class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays).
*
* @param path The path to the entry.
* @return The previous value, or null if the entry doesn't exist.
*/
public Object deleteAt(String path) {
return getObjectRest().delete(path);
}
//------------------------------------------------------------------------------------------------------------------
// Other methods
//------------------------------------------------------------------------------------------------------------------
@Override
public Object put(String key, Object value) {
if (valueFilter.test(value))
super.put(key, value);
return null;
}
/**
* Returns the {@link BeanSession} currently associated with this map.
*
* @return The {@link BeanSession} currently associated with this map.
*/
public BeanSession getBeanSession() {
return session;
}
/**
* Sets the {@link BeanSession} currently associated with this map.
*
* @param value The {@link BeanSession} currently associated with this map.
* @return This object.
*/
public JsonMap setBeanSession(BeanSession value) {
this.session = value;
return this;
}
/**
* Convenience method for inserting JSON directly into an attribute on this object.
*
*
* The JSON text can be an object (i.e. "{...}" ) or an array (i.e. "[...]" ).
*
* @param key The key.
* @param json The JSON text that will be parsed into an Object and then inserted into this map.
* @throws ParseException Malformed input encountered.
*/
public void putJson(String key, String json) throws ParseException {
this.put(key, JsonParser.DEFAULT.parse(json, Object.class));
}
/**
* 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.
*/
public String asString(WriterSerializer serializer) {
return serializer.toString(this);
}
/**
* Serialize this object to Simplified JSON using {@link Json5Serializer#DEFAULT}.
*
* @return This object serialized as a string.
*/
public String asString() {
if (Json5Serializer.DEFAULT == null)
return stringify(this);
return Json5Serializer.DEFAULT.toString(this);
}
/**
* Serialize this object to Simplified JSON using {@link Json5Serializer#DEFAULT_READABLE}.
*
* @return This object serialized as a string.
*/
public String asReadableString() {
if (Json5Serializer.DEFAULT_READABLE == null)
return stringify(this);
return Json5Serializer.DEFAULT_READABLE.toString(this);
}
/**
* Convenience method for serializing this map to the specified Writer using the
* {@link JsonSerializer#DEFAULT} serializer.
*
* @param w The writer to serialize this object to.
* @return This object.
* @throws IOException If a problem occurred trying to write to the writer.
* @throws SerializeException If a problem occurred trying to convert the output.
*/
public JsonMap writeTo(Writer w) throws IOException, SerializeException {
JsonSerializer.DEFAULT.serialize(this, w);
return this;
}
/**
* Returns true if this map is unmodifiable.
*
* @return true if this map is unmodifiable.
*/
public boolean isUnmodifiable() {
return false;
}
/**
* Returns a modifiable copy of this map if it's unmodifiable.
*
* @return A modifiable copy of this map if it's unmodifiable, or this map if it is already modifiable.
*/
public JsonMap modifiable() {
if (isUnmodifiable())
return new JsonMap(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 JsonMap unmodifiable() {
if (this instanceof UnmodifiableJsonMap)
return this;
return new UnmodifiableJsonMap(this);
}
//------------------------------------------------------------------------------------------------------------------
// Utility methods
//------------------------------------------------------------------------------------------------------------------
private BeanSession bs() {
if (session == null)
session = BeanContext.DEFAULT_SESSION;
return session;
}
private ObjectRest getObjectRest() {
if (objectRest == null)
objectRest = new ObjectRest(this);
return objectRest;
}
/*
* 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 cast2(ClassMeta cm) {
BeanSession bs = bs();
try {
Object value = get("value");
if (cm.isMap()) {
Map m2 = (cm.canCreateNewInstance() ? (Map)cm.newInstance() : new JsonMap(bs));
ClassMeta> kType = cm.getKeyType(), vType = cm.getValueType();
forEach((k,v) -> {
if (! k.equals(bs.getBeanTypePropertyName(cm))) {
// Attempt to recursively cast child maps.
if (v instanceof JsonMap)
v = ((JsonMap)v).cast(vType);
Object k2 = (kType.isString() ? k : bs.convertToType(k, kType));
v = (vType.isObject() ? v : bs.convertToType(v, vType));
m2.put(k2, 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.
forEach((k,v) -> {
if (! k.equals(bs.getBeanTypePropertyName(cm))) {
// Attempt to recursively cast child maps.
if (v instanceof JsonMap)
v = ((JsonMap)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.getInnerClass(),
"Error occurred attempting to cast to an object of type ''{0}''", cm.getInnerClass().getName());
}
throw new BeanRuntimeException(cm.getInnerClass(),
"Cannot convert to class type ''{0}''. Only beans and maps can be converted using this method.",
cm.getInnerClass().getName());
}
private void parse(Reader r, Parser p) throws ParseException {
if (p == null)
p = JsonParser.DEFAULT;
p.parseIntoMap(r, this, bs().string(), bs().object());
}
private static final class UnmodifiableJsonMap extends JsonMap {
private static final long serialVersionUID = 1L;
UnmodifiableJsonMap(JsonMap contents) {
if (contents != null)
contents.forEach(super::put);
}
@Override
public Object put(String key, Object val) {
throw new UnsupportedOperationException("Not supported on read-only object.");
}
@Override
public Object remove(Object key) {
throw new UnsupportedOperationException("Not supported on read-only object.");
}
@Override
public boolean isUnmodifiable() {
return true;
}
}
//------------------------------------------------------------------------------------------------------------------
// Overridden methods.
//------------------------------------------------------------------------------------------------------------------
@Override /* Map */
public Object get(Object key) {
Object o = super.get(key);
if (o == null && inner != null)
o = inner.get(key);
return o;
}
@Override /* Map */
public boolean containsKey(Object key) {
if (super.containsKey(key))
return true;
if (inner != null)
return inner.containsKey(key);
return false;
}
@Override /* Map */
public Set keySet() {
if (inner == null)
return super.keySet();
LinkedHashSet s = set();
s.addAll(inner.keySet());
s.addAll(super.keySet());
return s;
}
@Override /* Map */
public Set> entrySet() {
if (inner == null)
return super.entrySet();
final Set keySet = keySet();
final Iterator keys = keySet.iterator();
return new AbstractSet<>() {
@Override /* Iterable */
public Iterator> iterator() {
return new Iterator<>() {
@Override /* Iterator */
public boolean hasNext() {
return keys.hasNext();
}
@Override /* Iterator */
public Map.Entry next() {
return new Map.Entry<>() {
String key = keys.next();
@Override /* Map.Entry */
public String getKey() {
return key;
}
@Override /* Map.Entry */
public Object getValue() {
return get(key);
}
@Override /* Map.Entry */
public Object setValue(Object object) {
return put(key, object);
}
};
}
@Override /* Iterator */
public void remove() {
throw new UnsupportedOperationException("Not supported on read-only object.");
}
};
}
@Override /* Set */
public int size() {
return keySet.size();
}
};
}
/**
* A synonym for {@link #toString()}
*
* @return This object as a JSON string.
*/
public String asJson() {
return toString();
}
@Override /* Object */
public String toString() {
return Json5.of(this);
}
}