java-sdk/src/main/java/dev/openfeature/sdk/Value.java

284 lines
6.7 KiB
Java

package dev.openfeature.sdk;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.ToString;
/**
* Values serve as a generic return type for structure data from providers.
* Providers may deal in JSON, protobuf, XML or some other data-interchange format.
* This intermediate representation provides a good medium of exchange.
*/
@ToString
@EqualsAndHashCode
@SuppressWarnings({"PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType"})
public class Value implements Cloneable {
private final Object innerObject;
/**
* Construct a new null Value.
*/
public Value() {
this.innerObject = null;
}
/**
* Construct a new Value with an Object.
* @param value to be wrapped.
* @throws InstantiationException if value is not a valid type
* (boolean, string, int, double, list, structure, instant)
*/
public Value(Object value) throws InstantiationException {
this.innerObject = value;
if (!this.isNull()
&& !this.isBoolean()
&& !this.isString()
&& !this.isNumber()
&& !this.isStructure()
&& !this.isList()
&& !this.isInstant()) {
throw new InstantiationException("Invalid value type: " + value.getClass());
}
}
public Value(Value value) {
this.innerObject = value.innerObject;
}
public Value(Boolean value) {
this.innerObject = value;
}
public Value(String value) {
this.innerObject = value;
}
public Value(Integer value) {
this.innerObject = value;
}
public Value(Double value) {
this.innerObject = value;
}
public Value(Structure value) {
this.innerObject = value;
}
public Value(List<Value> value) {
this.innerObject = value;
}
public Value(Instant value) {
this.innerObject = value;
}
/**
* Check if this Value represents null.
*
* @return boolean
*/
public boolean isNull() {
return this.innerObject == null;
}
/**
* Check if this Value represents a Boolean.
*
* @return boolean
*/
public boolean isBoolean() {
return this.innerObject instanceof Boolean;
}
/**
* Check if this Value represents a String.
*
* @return boolean
*/
public boolean isString() {
return this.innerObject instanceof String;
}
/**
* Check if this Value represents a numeric value.
*
* @return boolean
*/
public boolean isNumber() {
return this.innerObject instanceof Number;
}
/**
* Check if this Value represents a Structure.
*
* @return boolean
*/
public boolean isStructure() {
return this.innerObject instanceof Structure;
}
/**
* Check if this Value represents a List of Values.
*
* @return boolean
*/
public boolean isList() {
if (!(this.innerObject instanceof List)) {
return false;
}
List<?> list = (List<?>) this.innerObject;
if (list.isEmpty()) {
return true;
}
for (Object obj : list) {
if (!(obj instanceof Value)) {
return false;
}
}
return true;
}
/**
* Check if this Value represents an Instant.
*
* @return boolean
*/
public boolean isInstant() {
return this.innerObject instanceof Instant;
}
/**
* Retrieve the underlying Boolean value, or null.
*
* @return Boolean
*/
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL",
justification = "This is not a plain true/false method. It's understood it can return null.")
public Boolean asBoolean() {
if (this.isBoolean()) {
return (Boolean)this.innerObject;
}
return null;
}
/**
* Retrieve the underlying object.
*
* @return Object
*/
public Object asObject() {
return this.innerObject;
}
/**
* Retrieve the underlying String value, or null.
*
* @return String
*/
public String asString() {
if (this.isString()) {
return (String)this.innerObject;
}
return null;
}
/**
* Retrieve the underlying numeric value as an Integer, or null.
* If the value is not an integer, it will be rounded using Math.round().
*
* @return Integer
*/
public Integer asInteger() {
if (this.isNumber() && !this.isNull()) {
return ((Number)this.innerObject).intValue();
}
return null;
}
/**
* Retrieve the underlying numeric value as a Double, or null.
*
* @return Double
*/
public Double asDouble() {
if (this.isNumber() && !isNull()) {
return ((Number)this.innerObject).doubleValue();
}
return null;
}
/**
* Retrieve the underlying Structure value, or null.
*
* @return Structure
*/
public Structure asStructure() {
if (this.isStructure()) {
return (Structure)this.innerObject;
}
return null;
}
/**
* Retrieve the underlying List value, or null.
*
* @return List
*/
public List<Value> asList() {
if (this.isList()) {
//noinspection rawtypes,unchecked
return (List) this.innerObject;
}
return null;
}
/**
* Retrieve the underlying Instant value, or null.
*
* @return Instant
*/
public Instant asInstant() {
if (this.isInstant()) {
return (Instant)this.innerObject;
}
return null;
}
/**
* Perform deep clone of value object.
*
* @return Value
*/
@SneakyThrows
@Override
protected Value clone() {
if (this.isList()) {
List<Value> copy = this.asList().stream().map(Value::new).collect(Collectors.toList());
return new Value(copy);
}
if (this.isStructure()) {
Map<String, Value> copy = this.asStructure().asMap().entrySet().stream().collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().clone()
));
return new Value(new ImmutableStructure(copy));
}
if (this.isInstant()) {
Instant copy = Instant.ofEpochMilli(this.asInstant().toEpochMilli());
return new Value(copy);
}
return new Value(this.asObject());
}
}