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

com.launchdarkly.client.value.LDValue Maven / Gradle / Ivy

package com.launchdarkly.client.value;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.stream.JsonWriter;
import com.launchdarkly.client.LDClientInterface;
import com.launchdarkly.client.LDUser;

import java.io.IOException;
import java.util.Map;

/**
 * An immutable instance of any data type that is allowed in JSON.
 * 

* This is used with the client's {@link LDClientInterface#jsonValueVariation(String, LDUser, LDValue)} * method, and is also used internally to hold feature flag values. *

* While the LaunchDarkly SDK uses Gson for JSON parsing, some of the Gson value types (object * and array) are mutable. In contexts where it is important for data to remain immutable after * it is created, these values are represented with {@link LDValue} instead. It is easily * convertible to primitive types and provides array element/object property accessors. * * @since 4.8.0 */ @JsonAdapter(LDValueTypeAdapter.class) public abstract class LDValue { static final Gson gson = new Gson(); private boolean haveComputedJsonElement = false; private JsonElement computedJsonElement = null; /** * Returns the same value if non-null, or {@link #ofNull()} if null. * * @param value an {@link LDValue} or null * @return an {@link LDValue} which will never be a null reference */ public static LDValue normalize(LDValue value) { return value == null ? ofNull() : value; } /** * Returns an instance for a null value. The same instance is always used. * * @return an LDValue containing null */ public static LDValue ofNull() { return LDValueNull.INSTANCE; } /** * Returns an instance for a boolean value. The same instances for {@code true} and {@code false} * are always used. * * @param value a boolean value * @return an LDValue containing that value */ public static LDValue of(boolean value) { return LDValueBool.fromBoolean(value); } /** * Returns an instance for a numeric value. * * @param value an integer numeric value * @return an LDValue containing that value */ public static LDValue of(int value) { return LDValueNumber.fromDouble(value); } /** * Returns an instance for a numeric value. *

* Note that the LaunchDarkly service, and most of the SDKs, represent numeric values internally * in 64-bit floating-point, which has slightly less precision than a signed 64-bit {@code long}; * therefore, the full range of {@code long} values cannot be accurately represented. If you need * to set a user attribute to a numeric value with more significant digits than will fit in a * {@code double}, it is best to encode it as a string. * * @param value a long integer numeric value * @return an LDValue containing that value */ public static LDValue of(long value) { return LDValueNumber.fromDouble(value); } /** * Returns an instance for a numeric value. * * @param value a floating-point numeric value * @return an LDValue containing that value */ public static LDValue of(float value) { return LDValueNumber.fromDouble(value); } /** * Returns an instance for a numeric value. * * @param value a floating-point numeric value * @return an LDValue containing that value */ public static LDValue of(double value) { return LDValueNumber.fromDouble(value); } /** * Returns an instance for a string value (or a null). * * @param value a nullable String reference * @return an LDValue containing a string, or {@link #ofNull()} if the value was null. */ public static LDValue of(String value) { return value == null ? ofNull() : LDValueString.fromString(value); } /** * Starts building an array value. *


   *     LDValue arrayOfInts = LDValue.buildArray().add(LDValue.int(1), LDValue.int(2)).build():
   * 
* If the values are all of the same type, you may also use {@link LDValue.Converter#arrayFrom(Iterable)} * or {@link LDValue.Converter#arrayOf(Object...)}. * * @return an {@link ArrayBuilder} */ public static ArrayBuilder buildArray() { return new ArrayBuilder(); } /** * Starts building an object value. *

   *     LDValue objectVal = LDValue.buildObject().put("key", LDValue.int(1)).build():
   * 
* If the values are all of the same type, you may also use {@link LDValue.Converter#objectFrom(Map)}. * * @return an {@link ObjectBuilder} */ public static ObjectBuilder buildObject() { return new ObjectBuilder(); } /** * Returns an instance based on a {@link JsonElement} value. If the value is a complex type, it is * deep-copied; primitive types are used as is. * * @param value a nullable {@link JsonElement} reference * @return an LDValue containing the specified value, or {@link #ofNull()} if the value was null. * @deprecated The Gson types may be removed from the public API at some point; it is preferable to * use factory methods like {@link #of(boolean)}. */ @Deprecated public static LDValue fromJsonElement(JsonElement value) { return value == null || value.isJsonNull() ? ofNull() : LDValueJsonElement.copyValue(value); } /** * Returns an instance that wraps an existing {@link JsonElement} value without copying it. This * method exists only to support deprecated SDK methods where a {@link JsonElement} is needed, to * avoid the inefficiency of a deep-copy; application code should not use it, since it can break * the immutability contract of {@link LDValue}. * * @param value a nullable {@link JsonElement} reference * @return an LDValue containing the specified value, or {@link #ofNull()} if the value was null. * @deprecated This method will be removed in a future version. Application code should use * {@link #fromJsonElement(JsonElement)} or, preferably, factory methods like {@link #of(boolean)}. */ @Deprecated public static LDValue unsafeFromJsonElement(JsonElement value) { return value == null || value.isJsonNull() ? ofNull() : LDValueJsonElement.wrapUnsafeValue(value); } /** * Gets the JSON type for this value. * * @return the appropriate {@link LDValueType} */ public abstract LDValueType getType(); /** * Tests whether this value is a null. * * @return {@code true} if this is a null value */ public boolean isNull() { return false; } /** * Returns this value as a boolean if it is explicitly a boolean. Otherwise returns {@code false}. * * @return a boolean */ public boolean booleanValue() { return false; } /** * Tests whether this value is a number (not a numeric string). * * @return {@code true} if this is a numeric value */ public boolean isNumber() { return false; } /** * Tests whether this value is a number that is also an integer. *

* JSON does not have separate types for integer and floating-point values; they are both just * numbers. This method returns true if and only if the actual numeric value has no fractional * component, so {@code LDValue.of(2).isInt()} and {@code LDValue.of(2.0f).isInt()} are both true. * * @return {@code true} if this is an integer value */ public boolean isInt() { return false; } /** * Returns this value as an {@code int} if it is numeric. Returns zero for all non-numeric values. *

* If the value is a number but not an integer, it will be rounded toward zero (truncated). * This is consistent with Java casting behavior, and with most other LaunchDarkly SDKs. * * @return an {@code int} value */ public int intValue() { return 0; } /** * Returns this value as a {@code long} if it is numeric. Returns zero for all non-numeric values. *

* If the value is a number but not an integer, it will be rounded toward zero (truncated). * This is consistent with Java casting behavior, and with most other LaunchDarkly SDKs. * * @return a {@code long} value */ public long longValue() { return 0; } /** * Returns this value as a {@code float} if it is numeric. Returns zero for all non-numeric values. * * @return a {@code float} value */ public float floatValue() { return 0; } /** * Returns this value as a {@code double} if it is numeric. Returns zero for all non-numeric values. * * @return a {@code double} value */ public double doubleValue() { return 0; } /** * Tests whether this value is a string. * * @return {@code true} if this is a string value */ public boolean isString() { return false; } /** * Returns this value as a {@code String} if it is a string. Returns {@code null} for all non-string values. * * @return a nullable string value */ public String stringValue() { return null; } /** * Returns the number of elements in an array or object. Returns zero for all other types. * * @return the number of array elements or object properties */ public int size() { return 0; } /** * Enumerates the property names in an object. Returns an empty iterable for all other types. * * @return the property names */ public Iterable keys() { return ImmutableList.of(); } /** * Enumerates the values in an array or object. Returns an empty iterable for all other types. * * @return an iterable of {@link LDValue} values */ public Iterable values() { return ImmutableList.of(); } /** * Enumerates the values in an array or object, converting them to a specific type. Returns an empty * iterable for all other types. *

* This is an efficient method because it does not copy values to a new list, but returns a view * into the existing array. *

* Example: *


   *     LDValue anArrayOfInts = LDValue.Convert.Integer.arrayOf(1, 2, 3);
   *     for (int i: anArrayOfInts.valuesAs(LDValue.Convert.Integer)) { println(i); }
   * 
* * @param the desired type * @param converter the {@link Converter} for the specified type * @return an iterable of values of the specified type */ public Iterable valuesAs(final Converter converter) { return Iterables.transform(values(), new Function() { public T apply(LDValue value) { return converter.toType(value); } }); } /** * Returns an array element by index. Returns {@link #ofNull()} if this is not an array or if the * index is out of range (will never throw an exception). * * @param index the array index * @return the element value or {@link #ofNull()} */ public LDValue get(int index) { return ofNull(); } /** * Returns an object property by name. Returns {@link #ofNull()} if this is not an object or if the * key is not found (will never throw an exception). * * @param name the property name * @return the property value or {@link #ofNull()} */ public LDValue get(String name) { return ofNull(); } /** * Converts this value to its JSON serialization. * * @return a JSON string */ public String toJsonString() { return gson.toJson(this); } /** * Converts this value to a {@link JsonElement}. If the value is a complex type, it is deep-copied * deep-copied, so modifying the return value will not affect the {@link LDValue}. * * @return a {@link JsonElement}, or {@code null} if the value is a null * @deprecated The Gson types may be removed from the public API at some point; it is preferable to * use getters like {@link #booleanValue()} and {@link #getType()}. */ @Deprecated public JsonElement asJsonElement() { return LDValueJsonElement.deepCopy(asUnsafeJsonElement()); } /** * Returns the original {@link JsonElement} if the value was created from one, otherwise converts the * value to a {@link JsonElement}. This method exists only to support deprecated SDK methods where a * {@link JsonElement} is needed, to avoid the inefficiency of a deep-copy; application code should not * use it, since it can break the immutability contract of {@link LDValue}. * * @return a {@link JsonElement}, or {@code null} if the value is a null * @deprecated This method will be removed in a future version. Application code should always use * {@link #asJsonElement()}. */ @Deprecated public JsonElement asUnsafeJsonElement() { // Lazily compute this value synchronized (this) { if (!haveComputedJsonElement) { computedJsonElement = computeJsonElement(); haveComputedJsonElement = true; } return computedJsonElement; } } abstract JsonElement computeJsonElement(); abstract void write(JsonWriter writer) throws IOException; static boolean isInteger(double value) { return value == (double)((int)value); } @Override public String toString() { return toJsonString(); } // equals() and hashCode() are defined here in the base class so that we don't have to worry about // whether a value is stored as LDValueJsonElement vs. one of our own primitive types. @Override public boolean equals(Object o) { if (o instanceof LDValue) { LDValue other = (LDValue)o; if (getType() == other.getType()) { switch (getType()) { case NULL: return other.isNull(); case BOOLEAN: return booleanValue() == other.booleanValue(); case NUMBER: return doubleValue() == other.doubleValue(); case STRING: return stringValue().equals(other.stringValue()); case ARRAY: if (size() != other.size()) { return false; } for (int i = 0; i < size(); i++) { if (!get(i).equals(other.get(i))) { return false; } } return true; case OBJECT: if (size() != other.size()) { return false; } for (String name: keys()) { if (!get(name).equals(other.get(name))) { return false; } } return true; } } } return false; } @Override public int hashCode() { switch (getType()) { case NULL: return 0; case BOOLEAN: return booleanValue() ? 1 : 0; case NUMBER: return intValue(); case STRING: return stringValue().hashCode(); case ARRAY: int ah = 0; for (LDValue v: values()) { ah = ah * 31 + v.hashCode(); } return ah; case OBJECT: int oh = 0; for (String name: keys()) { oh = (oh * 31 + name.hashCode()) * 31 + get(name).hashCode(); } return oh; default: return 0; } } /** * Defines a conversion between {@link LDValue} and some other type. *

* Besides converting individual values, this provides factory methods like {@link #arrayOf} * which transform a collection of the specified type to the corresponding {@link LDValue} * complex type. * * @param the type to convert from/to * @since 4.8.0 */ public static abstract class Converter { /** * Converts a value of the specified type to an {@link LDValue}. *

* This method should never throw an exception; if for some reason the value is invalid, * it should return {@link LDValue#ofNull()}. * * @param value a value of this type * @return an {@link LDValue} */ public abstract LDValue fromType(T value); /** * Converts an {@link LDValue} to a value of the specified type. *

* This method should never throw an exception; if the conversion cannot be done, it should * return the default value of the given type (zero for numbers, null for nullable types). * * @param value an {@link LDValue} * @return a value of this type */ public abstract T toType(LDValue value); /** * Initializes an {@link LDValue} as an array, from a sequence of this type. *

* Values are copied, so subsequent changes to the source values do not affect the array. *

* Example: *


     *     List<Integer> listOfInts = ImmutableList.<Integer>builder().add(1).add(2).add(3).build();
     *     LDValue arrayValue = LDValue.Convert.Integer.arrayFrom(listOfInts);
     * 
* * @param values a sequence of elements of the specified type * @return a value representing a JSON array, or {@link LDValue#ofNull()} if the parameter was null * @see LDValue#buildArray() */ public LDValue arrayFrom(Iterable values) { ArrayBuilder ab = LDValue.buildArray(); for (T value: values) { ab.add(fromType(value)); } return ab.build(); } /** * Initializes an {@link LDValue} as an array, from a sequence of this type. *

* Values are copied, so subsequent changes to the source values do not affect the array. *

* Example: *


     *     LDValue arrayValue = LDValue.Convert.Integer.arrayOf(1, 2, 3);
     * 
* * @param values a sequence of elements of the specified type * @return a value representing a JSON array, or {@link LDValue#ofNull()} if the parameter was null * @see LDValue#buildArray() */ @SuppressWarnings("unchecked") public LDValue arrayOf(T... values) { ArrayBuilder ab = LDValue.buildArray(); for (T value: values) { ab.add(fromType(value)); } return ab.build(); } /** * Initializes an {@link LDValue} as an object, from a map containing this type. *

* Values are copied, so subsequent changes to the source map do not affect the array. *

* Example: *


     *     Map<String, Integer> mapOfInts = ImmutableMap.<String, Integer>builder().put("a", 1).build();
     *     LDValue objectValue = LDValue.Convert.Integer.objectFrom(mapOfInts);
     * 
* * @param map a map with string keys and values of the specified type * @return a value representing a JSON object, or {@link LDValue#ofNull()} if the parameter was null * @see LDValue#buildObject() */ public LDValue objectFrom(Map map) { ObjectBuilder ob = LDValue.buildObject(); for (String key: map.keySet()) { ob.put(key, fromType(map.get(key))); } return ob.build(); } } /** * Predefined instances of {@link LDValue.Converter} for commonly used types. *

* These are mostly useful for methods that convert {@link LDValue} to or from a collection of * some type, such as {@link LDValue.Converter#arrayOf(Object...)} and * {@link LDValue#valuesAs(Converter)}. * * @since 4.8.0 */ public static abstract class Convert { private Convert() {} /** * A {@link LDValue.Converter} for booleans. */ public static final Converter Boolean = new Converter() { public LDValue fromType(java.lang.Boolean value) { return value == null ? LDValue.ofNull() : LDValue.of(value.booleanValue()); } public java.lang.Boolean toType(LDValue value) { return java.lang.Boolean.valueOf(value.booleanValue()); } }; /** * A {@link LDValue.Converter} for integers. */ public static final Converter Integer = new Converter() { public LDValue fromType(java.lang.Integer value) { return value == null ? LDValue.ofNull() : LDValue.of(value.intValue()); } public java.lang.Integer toType(LDValue value) { return java.lang.Integer.valueOf(value.intValue()); } }; /** * A {@link LDValue.Converter} for long integers. *

* Note that the LaunchDarkly service, and most of the SDKs, represent numeric values internally * in 64-bit floating-point, which has slightly less precision than a signed 64-bit {@code long}; * therefore, the full range of {@code long} values cannot be accurately represented. If you need * to set a user attribute to a numeric value with more significant digits than will fit in a * {@code double}, it is best to encode it as a string. */ public static final Converter Long = new Converter() { public LDValue fromType(java.lang.Long value) { return value == null ? LDValue.ofNull() : LDValue.of(value.longValue()); } public java.lang.Long toType(LDValue value) { return java.lang.Long.valueOf(value.longValue()); } }; /** * A {@link LDValue.Converter} for floats. */ public static final Converter Float = new Converter() { public LDValue fromType(java.lang.Float value) { return value == null ? LDValue.ofNull() : LDValue.of(value.floatValue()); } public java.lang.Float toType(LDValue value) { return java.lang.Float.valueOf(value.floatValue()); } }; /** * A {@link LDValue.Converter} for doubles. */ public static final Converter Double = new Converter() { public LDValue fromType(java.lang.Double value) { return value == null ? LDValue.ofNull() : LDValue.of(value.doubleValue()); } public java.lang.Double toType(LDValue value) { return java.lang.Double.valueOf(value.doubleValue()); } }; /** * A {@link LDValue.Converter} for strings. */ public static final Converter String = new Converter() { public LDValue fromType(java.lang.String value) { return LDValue.of(value); } public java.lang.String toType(LDValue value) { return value.stringValue(); } }; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy