Skip to content

Commit

Permalink
Changelogs v0.5.4 v0.6.0 were implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
Bohdan-Kim committed May 24, 2024
1 parent 1fffb68 commit e9daa6b
Show file tree
Hide file tree
Showing 51 changed files with 1,861 additions and 779 deletions.
22 changes: 7 additions & 15 deletions lib/src/main/java/growthbook/sdk/java/BucketRange.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package growthbook.sdk.java;

import com.google.gson.*;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonSerializer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.math3.util.Precision;

import java.lang.reflect.Type;
import java.util.Objects;

/**
Expand Down Expand Up @@ -37,6 +38,7 @@ static BucketRange fromJson(JsonElement jsonElement) {

/**
* Converts the bucket range to the serialized tuple
*
* @return JSON string of the bucket range
*/
public String toJson() {
Expand Down Expand Up @@ -88,23 +90,13 @@ static JsonElement getJson(BucketRange object) {
* @return serializer for {@link BucketRange}
*/
public static JsonSerializer<BucketRange> getSerializer() {
return new JsonSerializer<BucketRange>() {
@Override
public JsonElement serialize(BucketRange src, Type typeOfSrc, JsonSerializationContext context) {
return BucketRange.getJson(src);
}
};
return (src, typeOfSrc, context) -> BucketRange.getJson(src);
}

/**
* @return deserializer for {@link BucketRange}
*/
public static JsonDeserializer<BucketRange> getDeserializer() {
return new JsonDeserializer<BucketRange>() {
@Override
public BucketRange deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return BucketRange.fromJson(json);
}
};
return (json, typeOfT, context) -> BucketRange.fromJson(json);
}
}
80 changes: 48 additions & 32 deletions lib/src/main/java/growthbook/sdk/java/ConditionEvaluator.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.reflect.TypeToken;

