All Downloads are FREE. Search and download functionalities are using the official Maven repository.
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.
com.segment.analytics.ValueMap Maven / Gradle / Ivy
Go to download
The hassle-free way to add analytics to your Android app.
package com.segment.analytics;
import android.content.Context;
import android.content.SharedPreferences;
import com.segment.analytics.internal.Utils;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import static com.segment.analytics.internal.Utils.getSegmentSharedPreferences;
import static com.segment.analytics.internal.Utils.isNullOrEmpty;
/**
* A class that wraps an existing {@link Map} to expose value type functionality. All {@link
* java.util.Map} methods will simply be forwarded to a delegate map. This class is meant to
* subclassed and provide methods to access values in keys.
*
* Library users won't need to create instances of this class, they can use plain old {@link Map}
* instead, and our library will handle serializing them.
*
* Although it lets you use custom objects for values, note that type information is lost during
* serialization. You should use one of the coercion methods instead to get objects of a concrete
* type.
*/
public class ValueMap implements Map {
private final Map delegate;
/**
* Uses reflection to create an instance of a subclass of {@link ValueMap}. The subclass
* must declare a map constructor.
*/
static T createValueMap(Map map, Class clazz) {
try {
Constructor constructor = clazz.getDeclaredConstructor(Map.class);
constructor.setAccessible(true);
return constructor.newInstance(map);
} catch (Exception e) {
throw new AssertionError(
"Could not create instance of " + clazz.getCanonicalName() + ".\n" + e);
}
}
public ValueMap() {
delegate = new LinkedHashMap<>();
}
public ValueMap(int initialCapacity) {
delegate = new LinkedHashMap<>(initialCapacity);
}
public ValueMap(Map map) {
if (map == null) {
throw new IllegalArgumentException("Map must not be null.");
}
this.delegate = map;
}
@Override public void clear() {
delegate.clear();
}
@Override public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
@Override public boolean containsValue(Object value) {
return delegate.containsValue(value);
}
@Override public Set> entrySet() {
return delegate.entrySet();
}
@Override public Object get(Object key) {
return delegate.get(key);
}
@Override public boolean isEmpty() {
return delegate.isEmpty();
}
@Override public Set keySet() {
return delegate.keySet();
}
@Override public Object put(String key, Object value) {
return delegate.put(key, value);
}
@Override public void putAll(Map extends String, ?> map) {
delegate.putAll(map);
}
@Override public Object remove(Object key) {
return delegate.remove(key);
}
@Override public int size() {
return delegate.size();
}
@Override public Collection values() {
return delegate.values();
}
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass") //
@Override public boolean equals(Object object) {
return object == this || delegate.equals(object);
}
@Override public int hashCode() {
return delegate.hashCode();
}
@Override public String toString() {
return delegate.toString();
}
/** Helper method to be able to chain put methods. */
public ValueMap putValue(String key, Object value) {
delegate.put(key, value);
return this;
}
/**
* Returns the value mapped by {@code key} if it exists and is a integer or can be coerced to a
* integer. Returns {@code defaultValue} otherwise.
*/
public int getInt(String key, int defaultValue) {
Object value = get(key);
if (value instanceof Integer) {
return (int) value;
}
if (value instanceof Number) {
return ((Number) value).intValue();
} else if (value instanceof String) {
try {
return Integer.valueOf((String) value);
} catch (NumberFormatException ignored) {
}
}
return defaultValue;
}
/**
* Returns the value mapped by {@code key} if it exists and is a long or can be coerced to a
* long.
* Returns {@code defaultValue} otherwise.
*/
public long getLong(String key, long defaultValue) {
Object value = get(key);
if (value instanceof Long) {
return (long) value;
}
if (value instanceof Number) {
return ((Number) value).longValue();
} else if (value instanceof String) {
try {
return Long.valueOf((String) value);
} catch (NumberFormatException ignored) {
}
}
return defaultValue;
}
/**
* Returns the value mapped by {@code key} if it exists and is a float or can be coerced to a
* float. Returns {@code defaultValue} otherwise.
*/
public float getFloat(String key, float defaultValue) {
Object value = get(key);
return Utils.coerceToFloat(value, defaultValue);
}
/**
* Returns the value mapped by {@code key} if it exists and is a double or can be coerced to a
* double. Returns {@code defaultValue} otherwise.
*/
public double getDouble(String key, double defaultValue) {
Object value = get(key);
if (value instanceof Double) {
return (double) value;
}
if (value instanceof Number) {
return ((Number) value).doubleValue();
} else if (value instanceof String) {
try {
return Double.valueOf((String) value);
} catch (NumberFormatException ignored) {
}
}
return defaultValue;
}
/**
* Returns the value mapped by {@code key} if it exists and is a char or can be coerced to a
* char.
* Returns {@code defaultValue} otherwise.
*/
public char getChar(String key, char defaultValue) {
Object value = get(key);
if (value instanceof Character) {
return (Character) value;
}
if (value != null && value instanceof String) {
if (((String) value).length() == 1) {
return ((String) value).charAt(0);
}
}
return defaultValue;
}
/**
* Returns the value mapped by {@code key} if it exists and is a string or can be coerced to a
* string. Returns null otherwise.
*
* This will return null only if the value does not exist, since all types can have a String
* representation.
*/
public String getString(String key) {
Object value = get(key);
if (value instanceof String) {
return (String) value;
} else if (value != null) {
return String.valueOf(value);
}
return null;
}
/**
* Returns the value mapped by {@code key} if it exists and is a boolean or can be coerced to a
* boolean. Returns {@code defaultValue} otherwise.
*/
public boolean getBoolean(String key, boolean defaultValue) {
Object value = get(key);
if (value instanceof Boolean) {
return (boolean) value;
} else if (value instanceof String) {
return Boolean.valueOf((String) value);
}
return defaultValue;
}
/**
* Returns the value mapped by {@code key} if it exists and is a enum or can be coerced to an
* enum. Returns null otherwise.
*/
public > T getEnum(Class enumType, String key) {
if (enumType == null) {
throw new IllegalArgumentException("enumType may not be null");
}
Object value = get(key);
if (enumType.isInstance(value)) {
//noinspection unchecked
return (T) value;
} else if (value instanceof String) {
String stringValue = (String) value;
return Enum.valueOf(enumType, stringValue);
}
return null;
}
/**
* Returns the value mapped by {@code key} if it exists and is a {@link ValueMap}. Returns null
* otherwise.
*/
public ValueMap getValueMap(Object key) {
Object value = get(key);
if (value instanceof ValueMap) {
return (ValueMap) value;
} else if (value instanceof Map) {
//noinspection unchecked
return new ValueMap((Map) value);
} else {
return null;
}
}
/**
* Returns the value mapped by {@code key} if it exists and if it can be coerced to the given
* type. The expected subclass MUST have a constructor that accepts a {@link Map}.
*/
public T getValueMap(String key, Class clazz) {
Object value = get(key);
return coerceToValueMap(value, clazz);
}
/**
* Coerce an object to a JsonMap. It will first check if the object is already of the expected
* type. If not, it checks if the object a {@link Map} type, and feeds it to the constructor by
* reflection.
*/
private T coerceToValueMap(Object object, Class clazz) {
if (object == null) return null;
if (clazz.isAssignableFrom(object.getClass())) {
//noinspection unchecked
return (T) object;
}
if (object instanceof Map) return createValueMap((Map) object, clazz);
return null;
}
/**
* Returns the value mapped by {@code key} if it exists and is a List of {@code T}. Returns null
* otherwise.
*/
public List getList(Object key, Class clazz) {
Object value = get(key);
if (value instanceof List) {
List list = (List) value;
try {
ArrayList real = new ArrayList<>();
for (Object item : list) {
T typedValue = coerceToValueMap(item, clazz);
if (typedValue != null) {
real.add(typedValue);
}
}
return real;
} catch (Exception ignored) {
}
}
return null;
}
/** Return a copy of the contents of this map as a {@link JSONObject}. */
public JSONObject toJsonObject() {
return toJsonObject(delegate);
}
/**
* Return a copy of the contents of the given map as a {@link JSONObject}. Instead of failing on
* {@code null} values like the {@link JSONObject} map constructor, it cleans them up and
* correctly converts them to {@link JSONObject#NULL}.
*/
private static JSONObject toJsonObject(Map map) {
JSONObject jsonObject = new JSONObject();
for (Map.Entry entry : map.entrySet()) {
Object value = wrap(entry.getValue());
try {
jsonObject.put(entry.getKey(), value);
} catch (JSONException ignored) {
// Ignore values that JSONObject doesn't accept.
}
}
return jsonObject;
}
/**
* Wraps the given object if necessary. {@link JSONObject#wrap(Object)} is only available on API
* 19+, so we've copied the implementation. Deviates from the original implementation in
* that it always returns {@link JSONObject#NULL} instead of {@code null} in case of a failure,
* and returns the {@link Object#toString} of any object that is of a custom (non-primitive or
* non-collection/map) type.
*
* If the object is null or , returns {@link JSONObject#NULL}.
* If the object is a {@link JSONArray} or {@link JSONObject}, no wrapping is necessary.
* If the object is {@link JSONObject#NULL}, no wrapping is necessary.
* If the object is an array or {@link Collection}, returns an equivalent {@link JSONArray}.
* If the object is a {@link Map}, returns an equivalent {@link JSONObject}.
* If the object is a primitive wrapper type or {@link String}, returns the object.
* Otherwise returns the result of {@link Object#toString}.
* If wrapping fails, returns JSONObject.NULL.
*/
private static Object wrap(Object o) {
if (o == null) {
return JSONObject.NULL;
}
if (o instanceof JSONArray || o instanceof JSONObject) {
return o;
}
if (o.equals(JSONObject.NULL)) {
return o;
}
try {
if (o instanceof Collection) {
return new JSONArray((Collection) o);
} else if (o.getClass().isArray()) {
final int length = Array.getLength(o);
JSONArray array = new JSONArray();
for (int i = 0; i < length; ++i) {
array.put(wrap(Array.get(array, i)));
}
return array;
}
if (o instanceof Map) {
//noinspection unchecked
return toJsonObject((Map) o);
}
if (o instanceof Boolean
|| o instanceof Byte
|| o instanceof Character
|| o instanceof Double
|| o instanceof Float
|| o instanceof Integer
|| o instanceof Long
|| o instanceof Short
|| o instanceof String) {
return o;
}
// Deviate from original implementation and return the String representation of the object
// regardless of package.
return o.toString();
} catch (Exception ignored) {
}
// Deviate from original and return JSONObject.NULL instead of null.
return JSONObject.NULL;
}
/** Return a copy of the contents of this map as a {@code Map}. */
public Map toStringMap() {
Map map = new HashMap<>();
for (Map.Entry entry : entrySet()) {
map.put(entry.getKey(), String.valueOf(entry.getValue()));
}
return map;
}
/** A class to let you store arbitrary key - {@link ValueMap} pairs. */
static class Cache {
private final SharedPreferences preferences;
private final Cartographer cartographer;
private final String key;
private final Class clazz;
private T value;
Cache(Context context, Cartographer cartographer, String key, Class clazz) {
this.cartographer = cartographer;
this.preferences = getSegmentSharedPreferences(context);
this.key = key;
this.clazz = clazz;
}
T get() {
if (value == null) {
String json = preferences.getString(key, null);
if (isNullOrEmpty(json)) return null;
try {
Map map = cartographer.fromJson(json);
value = create(map);
} catch (IOException ignored) {
return null;
}
}
return value;
}
boolean isSet() {
return preferences.contains(key);
}
T create(Map map) {
return ValueMap.createValueMap(map, clazz);
}
void set(T value) {
this.value = value;
try {
String json = cartographer.toJson(value);
preferences.edit().putString(key, json).apply();
} catch (IOException ignored) {
}
}
void delete() {
preferences.edit().remove(key).apply();
}
}
}