import javax.annotation.Nullable;
import java.lang.reflect.Type;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -84,7 +87,7 @@ Boolean isOperatorObject(JsonElement object) {

Set<Map.Entry<String, JsonElement>> entries = ((JsonObject) object).entrySet();

if (entries.size() == 0) {
if (entries.isEmpty()) {
return true;
}

Expand All @@ -98,10 +101,10 @@ Boolean isOperatorObject(JsonElement object) {

/**
* Given attributes and a dot-separated path string,
* @return the value at that path (or null if the path doesn't exist)
*
* @param attributes User attributes
* @param path String path, e.g. path.to.something
* @return the value at that path (or null if the path doesn't exist)
*/
@Nullable
Object getPath(JsonElement attributes, String path) {
Expand Down Expand Up @@ -144,7 +147,7 @@ Object getPath(JsonElement attributes, String path) {
* }
* }
* </pre>
*
* <p>
* And the following attributes:
*
* <pre>
Expand All @@ -161,7 +164,7 @@ Object getPath(JsonElement attributes, String path) {
* i.e. <code>$eq, $ne, $lt, $lte, $gt, $gte, $regex</code>
* </p>
*
*<p>
* <p>
* There are 2 operators where conditionValue is an array,
* i.e. <code>$in, $nin</code>
* </p>
Expand All @@ -181,9 +184,9 @@ Object getPath(JsonElement attributes, String path) {
* i.e. <code>$exists, $type, $not</code>
* </p>
*
* @param actual Nullable JSON element
* @param actual Nullable JSON element
* @param operatorString String value of the operator
* @param expected The conditions to use to verify that the attributes match, based on the operator
* @param expected The conditions to use to verify that the attributes match, based on the operator
* @return if it's a match
*/
Boolean evalOperatorCondition(String operatorString, @Nullable JsonElement actual, JsonElement expected) {
Expand All @@ -207,21 +210,24 @@ Boolean evalOperatorCondition(String operatorString, @Nullable JsonElement actua

if (DataType.STRING == attributeDataType) {
String value = actual.getAsString();
Type listType = new TypeToken<ArrayList<String>>() {}.getType();
Type listType = new TypeToken<ArrayList<String>>() {
}.getType();
ArrayList<String> conditionsList = jsonUtils.gson.fromJson(expected, listType);
return conditionsList.contains(value);
}

if (DataType.NUMBER == attributeDataType) {
Float value = actual.getAsFloat();
Type listType = new TypeToken<ArrayList<Float>>() {}.getType();
Type listType = new TypeToken<ArrayList<Float>>() {
}.getType();
ArrayList<Float> conditionsList = jsonUtils.gson.fromJson(expected, listType);
return conditionsList.contains(value);
}

if (DataType.BOOLEAN == attributeDataType) {
Boolean value = actual.getAsBoolean();
Type listType = new TypeToken<ArrayList<Boolean>>() {}.getType();
Type listType = new TypeToken<ArrayList<Boolean>>() {
}.getType();
ArrayList<Boolean> conditionsList = jsonUtils.gson.fromJson(expected, listType);
return conditionsList.contains(value);
}
Expand All @@ -242,21 +248,24 @@ Boolean evalOperatorCondition(String operatorString, @Nullable JsonElement actua

if (DataType.STRING == attributeDataType) {
String value = actual.getAsString();
Type listType = new TypeToken<ArrayList<String>>() {}.getType();
Type listType = new TypeToken<ArrayList<String>>() {
}.getType();
ArrayList<String> conditionsList = jsonUtils.gson.fromJson(expected, listType);
return !conditionsList.contains(value);
}

if (DataType.NUMBER == attributeDataType) {
Float value = actual.getAsFloat();
Type listType = new TypeToken<ArrayList<Float>>() {}.getType();
Type listType = new TypeToken<ArrayList<Float>>() {
}.getType();
ArrayList<Float> conditionsList = jsonUtils.gson.fromJson(expected, listType);
return !conditionsList.contains(value);
}

if (DataType.BOOLEAN == attributeDataType) {
Boolean value = actual.getAsBoolean();
Type listType = new TypeToken<ArrayList<Boolean>>() {}.getType();
Type listType = new TypeToken<ArrayList<Boolean>>() {
}.getType();
ArrayList<Boolean> conditionsList = jsonUtils.gson.fromJson(expected, listType);
return !conditionsList.contains(value);
}
Expand All @@ -266,7 +275,7 @@ Boolean evalOperatorCondition(String operatorString, @Nullable JsonElement actua
case GT:
if (actual == null || DataType.NULL.equals(attributeDataType)) {
return (!expected.isJsonPrimitive() || expected.getAsJsonPrimitive().isNumber())
&& 0.0 > expected.getAsDouble();
&& 0.0 > expected.getAsDouble();
}
if (actual.getAsJsonPrimitive().isNumber()) {
return actual.getAsNumber().floatValue() > expected.getAsNumber().floatValue();
Expand All @@ -279,7 +288,7 @@ Boolean evalOperatorCondition(String operatorString, @Nullable JsonElement actua
case GTE:
if (actual == null || DataType.NULL.equals(attributeDataType)) {
return (!expected.isJsonPrimitive() || expected.getAsJsonPrimitive().isNumber())
&& 0.0 >= expected.getAsDouble();
&& 0.0 >= expected.getAsDouble();
}
if (actual.getAsJsonPrimitive().isNumber()) {
return actual.getAsNumber().floatValue() >= expected.getAsNumber().floatValue();
Expand All @@ -292,7 +301,7 @@ Boolean evalOperatorCondition(String operatorString, @Nullable JsonElement actua
case LT:
if (actual == null || DataType.NULL.equals(attributeDataType)) {
return (!expected.isJsonPrimitive() || expected.getAsJsonPrimitive().isNumber())
&& 0.0 < expected.getAsDouble();
&& 0.0 < expected.getAsDouble();
}
if (actual.getAsString().toLowerCase().matches("\\d+")) {
return Double.parseDouble(actual.getAsString()) < expected.getAsDouble();
Expand All @@ -308,7 +317,7 @@ Boolean evalOperatorCondition(String operatorString, @Nullable JsonElement actua
case LTE:
if (actual == null || DataType.NULL.equals(attributeDataType)) {
return (!expected.isJsonPrimitive() || expected.getAsJsonPrimitive().isNumber())
&& 0.0 <= expected.getAsDouble();
&& 0.0 <= expected.getAsDouble();
}
if (actual.getAsJsonPrimitive().isNumber()) {
return actual.getAsNumber().floatValue() <= expected.getAsNumber().floatValue();
Expand Down Expand Up @@ -384,40 +393,46 @@ Boolean evalOperatorCondition(String operatorString, @Nullable JsonElement actua
}

case VERSION_GT:
if (actual == null || expected == null || DataType.NULL.equals(attributeDataType)) return false;
if (actual == null || expected == null || DataType.NULL.equals(attributeDataType))
return false;

return StringUtils.paddedVersionString(actual.getAsString())
.compareTo(StringUtils.paddedVersionString(expected.getAsString())) > 0;
.compareTo(StringUtils.paddedVersionString(expected.getAsString())) > 0;

case VERSION_GTE:
if (actual == null || expected == null || DataType.NULL.equals(attributeDataType)) return false;
if (actual == null || expected == null || DataType.NULL.equals(attributeDataType))
return false;

return StringUtils.paddedVersionString(actual.getAsString())
.compareTo(StringUtils.paddedVersionString(expected.getAsString())) >= 0;
.compareTo(StringUtils.paddedVersionString(expected.getAsString())) >= 0;

case VERSION_LT:
if (actual == null || expected == null || DataType.NULL.equals(attributeDataType)) return false;
if (actual == null || expected == null || DataType.NULL.equals(attributeDataType))
return false;

return StringUtils.paddedVersionString(actual.getAsString())
.compareTo(StringUtils.paddedVersionString(expected.getAsString())) < 0;
.compareTo(StringUtils.paddedVersionString(expected.getAsString())) < 0;

case VERSION_LTE:
if (actual == null || expected == null || DataType.NULL.equals(attributeDataType)) return false;
if (actual == null || expected == null || DataType.NULL.equals(attributeDataType))
return false;

return StringUtils.paddedVersionString(actual.getAsString())
.compareTo(StringUtils.paddedVersionString(expected.getAsString())) <= 0;
.compareTo(StringUtils.paddedVersionString(expected.getAsString())) <= 0;

case VERSION_NE:
if (actual == null || expected == null || DataType.NULL.equals(attributeDataType)) return false;
if (actual == null || expected == null || DataType.NULL.equals(attributeDataType))
return false;

return StringUtils.paddedVersionString(actual.getAsString())
.compareTo(StringUtils.paddedVersionString(expected.getAsString())) != 0;
.compareTo(StringUtils.paddedVersionString(expected.getAsString())) != 0;

case VERSION_EQ:
if (actual == null || expected == null || DataType.NULL.equals(attributeDataType)) return false;
if (actual == null || expected == null || DataType.NULL.equals(attributeDataType))
return false;

return StringUtils.paddedVersionString(actual.getAsString())
.compareTo(StringUtils.paddedVersionString(expected.getAsString())) == 0;
.compareTo(StringUtils.paddedVersionString(expected.getAsString())) == 0;

default:
return false;
Expand All @@ -427,6 +442,7 @@ Boolean evalOperatorCondition(String operatorString, @Nullable JsonElement actua

/**
* Compares two primitives for equality.
*
* @param a left side primitive
* @param b right side primitive
* @param dataType The data type of the primitives
Expand Down Expand Up @@ -528,7 +544,7 @@ else if (evaluateCondition(actualElement.toString(), expected.toString())) {
* @return if matches
*/
Boolean evalOr(JsonElement attributes, JsonArray conditions) {
if (conditions.size() == 0) {
if (conditions.isEmpty()) {
return true;
}

Expand Down Expand Up @@ -570,7 +586,7 @@ private Boolean isIn(JsonElement actual, JsonArray expected) {

JsonArray actualArr = actual.getAsJsonArray();

if (actualArr.size() == 0) return false;
if (actualArr.isEmpty()) return false;

DataType attributeDataType = GrowthBookJsonUtils.getElementType(actualArr.get(0));
ArrayList<Object> actualAsList = jsonUtils.gson.fromJson(actualArr, listType);
Expand Down
16 changes: 13 additions & 3 deletions lib/src/main/java/growthbook/sdk/java/Experiment.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

import javax.annotation.Nullable;
import java.util.ArrayList;

Expand Down Expand Up @@ -48,9 +47,18 @@ public class Experiment<ValueType> {
/**
* Optional targeting condition
*/
String conditionJson;
JsonElement conditionJson;

/**
* Each item defines a prerequisite where a `condition` must evaluate against
* a parent feature's value (identified by `id`). If `gate` is true, then this is a blocking
* feature-level prerequisite; otherwise it applies to the current rule only.
*/
@Nullable
ArrayList<ParentCondition> parentConditions;

@Nullable
@Deprecated
Namespace namespace;

/**
Expand Down Expand Up @@ -100,6 +108,7 @@ public class Experiment<ValueType> {

/**
* Get a Gson JsonElement of the experiment
*
* @return JsonElement
*/
public String toJson() {
Expand All @@ -116,7 +125,8 @@ public String toString() {

/**
* Get a Gson JsonElement of the experiment
* @param object experiment
*
* @param object experiment
* @param <ValueType> value type for the experiment
* @return JsonElement
*/
Expand Down

0 comments on commit e9daa6b

Please sign in to comment.