From 425176eda1740a1785199834b97fe0e1a93d6aea Mon Sep 17 00:00:00 2001 From: Athira M Date: Tue, 26 Nov 2024 10:32:43 +0530 Subject: [PATCH 01/78] Implementation for evaluation of server side remote config --- .../firebase/remoteconfig/AndCondition.java | 41 +++ .../remoteconfig/ConditionEvaluator.java | 316 ++++++++++++++++++ .../remoteconfig/CustomSignalCondition.java | 72 ++++ .../remoteconfig/CustomSignalOperator.java | 54 +++ .../remoteconfig/MicroPercentRange.java | 52 +++ .../firebase/remoteconfig/OneOfCondition.java | 133 ++++++++ .../firebase/remoteconfig/OrCondition.java | 41 +++ .../remoteconfig/PercentCondition.java | 96 ++++++ .../PercentConditionOperator.java | 46 +++ .../firebase/remoteconfig/ServerConfig.java | 75 +++++ .../firebase/remoteconfig/ServerTemplate.java | 114 +++++++ .../remoteconfig/ServerTemplateData.java | 131 ++++++++ .../google/firebase/remoteconfig/Value.java | 131 ++++++++ .../internal/ServerTemplateResponse.java | 49 +++ 14 files changed, 1351 insertions(+) create mode 100644 src/main/java/com/google/firebase/remoteconfig/AndCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/OrCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/PercentCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerConfig.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/Value.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java diff --git a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java new file mode 100644 index 000000000..6e1622f5c --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +import com.google.common.collect.ImmutableList; + +/** + * Represents a collection of conditions that evaluate to true if all are true. + */ +public class AndCondition { + private final ImmutableList conditions; + + /** + * Creates AndCondition joining subconditions. + */ + public AndCondition(ImmutableList conditions) { + this.conditions = conditions; + } + + /** + * Gets the list of {@link OneOfCondition} + * + * @return List of conditions to evaluate. + */ + public ImmutableList getConditions() { + return conditions; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java new file mode 100644 index 000000000..f44de24fd --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java @@ -0,0 +1,316 @@ +package com.google.firebase.remoteconfig; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javax.management.RuntimeErrorException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Encapsulates condition evaluation logic to simplify organization and + * facilitate testing. + */ +public class ConditionEvaluator { + private static final int MAX_CONDITION_RECURSION_DEPTH = 10; + private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class); + + /** + * @param conditions A named condition map of string to {@link OneOfCondition}. + * @param context Represents template evaluation input signals which can be + * user provided or predefined. + * @return Evaluated conditions represented as map of condition name to boolean. + */ + public ImmutableMap evaluateConditions(ImmutableMap conditions, + ImmutableMap context) { + ImmutableMap.Builder evaluatedConditions = ImmutableMap.builder(); + int nestingLevel = 0; + + for (ImmutableMap.Entry condition : conditions.entrySet()) { + evaluatedConditions.put(condition.getKey(), evaluateCondition(condition.getValue(), context, nestingLevel)); + } + + return evaluatedConditions.build(); + } + + private boolean evaluateCondition(OneOfCondition condition, ImmutableMap context, + int nestingLevel) { + if (nestingLevel > MAX_CONDITION_RECURSION_DEPTH) { + logger.warn("Maximum condition recursion depth exceeded."); + return false; + } + + if (condition.getOrCondition() != null) { + return evaluateOrCondition(condition.getOrCondition(), context, nestingLevel + 1); + } else if (condition.getAndCondition() != null) { + return evaluateAndCondition(condition.getAndCondition(), context, nestingLevel + 1); + } else if (condition.isTrue() != null) { + return true; + } else if (condition.isFalse() != null) { + return false; + } else if (condition.getPercent() != null) { + return evaluatePercentCondition(condition.getPercent(), context); + } else if (condition.getCustomSignal() != null) { + return evaluateCustomSignalCondition(condition.getCustomSignal(), context); + } + return false; + } + + private boolean evaluateOrCondition(OrCondition condition, ImmutableMap context, int nestingLevel) { + ImmutableList subConditions = condition.getConditions(); + for (OneOfCondition subCondition : subConditions) { + // Short-circuit the evaluation result for true. + if (evaluateCondition(subCondition, context, nestingLevel + 1)) { + return true; + } + } + return false; + } + + private boolean evaluateAndCondition(AndCondition condition, ImmutableMap context, + int nestingLevel) { + ImmutableList subConditions = condition.getConditions(); + for (OneOfCondition subCondition : subConditions) { + // Short-circuit the evaluation result for false. + if (!evaluateCondition(subCondition, context, nestingLevel + 1)) { + return false; + } + } + return true; + } + + private boolean evaluatePercentCondition(PercentCondition condition, ImmutableMap context) { + if (!context.containsKey("randomizationId")) { + logger.warn("Percentage operation cannot be performed without randomizationId"); + return false; + } + + PercentConditionOperator operator = condition.getPercentConditionOperator(); + if (operator == null) { + logger.warn("Percentage operator is not set."); + return false; + } + + // The micro-percent interval to be used with the BETWEEN operator. + MicroPercentRange microPercentRange = condition.getMicroPercentRange(); + int microPercentUpperBound = microPercentRange != null ? microPercentRange.getMicroPercentUpperBound() : 0; + int microPercentLowerBound = microPercentRange != null ? microPercentRange.getMicroPercentLowerBound() : 0; + // The limit of percentiles to target in micro-percents when using the + // LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0 + // and 100000000]. + int microPercent = condition.getMicroPercent(); + BigInteger microPercentile = getMicroPercentile(condition.getSeed(), context.get("randomizationId")); + switch (operator) { + case LESS_OR_EQUAL: + return microPercentile.compareTo(BigInteger.valueOf(microPercent)) <= 0; + case GREATER_THAN: + return microPercentile.compareTo(BigInteger.valueOf(microPercent)) > 0; + case BETWEEN: + return microPercentile.compareTo(BigInteger.valueOf(microPercentLowerBound)) > 0 + && microPercentile.compareTo(BigInteger.valueOf(microPercentUpperBound)) <= 0; + case UNSPECIFIED: + default: + return false; + } + } + + private boolean evaluateCustomSignalCondition(CustomSignalCondition condition, + ImmutableMap context) { + CustomSignalOperator customSignalOperator = condition.getCustomSignalOperator(); + String customSignalKey = condition.getCustomSignalKey(); + ImmutableList targetCustomSignalValues = condition.getTargetCustomSignalValues(); + + if (customSignalOperator == null || customSignalKey == null || targetCustomSignalValues == null + || targetCustomSignalValues.isEmpty()) { + logger.warn( + String.format("Values must be assigned to all custom signal fields. Operator:%s, Key:%s, Values:%s", + customSignalOperator, customSignalKey, targetCustomSignalValues)); + return false; + } + + Object customSignalValue = context.get(customSignalKey); + if (customSignalValue == null) { + return false; + } + + switch (customSignalOperator) { + case STRING_CONTAINS: + case STRING_DOES_NOT_CONTAIN: + case STRING_EXACTLY_MATCHES: + case STRING_CONTAINS_REGEX: + return compareStrings(targetCustomSignalValues, customSignalValue, customSignalOperator); + case NUMERIC_LESS_THAN: + case NUMERIC_LESS_EQUAL: + case NUMERIC_EQUAL: + case NUMERIC_NOT_EQUAL: + case NUMERIC_GREATER_THAN: + case NUMERIC_GREATER_EQUAL: + return compareNumbers(targetCustomSignalValues, customSignalValue, customSignalOperator); + case SEMANTIC_VERSION_EQUAL: + case SEMANTIC_VERSION_GREATER_EQUAL: + case SEMANTIC_VERSION_GREATER_THAN: + case SEMANTIC_VERSION_LESS_EQUAL: + case SEMANTIC_VERSION_LESS_THAN: + case SEMANTIC_VERSION_NOT_EQUAL: + return compareSemanticVersions(targetCustomSignalValues, customSignalValue, customSignalOperator); + default: + return false; + } + } + + private BigInteger getMicroPercentile(String seed, Object randomizationId) { + String seedPrefix = seed != null && !seed.isEmpty() ? seed + "." : ""; + String stringToHash = seedPrefix + randomizationId; + BigInteger hash = hashSeededRandomizationId(stringToHash); + BigInteger modValue = new BigInteger(Integer.toString(100 * 1_000_000)); + BigInteger microPercentile = hash.mod(modValue); + + return microPercentile; + } + + private BigInteger hashSeededRandomizationId(String seededRandomizationId) { + try { + // Create a SHA-256 hash. + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hashBytes = digest.digest(seededRandomizationId.getBytes(StandardCharsets.UTF_8)); + + // Convert the hash bytes to a hexadecimal string. + StringBuilder hexString = new StringBuilder(); + for (byte b : hashBytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) + hexString.append('0'); + hexString.append(hex); + } + + // Convert the hexadecimal string to a BigInteger + return new BigInteger(hexString.toString(), 16); + + } catch (NoSuchAlgorithmException e) { + logger.error("SHA-256 algorithm not found", e); + throw new RuntimeException("SHA-256 algorithm not found", e); + } + } + + private boolean compareStrings(ImmutableList targetValues, Object customSignalValue, + CustomSignalOperator operator) { + String value = customSignalValue.toString(); + + for (String target : targetValues) { + if (operator == CustomSignalOperator.STRING_CONTAINS && value.contains(target)) { + return true; + } else if (operator == CustomSignalOperator.STRING_DOES_NOT_CONTAIN && !value.contains(target)) { + return true; + } else if (operator == CustomSignalOperator.STRING_EXACTLY_MATCHES && value.equals(target)) { + return true; + } else if (operator == CustomSignalOperator.STRING_CONTAINS_REGEX + && Pattern.compile(value).matcher(target).find()) { + return true; + } + } + return false; + } + + private boolean compareNumbers(ImmutableList targetValues, Object customSignalValue, + CustomSignalOperator operator) { + if (targetValues.size() != 1) { + logger.warn(String.format("Target values must contain 1 element for operation %s", operator)); + return false; + } + + double value = (Double) customSignalValue; + double target = Double.parseDouble(targetValues.get(0)); + + switch (operator) { + case NUMERIC_EQUAL: + return value == target; + case NUMERIC_GREATER_THAN: + return value > target; + case NUMERIC_GREATER_EQUAL: + return value >= target; + case NUMERIC_LESS_EQUAL: + return value <= target; + case NUMERIC_LESS_THAN: + return value < target; + case NUMERIC_NOT_EQUAL: + return value != target; + default: + return false; + } + } + + private boolean compareSemanticVersions(ImmutableList targetValues, Object customSignalValue, + CustomSignalOperator operator) throws RuntimeErrorException { + if (targetValues.size() != 1) { + logger.warn(String.format("Target values must contain 1 element for operation %s", operator)); + return false; + } + + // Max number of segments a numeric version can have. This is enforced by the + // server as well. + int maxLength = 5; + + List version1 = Arrays.stream(customSignalValue.toString().split("\\.")) + .map(s -> Integer.valueOf(s)).collect(Collectors.toList()); + List version2 = Arrays.stream(targetValues.get(0).split("\\.")).map(s -> Integer.valueOf(s)) + .collect(Collectors.toList()); + for (int i = 0; i < maxLength; i++) { + // Check to see if segments are present. + Boolean version1HasSegment = version1.get(i) != null; + Boolean version2HasSegment = version2.get(i) != null; + + // If both are undefined, we've consumed everything and they're equal. + if (!version1HasSegment && !version2HasSegment) { + return operator == CustomSignalOperator.SEMANTIC_VERSION_EQUAL; + } + + // Insert zeros if undefined for easier comparison. + if (!version1HasSegment) + version1.add(i, 0); + if (!version2HasSegment) + version2.add(i, 0); + + switch (operator) { + case SEMANTIC_VERSION_GREATER_EQUAL: + if (version1.get(i).compareTo(version2.get(i)) >= 0) + return true; + break; + case SEMANTIC_VERSION_GREATER_THAN: + if (version1.get(i).compareTo(version2.get(i)) > 0) + return true; + break; + case SEMANTIC_VERSION_LESS_EQUAL: + if (version1.get(i).compareTo(version2.get(i)) <= 0) + return true; + break; + case SEMANTIC_VERSION_LESS_THAN: + if (version1.get(i).compareTo(version2.get(i)) < 0) + return true; + break; + case SEMANTIC_VERSION_EQUAL: + if (version1.get(i).compareTo(version2.get(i)) != 0) + return false; + break; + case SEMANTIC_VERSION_NOT_EQUAL: + if (version1.get(i).compareTo(version2.get(i)) != 0) + return true; + break; + default: + return false; + } + } + logger.warn(String.format("Semantic version max length(5) exceeded. Target: %s, Custom Signal: %s", version1, + version2)); + return false; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java new file mode 100644 index 000000000..fb2984eac --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java @@ -0,0 +1,72 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +import com.google.common.collect.ImmutableList; + +/** + * Represents a condition that compares provided signals against a target value. + */ +public class CustomSignalCondition { + private final String customSignalKey; + private final CustomSignalOperator customSignalOperator; + private final ImmutableList targetCustomSignalValues; + + /** + * Creates new Custom signal condition. + * + * @param customSignalKey + * @param customSignalOperator + * @param targetCustomSignalValues + */ + public CustomSignalCondition(String customSignalKey, CustomSignalOperator customSignalOperator, ImmutableList targetCustomSignalValues){ + this.customSignalKey = customSignalKey; + this.customSignalOperator = customSignalOperator; + this.targetCustomSignalValues = targetCustomSignalValues; + } + + /** + * Gets the key of the signal set in the EvaluationContext + * {@link CustomSignalCondition.customSignalKey}. + * + * @return Custom signal key. + */ + public String getCustomSignalKey() { + return customSignalKey; + } + + /** + * Gets the choice of custom signal operator + * {@link CustomSignalCondition.customSignalOperator} to determine how to + * compare targets to value(s). + * + * @return Custom signal operator. + */ + public CustomSignalOperator getCustomSignalOperator() { + return customSignalOperator; + } + + /** + * Gets the list of at most 100 target custom signal values + * {@link CustomSignalCondition.targetCustomSignalValues}. For numeric + * operators, this will have exactly ONE target value. + * + * @return List of target values. + */ + public ImmutableList getTargetCustomSignalValues() { + return targetCustomSignalValues; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java new file mode 100644 index 000000000..49da72714 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +/** + * Defines supported operators for custom signal conditions. + */ +public enum CustomSignalOperator { + NUMERIC_EQUAL("NUMERIC_EQUAL"), + NUMERIC_GREATER_EQUAL("NUMERIC_GREATER_EQUAL"), + NUMERIC_GREATER_THAN("NUMERIC_GREATER_THAN"), + NUMERIC_LESS_EQUAL("NUMERIC_LESS_EQUAL"), + NUMERIC_LESS_THAN("NUMERIC_LESS_THAN"), + NUMERIC_NOT_EQUAL("NUMERIC_NOT_EQUAL"), + SEMANTIC_VERSION_EQUAL("SEMANTIC_VERSION_EQUAL"), + SEMANTIC_VERSION_GREATER_EQUAL("SEMANTIC_VERSION_GREATER_EQUAL"), + SEMANTIC_VERSION_GREATER_THAN("SEMANTIC_VERSION_GREATER_THAN"), + SEMANTIC_VERSION_LESS_EQUAL("SEMANTIC_VERSION_LESS_EQUAL"), + SEMANTIC_VERSION_LESS_THAN("SEMANTIC_VERSION_LESS_THAN"), + SEMANTIC_VERSION_NOT_EQUAL("SEMANTIC_VERSION_NOT_EQUAL"), + STRING_CONTAINS("STRING_CONTAINS"), + STRING_CONTAINS_REGEX("STRING_CONTAINS_REGEX"), + STRING_DOES_NOT_CONTAIN("STRING_DOES_NOT_CONTAIN"), + STRING_EXACTLY_MATCHES("STRING_EXACTLY_MATCHES"), + UNSPECIFIED("CUSTOM_SIGNAL_OPERATOR_UNSPECIFIED"); + + private final String operator; + + CustomSignalOperator(String operator) { + this.operator = operator; + } + + /** + * Get the custom signal operator {@link CustomSignalOperator}. + * + * @return operator. + */ + public String getOperator() { + return operator; + } + } diff --git a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java new file mode 100644 index 000000000..3f889439a --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +/** + * Represents the limit of percentiles to target in micro-percents. The value + * must be in the range [0 and 100000000]. + */ +public class MicroPercentRange { + private final int microPercentLowerBound; + private final int microPercentUpperBound; + + public MicroPercentRange(int microPercentLowerBound, int microPercentUpperBound){ + this.microPercentLowerBound = microPercentLowerBound; + this.microPercentUpperBound = microPercentUpperBound; + } + + /** + * The lower limit of percentiles + * {@link MicroPercentRange.microPercentLowerBound} to target in micro-percents. + * The value must be in the range [0 and 100000000]. + * + * @return micropercentile lower bound. + */ + public int getMicroPercentLowerBound() { + return microPercentLowerBound; + } + + /** + * The upper limit of percentiles + * {@link MicroPercentRange.microPercentUpperBound} to target in micro-percents. + * The value must be in the range [0 and 100000000]. + * + * @return micropercentile upper bound. + */ + public int getMicroPercentUpperBound() { + return microPercentUpperBound; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java new file mode 100644 index 000000000..84494c141 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java @@ -0,0 +1,133 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +/** + * Represents a condition that may be one of several types. Only the first + * defined field will be processed. + */ +public class OneOfCondition { + private OrCondition orCondition; + private AndCondition andCondition; + private PercentCondition percent; + private CustomSignalCondition customSignal; + private String trueValue; + private String falseValue; + + /** + * Gets {@link OrCondition} with conditions on which OR operation will be applied. + * + * @return list of conditions. + */ + public OrCondition getOrCondition() { + return orCondition; + } + + /** + * Gets {@link AndCondition} with conditions on which AND operation will be applied. + * + * @return list of conditions. + */ + public AndCondition getAndCondition() { + return andCondition; + } + + /** + * Returns true indicating the expression has evaluated to true. + * + * @return true. + */ + public String isTrue() { + return trueValue; + } + + /** + * Returns false indicating the expression has evaluated to false. + * + * @return false. + */ + public String isFalse() { + return falseValue; + } + + /** + * Gets {@link PercentCondition}. + * + * @return percent condition. + */ + public PercentCondition getPercent() { + return percent; + } + + /** + * Gets {@link CustomSignalCondition}. + * + * @return custom condition. + */ + public CustomSignalCondition getCustomSignal() { + return customSignal; + } + + /** + * Sets list of conditions on which OR operation will be applied. + * + * @param orCondition + */ + public void setOrCondition(OrCondition orCondition) { + this.orCondition = orCondition; + } + + /** + * Sets list of conditions on which AND operation will be applied. + * + * @param andCondition + */ + public void setAndCondition(AndCondition andCondition) { + this.andCondition = andCondition; + } + + /** + * Sets {@link PercentCondition}. + * + * @param percent + */ + public void setPercent(PercentCondition percent) { + this.percent = percent; + } + + /** + * Sets {@link CustomSignalCondition}. + * + * @param customSignal + */ + public void setCustomSignal(CustomSignalCondition customSignal) { + this.customSignal = customSignal; + } + + /** + * Sets evaluation value to true. + */ + public void setTrue() { + this.trueValue = "true"; + } + + /** + * Sets evaluation value to false. + */ + public void setFalse() { + this.falseValue = "false"; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/OrCondition.java b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java new file mode 100644 index 000000000..7fedd4297 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +import com.google.common.collect.ImmutableList; + +/** + * Represents a collection of conditions that evaluate to true if one of them is true. + */ +public class OrCondition { + private final ImmutableList conditions; + + /** + * Creates OrCondition joining subconditions. + */ + public OrCondition(ImmutableList conditions) { + this.conditions = conditions; + } + + /** + * Gets the list of {@link OneOfCondition} + * + * @return List of conditions to evaluate. + */ + public ImmutableList getConditions() { + return conditions; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java new file mode 100644 index 000000000..ea31c69ef --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +/** + * Represents a condition that compares the instance pseudo-random percentile to + * a given limit. + */ +public class PercentCondition { + private int microPercent; + private MicroPercentRange microPercentRange; + private final PercentConditionOperator percentConditionOperator; + private final String seed; + + /** + * Create a percent condition for {@link PercentConditionOperator.BETWEEN}. + * + * @param microPercent + * @param percentConditionOperator + * @param seed + */ + public PercentCondition(int microPercent, PercentConditionOperator percentConditionOperator, String seed) { + this.microPercent = microPercent; + this.percentConditionOperator = percentConditionOperator; + this.seed = seed; + } + + /** + * Create a percent condition for {@link PercentConditionOperator.GREATER_THAN} + * and {@link PercentConditionOperator.LESS_OR_EQUAL}. + * + * @param microPercentRange + * @param percentConditionOperator + * @param seed + */ + public PercentCondition(MicroPercentRange microPercentRange, PercentConditionOperator percentConditionOperator, + String seed) { + this.microPercentRange = microPercentRange; + this.percentConditionOperator = percentConditionOperator; + this.seed = seed; + } + + /** + * Gets the limit of percentiles to target in micro-percents when using the + * LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0 + * and 100000000]. + * + * @return micro percent. + */ + public int getMicroPercent() { + return microPercent; + } + + /** + * Gets micro-percent interval to be used with the BETWEEN operator. + * + * @return micro percent range. + */ + public MicroPercentRange getMicroPercentRange() { + return microPercentRange; + } + + /** + * Gets choice of percent operator to determine how to compare targets to + * percent(s). + * + * @return operator. + */ + public PercentConditionOperator getPercentConditionOperator() { + return percentConditionOperator; + } + + /** + * The seed used when evaluating the hash function to map an instance to a value + * in the hash space. This is a string which can have 0 - 32 characters and can + * contain ASCII characters [-_.0-9a-zA-Z].The string is case-sensitive. + * + * @return seed. + */ + public String getSeed() { + return seed; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java new file mode 100644 index 000000000..115da0a20 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +/** + * Defines supported operators for percent conditions. + */ +public enum PercentConditionOperator { + BETWEEN("BETWEEN"), + GREATER_THAN("GREATER_THAN"), + LESS_OR_EQUAL("LESS_OR_EQUAL"), + UNSPECIFIED("PERCENT_OPERATOR_UNSPECIFIED"); + + private final String operator; + + /** + * Creates percent condition operator. + * + * @param operator + */ + PercentConditionOperator(String operator) { + this.operator = operator; + } + + /** + * Gets percent condition operator. + * + * @return operator. + */ + public String getOperator() { + return operator; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java new file mode 100644 index 000000000..fe349cbd2 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java @@ -0,0 +1,75 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +import java.util.Map; + +/** + * Represents the configuration produced by evaluating a server template. + */ +public class ServerConfig { + private final Map configValues; + + ServerConfig(Map configValues) { + this.configValues = configValues; + } + + /** + * Gets the value for the given key as a string. Convenience method for calling + * serverConfig.getValue(key).asString(). + * + * @param key + * @return config value for the given key as string. + */ + public String getString(String key) { + return configValues.get(key).asString(); + } + + /** + * Gets the value for the given key as a boolean.Convenience method for calling + * serverConfig.getValue(key).asBoolean(). + * + * @param key + * @return config value for the given key as boolean. + */ + public boolean getBoolean(String key) { + return Boolean.parseBoolean(getString(key)); + } + + /** + * Gets the value for the given key as long.Convenience method for calling + * serverConfig.getValue(key).asLong(). + * + * @param key + * @return config value for the given key as long. + */ + public long getLong(String key) { + return Long.parseLong(getString(key)); + } + + /** + * Gets the value for the given key as double.Convenience method for calling + * serverConfig.getValue(key).asDouble(). + * + * @param key + * @return config value for the given key as double. + */ + public double getDouble(String key) { + return Double.parseDouble(getString(key)); + } + +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java new file mode 100644 index 000000000..3af6edd26 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java @@ -0,0 +1,114 @@ +package com.google.firebase.remoteconfig; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableMap; +import com.google.firebase.ErrorCode; +import com.google.firebase.internal.Nullable; +import com.google.firebase.remoteconfig.Value.ValueSource; + +/** + * Represents a stateful abstraction for a Remote Config server template. + */ +public class ServerTemplate { + private ServerTemplateData cache; + private Map stringifiedDefaultConfig; + private final ConditionEvaluator conditionEvaluator; + private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class); + + /** + * Creates a new {@link ServerTemplate}. + * + * @param conditionEvaluator + * @param defaultConfig + */ + public ServerTemplate(ConditionEvaluator conditionEvaluator, ImmutableMap defaultConfig) { + for (String configName : defaultConfig.keySet()) { + stringifiedDefaultConfig.put(configName, defaultConfig.get(configName)); + } + this.conditionEvaluator = conditionEvaluator; + } + + /** + * Evaluates the current server template in cache to produce a + * {@link ServerConfig}. + * + * @param context + * @return evaluated server config. + * @throws FirebaseRemoteConfigException + */ + public ServerConfig evaluate(@Nullable ImmutableMap context) throws FirebaseRemoteConfigException { + if (cache == null) { + throw new FirebaseRemoteConfigException(ErrorCode.FAILED_PRECONDITION, + "No Remote Config Server template in cache. Call load() before calling evaluate()."); + } + + ImmutableMap evaluatedCondition = conditionEvaluator + .evaluateConditions(ImmutableMap.copyOf(cache.getConditions()), context); + + Map configValues = new HashMap<>(); + // Initializes config Value objects with default values. + for (String configName : stringifiedDefaultConfig.keySet()) { + configValues.put(configName, stringifiedDefaultConfig.get(configName)); + } + + ImmutableMap parameters = ImmutableMap.copyOf(cache.getParameters()); + mergeDerivedConfigValues(evaluatedCondition, parameters, configValues); + return new ServerConfig(configValues); + } + + private void mergeDerivedConfigValues(ImmutableMap evaluatedCondition, + ImmutableMap parameters, Map configValues) { + // Overlays config Value objects derived by evaluating the template. + for (String parameterName : parameters.keySet()) { + Parameter parameter = parameters.get(parameterName); + if (parameter == null) { + logger.warn(String.format("Parameter value is not assigned for %s", parameterName)); + continue; + } + + Map conditionalValues = parameter.getConditionalValues(); + ParameterValue defaultValue = parameter.getDefaultValue(); + ParameterValue derivedValue = null; + + for (String conditionName : evaluatedCondition.keySet()) { + boolean conditionEvaluation = evaluatedCondition.get(conditionName); + if (conditionalValues.containsKey(conditionName) && conditionEvaluation) { + derivedValue = conditionalValues.get(conditionName); + break; + } + } + + if (derivedValue != null && derivedValue.toParameterValueResponse().isUseInAppDefault()) { + logger.warn( + String.format("Derived value for %s is set to use in app default.", parameterName)); + continue; + } + + if (derivedValue != null) { + String parameterValue = derivedValue.toParameterValueResponse().getValue(); + Value value = new Value(ValueSource.REMOTE, parameterValue); + configValues.put(parameterName, value); + continue; + } + + if (defaultValue == null) { + logger.warn(String.format("Default parameter value for %s is set to null.", parameterName)); + continue; + } + + if (defaultValue.toParameterValueResponse().isUseInAppDefault()) { + logger.info(String.format("Default value for %s is set to use in app default.", parameterName)); + continue; + } + + String parameterDefaultValue = defaultValue.toParameterValueResponse().getValue(); + Value value = new Value(ValueSource.REMOTE, parameterDefaultValue); + configValues.put(parameterName, value); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java new file mode 100644 index 000000000..6945534ee --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java @@ -0,0 +1,131 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +import java.util.HashMap; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; +import com.google.firebase.internal.NonNull; + +/** + * Represents data stored in a server side Remote Config template. + */ +public class ServerTemplateData { + private String etag; + private Map parameters; + private Map conditions; + private Version version; + + /** + * Creates a new {@link ServerTemplateData} object. + * + * @param etag The ETag of this template. + */ + public ServerTemplateData(String etag) { + this.parameters = new HashMap<>(); + this.conditions = new HashMap<>(); + this.etag = etag; + } + + /** + * Creates a new {@link ServerTemplateData} object with an empty etag. + */ + public ServerTemplateData() { + this((String) null); + } + + /** + * Gets the map of parameters of the server side template data. + * + * @return A non-null map of parameter keys to their optional default values and + * optional + * conditional values. + */ + @NonNull + public Map getParameters() { + return this.parameters; + } + + /** + * Gets the map of parameters of the server side template data. + * + * @return A non-null map of condition keys to their conditions. + */ + @NonNull + public Map getConditions() { + return this.conditions; + } + + /** + * Gets the ETag of the server template data. + * + * @return The ETag of the server template data. + */ + public String getETag() { + return this.etag; + } + + /** + * Gets the version information of the template. + * + * @return The version information of the template. + */ + public Version getVersion() { + return version; + } + + /** + * Sets the map of parameters of the server side template data. + * + * @param parameters A non-null map of parameter keys to their optional default + * values and + * optional conditional values. + * @return This {@link ServerTemplateData} instance. + */ + public ServerTemplateData setParameters( + @NonNull Map parameters) { + checkNotNull(parameters, "parameters must not be null."); + this.parameters = parameters; + return this; + } + + /** + * Sets the map of conditions of the server side template data. + * + * @param conditions A non-null map of conditions. + * @return This {@link ServerTemplateData} instance. + */ + public ServerTemplateData setConditions( + @NonNull Map conditions) { + checkNotNull(conditions, "conditions must not be null."); + this.conditions = conditions; + return this; + } + + /** + * Sets the version information of the template. + * Only the version's description field can be specified here. + * + * @param version A {@link Version} instance. + * @return This {@link Template} instance. + */ + public ServerTemplateData setVersion(Version version) { + this.version = version; + return this; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/Value.java b/src/main/java/com/google/firebase/remoteconfig/Value.java new file mode 100644 index 000000000..d97cb129e --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/Value.java @@ -0,0 +1,131 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; + +/** + * Wraps a parameter value with metadata and type-safe getters. Type-safe + * getters insulate application logic from remote changes to parameter names and + * types. + */ +public class Value { + private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class); + private static final boolean DEFAULT_VALUE_FOR_BOOLEAN = false; + private static final String DEFAULT_VALUE_FOR_STRING = ""; + private static final long DEFAULT_VALUE_FOR_LONG = 0; + private static final double DEFAULT_VALUE_FOR_DOUBLE = 0; + private static final ImmutableList BOOLEAN_TRUTHY_VALUES = ImmutableList.of("1", "true", "t", "yes", "y", + "on"); + + public enum ValueSource { + STATIC, + REMOTE, + DEFAULT + } + + private final ValueSource source; + private final String value; + + /** + * Creates a new {@link Value} object. + * + * @param source + * @param value + */ + public Value(ValueSource source, String value) { + this.source = source; + this.value = value; + } + + /** + * Creates a new {@link Value} object with default value. + * + * @param source + */ + public Value(ValueSource source) { + this(source, DEFAULT_VALUE_FOR_STRING); + } + + /** + * Gets the value as a string. + * + * @return value as string + */ + public String asString() { + return this.value; + } + + /** + * Gets the value as a boolean.The following values (case + * insensitive) are interpreted as true: "1", "true", "t", "yes", "y", "on". + * Other values are interpreted as false. + * + * @return value as boolean + */ + public boolean asBoolean() { + if (source == ValueSource.STATIC) { + return DEFAULT_VALUE_FOR_BOOLEAN; + } + return BOOLEAN_TRUTHY_VALUES.contains(value.toLowerCase()); + } + + /** + * Gets the value as long. Comparable to calling Number(value) || 0. + * + * @return value as long + */ + public long asLong() { + if (source == ValueSource.STATIC) { + return DEFAULT_VALUE_FOR_LONG; + } + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + logger.warn("Unable to convert %s to long type.", value); + return DEFAULT_VALUE_FOR_LONG; + } + } + + /** + * Gets the value as double. Comparable to calling Number(value) || 0. + * + * @return value as double + */ + public double asDouble() { + if (source == ValueSource.STATIC) { + return DEFAULT_VALUE_FOR_DOUBLE; + } + try { + return Double.parseDouble(this.value); + } catch (NumberFormatException e) { + logger.warn("Unable to convert %s to double type.", value); + return DEFAULT_VALUE_FOR_DOUBLE; + } + } + + /** + * Gets the {@link ValueSource} for the given key. + * + * @return source. + */ + public ValueSource getSource() { + return source; + } +} \ No newline at end of file diff --git a/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java new file mode 100644 index 000000000..6db7ef5ef --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig.internal; + +import com.google.api.client.util.Key; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ConditionResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterGroupResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse.VersionResponse; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Condition; + +/** + * The Data Transfer Object for parsing Remote Config server template responses from the + * Remote Config service. + **/ +public class ServerTemplateResponse { + + @Key("parameters") + private Map parameters; + + @Key("conditions") + private Map conditions; + + @Key("version") + private VersionResponse version; + + // For local JSON serialization and deserialization purposes only. + // ETag in response type is never set by the HTTP response. + @Key("etag") + private String etag; + +} From 31f5c42658baea9ae95a70587ea8fad5c454215d Mon Sep 17 00:00:00 2001 From: Athira M Date: Mon, 2 Dec 2024 18:40:21 +0530 Subject: [PATCH 02/78] Add nullable and non-nullable annotations --- .../firebase/remoteconfig/AndCondition.java | 6 ++- .../remoteconfig/ConditionEvaluator.java | 6 ++- .../remoteconfig/CustomSignalCondition.java | 6 ++- .../remoteconfig/CustomSignalOperator.java | 5 +- .../remoteconfig/MicroPercentRange.java | 6 ++- .../firebase/remoteconfig/OneOfCondition.java | 17 +++++-- .../firebase/remoteconfig/OrCondition.java | 4 +- .../remoteconfig/PercentCondition.java | 11 ++++- .../PercentConditionOperator.java | 5 +- .../firebase/remoteconfig/ServerConfig.java | 14 ++++-- .../firebase/remoteconfig/ServerTemplate.java | 3 +- .../remoteconfig/ServerTemplateData.java | 7 ++- .../google/firebase/remoteconfig/Value.java | 10 +++- .../internal/ServerTemplateResponse.java | 49 ------------------- 14 files changed, 77 insertions(+), 72 deletions(-) delete mode 100644 src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java diff --git a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java index 6e1622f5c..47c6daa7d 100644 --- a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java @@ -16,6 +16,7 @@ package com.google.firebase.remoteconfig; import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; /** * Represents a collection of conditions that evaluate to true if all are true. @@ -24,9 +25,9 @@ public class AndCondition { private final ImmutableList conditions; /** - * Creates AndCondition joining subconditions. + * Creates a new {@link AndCondition} joining subconditions. */ - public AndCondition(ImmutableList conditions) { + public AndCondition(@NonNull ImmutableList conditions) { this.conditions = conditions; } @@ -35,6 +36,7 @@ public AndCondition(ImmutableList conditions) { * * @return List of conditions to evaluate. */ + @NonNull public ImmutableList getConditions() { return conditions; } diff --git a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java index f44de24fd..24822280b 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java +++ b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.firebase.internal.NonNull; /** * Encapsulates condition evaluation logic to simplify organization and @@ -31,8 +32,9 @@ public class ConditionEvaluator { * user provided or predefined. * @return Evaluated conditions represented as map of condition name to boolean. */ - public ImmutableMap evaluateConditions(ImmutableMap conditions, - ImmutableMap context) { + @NonNull + public ImmutableMap evaluateConditions(@NonNull ImmutableMap conditions, + @NonNull ImmutableMap context) { ImmutableMap.Builder evaluatedConditions = ImmutableMap.builder(); int nestingLevel = 0; diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java index fb2984eac..d418cb483 100644 --- a/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java @@ -16,6 +16,7 @@ package com.google.firebase.remoteconfig; import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; /** * Represents a condition that compares provided signals against a target value. @@ -32,7 +33,7 @@ public class CustomSignalCondition { * @param customSignalOperator * @param targetCustomSignalValues */ - public CustomSignalCondition(String customSignalKey, CustomSignalOperator customSignalOperator, ImmutableList targetCustomSignalValues){ + public CustomSignalCondition(@NonNull String customSignalKey, @NonNull CustomSignalOperator customSignalOperator, @NonNull ImmutableList targetCustomSignalValues){ this.customSignalKey = customSignalKey; this.customSignalOperator = customSignalOperator; this.targetCustomSignalValues = targetCustomSignalValues; @@ -44,6 +45,7 @@ public CustomSignalCondition(String customSignalKey, CustomSignalOperator custom * * @return Custom signal key. */ + @NonNull public String getCustomSignalKey() { return customSignalKey; } @@ -55,6 +57,7 @@ public String getCustomSignalKey() { * * @return Custom signal operator. */ + @NonNull public CustomSignalOperator getCustomSignalOperator() { return customSignalOperator; } @@ -66,6 +69,7 @@ public CustomSignalOperator getCustomSignalOperator() { * * @return List of target values. */ + @NonNull public ImmutableList getTargetCustomSignalValues() { return targetCustomSignalValues; } diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java index 49da72714..fc7890127 100644 --- a/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java @@ -15,6 +15,8 @@ */ package com.google.firebase.remoteconfig; +import com.google.firebase.internal.NonNull; + /** * Defines supported operators for custom signal conditions. */ @@ -39,7 +41,7 @@ public enum CustomSignalOperator { private final String operator; - CustomSignalOperator(String operator) { + CustomSignalOperator(@NonNull String operator) { this.operator = operator; } @@ -48,6 +50,7 @@ public enum CustomSignalOperator { * * @return operator. */ + @NonNull public String getOperator() { return operator; } diff --git a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java index 3f889439a..ece4b5073 100644 --- a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java +++ b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java @@ -15,6 +15,8 @@ */ package com.google.firebase.remoteconfig; +import com.google.firebase.internal.NonNull; + /** * Represents the limit of percentiles to target in micro-percents. The value * must be in the range [0 and 100000000]. @@ -23,7 +25,7 @@ public class MicroPercentRange { private final int microPercentLowerBound; private final int microPercentUpperBound; - public MicroPercentRange(int microPercentLowerBound, int microPercentUpperBound){ + public MicroPercentRange(@NonNull int microPercentLowerBound, @NonNull int microPercentUpperBound){ this.microPercentLowerBound = microPercentLowerBound; this.microPercentUpperBound = microPercentUpperBound; } @@ -35,6 +37,7 @@ public MicroPercentRange(int microPercentLowerBound, int microPercentUpperBound) * * @return micropercentile lower bound. */ + @NonNull public int getMicroPercentLowerBound() { return microPercentLowerBound; } @@ -46,6 +49,7 @@ public int getMicroPercentLowerBound() { * * @return micropercentile upper bound. */ + @NonNull public int getMicroPercentUpperBound() { return microPercentUpperBound; } diff --git a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java index 84494c141..9b4b009e4 100644 --- a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java @@ -15,6 +15,9 @@ */ package com.google.firebase.remoteconfig; +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; + /** * Represents a condition that may be one of several types. Only the first * defined field will be processed. @@ -32,6 +35,7 @@ public class OneOfCondition { * * @return list of conditions. */ + @Nullable public OrCondition getOrCondition() { return orCondition; } @@ -41,6 +45,7 @@ public OrCondition getOrCondition() { * * @return list of conditions. */ + @Nullable public AndCondition getAndCondition() { return andCondition; } @@ -50,6 +55,7 @@ public AndCondition getAndCondition() { * * @return true. */ + @Nullable public String isTrue() { return trueValue; } @@ -59,6 +65,7 @@ public String isTrue() { * * @return false. */ + @Nullable public String isFalse() { return falseValue; } @@ -68,6 +75,7 @@ public String isFalse() { * * @return percent condition. */ + @Nullable public PercentCondition getPercent() { return percent; } @@ -77,6 +85,7 @@ public PercentCondition getPercent() { * * @return custom condition. */ + @Nullable public CustomSignalCondition getCustomSignal() { return customSignal; } @@ -86,7 +95,7 @@ public CustomSignalCondition getCustomSignal() { * * @param orCondition */ - public void setOrCondition(OrCondition orCondition) { + public void setOrCondition(@NonNull OrCondition orCondition) { this.orCondition = orCondition; } @@ -95,7 +104,7 @@ public void setOrCondition(OrCondition orCondition) { * * @param andCondition */ - public void setAndCondition(AndCondition andCondition) { + public void setAndCondition(@NonNull AndCondition andCondition) { this.andCondition = andCondition; } @@ -104,7 +113,7 @@ public void setAndCondition(AndCondition andCondition) { * * @param percent */ - public void setPercent(PercentCondition percent) { + public void setPercent(@NonNull PercentCondition percent) { this.percent = percent; } @@ -113,7 +122,7 @@ public void setPercent(PercentCondition percent) { * * @param customSignal */ - public void setCustomSignal(CustomSignalCondition customSignal) { + public void setCustomSignal(@NonNull CustomSignalCondition customSignal) { this.customSignal = customSignal; } diff --git a/src/main/java/com/google/firebase/remoteconfig/OrCondition.java b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java index 7fedd4297..0c5c4956f 100644 --- a/src/main/java/com/google/firebase/remoteconfig/OrCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java @@ -16,6 +16,7 @@ package com.google.firebase.remoteconfig; import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; /** * Represents a collection of conditions that evaluate to true if one of them is true. @@ -26,7 +27,7 @@ public class OrCondition { /** * Creates OrCondition joining subconditions. */ - public OrCondition(ImmutableList conditions) { + public OrCondition(@NonNull ImmutableList conditions) { this.conditions = conditions; } @@ -35,6 +36,7 @@ public OrCondition(ImmutableList conditions) { * * @return List of conditions to evaluate. */ + @NonNull public ImmutableList getConditions() { return conditions; } diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java index ea31c69ef..f63524551 100644 --- a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java @@ -15,6 +15,9 @@ */ package com.google.firebase.remoteconfig; +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; + /** * Represents a condition that compares the instance pseudo-random percentile to * a given limit. @@ -32,7 +35,7 @@ public class PercentCondition { * @param percentConditionOperator * @param seed */ - public PercentCondition(int microPercent, PercentConditionOperator percentConditionOperator, String seed) { + public PercentCondition(@NonNull int microPercent, @NonNull PercentConditionOperator percentConditionOperator, @NonNull String seed) { this.microPercent = microPercent; this.percentConditionOperator = percentConditionOperator; this.seed = seed; @@ -46,7 +49,7 @@ public PercentCondition(int microPercent, PercentConditionOperator percentCondit * @param percentConditionOperator * @param seed */ - public PercentCondition(MicroPercentRange microPercentRange, PercentConditionOperator percentConditionOperator, + public PercentCondition(@NonNull MicroPercentRange microPercentRange, @NonNull PercentConditionOperator percentConditionOperator, String seed) { this.microPercentRange = microPercentRange; this.percentConditionOperator = percentConditionOperator; @@ -60,6 +63,7 @@ public PercentCondition(MicroPercentRange microPercentRange, PercentConditionOpe * * @return micro percent. */ + @Nullable public int getMicroPercent() { return microPercent; } @@ -69,6 +73,7 @@ public int getMicroPercent() { * * @return micro percent range. */ + @Nullable public MicroPercentRange getMicroPercentRange() { return microPercentRange; } @@ -79,6 +84,7 @@ public MicroPercentRange getMicroPercentRange() { * * @return operator. */ + @NonNull public PercentConditionOperator getPercentConditionOperator() { return percentConditionOperator; } @@ -90,6 +96,7 @@ public PercentConditionOperator getPercentConditionOperator() { * * @return seed. */ + @NonNull public String getSeed() { return seed; } diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java index 115da0a20..27b15bca1 100644 --- a/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java +++ b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java @@ -15,6 +15,8 @@ */ package com.google.firebase.remoteconfig; +import com.google.firebase.internal.NonNull; + /** * Defines supported operators for percent conditions. */ @@ -31,7 +33,7 @@ public enum PercentConditionOperator { * * @param operator */ - PercentConditionOperator(String operator) { + PercentConditionOperator(@NonNull String operator) { this.operator = operator; } @@ -40,6 +42,7 @@ public enum PercentConditionOperator { * * @return operator. */ + @NonNull public String getOperator() { return operator; } diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java index fe349cbd2..49582c148 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java @@ -18,6 +18,8 @@ import java.util.Map; +import com.google.firebase.internal.NonNull; + /** * Represents the configuration produced by evaluating a server template. */ @@ -35,7 +37,8 @@ public class ServerConfig { * @param key * @return config value for the given key as string. */ - public String getString(String key) { + @NonNull + public String getString(@NonNull String key) { return configValues.get(key).asString(); } @@ -46,7 +49,8 @@ public String getString(String key) { * @param key * @return config value for the given key as boolean. */ - public boolean getBoolean(String key) { + @NonNull + public boolean getBoolean(@NonNull String key) { return Boolean.parseBoolean(getString(key)); } @@ -57,7 +61,8 @@ public boolean getBoolean(String key) { * @param key * @return config value for the given key as long. */ - public long getLong(String key) { + @NonNull + public long getLong(@NonNull String key) { return Long.parseLong(getString(key)); } @@ -68,7 +73,8 @@ public long getLong(String key) { * @param key * @return config value for the given key as double. */ - public double getDouble(String key) { + @NonNull + public double getDouble(@NonNull String key) { return Double.parseDouble(getString(key)); } diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java index 3af6edd26..8582018ac 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java @@ -8,6 +8,7 @@ import com.google.common.collect.ImmutableMap; import com.google.firebase.ErrorCode; +import com.google.firebase.internal.NonNull; import com.google.firebase.internal.Nullable; import com.google.firebase.remoteconfig.Value.ValueSource; @@ -26,7 +27,7 @@ public class ServerTemplate { * @param conditionEvaluator * @param defaultConfig */ - public ServerTemplate(ConditionEvaluator conditionEvaluator, ImmutableMap defaultConfig) { + public ServerTemplate(@NonNull ConditionEvaluator conditionEvaluator, @NonNull ImmutableMap defaultConfig) { for (String configName : defaultConfig.keySet()) { stringifiedDefaultConfig.put(configName, defaultConfig.get(configName)); } diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java index 6945534ee..f61a965c7 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java @@ -36,7 +36,7 @@ public class ServerTemplateData { * * @param etag The ETag of this template. */ - public ServerTemplateData(String etag) { + public ServerTemplateData(@NonNull String etag) { this.parameters = new HashMap<>(); this.conditions = new HashMap<>(); this.etag = etag; @@ -76,6 +76,7 @@ public Map getConditions() { * * @return The ETag of the server template data. */ + @NonNull public String getETag() { return this.etag; } @@ -85,6 +86,7 @@ public String getETag() { * * @return The version information of the template. */ + @NonNull public Version getVersion() { return version; } @@ -97,6 +99,7 @@ public Version getVersion() { * optional conditional values. * @return This {@link ServerTemplateData} instance. */ + @NonNull public ServerTemplateData setParameters( @NonNull Map parameters) { checkNotNull(parameters, "parameters must not be null."); @@ -110,6 +113,7 @@ public ServerTemplateData setParameters( * @param conditions A non-null map of conditions. * @return This {@link ServerTemplateData} instance. */ + @NonNull public ServerTemplateData setConditions( @NonNull Map conditions) { checkNotNull(conditions, "conditions must not be null."); @@ -124,6 +128,7 @@ public ServerTemplateData setConditions( * @param version A {@link Version} instance. * @return This {@link Template} instance. */ + @NonNull public ServerTemplateData setVersion(Version version) { this.version = version; return this; diff --git a/src/main/java/com/google/firebase/remoteconfig/Value.java b/src/main/java/com/google/firebase/remoteconfig/Value.java index d97cb129e..61552099d 100644 --- a/src/main/java/com/google/firebase/remoteconfig/Value.java +++ b/src/main/java/com/google/firebase/remoteconfig/Value.java @@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; /** * Wraps a parameter value with metadata and type-safe getters. Type-safe @@ -49,7 +50,7 @@ public enum ValueSource { * @param source * @param value */ - public Value(ValueSource source, String value) { + public Value(@NonNull ValueSource source, @NonNull String value) { this.source = source; this.value = value; } @@ -59,7 +60,7 @@ public Value(ValueSource source, String value) { * * @param source */ - public Value(ValueSource source) { + public Value(@NonNull ValueSource source) { this(source, DEFAULT_VALUE_FOR_STRING); } @@ -68,6 +69,7 @@ public Value(ValueSource source) { * * @return value as string */ + @NonNull public String asString() { return this.value; } @@ -79,6 +81,7 @@ public String asString() { * * @return value as boolean */ + @NonNull public boolean asBoolean() { if (source == ValueSource.STATIC) { return DEFAULT_VALUE_FOR_BOOLEAN; @@ -91,6 +94,7 @@ public boolean asBoolean() { * * @return value as long */ + @NonNull public long asLong() { if (source == ValueSource.STATIC) { return DEFAULT_VALUE_FOR_LONG; @@ -108,6 +112,7 @@ public long asLong() { * * @return value as double */ + @NonNull public double asDouble() { if (source == ValueSource.STATIC) { return DEFAULT_VALUE_FOR_DOUBLE; @@ -125,6 +130,7 @@ public double asDouble() { * * @return source. */ + @NonNull public ValueSource getSource() { return source; } diff --git a/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java deleted file mode 100644 index 6db7ef5ef..000000000 --- a/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed 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 com.google.firebase.remoteconfig.internal; - -import com.google.api.client.util.Key; -import com.google.firebase.remoteconfig.internal.TemplateResponse.ConditionResponse; -import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterGroupResponse; -import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterResponse; -import com.google.firebase.remoteconfig.internal.TemplateResponse.VersionResponse; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.locks.Condition; - -/** - * The Data Transfer Object for parsing Remote Config server template responses from the - * Remote Config service. - **/ -public class ServerTemplateResponse { - - @Key("parameters") - private Map parameters; - - @Key("conditions") - private Map conditions; - - @Key("version") - private VersionResponse version; - - // For local JSON serialization and deserialization purposes only. - // ETag in response type is never set by the HTTP response. - @Key("etag") - private String etag; - -} From b973fd8658ac402eca5140f0ede2652a8d295806 Mon Sep 17 00:00:00 2001 From: Athira M Date: Mon, 2 Dec 2024 18:40:21 +0530 Subject: [PATCH 03/78] Add nullable and non-nullable annotations --- .../firebase/remoteconfig/AndCondition.java | 6 ++- .../remoteconfig/ConditionEvaluator.java | 13 ++--- .../remoteconfig/CustomSignalCondition.java | 6 ++- .../remoteconfig/CustomSignalOperator.java | 5 +- .../remoteconfig/MicroPercentRange.java | 6 ++- .../firebase/remoteconfig/OneOfCondition.java | 17 +++++-- .../firebase/remoteconfig/OrCondition.java | 4 +- .../remoteconfig/PercentCondition.java | 11 ++++- .../PercentConditionOperator.java | 5 +- .../firebase/remoteconfig/ServerConfig.java | 14 ++++-- .../firebase/remoteconfig/ServerTemplate.java | 3 +- .../remoteconfig/ServerTemplateData.java | 7 ++- .../google/firebase/remoteconfig/Value.java | 10 +++- .../internal/ServerTemplateResponse.java | 49 ------------------- 14 files changed, 78 insertions(+), 78 deletions(-) delete mode 100644 src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java diff --git a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java index 6e1622f5c..47c6daa7d 100644 --- a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java @@ -16,6 +16,7 @@ package com.google.firebase.remoteconfig; import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; /** * Represents a collection of conditions that evaluate to true if all are true. @@ -24,9 +25,9 @@ public class AndCondition { private final ImmutableList conditions; /** - * Creates AndCondition joining subconditions. + * Creates a new {@link AndCondition} joining subconditions. */ - public AndCondition(ImmutableList conditions) { + public AndCondition(@NonNull ImmutableList conditions) { this.conditions = conditions; } @@ -35,6 +36,7 @@ public AndCondition(ImmutableList conditions) { * * @return List of conditions to evaluate. */ + @NonNull public ImmutableList getConditions() { return conditions; } diff --git a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java index f44de24fd..822daf727 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java +++ b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.firebase.internal.NonNull; /** * Encapsulates condition evaluation logic to simplify organization and @@ -31,8 +32,9 @@ public class ConditionEvaluator { * user provided or predefined. * @return Evaluated conditions represented as map of condition name to boolean. */ - public ImmutableMap evaluateConditions(ImmutableMap conditions, - ImmutableMap context) { + @NonNull + public ImmutableMap evaluateConditions(@NonNull ImmutableMap conditions, + @NonNull ImmutableMap context) { ImmutableMap.Builder evaluatedConditions = ImmutableMap.builder(); int nestingLevel = 0; @@ -96,10 +98,6 @@ private boolean evaluatePercentCondition(PercentCondition condition, ImmutableMa } PercentConditionOperator operator = condition.getPercentConditionOperator(); - if (operator == null) { - logger.warn("Percentage operator is not set."); - return false; - } // The micro-percent interval to be used with the BETWEEN operator. MicroPercentRange microPercentRange = condition.getMicroPercentRange(); @@ -130,8 +128,7 @@ private boolean evaluateCustomSignalCondition(CustomSignalCondition condition, String customSignalKey = condition.getCustomSignalKey(); ImmutableList targetCustomSignalValues = condition.getTargetCustomSignalValues(); - if (customSignalOperator == null || customSignalKey == null || targetCustomSignalValues == null - || targetCustomSignalValues.isEmpty()) { + if (targetCustomSignalValues.isEmpty()) { logger.warn( String.format("Values must be assigned to all custom signal fields. Operator:%s, Key:%s, Values:%s", customSignalOperator, customSignalKey, targetCustomSignalValues)); diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java index fb2984eac..d418cb483 100644 --- a/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java @@ -16,6 +16,7 @@ package com.google.firebase.remoteconfig; import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; /** * Represents a condition that compares provided signals against a target value. @@ -32,7 +33,7 @@ public class CustomSignalCondition { * @param customSignalOperator * @param targetCustomSignalValues */ - public CustomSignalCondition(String customSignalKey, CustomSignalOperator customSignalOperator, ImmutableList targetCustomSignalValues){ + public CustomSignalCondition(@NonNull String customSignalKey, @NonNull CustomSignalOperator customSignalOperator, @NonNull ImmutableList targetCustomSignalValues){ this.customSignalKey = customSignalKey; this.customSignalOperator = customSignalOperator; this.targetCustomSignalValues = targetCustomSignalValues; @@ -44,6 +45,7 @@ public CustomSignalCondition(String customSignalKey, CustomSignalOperator custom * * @return Custom signal key. */ + @NonNull public String getCustomSignalKey() { return customSignalKey; } @@ -55,6 +57,7 @@ public String getCustomSignalKey() { * * @return Custom signal operator. */ + @NonNull public CustomSignalOperator getCustomSignalOperator() { return customSignalOperator; } @@ -66,6 +69,7 @@ public CustomSignalOperator getCustomSignalOperator() { * * @return List of target values. */ + @NonNull public ImmutableList getTargetCustomSignalValues() { return targetCustomSignalValues; } diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java index 49da72714..fc7890127 100644 --- a/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java @@ -15,6 +15,8 @@ */ package com.google.firebase.remoteconfig; +import com.google.firebase.internal.NonNull; + /** * Defines supported operators for custom signal conditions. */ @@ -39,7 +41,7 @@ public enum CustomSignalOperator { private final String operator; - CustomSignalOperator(String operator) { + CustomSignalOperator(@NonNull String operator) { this.operator = operator; } @@ -48,6 +50,7 @@ public enum CustomSignalOperator { * * @return operator. */ + @NonNull public String getOperator() { return operator; } diff --git a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java index 3f889439a..ece4b5073 100644 --- a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java +++ b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java @@ -15,6 +15,8 @@ */ package com.google.firebase.remoteconfig; +import com.google.firebase.internal.NonNull; + /** * Represents the limit of percentiles to target in micro-percents. The value * must be in the range [0 and 100000000]. @@ -23,7 +25,7 @@ public class MicroPercentRange { private final int microPercentLowerBound; private final int microPercentUpperBound; - public MicroPercentRange(int microPercentLowerBound, int microPercentUpperBound){ + public MicroPercentRange(@NonNull int microPercentLowerBound, @NonNull int microPercentUpperBound){ this.microPercentLowerBound = microPercentLowerBound; this.microPercentUpperBound = microPercentUpperBound; } @@ -35,6 +37,7 @@ public MicroPercentRange(int microPercentLowerBound, int microPercentUpperBound) * * @return micropercentile lower bound. */ + @NonNull public int getMicroPercentLowerBound() { return microPercentLowerBound; } @@ -46,6 +49,7 @@ public int getMicroPercentLowerBound() { * * @return micropercentile upper bound. */ + @NonNull public int getMicroPercentUpperBound() { return microPercentUpperBound; } diff --git a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java index 84494c141..9b4b009e4 100644 --- a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java @@ -15,6 +15,9 @@ */ package com.google.firebase.remoteconfig; +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; + /** * Represents a condition that may be one of several types. Only the first * defined field will be processed. @@ -32,6 +35,7 @@ public class OneOfCondition { * * @return list of conditions. */ + @Nullable public OrCondition getOrCondition() { return orCondition; } @@ -41,6 +45,7 @@ public OrCondition getOrCondition() { * * @return list of conditions. */ + @Nullable public AndCondition getAndCondition() { return andCondition; } @@ -50,6 +55,7 @@ public AndCondition getAndCondition() { * * @return true. */ + @Nullable public String isTrue() { return trueValue; } @@ -59,6 +65,7 @@ public String isTrue() { * * @return false. */ + @Nullable public String isFalse() { return falseValue; } @@ -68,6 +75,7 @@ public String isFalse() { * * @return percent condition. */ + @Nullable public PercentCondition getPercent() { return percent; } @@ -77,6 +85,7 @@ public PercentCondition getPercent() { * * @return custom condition. */ + @Nullable public CustomSignalCondition getCustomSignal() { return customSignal; } @@ -86,7 +95,7 @@ public CustomSignalCondition getCustomSignal() { * * @param orCondition */ - public void setOrCondition(OrCondition orCondition) { + public void setOrCondition(@NonNull OrCondition orCondition) { this.orCondition = orCondition; } @@ -95,7 +104,7 @@ public void setOrCondition(OrCondition orCondition) { * * @param andCondition */ - public void setAndCondition(AndCondition andCondition) { + public void setAndCondition(@NonNull AndCondition andCondition) { this.andCondition = andCondition; } @@ -104,7 +113,7 @@ public void setAndCondition(AndCondition andCondition) { * * @param percent */ - public void setPercent(PercentCondition percent) { + public void setPercent(@NonNull PercentCondition percent) { this.percent = percent; } @@ -113,7 +122,7 @@ public void setPercent(PercentCondition percent) { * * @param customSignal */ - public void setCustomSignal(CustomSignalCondition customSignal) { + public void setCustomSignal(@NonNull CustomSignalCondition customSignal) { this.customSignal = customSignal; } diff --git a/src/main/java/com/google/firebase/remoteconfig/OrCondition.java b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java index 7fedd4297..0c5c4956f 100644 --- a/src/main/java/com/google/firebase/remoteconfig/OrCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java @@ -16,6 +16,7 @@ package com.google.firebase.remoteconfig; import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; /** * Represents a collection of conditions that evaluate to true if one of them is true. @@ -26,7 +27,7 @@ public class OrCondition { /** * Creates OrCondition joining subconditions. */ - public OrCondition(ImmutableList conditions) { + public OrCondition(@NonNull ImmutableList conditions) { this.conditions = conditions; } @@ -35,6 +36,7 @@ public OrCondition(ImmutableList conditions) { * * @return List of conditions to evaluate. */ + @NonNull public ImmutableList getConditions() { return conditions; } diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java index ea31c69ef..f63524551 100644 --- a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java @@ -15,6 +15,9 @@ */ package com.google.firebase.remoteconfig; +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; + /** * Represents a condition that compares the instance pseudo-random percentile to * a given limit. @@ -32,7 +35,7 @@ public class PercentCondition { * @param percentConditionOperator * @param seed */ - public PercentCondition(int microPercent, PercentConditionOperator percentConditionOperator, String seed) { + public PercentCondition(@NonNull int microPercent, @NonNull PercentConditionOperator percentConditionOperator, @NonNull String seed) { this.microPercent = microPercent; this.percentConditionOperator = percentConditionOperator; this.seed = seed; @@ -46,7 +49,7 @@ public PercentCondition(int microPercent, PercentConditionOperator percentCondit * @param percentConditionOperator * @param seed */ - public PercentCondition(MicroPercentRange microPercentRange, PercentConditionOperator percentConditionOperator, + public PercentCondition(@NonNull MicroPercentRange microPercentRange, @NonNull PercentConditionOperator percentConditionOperator, String seed) { this.microPercentRange = microPercentRange; this.percentConditionOperator = percentConditionOperator; @@ -60,6 +63,7 @@ public PercentCondition(MicroPercentRange microPercentRange, PercentConditionOpe * * @return micro percent. */ + @Nullable public int getMicroPercent() { return microPercent; } @@ -69,6 +73,7 @@ public int getMicroPercent() { * * @return micro percent range. */ + @Nullable public MicroPercentRange getMicroPercentRange() { return microPercentRange; } @@ -79,6 +84,7 @@ public MicroPercentRange getMicroPercentRange() { * * @return operator. */ + @NonNull public PercentConditionOperator getPercentConditionOperator() { return percentConditionOperator; } @@ -90,6 +96,7 @@ public PercentConditionOperator getPercentConditionOperator() { * * @return seed. */ + @NonNull public String getSeed() { return seed; } diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java index 115da0a20..27b15bca1 100644 --- a/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java +++ b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java @@ -15,6 +15,8 @@ */ package com.google.firebase.remoteconfig; +import com.google.firebase.internal.NonNull; + /** * Defines supported operators for percent conditions. */ @@ -31,7 +33,7 @@ public enum PercentConditionOperator { * * @param operator */ - PercentConditionOperator(String operator) { + PercentConditionOperator(@NonNull String operator) { this.operator = operator; } @@ -40,6 +42,7 @@ public enum PercentConditionOperator { * * @return operator. */ + @NonNull public String getOperator() { return operator; } diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java index fe349cbd2..49582c148 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java @@ -18,6 +18,8 @@ import java.util.Map; +import com.google.firebase.internal.NonNull; + /** * Represents the configuration produced by evaluating a server template. */ @@ -35,7 +37,8 @@ public class ServerConfig { * @param key * @return config value for the given key as string. */ - public String getString(String key) { + @NonNull + public String getString(@NonNull String key) { return configValues.get(key).asString(); } @@ -46,7 +49,8 @@ public String getString(String key) { * @param key * @return config value for the given key as boolean. */ - public boolean getBoolean(String key) { + @NonNull + public boolean getBoolean(@NonNull String key) { return Boolean.parseBoolean(getString(key)); } @@ -57,7 +61,8 @@ public boolean getBoolean(String key) { * @param key * @return config value for the given key as long. */ - public long getLong(String key) { + @NonNull + public long getLong(@NonNull String key) { return Long.parseLong(getString(key)); } @@ -68,7 +73,8 @@ public long getLong(String key) { * @param key * @return config value for the given key as double. */ - public double getDouble(String key) { + @NonNull + public double getDouble(@NonNull String key) { return Double.parseDouble(getString(key)); } diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java index 3af6edd26..8582018ac 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java @@ -8,6 +8,7 @@ import com.google.common.collect.ImmutableMap; import com.google.firebase.ErrorCode; +import com.google.firebase.internal.NonNull; import com.google.firebase.internal.Nullable; import com.google.firebase.remoteconfig.Value.ValueSource; @@ -26,7 +27,7 @@ public class ServerTemplate { * @param conditionEvaluator * @param defaultConfig */ - public ServerTemplate(ConditionEvaluator conditionEvaluator, ImmutableMap defaultConfig) { + public ServerTemplate(@NonNull ConditionEvaluator conditionEvaluator, @NonNull ImmutableMap defaultConfig) { for (String configName : defaultConfig.keySet()) { stringifiedDefaultConfig.put(configName, defaultConfig.get(configName)); } diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java index 6945534ee..f61a965c7 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java @@ -36,7 +36,7 @@ public class ServerTemplateData { * * @param etag The ETag of this template. */ - public ServerTemplateData(String etag) { + public ServerTemplateData(@NonNull String etag) { this.parameters = new HashMap<>(); this.conditions = new HashMap<>(); this.etag = etag; @@ -76,6 +76,7 @@ public Map getConditions() { * * @return The ETag of the server template data. */ + @NonNull public String getETag() { return this.etag; } @@ -85,6 +86,7 @@ public String getETag() { * * @return The version information of the template. */ + @NonNull public Version getVersion() { return version; } @@ -97,6 +99,7 @@ public Version getVersion() { * optional conditional values. * @return This {@link ServerTemplateData} instance. */ + @NonNull public ServerTemplateData setParameters( @NonNull Map parameters) { checkNotNull(parameters, "parameters must not be null."); @@ -110,6 +113,7 @@ public ServerTemplateData setParameters( * @param conditions A non-null map of conditions. * @return This {@link ServerTemplateData} instance. */ + @NonNull public ServerTemplateData setConditions( @NonNull Map conditions) { checkNotNull(conditions, "conditions must not be null."); @@ -124,6 +128,7 @@ public ServerTemplateData setConditions( * @param version A {@link Version} instance. * @return This {@link Template} instance. */ + @NonNull public ServerTemplateData setVersion(Version version) { this.version = version; return this; diff --git a/src/main/java/com/google/firebase/remoteconfig/Value.java b/src/main/java/com/google/firebase/remoteconfig/Value.java index d97cb129e..61552099d 100644 --- a/src/main/java/com/google/firebase/remoteconfig/Value.java +++ b/src/main/java/com/google/firebase/remoteconfig/Value.java @@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; /** * Wraps a parameter value with metadata and type-safe getters. Type-safe @@ -49,7 +50,7 @@ public enum ValueSource { * @param source * @param value */ - public Value(ValueSource source, String value) { + public Value(@NonNull ValueSource source, @NonNull String value) { this.source = source; this.value = value; } @@ -59,7 +60,7 @@ public Value(ValueSource source, String value) { * * @param source */ - public Value(ValueSource source) { + public Value(@NonNull ValueSource source) { this(source, DEFAULT_VALUE_FOR_STRING); } @@ -68,6 +69,7 @@ public Value(ValueSource source) { * * @return value as string */ + @NonNull public String asString() { return this.value; } @@ -79,6 +81,7 @@ public String asString() { * * @return value as boolean */ + @NonNull public boolean asBoolean() { if (source == ValueSource.STATIC) { return DEFAULT_VALUE_FOR_BOOLEAN; @@ -91,6 +94,7 @@ public boolean asBoolean() { * * @return value as long */ + @NonNull public long asLong() { if (source == ValueSource.STATIC) { return DEFAULT_VALUE_FOR_LONG; @@ -108,6 +112,7 @@ public long asLong() { * * @return value as double */ + @NonNull public double asDouble() { if (source == ValueSource.STATIC) { return DEFAULT_VALUE_FOR_DOUBLE; @@ -125,6 +130,7 @@ public double asDouble() { * * @return source. */ + @NonNull public ValueSource getSource() { return source; } diff --git a/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java deleted file mode 100644 index 6db7ef5ef..000000000 --- a/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed 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 com.google.firebase.remoteconfig.internal; - -import com.google.api.client.util.Key; -import com.google.firebase.remoteconfig.internal.TemplateResponse.ConditionResponse; -import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterGroupResponse; -import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterResponse; -import com.google.firebase.remoteconfig.internal.TemplateResponse.VersionResponse; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.locks.Condition; - -/** - * The Data Transfer Object for parsing Remote Config server template responses from the - * Remote Config service. - **/ -public class ServerTemplateResponse { - - @Key("parameters") - private Map parameters; - - @Key("conditions") - private Map conditions; - - @Key("version") - private VersionResponse version; - - // For local JSON serialization and deserialization purposes only. - // ETag in response type is never set by the HTTP response. - @Key("etag") - private String etag; - -} From 4cec778f8d8313d49a609d7e751d53466a8bdfc3 Mon Sep 17 00:00:00 2001 From: Athira M Date: Tue, 3 Dec 2024 18:06:15 +0530 Subject: [PATCH 04/78] Fix build failures due to checkstyle errors. --- .../firebase/remoteconfig/AndCondition.java | 33 +- .../remoteconfig/ConditionEvaluator.java | 568 ++++++++++-------- .../remoteconfig/CustomSignalCondition.java | 96 +-- .../remoteconfig/CustomSignalOperator.java | 67 ++- .../remoteconfig/MicroPercentRange.java | 56 +- .../firebase/remoteconfig/OneOfCondition.java | 211 +++---- .../firebase/remoteconfig/OrCondition.java | 36 +- .../remoteconfig/PercentCondition.java | 154 ++--- .../PercentConditionOperator.java | 4 +- .../firebase/remoteconfig/ServerConfig.java | 100 +-- .../firebase/remoteconfig/ServerTemplate.java | 211 ++++--- .../remoteconfig/ServerTemplateData.java | 203 +++---- .../google/firebase/remoteconfig/Value.java | 17 +- 13 files changed, 921 insertions(+), 835 deletions(-) diff --git a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java index 47c6daa7d..0246bf720 100644 --- a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.google.firebase.remoteconfig; import com.google.common.collect.ImmutableList; @@ -22,22 +23,22 @@ * Represents a collection of conditions that evaluate to true if all are true. */ public class AndCondition { - private final ImmutableList conditions; + private final ImmutableList conditions; - /** - * Creates a new {@link AndCondition} joining subconditions. - */ - public AndCondition(@NonNull ImmutableList conditions) { - this.conditions = conditions; - } + /** + * Creates a new {@link AndCondition} joining subconditions. + */ + public AndCondition(@NonNull ImmutableList conditions) { + this.conditions = conditions; + } - /** - * Gets the list of {@link OneOfCondition} - * - * @return List of conditions to evaluate. - */ - @NonNull - public ImmutableList getConditions() { - return conditions; - } + /** + * Gets the list of {@link OneOfCondition} + * + * @return List of conditions to evaluate. + */ + @NonNull + public ImmutableList getConditions() { + return conditions; + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java index 822daf727..072e1d377 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java +++ b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java @@ -1,5 +1,25 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.firebase.internal.NonNull; + import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -14,300 +34,320 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.firebase.internal.NonNull; - /** * Encapsulates condition evaluation logic to simplify organization and * facilitate testing. */ public class ConditionEvaluator { - private static final int MAX_CONDITION_RECURSION_DEPTH = 10; - private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class); - - /** - * @param conditions A named condition map of string to {@link OneOfCondition}. - * @param context Represents template evaluation input signals which can be - * user provided or predefined. - * @return Evaluated conditions represented as map of condition name to boolean. - */ - @NonNull - public ImmutableMap evaluateConditions(@NonNull ImmutableMap conditions, - @NonNull ImmutableMap context) { - ImmutableMap.Builder evaluatedConditions = ImmutableMap.builder(); - int nestingLevel = 0; - - for (ImmutableMap.Entry condition : conditions.entrySet()) { - evaluatedConditions.put(condition.getKey(), evaluateCondition(condition.getValue(), context, nestingLevel)); - } - - return evaluatedConditions.build(); + private static final int MAX_CONDITION_RECURSION_DEPTH = 10; + private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class); + + /** + * @param conditions A named condition map of string to {@link OneOfCondition}. + * @param context Represents template evaluation input signals which can be + * user provided or predefined. + * @return Evaluated conditions represented as map of condition name to boolean. + */ + @NonNull + public ImmutableMap evaluateConditions( + @NonNull ImmutableMap conditions, + @NonNull ImmutableMap context) { + ImmutableMap.Builder evaluatedConditions = ImmutableMap.builder(); + int nestingLevel = 0; + + for (ImmutableMap.Entry condition : conditions.entrySet()) { + evaluatedConditions.put(condition.getKey(), evaluateCondition(condition.getValue(), + context, nestingLevel)); } - private boolean evaluateCondition(OneOfCondition condition, ImmutableMap context, - int nestingLevel) { - if (nestingLevel > MAX_CONDITION_RECURSION_DEPTH) { - logger.warn("Maximum condition recursion depth exceeded."); - return false; - } + return evaluatedConditions.build(); + } - if (condition.getOrCondition() != null) { - return evaluateOrCondition(condition.getOrCondition(), context, nestingLevel + 1); - } else if (condition.getAndCondition() != null) { - return evaluateAndCondition(condition.getAndCondition(), context, nestingLevel + 1); - } else if (condition.isTrue() != null) { - return true; - } else if (condition.isFalse() != null) { - return false; - } else if (condition.getPercent() != null) { - return evaluatePercentCondition(condition.getPercent(), context); - } else if (condition.getCustomSignal() != null) { - return evaluateCustomSignalCondition(condition.getCustomSignal(), context); - } - return false; + private boolean evaluateCondition(OneOfCondition condition, ImmutableMap context, + int nestingLevel) { + if (nestingLevel > MAX_CONDITION_RECURSION_DEPTH) { + logger.warn("Maximum condition recursion depth exceeded."); + return false; } - private boolean evaluateOrCondition(OrCondition condition, ImmutableMap context, int nestingLevel) { - ImmutableList subConditions = condition.getConditions(); - for (OneOfCondition subCondition : subConditions) { - // Short-circuit the evaluation result for true. - if (evaluateCondition(subCondition, context, nestingLevel + 1)) { - return true; - } - } - return false; + if (condition.getOrCondition() != null) { + return evaluateOrCondition(condition.getOrCondition(), context, nestingLevel + 1); + } else if (condition.getAndCondition() != null) { + return evaluateAndCondition(condition.getAndCondition(), context, nestingLevel + 1); + } else if (condition.isTrue() != null) { + return true; + } else if (condition.isFalse() != null) { + return false; + } else if (condition.getPercent() != null) { + return evaluatePercentCondition(condition.getPercent(), context); + } else if (condition.getCustomSignal() != null) { + return evaluateCustomSignalCondition(condition.getCustomSignal(), context); } - - private boolean evaluateAndCondition(AndCondition condition, ImmutableMap context, - int nestingLevel) { - ImmutableList subConditions = condition.getConditions(); - for (OneOfCondition subCondition : subConditions) { - // Short-circuit the evaluation result for false. - if (!evaluateCondition(subCondition, context, nestingLevel + 1)) { - return false; - } - } + return false; + } + + private boolean evaluateOrCondition(OrCondition condition, ImmutableMap context, + int nestingLevel) { + ImmutableList subConditions = condition.getConditions(); + for (OneOfCondition subCondition : subConditions) { + // Short-circuit the evaluation result for true. + if (evaluateCondition(subCondition, context, nestingLevel + 1)) { return true; + } + } + return false; + } + + private boolean evaluateAndCondition(AndCondition condition, + ImmutableMap context, + int nestingLevel) { + ImmutableList subConditions = condition.getConditions(); + for (OneOfCondition subCondition : subConditions) { + // Short-circuit the evaluation result for false. + if (!evaluateCondition(subCondition, context, nestingLevel + 1)) { + return false; + } + } + return true; + } + + private boolean evaluatePercentCondition(PercentCondition condition, + ImmutableMap context) { + if (!context.containsKey("randomizationId")) { + logger.warn("Percentage operation cannot be performed without randomizationId"); + return false; } - private boolean evaluatePercentCondition(PercentCondition condition, ImmutableMap context) { - if (!context.containsKey("randomizationId")) { - logger.warn("Percentage operation cannot be performed without randomizationId"); - return false; - } - - PercentConditionOperator operator = condition.getPercentConditionOperator(); - - // The micro-percent interval to be used with the BETWEEN operator. - MicroPercentRange microPercentRange = condition.getMicroPercentRange(); - int microPercentUpperBound = microPercentRange != null ? microPercentRange.getMicroPercentUpperBound() : 0; - int microPercentLowerBound = microPercentRange != null ? microPercentRange.getMicroPercentLowerBound() : 0; - // The limit of percentiles to target in micro-percents when using the - // LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0 - // and 100000000]. - int microPercent = condition.getMicroPercent(); - BigInteger microPercentile = getMicroPercentile(condition.getSeed(), context.get("randomizationId")); - switch (operator) { - case LESS_OR_EQUAL: - return microPercentile.compareTo(BigInteger.valueOf(microPercent)) <= 0; - case GREATER_THAN: - return microPercentile.compareTo(BigInteger.valueOf(microPercent)) > 0; - case BETWEEN: - return microPercentile.compareTo(BigInteger.valueOf(microPercentLowerBound)) > 0 - && microPercentile.compareTo(BigInteger.valueOf(microPercentUpperBound)) <= 0; - case UNSPECIFIED: - default: - return false; - } + PercentConditionOperator operator = condition.getPercentConditionOperator(); + + // The micro-percent interval to be used with the BETWEEN operator. + MicroPercentRange microPercentRange = condition.getMicroPercentRange(); + int microPercentUpperBound = microPercentRange != null + ? microPercentRange.getMicroPercentUpperBound() + : 0; + int microPercentLowerBound = microPercentRange != null + ? microPercentRange.getMicroPercentLowerBound() + : 0; + // The limit of percentiles to target in micro-percents when using the + // LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0 + // and 100000000]. + int microPercent = condition.getMicroPercent(); + BigInteger microPercentile = getMicroPercentile(condition.getSeed(), + context.get("randomizationId")); + switch (operator) { + case LESS_OR_EQUAL: + return microPercentile.compareTo(BigInteger.valueOf(microPercent)) <= 0; + case GREATER_THAN: + return microPercentile.compareTo(BigInteger.valueOf(microPercent)) > 0; + case BETWEEN: + return microPercentile.compareTo(BigInteger.valueOf(microPercentLowerBound)) > 0 + && microPercentile.compareTo(BigInteger.valueOf(microPercentUpperBound)) <= 0; + case UNSPECIFIED: + default: + return false; + } + } + + private boolean evaluateCustomSignalCondition(CustomSignalCondition condition, + ImmutableMap context) { + CustomSignalOperator customSignalOperator = condition.getCustomSignalOperator(); + String customSignalKey = condition.getCustomSignalKey(); + ImmutableList targetCustomSignalValues = condition.getTargetCustomSignalValues(); + + if (targetCustomSignalValues.isEmpty()) { + logger.warn(String.format( + "Values must be assigned to all custom signal fields. Operator:%s, Key:%s, Values:%s", + customSignalOperator, customSignalKey, targetCustomSignalValues)); + return false; } - private boolean evaluateCustomSignalCondition(CustomSignalCondition condition, - ImmutableMap context) { - CustomSignalOperator customSignalOperator = condition.getCustomSignalOperator(); - String customSignalKey = condition.getCustomSignalKey(); - ImmutableList targetCustomSignalValues = condition.getTargetCustomSignalValues(); + Object customSignalValue = context.get(customSignalKey); + if (customSignalValue == null) { + return false; + } - if (targetCustomSignalValues.isEmpty()) { - logger.warn( - String.format("Values must be assigned to all custom signal fields. Operator:%s, Key:%s, Values:%s", - customSignalOperator, customSignalKey, targetCustomSignalValues)); - return false; + switch (customSignalOperator) { + case STRING_CONTAINS: + case STRING_DOES_NOT_CONTAIN: + case STRING_EXACTLY_MATCHES: + case STRING_CONTAINS_REGEX: + return compareStrings(targetCustomSignalValues, customSignalValue, customSignalOperator); + case NUMERIC_LESS_THAN: + case NUMERIC_LESS_EQUAL: + case NUMERIC_EQUAL: + case NUMERIC_NOT_EQUAL: + case NUMERIC_GREATER_THAN: + case NUMERIC_GREATER_EQUAL: + return compareNumbers(targetCustomSignalValues, customSignalValue, customSignalOperator); + case SEMANTIC_VERSION_EQUAL: + case SEMANTIC_VERSION_GREATER_EQUAL: + case SEMANTIC_VERSION_GREATER_THAN: + case SEMANTIC_VERSION_LESS_EQUAL: + case SEMANTIC_VERSION_LESS_THAN: + case SEMANTIC_VERSION_NOT_EQUAL: + return compareSemanticVersions(targetCustomSignalValues, customSignalValue, + customSignalOperator); + default: + return false; + } + } + + private BigInteger getMicroPercentile(String seed, Object randomizationId) { + String seedPrefix = seed != null && !seed.isEmpty() ? seed + "." : ""; + String stringToHash = seedPrefix + randomizationId; + BigInteger hash = hashSeededRandomizationId(stringToHash); + BigInteger modValue = new BigInteger(Integer.toString(100 * 1_000_000)); + BigInteger microPercentile = hash.mod(modValue); + + return microPercentile; + } + + private BigInteger hashSeededRandomizationId(String seededRandomizationId) { + try { + // Create a SHA-256 hash. + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hashBytes = digest.digest(seededRandomizationId.getBytes(StandardCharsets.UTF_8)); + + // Convert the hash bytes to a hexadecimal string. + StringBuilder hexString = new StringBuilder(); + for (byte b : hashBytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); } + hexString.append(hex); + } - Object customSignalValue = context.get(customSignalKey); - if (customSignalValue == null) { - return false; - } + // Convert the hexadecimal string to a BigInteger + return new BigInteger(hexString.toString(), 16); - switch (customSignalOperator) { - case STRING_CONTAINS: - case STRING_DOES_NOT_CONTAIN: - case STRING_EXACTLY_MATCHES: - case STRING_CONTAINS_REGEX: - return compareStrings(targetCustomSignalValues, customSignalValue, customSignalOperator); - case NUMERIC_LESS_THAN: - case NUMERIC_LESS_EQUAL: - case NUMERIC_EQUAL: - case NUMERIC_NOT_EQUAL: - case NUMERIC_GREATER_THAN: - case NUMERIC_GREATER_EQUAL: - return compareNumbers(targetCustomSignalValues, customSignalValue, customSignalOperator); - case SEMANTIC_VERSION_EQUAL: - case SEMANTIC_VERSION_GREATER_EQUAL: - case SEMANTIC_VERSION_GREATER_THAN: - case SEMANTIC_VERSION_LESS_EQUAL: - case SEMANTIC_VERSION_LESS_THAN: - case SEMANTIC_VERSION_NOT_EQUAL: - return compareSemanticVersions(targetCustomSignalValues, customSignalValue, customSignalOperator); - default: - return false; - } + } catch (NoSuchAlgorithmException e) { + logger.error("SHA-256 algorithm not found", e); + throw new RuntimeException("SHA-256 algorithm not found", e); } + } - private BigInteger getMicroPercentile(String seed, Object randomizationId) { - String seedPrefix = seed != null && !seed.isEmpty() ? seed + "." : ""; - String stringToHash = seedPrefix + randomizationId; - BigInteger hash = hashSeededRandomizationId(stringToHash); - BigInteger modValue = new BigInteger(Integer.toString(100 * 1_000_000)); - BigInteger microPercentile = hash.mod(modValue); + private boolean compareStrings(ImmutableList targetValues, Object customSignalValue, + CustomSignalOperator operator) { + String value = customSignalValue.toString(); - return microPercentile; + for (String target : targetValues) { + if (operator == CustomSignalOperator.STRING_CONTAINS && value.contains(target)) { + return true; + } else if (operator == CustomSignalOperator.STRING_DOES_NOT_CONTAIN + && !value.contains(target)) { + return true; + } else if (operator == CustomSignalOperator.STRING_EXACTLY_MATCHES && value.equals(target)) { + return true; + } else if (operator == CustomSignalOperator.STRING_CONTAINS_REGEX + && Pattern.compile(value).matcher(target).find()) { + return true; + } } - - private BigInteger hashSeededRandomizationId(String seededRandomizationId) { - try { - // Create a SHA-256 hash. - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] hashBytes = digest.digest(seededRandomizationId.getBytes(StandardCharsets.UTF_8)); - - // Convert the hash bytes to a hexadecimal string. - StringBuilder hexString = new StringBuilder(); - for (byte b : hashBytes) { - String hex = Integer.toHexString(0xff & b); - if (hex.length() == 1) - hexString.append('0'); - hexString.append(hex); - } - - // Convert the hexadecimal string to a BigInteger - return new BigInteger(hexString.toString(), 16); - - } catch (NoSuchAlgorithmException e) { - logger.error("SHA-256 algorithm not found", e); - throw new RuntimeException("SHA-256 algorithm not found", e); - } + return false; + } + + private boolean compareNumbers(ImmutableList targetValues, Object customSignalValue, + CustomSignalOperator operator) { + if (targetValues.size() != 1) { + logger.warn(String.format("Target values must contain 1 element for operation %s", operator)); + return false; } - private boolean compareStrings(ImmutableList targetValues, Object customSignalValue, - CustomSignalOperator operator) { - String value = customSignalValue.toString(); - - for (String target : targetValues) { - if (operator == CustomSignalOperator.STRING_CONTAINS && value.contains(target)) { - return true; - } else if (operator == CustomSignalOperator.STRING_DOES_NOT_CONTAIN && !value.contains(target)) { - return true; - } else if (operator == CustomSignalOperator.STRING_EXACTLY_MATCHES && value.equals(target)) { - return true; - } else if (operator == CustomSignalOperator.STRING_CONTAINS_REGEX - && Pattern.compile(value).matcher(target).find()) { - return true; - } - } + double value = (Double) customSignalValue; + double target = Double.parseDouble(targetValues.get(0)); + + switch (operator) { + case NUMERIC_EQUAL: + return value == target; + case NUMERIC_GREATER_THAN: + return value > target; + case NUMERIC_GREATER_EQUAL: + return value >= target; + case NUMERIC_LESS_EQUAL: + return value <= target; + case NUMERIC_LESS_THAN: + return value < target; + case NUMERIC_NOT_EQUAL: + return value != target; + default: return false; } - - private boolean compareNumbers(ImmutableList targetValues, Object customSignalValue, - CustomSignalOperator operator) { - if (targetValues.size() != 1) { - logger.warn(String.format("Target values must contain 1 element for operation %s", operator)); - return false; - } - - double value = (Double) customSignalValue; - double target = Double.parseDouble(targetValues.get(0)); - - switch (operator) { - case NUMERIC_EQUAL: - return value == target; - case NUMERIC_GREATER_THAN: - return value > target; - case NUMERIC_GREATER_EQUAL: - return value >= target; - case NUMERIC_LESS_EQUAL: - return value <= target; - case NUMERIC_LESS_THAN: - return value < target; - case NUMERIC_NOT_EQUAL: - return value != target; - default: - return false; - } + } + + private boolean compareSemanticVersions(ImmutableList targetValues, + Object customSignalValue, CustomSignalOperator operator) throws RuntimeErrorException { + if (targetValues.size() != 1) { + logger.warn(String.format("Target values must contain 1 element for operation %s", + operator)); + return false; } - private boolean compareSemanticVersions(ImmutableList targetValues, Object customSignalValue, - CustomSignalOperator operator) throws RuntimeErrorException { - if (targetValues.size() != 1) { - logger.warn(String.format("Target values must contain 1 element for operation %s", operator)); + // Max number of segments a numeric version can have. This is enforced by the + // server as well. + int maxLength = 5; + + List version1 = Arrays.stream(customSignalValue.toString().split("\\.")) + .map(s -> Integer.valueOf(s)).collect(Collectors.toList()); + List version2 = Arrays.stream(targetValues.get(0).split("\\.")) + .map(s -> Integer.valueOf(s)) + .collect(Collectors.toList()); + for (int i = 0; i < maxLength; i++) { + // Check to see if segments are present. + Boolean version1HasSegment = version1.get(i) != null; + Boolean version2HasSegment = version2.get(i) != null; + + // If both are undefined, we've consumed everything and they're equal. + if (!version1HasSegment && !version2HasSegment) { + return operator == CustomSignalOperator.SEMANTIC_VERSION_EQUAL; + } + + // Insert zeros if undefined for easier comparison. + if (!version1HasSegment) { + version1.add(i, 0); + } + if (!version2HasSegment) { + version2.add(i, 0); + } + + switch (operator) { + case SEMANTIC_VERSION_GREATER_EQUAL: + if (version1.get(i).compareTo(version2.get(i)) >= 0) { + return true; + } + break; + case SEMANTIC_VERSION_GREATER_THAN: + if (version1.get(i).compareTo(version2.get(i)) > 0) { + return true; + } + break; + case SEMANTIC_VERSION_LESS_EQUAL: + if (version1.get(i).compareTo(version2.get(i)) <= 0) { + return true; + } + break; + case SEMANTIC_VERSION_LESS_THAN: + if (version1.get(i).compareTo(version2.get(i)) < 0) { + return true; + } + break; + case SEMANTIC_VERSION_EQUAL: + if (version1.get(i).compareTo(version2.get(i)) != 0) { return false; - } - - // Max number of segments a numeric version can have. This is enforced by the - // server as well. - int maxLength = 5; - - List version1 = Arrays.stream(customSignalValue.toString().split("\\.")) - .map(s -> Integer.valueOf(s)).collect(Collectors.toList()); - List version2 = Arrays.stream(targetValues.get(0).split("\\.")).map(s -> Integer.valueOf(s)) - .collect(Collectors.toList()); - for (int i = 0; i < maxLength; i++) { - // Check to see if segments are present. - Boolean version1HasSegment = version1.get(i) != null; - Boolean version2HasSegment = version2.get(i) != null; - - // If both are undefined, we've consumed everything and they're equal. - if (!version1HasSegment && !version2HasSegment) { - return operator == CustomSignalOperator.SEMANTIC_VERSION_EQUAL; - } - - // Insert zeros if undefined for easier comparison. - if (!version1HasSegment) - version1.add(i, 0); - if (!version2HasSegment) - version2.add(i, 0); - - switch (operator) { - case SEMANTIC_VERSION_GREATER_EQUAL: - if (version1.get(i).compareTo(version2.get(i)) >= 0) - return true; - break; - case SEMANTIC_VERSION_GREATER_THAN: - if (version1.get(i).compareTo(version2.get(i)) > 0) - return true; - break; - case SEMANTIC_VERSION_LESS_EQUAL: - if (version1.get(i).compareTo(version2.get(i)) <= 0) - return true; - break; - case SEMANTIC_VERSION_LESS_THAN: - if (version1.get(i).compareTo(version2.get(i)) < 0) - return true; - break; - case SEMANTIC_VERSION_EQUAL: - if (version1.get(i).compareTo(version2.get(i)) != 0) - return false; - break; - case SEMANTIC_VERSION_NOT_EQUAL: - if (version1.get(i).compareTo(version2.get(i)) != 0) - return true; - break; - default: - return false; - } - } - logger.warn(String.format("Semantic version max length(5) exceeded. Target: %s, Custom Signal: %s", version1, - version2)); - return false; + } + break; + case SEMANTIC_VERSION_NOT_EQUAL: + if (version1.get(i).compareTo(version2.get(i)) != 0) { + return true; + } + break; + default: + return false; + } } + logger.warn(String.format( + "Semantic version max length(5) exceeded. Target: %s, Custom Signal: %s", + version1, version2)); + return false; + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java index d418cb483..458ffd64e 100644 --- a/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.google.firebase.remoteconfig; import com.google.common.collect.ImmutableList; @@ -22,55 +23,56 @@ * Represents a condition that compares provided signals against a target value. */ public class CustomSignalCondition { - private final String customSignalKey; - private final CustomSignalOperator customSignalOperator; - private final ImmutableList targetCustomSignalValues; + private final String customSignalKey; + private final CustomSignalOperator customSignalOperator; + private final ImmutableList targetCustomSignalValues; - /** - * Creates new Custom signal condition. - * - * @param customSignalKey - * @param customSignalOperator - * @param targetCustomSignalValues - */ - public CustomSignalCondition(@NonNull String customSignalKey, @NonNull CustomSignalOperator customSignalOperator, @NonNull ImmutableList targetCustomSignalValues){ - this.customSignalKey = customSignalKey; - this.customSignalOperator = customSignalOperator; - this.targetCustomSignalValues = targetCustomSignalValues; - } + /** + * Creates new Custom signal condition. + * + * @param customSignalKey The key of the signal set in the EvaluationContext + * @param customSignalOperator The choice of custom signal operator to determine how to + * compare targets to value(s). + * @param targetCustomSignalValues A list of at most 100 target custom signal values. + * For numeric operators, this will have exactly ONE target value. + */ + public CustomSignalCondition(@NonNull String customSignalKey, + @NonNull CustomSignalOperator customSignalOperator, + @NonNull ImmutableList targetCustomSignalValues) { + this.customSignalKey = customSignalKey; + this.customSignalOperator = customSignalOperator; + this.targetCustomSignalValues = targetCustomSignalValues; + } - /** - * Gets the key of the signal set in the EvaluationContext - * {@link CustomSignalCondition.customSignalKey}. - * - * @return Custom signal key. - */ - @NonNull - public String getCustomSignalKey() { - return customSignalKey; - } + /** + * Gets the key of the signal set in the EvaluationContext {@link customSignalKey}. + * + * @return Custom signal key. + */ + @NonNull + public String getCustomSignalKey() { + return customSignalKey; + } - /** - * Gets the choice of custom signal operator - * {@link CustomSignalCondition.customSignalOperator} to determine how to - * compare targets to value(s). - * - * @return Custom signal operator. - */ - @NonNull - public CustomSignalOperator getCustomSignalOperator() { - return customSignalOperator; - } + /** + * Gets the choice of custom signal operator {@link customSignalOperator} to determine how to + * compare targets to value(s). + * + * @return Custom signal operator. + */ + @NonNull + public CustomSignalOperator getCustomSignalOperator() { + return customSignalOperator; + } - /** - * Gets the list of at most 100 target custom signal values - * {@link CustomSignalCondition.targetCustomSignalValues}. For numeric - * operators, this will have exactly ONE target value. - * - * @return List of target values. - */ - @NonNull - public ImmutableList getTargetCustomSignalValues() { - return targetCustomSignalValues; - } + /** + * Gets the list of at most 100 target custom signal values {@link targetCustomSignalValues}. + * For numeric operators, this will have exactly ONE target value. + * + * @return List of target values. + */ + @NonNull + public ImmutableList getTargetCustomSignalValues() { + return targetCustomSignalValues; + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java index fc7890127..04b72ab1c 100644 --- a/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.google.firebase.remoteconfig; import com.google.firebase.internal.NonNull; @@ -20,38 +21,38 @@ /** * Defines supported operators for custom signal conditions. */ -public enum CustomSignalOperator { - NUMERIC_EQUAL("NUMERIC_EQUAL"), - NUMERIC_GREATER_EQUAL("NUMERIC_GREATER_EQUAL"), - NUMERIC_GREATER_THAN("NUMERIC_GREATER_THAN"), - NUMERIC_LESS_EQUAL("NUMERIC_LESS_EQUAL"), - NUMERIC_LESS_THAN("NUMERIC_LESS_THAN"), - NUMERIC_NOT_EQUAL("NUMERIC_NOT_EQUAL"), - SEMANTIC_VERSION_EQUAL("SEMANTIC_VERSION_EQUAL"), - SEMANTIC_VERSION_GREATER_EQUAL("SEMANTIC_VERSION_GREATER_EQUAL"), - SEMANTIC_VERSION_GREATER_THAN("SEMANTIC_VERSION_GREATER_THAN"), - SEMANTIC_VERSION_LESS_EQUAL("SEMANTIC_VERSION_LESS_EQUAL"), - SEMANTIC_VERSION_LESS_THAN("SEMANTIC_VERSION_LESS_THAN"), - SEMANTIC_VERSION_NOT_EQUAL("SEMANTIC_VERSION_NOT_EQUAL"), - STRING_CONTAINS("STRING_CONTAINS"), - STRING_CONTAINS_REGEX("STRING_CONTAINS_REGEX"), - STRING_DOES_NOT_CONTAIN("STRING_DOES_NOT_CONTAIN"), - STRING_EXACTLY_MATCHES("STRING_EXACTLY_MATCHES"), - UNSPECIFIED("CUSTOM_SIGNAL_OPERATOR_UNSPECIFIED"); +public enum CustomSignalOperator { + NUMERIC_EQUAL("NUMERIC_EQUAL"), + NUMERIC_GREATER_EQUAL("NUMERIC_GREATER_EQUAL"), + NUMERIC_GREATER_THAN("NUMERIC_GREATER_THAN"), + NUMERIC_LESS_EQUAL("NUMERIC_LESS_EQUAL"), + NUMERIC_LESS_THAN("NUMERIC_LESS_THAN"), + NUMERIC_NOT_EQUAL("NUMERIC_NOT_EQUAL"), + SEMANTIC_VERSION_EQUAL("SEMANTIC_VERSION_EQUAL"), + SEMANTIC_VERSION_GREATER_EQUAL("SEMANTIC_VERSION_GREATER_EQUAL"), + SEMANTIC_VERSION_GREATER_THAN("SEMANTIC_VERSION_GREATER_THAN"), + SEMANTIC_VERSION_LESS_EQUAL("SEMANTIC_VERSION_LESS_EQUAL"), + SEMANTIC_VERSION_LESS_THAN("SEMANTIC_VERSION_LESS_THAN"), + SEMANTIC_VERSION_NOT_EQUAL("SEMANTIC_VERSION_NOT_EQUAL"), + STRING_CONTAINS("STRING_CONTAINS"), + STRING_CONTAINS_REGEX("STRING_CONTAINS_REGEX"), + STRING_DOES_NOT_CONTAIN("STRING_DOES_NOT_CONTAIN"), + STRING_EXACTLY_MATCHES("STRING_EXACTLY_MATCHES"), + UNSPECIFIED("CUSTOM_SIGNAL_OPERATOR_UNSPECIFIED"); + + private final String operator; + + CustomSignalOperator(@NonNull String operator) { + this.operator = operator; + } - private final String operator; - - CustomSignalOperator(@NonNull String operator) { - this.operator = operator; - } - - /** - * Get the custom signal operator {@link CustomSignalOperator}. - * - * @return operator. - */ - @NonNull - public String getOperator() { - return operator; - } + /** + * Get the custom signal operator {@link CustomSignalOperator}. + * + * @return operator. + */ + @NonNull + public String getOperator() { + return operator; } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java index ece4b5073..c5106ba24 100644 --- a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java +++ b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.google.firebase.remoteconfig; import com.google.firebase.internal.NonNull; @@ -22,35 +23,34 @@ * must be in the range [0 and 100000000]. */ public class MicroPercentRange { - private final int microPercentLowerBound; - private final int microPercentUpperBound; + private final int microPercentLowerBound; + private final int microPercentUpperBound; - public MicroPercentRange(@NonNull int microPercentLowerBound, @NonNull int microPercentUpperBound){ - this.microPercentLowerBound = microPercentLowerBound; - this.microPercentUpperBound = microPercentUpperBound; - } + public MicroPercentRange(@NonNull int microPercentLowerBound, + @NonNull int microPercentUpperBound) { + this.microPercentLowerBound = microPercentLowerBound; + this.microPercentUpperBound = microPercentUpperBound; + } - /** - * The lower limit of percentiles - * {@link MicroPercentRange.microPercentLowerBound} to target in micro-percents. - * The value must be in the range [0 and 100000000]. - * - * @return micropercentile lower bound. - */ - @NonNull - public int getMicroPercentLowerBound() { - return microPercentLowerBound; - } + /** + * The lower limit of percentiles {@link microPercentLowerBound} to target in micro-percents. + * The value must be in the range [0 and 100000000]. + * + * @return micropercentile lower bound. + */ + @NonNull + public int getMicroPercentLowerBound() { + return microPercentLowerBound; + } - /** - * The upper limit of percentiles - * {@link MicroPercentRange.microPercentUpperBound} to target in micro-percents. - * The value must be in the range [0 and 100000000]. - * - * @return micropercentile upper bound. - */ - @NonNull - public int getMicroPercentUpperBound() { - return microPercentUpperBound; - } + /** + * The upper limit of percentiles {@link microPercentUpperBound} to target in micro-percents. + * The value must be in the range [0 and 100000000]. + * + * @return micropercentile upper bound. + */ + @NonNull + public int getMicroPercentUpperBound() { + return microPercentUpperBound; + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java index 9b4b009e4..6669f7f70 100644 --- a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.google.firebase.remoteconfig; import com.google.firebase.internal.NonNull; @@ -23,120 +24,122 @@ * defined field will be processed. */ public class OneOfCondition { - private OrCondition orCondition; - private AndCondition andCondition; - private PercentCondition percent; - private CustomSignalCondition customSignal; - private String trueValue; - private String falseValue; + private OrCondition orCondition; + private AndCondition andCondition; + private PercentCondition percent; + private CustomSignalCondition customSignal; + private String trueValue; + private String falseValue; - /** - * Gets {@link OrCondition} with conditions on which OR operation will be applied. - * - * @return list of conditions. - */ - @Nullable - public OrCondition getOrCondition() { - return orCondition; - } + /** + * Gets {@link OrCondition} with conditions on which OR operation will be + * applied. + * + * @return list of conditions. + */ + @Nullable + public OrCondition getOrCondition() { + return orCondition; + } - /** - * Gets {@link AndCondition} with conditions on which AND operation will be applied. - * - * @return list of conditions. - */ - @Nullable - public AndCondition getAndCondition() { - return andCondition; - } + /** + * Gets {@link AndCondition} with conditions on which AND operation will be + * applied. + * + * @return list of conditions. + */ + @Nullable + public AndCondition getAndCondition() { + return andCondition; + } - /** - * Returns true indicating the expression has evaluated to true. - * - * @return true. - */ - @Nullable - public String isTrue() { - return trueValue; - } + /** + * Returns true indicating the expression has evaluated to true. + * + * @return true. + */ + @Nullable + public String isTrue() { + return trueValue; + } - /** - * Returns false indicating the expression has evaluated to false. - * - * @return false. - */ - @Nullable - public String isFalse() { - return falseValue; - } + /** + * Returns false indicating the expression has evaluated to false. + * + * @return false. + */ + @Nullable + public String isFalse() { + return falseValue; + } - /** - * Gets {@link PercentCondition}. - * - * @return percent condition. - */ - @Nullable - public PercentCondition getPercent() { - return percent; - } + /** + * Gets {@link PercentCondition}. + * + * @return percent condition. + */ + @Nullable + public PercentCondition getPercent() { + return percent; + } - /** - * Gets {@link CustomSignalCondition}. - * - * @return custom condition. - */ - @Nullable - public CustomSignalCondition getCustomSignal() { - return customSignal; - } + /** + * Gets {@link CustomSignalCondition}. + * + * @return custom condition. + */ + @Nullable + public CustomSignalCondition getCustomSignal() { + return customSignal; + } - /** - * Sets list of conditions on which OR operation will be applied. - * - * @param orCondition - */ - public void setOrCondition(@NonNull OrCondition orCondition) { - this.orCondition = orCondition; - } + /** + * Sets list of conditions on which OR operation will be applied. + * + * @param orCondition Makes this condition an OR condition. + */ + public void setOrCondition(@NonNull OrCondition orCondition) { + this.orCondition = orCondition; + } - /** - * Sets list of conditions on which AND operation will be applied. - * - * @param andCondition - */ - public void setAndCondition(@NonNull AndCondition andCondition) { - this.andCondition = andCondition; - } + /** + * Sets list of conditions on which AND operation will be applied. + * + * @param andCondition Makes this condition an AND condition. + */ + public void setAndCondition(@NonNull AndCondition andCondition) { + this.andCondition = andCondition; + } - /** - * Sets {@link PercentCondition}. - * - * @param percent - */ - public void setPercent(@NonNull PercentCondition percent) { - this.percent = percent; - } + /** + * Sets {@link PercentCondition}. + * + * @param percent Makes this condition a percent condition. + */ + public void setPercent(@NonNull PercentCondition percent) { + this.percent = percent; + } - /** - * Sets {@link CustomSignalCondition}. - * - * @param customSignal - */ - public void setCustomSignal(@NonNull CustomSignalCondition customSignal) { - this.customSignal = customSignal; - } + /** + * Sets {@link CustomSignalCondition}. + * + * @param customSignal Makes this condition a custom signal condition. + */ + public void setCustomSignal(@NonNull CustomSignalCondition customSignal) { + this.customSignal = customSignal; + } - /** - * Sets evaluation value to true. - */ - public void setTrue() { - this.trueValue = "true"; - } + /** + * Sets evaluation value to true. + */ + public void setTrue() { + this.trueValue = "true"; + } - /** - * Sets evaluation value to false. - */ - public void setFalse() { - this.falseValue = "false"; - } + /** + * Sets evaluation value to false. + */ + public void setFalse() { + this.falseValue = "false"; + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/OrCondition.java b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java index 0c5c4956f..a801d815a 100644 --- a/src/main/java/com/google/firebase/remoteconfig/OrCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java @@ -13,31 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.google.firebase.remoteconfig; import com.google.common.collect.ImmutableList; import com.google.firebase.internal.NonNull; /** - * Represents a collection of conditions that evaluate to true if one of them is true. + * Represents a collection of conditions that evaluate to true if one of them is + * true. */ public class OrCondition { - private final ImmutableList conditions; + private final ImmutableList conditions; - /** - * Creates OrCondition joining subconditions. - */ - public OrCondition(@NonNull ImmutableList conditions) { - this.conditions = conditions; - } + /** + * Creates OrCondition joining subconditions. + */ + public OrCondition(@NonNull ImmutableList conditions) { + this.conditions = conditions; + } - /** - * Gets the list of {@link OneOfCondition} - * - * @return List of conditions to evaluate. - */ - @NonNull - public ImmutableList getConditions() { - return conditions; - } + /** + * Gets the list of {@link OneOfCondition} + * + * @return List of conditions to evaluate. + */ + @NonNull + public ImmutableList getConditions() { + return conditions; + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java index f63524551..b2e91c6b7 100644 --- a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.google.firebase.remoteconfig; import com.google.firebase.internal.NonNull; @@ -23,81 +24,92 @@ * a given limit. */ public class PercentCondition { - private int microPercent; - private MicroPercentRange microPercentRange; - private final PercentConditionOperator percentConditionOperator; - private final String seed; + private int microPercent; + private MicroPercentRange microPercentRange; + private final PercentConditionOperator percentConditionOperator; + private final String seed; - /** - * Create a percent condition for {@link PercentConditionOperator.BETWEEN}. - * - * @param microPercent - * @param percentConditionOperator - * @param seed - */ - public PercentCondition(@NonNull int microPercent, @NonNull PercentConditionOperator percentConditionOperator, @NonNull String seed) { - this.microPercent = microPercent; - this.percentConditionOperator = percentConditionOperator; - this.seed = seed; - } + /** + * Create a percent condition for {@link PercentConditionOperator.BETWEEN}. + * + * @param microPercent The limit of percentiles to target in micro-percents when using the + * LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range + * [0 and 100000000]. + * @param percentConditionOperator The choice of percent operator to determine how to compare + * targets to percent(s). + * @param seed The seed used when evaluating the hash function to map an instance to a value in + * the hash space. This is a string which can have 0 - 32 characters and can contain ASCII + * characters [-_.0-9a-zA-Z].The string is case-sensitive. + */ + public PercentCondition(@NonNull int microPercent, + @NonNull PercentConditionOperator percentConditionOperator, + @NonNull String seed) { + this.microPercent = microPercent; + this.percentConditionOperator = percentConditionOperator; + this.seed = seed; + } - /** - * Create a percent condition for {@link PercentConditionOperator.GREATER_THAN} - * and {@link PercentConditionOperator.LESS_OR_EQUAL}. - * - * @param microPercentRange - * @param percentConditionOperator - * @param seed - */ - public PercentCondition(@NonNull MicroPercentRange microPercentRange, @NonNull PercentConditionOperator percentConditionOperator, - String seed) { - this.microPercentRange = microPercentRange; - this.percentConditionOperator = percentConditionOperator; - this.seed = seed; - } + /** + * Create a percent condition for {@link PercentConditionOperator.GREATER_THAN} + * and {@link PercentConditionOperator.LESS_OR_EQUAL}. + * + * @param microPercentRange The micro-percent interval to be used with the BETWEEN operator. + * @param percentConditionOperator The choice of percent operator to determine how to compare + * targets to percent(s). + * @param seed The seed used when evaluating the hash function to map an instance to a value + * in the hash space. This is a string which can have 0 - 32 characters and can contain + * ASCII characters [-_.0-9a-zA-Z].The string is case-sensitive. + */ + public PercentCondition(@NonNull MicroPercentRange microPercentRange, + @NonNull PercentConditionOperator percentConditionOperator, + String seed) { + this.microPercentRange = microPercentRange; + this.percentConditionOperator = percentConditionOperator; + this.seed = seed; + } - /** - * Gets the limit of percentiles to target in micro-percents when using the - * LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0 - * and 100000000]. - * - * @return micro percent. - */ - @Nullable - public int getMicroPercent() { - return microPercent; - } + /** + * Gets the limit of percentiles to target in micro-percents when using the + * LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0 + * and 100000000]. + * + * @return micro percent. + */ + @Nullable + public int getMicroPercent() { + return microPercent; + } - /** - * Gets micro-percent interval to be used with the BETWEEN operator. - * - * @return micro percent range. - */ - @Nullable - public MicroPercentRange getMicroPercentRange() { - return microPercentRange; - } + /** + * Gets micro-percent interval to be used with the BETWEEN operator. + * + * @return micro percent range. + */ + @Nullable + public MicroPercentRange getMicroPercentRange() { + return microPercentRange; + } - /** - * Gets choice of percent operator to determine how to compare targets to - * percent(s). - * - * @return operator. - */ - @NonNull - public PercentConditionOperator getPercentConditionOperator() { - return percentConditionOperator; - } + /** + * Gets choice of percent operator to determine how to compare targets to + * percent(s). + * + * @return operator. + */ + @NonNull + public PercentConditionOperator getPercentConditionOperator() { + return percentConditionOperator; + } - /** - * The seed used when evaluating the hash function to map an instance to a value - * in the hash space. This is a string which can have 0 - 32 characters and can - * contain ASCII characters [-_.0-9a-zA-Z].The string is case-sensitive. - * - * @return seed. - */ - @NonNull - public String getSeed() { - return seed; - } + /** + * The seed used when evaluating the hash function to map an instance to a value + * in the hash space. This is a string which can have 0 - 32 characters and can + * contain ASCII characters [-_.0-9a-zA-Z].The string is case-sensitive. + * + * @return seed. + */ + @NonNull + public String getSeed() { + return seed; + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java index 27b15bca1..313785bd6 100644 --- a/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java +++ b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.google.firebase.remoteconfig; import com.google.firebase.internal.NonNull; @@ -31,7 +32,8 @@ public enum PercentConditionOperator { /** * Creates percent condition operator. * - * @param operator + * @param operator The choice of percent operator to determine how to compare targets to + * percent(s). */ PercentConditionOperator(@NonNull String operator) { this.operator = operator; diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java index 49582c148..535d2fe85 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java @@ -16,66 +16,66 @@ package com.google.firebase.remoteconfig; -import java.util.Map; - import com.google.firebase.internal.NonNull; +import java.util.Map; + /** * Represents the configuration produced by evaluating a server template. */ public class ServerConfig { - private final Map configValues; + private final Map configValues; - ServerConfig(Map configValues) { - this.configValues = configValues; - } + ServerConfig(Map configValues) { + this.configValues = configValues; + } - /** - * Gets the value for the given key as a string. Convenience method for calling - * serverConfig.getValue(key).asString(). - * - * @param key - * @return config value for the given key as string. - */ - @NonNull - public String getString(@NonNull String key) { - return configValues.get(key).asString(); - } + /** + * Gets the value for the given key as a string. Convenience method for calling + * serverConfig.getValue(key).asString(). + * + * @param key The name of the parameter. + * @return config value for the given key as string. + */ + @NonNull + public String getString(@NonNull String key) { + return configValues.get(key).asString(); + } - /** - * Gets the value for the given key as a boolean.Convenience method for calling - * serverConfig.getValue(key).asBoolean(). - * - * @param key - * @return config value for the given key as boolean. - */ - @NonNull - public boolean getBoolean(@NonNull String key) { - return Boolean.parseBoolean(getString(key)); - } + /** + * Gets the value for the given key as a boolean.Convenience method for calling + * serverConfig.getValue(key).asBoolean(). + * + * @param key The name of the parameter. + * @return config value for the given key as boolean. + */ + @NonNull + public boolean getBoolean(@NonNull String key) { + return Boolean.parseBoolean(getString(key)); + } - /** - * Gets the value for the given key as long.Convenience method for calling - * serverConfig.getValue(key).asLong(). - * - * @param key - * @return config value for the given key as long. - */ - @NonNull - public long getLong(@NonNull String key) { - return Long.parseLong(getString(key)); - } + /** + * Gets the value for the given key as long.Convenience method for calling + * serverConfig.getValue(key).asLong(). + * + * @param key The name of the parameter. + * @return config value for the given key as long. + */ + @NonNull + public long getLong(@NonNull String key) { + return Long.parseLong(getString(key)); + } - /** - * Gets the value for the given key as double.Convenience method for calling - * serverConfig.getValue(key).asDouble(). - * - * @param key - * @return config value for the given key as double. - */ - @NonNull - public double getDouble(@NonNull String key) { - return Double.parseDouble(getString(key)); - } + /** + * Gets the value for the given key as double.Convenience method for calling + * serverConfig.getValue(key).asDouble(). + * + * @param key The name of the parameter. + * @return config value for the given key as double. + */ + @NonNull + public double getDouble(@NonNull String key) { + return Double.parseDouble(getString(key)); + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java index 8582018ac..66614767d 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java @@ -1,10 +1,20 @@ -package com.google.firebase.remoteconfig; - -import java.util.HashMap; -import java.util.Map; +/* + * Copyright 2020 Google LLC + * + * Licensed 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. + */ -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +package com.google.firebase.remoteconfig; import com.google.common.collect.ImmutableMap; import com.google.firebase.ErrorCode; @@ -12,104 +22,115 @@ import com.google.firebase.internal.Nullable; import com.google.firebase.remoteconfig.Value.ValueSource; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Represents a stateful abstraction for a Remote Config server template. */ public class ServerTemplate { - private ServerTemplateData cache; - private Map stringifiedDefaultConfig; - private final ConditionEvaluator conditionEvaluator; - private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class); - - /** - * Creates a new {@link ServerTemplate}. - * - * @param conditionEvaluator - * @param defaultConfig - */ - public ServerTemplate(@NonNull ConditionEvaluator conditionEvaluator, @NonNull ImmutableMap defaultConfig) { - for (String configName : defaultConfig.keySet()) { - stringifiedDefaultConfig.put(configName, defaultConfig.get(configName)); - } - this.conditionEvaluator = conditionEvaluator; + private ServerTemplateData cache; + private Map stringifiedDefaultConfig; + private final ConditionEvaluator conditionEvaluator; + private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class); + + /** + * Creates a new {@link ServerTemplate}. + * + * @param conditionEvaluator Evaluator to use on the given condition. + * @param defaultConfig Defines the format for in-app default parameter + * values. + */ + public ServerTemplate(@NonNull ConditionEvaluator conditionEvaluator, + @NonNull ImmutableMap defaultConfig) { + for (String configName : defaultConfig.keySet()) { + stringifiedDefaultConfig.put(configName, defaultConfig.get(configName)); + } + this.conditionEvaluator = conditionEvaluator; + } + + /** + * Evaluates the current server template in cache to produce a + * {@link ServerConfig}. + * + * @param context Represents template evaluation input signals. + * @return evaluated server config. + * @throws FirebaseRemoteConfigException when there cache is empty. + */ + public ServerConfig evaluate(@Nullable ImmutableMap context) + throws FirebaseRemoteConfigException { + if (cache == null) { + throw new FirebaseRemoteConfigException(ErrorCode.FAILED_PRECONDITION, + "No Remote Config Server template in cache. Call load() before callingevaluate()."); } - /** - * Evaluates the current server template in cache to produce a - * {@link ServerConfig}. - * - * @param context - * @return evaluated server config. - * @throws FirebaseRemoteConfigException - */ - public ServerConfig evaluate(@Nullable ImmutableMap context) throws FirebaseRemoteConfigException { - if (cache == null) { - throw new FirebaseRemoteConfigException(ErrorCode.FAILED_PRECONDITION, - "No Remote Config Server template in cache. Call load() before calling evaluate()."); - } - - ImmutableMap evaluatedCondition = conditionEvaluator - .evaluateConditions(ImmutableMap.copyOf(cache.getConditions()), context); - - Map configValues = new HashMap<>(); - // Initializes config Value objects with default values. - for (String configName : stringifiedDefaultConfig.keySet()) { - configValues.put(configName, stringifiedDefaultConfig.get(configName)); - } + ImmutableMap evaluatedCondition = conditionEvaluator + .evaluateConditions(ImmutableMap.copyOf(cache.getConditions()), context); - ImmutableMap parameters = ImmutableMap.copyOf(cache.getParameters()); - mergeDerivedConfigValues(evaluatedCondition, parameters, configValues); - return new ServerConfig(configValues); + Map configValues = new HashMap<>(); + // Initializes config Value objects with default values. + for (String configName : stringifiedDefaultConfig.keySet()) { + configValues.put(configName, stringifiedDefaultConfig.get(configName)); } - private void mergeDerivedConfigValues(ImmutableMap evaluatedCondition, - ImmutableMap parameters, Map configValues) { - // Overlays config Value objects derived by evaluating the template. - for (String parameterName : parameters.keySet()) { - Parameter parameter = parameters.get(parameterName); - if (parameter == null) { - logger.warn(String.format("Parameter value is not assigned for %s", parameterName)); - continue; - } - - Map conditionalValues = parameter.getConditionalValues(); - ParameterValue defaultValue = parameter.getDefaultValue(); - ParameterValue derivedValue = null; - - for (String conditionName : evaluatedCondition.keySet()) { - boolean conditionEvaluation = evaluatedCondition.get(conditionName); - if (conditionalValues.containsKey(conditionName) && conditionEvaluation) { - derivedValue = conditionalValues.get(conditionName); - break; - } - } - - if (derivedValue != null && derivedValue.toParameterValueResponse().isUseInAppDefault()) { - logger.warn( - String.format("Derived value for %s is set to use in app default.", parameterName)); - continue; - } - - if (derivedValue != null) { - String parameterValue = derivedValue.toParameterValueResponse().getValue(); - Value value = new Value(ValueSource.REMOTE, parameterValue); - configValues.put(parameterName, value); - continue; - } - - if (defaultValue == null) { - logger.warn(String.format("Default parameter value for %s is set to null.", parameterName)); - continue; - } - - if (defaultValue.toParameterValueResponse().isUseInAppDefault()) { - logger.info(String.format("Default value for %s is set to use in app default.", parameterName)); - continue; - } - - String parameterDefaultValue = defaultValue.toParameterValueResponse().getValue(); - Value value = new Value(ValueSource.REMOTE, parameterDefaultValue); - configValues.put(parameterName, value); + ImmutableMap parameters = ImmutableMap.copyOf(cache.getParameters()); + mergeDerivedConfigValues(evaluatedCondition, parameters, configValues); + return new ServerConfig(configValues); + } + + private void mergeDerivedConfigValues(ImmutableMap evaluatedCondition, + ImmutableMap parameters, Map configValues) { + // Overlays config Value objects derived by evaluating the template. + for (String parameterName : parameters.keySet()) { + Parameter parameter = parameters.get(parameterName); + if (parameter == null) { + logger.warn(String.format("Parameter value is not assigned for %s", parameterName)); + continue; + } + + Map conditionalValues = parameter.getConditionalValues(); + ParameterValue derivedValue = null; + + for (String conditionName : evaluatedCondition.keySet()) { + boolean conditionEvaluation = evaluatedCondition.get(conditionName); + if (conditionalValues.containsKey(conditionName) && conditionEvaluation) { + derivedValue = conditionalValues.get(conditionName); + break; } + } + + if (derivedValue != null && derivedValue.toParameterValueResponse().isUseInAppDefault()) { + logger.warn( + String.format("Derived value for %s is set to use in app default.", parameterName)); + continue; + } + + if (derivedValue != null) { + String parameterValue = derivedValue.toParameterValueResponse().getValue(); + Value value = new Value(ValueSource.REMOTE, parameterValue); + configValues.put(parameterName, value); + continue; + } + + ParameterValue defaultValue = parameter.getDefaultValue(); + if (defaultValue == null) { + logger.warn(String.format("Default parameter value for %s is set to null.", + parameterName)); + continue; + } + + if (defaultValue.toParameterValueResponse().isUseInAppDefault()) { + logger.info(String.format("Default value for %s is set to use in app default.", + parameterName)); + continue; + } + + String parameterDefaultValue = defaultValue.toParameterValueResponse().getValue(); + Value value = new Value(ValueSource.REMOTE, parameterDefaultValue); + configValues.put(parameterName, value); } + } } \ No newline at end of file diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java index f61a965c7..060d976ff 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java @@ -16,121 +16,122 @@ package com.google.firebase.remoteconfig; -import java.util.HashMap; -import java.util.Map; - import static com.google.common.base.Preconditions.checkNotNull; + import com.google.firebase.internal.NonNull; +import java.util.HashMap; +import java.util.Map; + /** * Represents data stored in a server side Remote Config template. */ public class ServerTemplateData { - private String etag; - private Map parameters; - private Map conditions; - private Version version; + private String etag; + private Map parameters; + private Map conditions; + private Version version; - /** - * Creates a new {@link ServerTemplateData} object. - * - * @param etag The ETag of this template. - */ - public ServerTemplateData(@NonNull String etag) { - this.parameters = new HashMap<>(); - this.conditions = new HashMap<>(); - this.etag = etag; - } + /** + * Creates a new {@link ServerTemplateData} object. + * + * @param etag The ETag of this template. + */ + public ServerTemplateData(@NonNull String etag) { + this.parameters = new HashMap<>(); + this.conditions = new HashMap<>(); + this.etag = etag; + } - /** - * Creates a new {@link ServerTemplateData} object with an empty etag. - */ - public ServerTemplateData() { - this((String) null); - } + /** + * Creates a new {@link ServerTemplateData} object with an empty etag. + */ + public ServerTemplateData() { + this((String) null); + } - /** - * Gets the map of parameters of the server side template data. - * - * @return A non-null map of parameter keys to their optional default values and - * optional - * conditional values. - */ - @NonNull - public Map getParameters() { - return this.parameters; - } + /** + * Gets the map of parameters of the server side template data. + * + * @return A non-null map of parameter keys to their optional default values and + * optional + * conditional values. + */ + @NonNull + public Map getParameters() { + return this.parameters; + } - /** - * Gets the map of parameters of the server side template data. - * - * @return A non-null map of condition keys to their conditions. - */ - @NonNull - public Map getConditions() { - return this.conditions; - } + /** + * Gets the map of parameters of the server side template data. + * + * @return A non-null map of condition keys to their conditions. + */ + @NonNull + public Map getConditions() { + return this.conditions; + } - /** - * Gets the ETag of the server template data. - * - * @return The ETag of the server template data. - */ - @NonNull - public String getETag() { - return this.etag; - } + /** + * Gets the ETag of the server template data. + * + * @return The ETag of the server template data. + */ + @NonNull + public String getETag() { + return this.etag; + } - /** - * Gets the version information of the template. - * - * @return The version information of the template. - */ - @NonNull - public Version getVersion() { - return version; - } + /** + * Gets the version information of the template. + * + * @return The version information of the template. + */ + @NonNull + public Version getVersion() { + return version; + } - /** - * Sets the map of parameters of the server side template data. - * - * @param parameters A non-null map of parameter keys to their optional default - * values and - * optional conditional values. - * @return This {@link ServerTemplateData} instance. - */ - @NonNull - public ServerTemplateData setParameters( - @NonNull Map parameters) { - checkNotNull(parameters, "parameters must not be null."); - this.parameters = parameters; - return this; - } + /** + * Sets the map of parameters of the server side template data. + * + * @param parameters A non-null map of parameter keys to their optional default + * values and + * optional conditional values. + * @return This {@link ServerTemplateData} instance. + */ + @NonNull + public ServerTemplateData setParameters( + @NonNull Map parameters) { + checkNotNull(parameters, "parameters must not be null."); + this.parameters = parameters; + return this; + } - /** - * Sets the map of conditions of the server side template data. - * - * @param conditions A non-null map of conditions. - * @return This {@link ServerTemplateData} instance. - */ - @NonNull - public ServerTemplateData setConditions( - @NonNull Map conditions) { - checkNotNull(conditions, "conditions must not be null."); - this.conditions = conditions; - return this; - } + /** + * Sets the map of conditions of the server side template data. + * + * @param conditions A non-null map of conditions. + * @return This {@link ServerTemplateData} instance. + */ + @NonNull + public ServerTemplateData setConditions( + @NonNull Map conditions) { + checkNotNull(conditions, "conditions must not be null."); + this.conditions = conditions; + return this; + } - /** - * Sets the version information of the template. - * Only the version's description field can be specified here. - * - * @param version A {@link Version} instance. - * @return This {@link Template} instance. - */ - @NonNull - public ServerTemplateData setVersion(Version version) { - this.version = version; - return this; - } + /** + * Sets the version information of the template. + * Only the version's description field can be specified here. + * + * @param version A {@link Version} instance. + * @return This {@link Template} instance. + */ + @NonNull + public ServerTemplateData setVersion(Version version) { + this.version = version; + return this; + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/Value.java b/src/main/java/com/google/firebase/remoteconfig/Value.java index 61552099d..bce0ad42e 100644 --- a/src/main/java/com/google/firebase/remoteconfig/Value.java +++ b/src/main/java/com/google/firebase/remoteconfig/Value.java @@ -13,14 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.firebase.remoteconfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +package com.google.firebase.remoteconfig; import com.google.common.collect.ImmutableList; import com.google.firebase.internal.NonNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Wraps a parameter value with metadata and type-safe getters. Type-safe * getters insulate application logic from remote changes to parameter names and @@ -32,8 +33,8 @@ public class Value { private static final String DEFAULT_VALUE_FOR_STRING = ""; private static final long DEFAULT_VALUE_FOR_LONG = 0; private static final double DEFAULT_VALUE_FOR_DOUBLE = 0; - private static final ImmutableList BOOLEAN_TRUTHY_VALUES = ImmutableList.of("1", "true", "t", "yes", "y", - "on"); + private static final ImmutableList BOOLEAN_TRUTHY_VALUES = ImmutableList.of("1", "true", + "t", "yes", "y", "on"); public enum ValueSource { STATIC, @@ -47,8 +48,8 @@ public enum ValueSource { /** * Creates a new {@link Value} object. * - * @param source - * @param value + * @param source Indicates the source of a value. + * @param value Indicates a parameter value. */ public Value(@NonNull ValueSource source, @NonNull String value) { this.source = source; @@ -58,7 +59,7 @@ public Value(@NonNull ValueSource source, @NonNull String value) { /** * Creates a new {@link Value} object with default value. * - * @param source + * @param source Indicates the source of a value. */ public Value(@NonNull ValueSource source) { this(source, DEFAULT_VALUE_FOR_STRING); From 8e32f135b72a788dea3650f2cdd0ee09a50557d0 Mon Sep 17 00:00:00 2001 From: Athira M Date: Tue, 3 Dec 2024 21:15:37 +0530 Subject: [PATCH 05/78] Unit tests for Value implementation --- .../firebase/remoteconfig/ValueTest.java | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/test/java/com/google/firebase/remoteconfig/ValueTest.java diff --git a/src/test/java/com/google/firebase/remoteconfig/ValueTest.java b/src/test/java/com/google/firebase/remoteconfig/ValueTest.java new file mode 100644 index 000000000..766387b85 --- /dev/null +++ b/src/test/java/com/google/firebase/remoteconfig/ValueTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +import static org.junit.Assert.assertEquals; + +import com.google.firebase.remoteconfig.Value.ValueSource; + +import org.junit.Test; + +public class ValueTest { + @Test + public void testGetSourceReturnsValueSource() { + Value value = new Value(ValueSource.STATIC); + assertEquals(value.getSource(), ValueSource.STATIC); + } + + @Test + public void testAsStringReturnsValueAsString() { + Value value = new Value(ValueSource.STATIC, "sample-string"); + assertEquals(value.asString(), "sample-string"); + } + + @Test + public void testAsStringReturnsDefaultEmptyString() { + Value value = new Value(ValueSource.STATIC); + assertEquals(value.asString(), ""); + } + + @Test + public void testAsLongReturnsDefaultValueForStaticSource() { + Value value = new Value(ValueSource.STATIC); + assertEquals(value.asLong(), 0L); + } + + @Test + public void testAsLongReturnsDefaultValueForInvalidSourceValue() { + Value value = new Value(ValueSource.REMOTE, "sample-string"); + assertEquals(value.asLong(), 0L); + } + + @Test + public void testAsLongReturnsSourceValueAsLong() { + Value value = new Value(ValueSource.REMOTE, "123"); + assertEquals(value.asLong(), 123L); + } + + @Test + public void testAsDoubleReturnsDefaultValueForStaticSource() { + Value value = new Value(ValueSource.STATIC); + assertEquals(value.asDouble(), 0, 0); + } + + @Test + public void testAsDoubleReturnsDefaultValueForInvalidSourceValue() { + Value value = new Value(ValueSource.REMOTE, "sample-string"); + assertEquals(value.asDouble(), 0, 0); + } + + @Test + public void testAsDoubleReturnsSourceValueAsDouble() { + Value value = new Value(ValueSource.REMOTE, "123.34"); + assertEquals(value.asDouble(), 123.34, 0); + } + + @Test + public void testAsBooleanReturnsDefaultValueForStaticSource() { + Value value = new Value(ValueSource.STATIC); + assertEquals(value.asBoolean(), false); + } + + @Test + public void testAsBooleanReturnsDefaultValueForInvalidSourceValue() { + Value value = new Value(ValueSource.REMOTE, "sample-string"); + assertEquals(value.asBoolean(), false); + } + + @Test + public void testAsBooleanReturnsSourceValueAsBoolean() { + Value value = new Value(ValueSource.REMOTE, "1"); + assertEquals(value.asBoolean(), true); + } + + @Test + public void testAsBooleanReturnsSourceValueYesAsBoolean() { + Value value = new Value(ValueSource.REMOTE, "YeS"); + assertEquals(value.asBoolean(), true); + } +} From f36ec6797156f980aa6ceb6e883e8c387e9765c5 Mon Sep 17 00:00:00 2001 From: Athira M Date: Tue, 3 Dec 2024 21:58:28 +0530 Subject: [PATCH 06/78] Change argument type of evaluate from immutable map tp map --- .../firebase/remoteconfig/ConditionEvaluator.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java index 072e1d377..0a942fd62 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java +++ b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java @@ -26,6 +26,7 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -50,14 +51,16 @@ public class ConditionEvaluator { */ @NonNull public ImmutableMap evaluateConditions( - @NonNull ImmutableMap conditions, - @NonNull ImmutableMap context) { + @NonNull Map conditions, + @NonNull Map context) { + ImmutableMap immutableConditions = ImmutableMap.copyOf(conditions); + ImmutableMap immutableContext = ImmutableMap.copyOf(context); ImmutableMap.Builder evaluatedConditions = ImmutableMap.builder(); int nestingLevel = 0; - for (ImmutableMap.Entry condition : conditions.entrySet()) { + for (ImmutableMap.Entry condition : immutableConditions.entrySet()) { evaluatedConditions.put(condition.getKey(), evaluateCondition(condition.getValue(), - context, nestingLevel)); + immutableContext, nestingLevel)); } return evaluatedConditions.build(); From 8e1dcfca6f2a3ffece2e13e9a33881ae390a40f0 Mon Sep 17 00:00:00 2001 From: Athira M Date: Mon, 9 Dec 2024 06:42:11 +0530 Subject: [PATCH 07/78] Bug fixes and unit tests for evaluation --- .../remoteconfig/ConditionEvaluator.java | 195 ++-- .../remoteconfig/MicroPercentRange.java | 9 +- .../remoteconfig/PercentCondition.java | 4 +- .../remoteconfig/ConditionEvaluatorTest.java | 848 ++++++++++++++++++ .../firebase/remoteconfig/ValueTest.java | 10 +- 5 files changed, 967 insertions(+), 99 deletions(-) create mode 100644 src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java diff --git a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java index 0a942fd62..7774e0ddb 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java +++ b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java @@ -43,6 +43,16 @@ public class ConditionEvaluator { private static final int MAX_CONDITION_RECURSION_DEPTH = 10; private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class); + @FunctionalInterface + interface CompareStringFunction { + boolean apply(String signalVaue, String targetValue); + } + + @FunctionalInterface + interface CompareNumberFunction { + boolean apply(Integer value); + } + /** * @param conditions A named condition map of string to {@link OneOfCondition}. * @param context Represents template evaluation input signals which can be @@ -170,26 +180,60 @@ private boolean evaluateCustomSignalCondition(CustomSignalCondition condition, } switch (customSignalOperator) { + // String operations. case STRING_CONTAINS: + return compareStrings(targetCustomSignalValues, customSignalValue, + (customSignal, targetSignal) -> customSignal.contains(targetSignal)); case STRING_DOES_NOT_CONTAIN: + return !compareStrings(targetCustomSignalValues, customSignalValue, + (customSignal, targetSignal) -> customSignal.contains(targetSignal)); case STRING_EXACTLY_MATCHES: + return compareStrings(targetCustomSignalValues, customSignalValue, + (customSignal, targetSignal) -> customSignal.equals(targetSignal)); case STRING_CONTAINS_REGEX: - return compareStrings(targetCustomSignalValues, customSignalValue, customSignalOperator); + return compareStrings(targetCustomSignalValues, customSignalValue, + (customSignal, targetSignal) -> Pattern.compile(targetSignal) + .matcher(customSignal).matches()); + + // Numeric operations. case NUMERIC_LESS_THAN: + return compareNumbers(targetCustomSignalValues, customSignalValue, + (result) -> result < 0); case NUMERIC_LESS_EQUAL: + return compareNumbers(targetCustomSignalValues, customSignalValue, + (result) -> result <= 0); case NUMERIC_EQUAL: + return compareNumbers(targetCustomSignalValues, customSignalValue, + (result) -> result == 0); case NUMERIC_NOT_EQUAL: + return compareNumbers(targetCustomSignalValues, customSignalValue, + (result) -> result != 0); case NUMERIC_GREATER_THAN: + return compareNumbers(targetCustomSignalValues, customSignalValue, + (result) -> result > 0); case NUMERIC_GREATER_EQUAL: - return compareNumbers(targetCustomSignalValues, customSignalValue, customSignalOperator); + return compareNumbers(targetCustomSignalValues, customSignalValue, + (result) -> result >= 0); + + // Semantic operations. case SEMANTIC_VERSION_EQUAL: + return compareSemanticVersions(targetCustomSignalValues, customSignalValue, + (result) -> result == 0); case SEMANTIC_VERSION_GREATER_EQUAL: + return compareSemanticVersions(targetCustomSignalValues, customSignalValue, + (result) -> result >= 0); case SEMANTIC_VERSION_GREATER_THAN: + return compareSemanticVersions(targetCustomSignalValues, customSignalValue, + (result) -> result > 0); case SEMANTIC_VERSION_LESS_EQUAL: + return compareSemanticVersions(targetCustomSignalValues, customSignalValue, + (result) -> result <= 0); case SEMANTIC_VERSION_LESS_THAN: + return compareSemanticVersions(targetCustomSignalValues, customSignalValue, + (result) -> result < 0); case SEMANTIC_VERSION_NOT_EQUAL: return compareSemanticVersions(targetCustomSignalValues, customSignalValue, - customSignalOperator); + (result) -> result != 0); default: return false; } @@ -230,127 +274,100 @@ private BigInteger hashSeededRandomizationId(String seededRandomizationId) { } } - private boolean compareStrings(ImmutableList targetValues, Object customSignalValue, - CustomSignalOperator operator) { - String value = customSignalValue.toString(); - - for (String target : targetValues) { - if (operator == CustomSignalOperator.STRING_CONTAINS && value.contains(target)) { - return true; - } else if (operator == CustomSignalOperator.STRING_DOES_NOT_CONTAIN - && !value.contains(target)) { - return true; - } else if (operator == CustomSignalOperator.STRING_EXACTLY_MATCHES && value.equals(target)) { - return true; - } else if (operator == CustomSignalOperator.STRING_CONTAINS_REGEX - && Pattern.compile(value).matcher(target).find()) { + private boolean compareStrings(ImmutableList targetValues, Object customSignal, + CompareStringFunction compareFunction) { + String customSignalValue = customSignal.toString(); + for (String targetValue : targetValues) { + if (compareFunction.apply(customSignalValue, targetValue)) { return true; } } return false; } - private boolean compareNumbers(ImmutableList targetValues, Object customSignalValue, - CustomSignalOperator operator) { + private boolean compareNumbers(ImmutableList targetValues, Object customSignal, + CompareNumberFunction compareFunction) { if (targetValues.size() != 1) { - logger.warn(String.format("Target values must contain 1 element for operation %s", operator)); + logger.warn(String.format( + "Target values must contain 1 element for numeric operations. Target Value: %s", + targetValues)); return false; } - double value = (Double) customSignalValue; - double target = Double.parseDouble(targetValues.get(0)); - - switch (operator) { - case NUMERIC_EQUAL: - return value == target; - case NUMERIC_GREATER_THAN: - return value > target; - case NUMERIC_GREATER_EQUAL: - return value >= target; - case NUMERIC_LESS_EQUAL: - return value <= target; - case NUMERIC_LESS_THAN: - return value < target; - case NUMERIC_NOT_EQUAL: - return value != target; - default: - return false; + double customSignalValue; + double targetValue; + try { + customSignalValue = Double.parseDouble(customSignal.toString()); + targetValue = Double.parseDouble(targetValues.get(0)); + } catch (NumberFormatException e) { + return false; } + + return compareFunction.apply(customSignalValue < targetValue ? -1 + : customSignalValue > targetValue ? 1 : 0); } private boolean compareSemanticVersions(ImmutableList targetValues, - Object customSignalValue, CustomSignalOperator operator) throws RuntimeErrorException { + Object customSignalValue, + CompareNumberFunction compareFunction) throws RuntimeErrorException { if (targetValues.size() != 1) { - logger.warn(String.format("Target values must contain 1 element for operation %s", - operator)); + logger.warn(String.format("Target values must contain 1 element for semantic operation.")); + return false; + } + + String targetValueString = targetValues.get(0); + String customSignalValueString = customSignalValue.toString(); + if (!validateSemanticVersion(targetValueString) + || !validateSemanticVersion(customSignalValueString)) { return false; } // Max number of segments a numeric version can have. This is enforced by the // server as well. int maxLength = 5; - - List version1 = Arrays.stream(customSignalValue.toString().split("\\.")) - .map(s -> Integer.valueOf(s)).collect(Collectors.toList()); - List version2 = Arrays.stream(targetValues.get(0).split("\\.")) - .map(s -> Integer.valueOf(s)) + List targetVersion = Arrays.stream(targetValueString.split("\\.")) + .map(Integer::parseInt) .collect(Collectors.toList()); - for (int i = 0; i < maxLength; i++) { + List customSignalVersion = Arrays.stream(customSignalValueString.split("\\.")) + .map(Integer::parseInt) + .collect(Collectors.toList()); + + int targetVersionSize = targetVersion.size(); + int customSignalVersionSize = customSignalVersion.size(); + for (int i = 0; i <= maxLength; i++) { // Check to see if segments are present. - Boolean version1HasSegment = version1.get(i) != null; - Boolean version2HasSegment = version2.get(i) != null; + Boolean targetVersionHasSegment = (i < targetVersionSize); + Boolean customSignalVersionHasSegment = (i < customSignalVersionSize); // If both are undefined, we've consumed everything and they're equal. - if (!version1HasSegment && !version2HasSegment) { - return operator == CustomSignalOperator.SEMANTIC_VERSION_EQUAL; + if (!targetVersionHasSegment && !customSignalVersionHasSegment) { + return compareFunction.apply(0); } // Insert zeros if undefined for easier comparison. - if (!version1HasSegment) { - version1.add(i, 0); + if (!targetVersionHasSegment) { + targetVersion.add(0); } - if (!version2HasSegment) { - version2.add(i, 0); + if (!customSignalVersionHasSegment) { + customSignalVersion.add(0); } - switch (operator) { - case SEMANTIC_VERSION_GREATER_EQUAL: - if (version1.get(i).compareTo(version2.get(i)) >= 0) { - return true; - } - break; - case SEMANTIC_VERSION_GREATER_THAN: - if (version1.get(i).compareTo(version2.get(i)) > 0) { - return true; - } - break; - case SEMANTIC_VERSION_LESS_EQUAL: - if (version1.get(i).compareTo(version2.get(i)) <= 0) { - return true; - } - break; - case SEMANTIC_VERSION_LESS_THAN: - if (version1.get(i).compareTo(version2.get(i)) < 0) { - return true; - } - break; - case SEMANTIC_VERSION_EQUAL: - if (version1.get(i).compareTo(version2.get(i)) != 0) { - return false; - } - break; - case SEMANTIC_VERSION_NOT_EQUAL: - if (version1.get(i).compareTo(version2.get(i)) != 0) { - return true; - } - break; - default: - return false; + // Check if we have a difference in segments. Otherwise continue to next + // segment. + if (customSignalVersion.get(i).compareTo(targetVersion.get(i)) < 0) { + return compareFunction.apply(-1); + } else if (customSignalVersion.get(i).compareTo(targetVersion.get(i)) > 0) { + return compareFunction.apply(1); } } logger.warn(String.format( "Semantic version max length(5) exceeded. Target: %s, Custom Signal: %s", - version1, version2)); + targetValueString, customSignalValueString)); return false; } + + private boolean validateSemanticVersion(String version) { + Pattern pattern = Pattern.compile("^[0-9]+(?:\\.[0-9]+){0,4}$"); + return pattern.matcher(version).matches(); + } } diff --git a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java index c5106ba24..3fe8f5fdb 100644 --- a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java +++ b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java @@ -17,6 +17,7 @@ package com.google.firebase.remoteconfig; import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; /** * Represents the limit of percentiles to target in micro-percents. The value @@ -26,10 +27,10 @@ public class MicroPercentRange { private final int microPercentLowerBound; private final int microPercentUpperBound; - public MicroPercentRange(@NonNull int microPercentLowerBound, - @NonNull int microPercentUpperBound) { - this.microPercentLowerBound = microPercentLowerBound; - this.microPercentUpperBound = microPercentUpperBound; + public MicroPercentRange(@Nullable Integer microPercentLowerBound, + @Nullable Integer microPercentUpperBound) { + this.microPercentLowerBound = microPercentLowerBound != null ? microPercentLowerBound : 0; + this.microPercentUpperBound = microPercentUpperBound != null ? microPercentUpperBound : 0; } /** diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java index b2e91c6b7..e49084677 100644 --- a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java +++ b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java @@ -41,10 +41,10 @@ public class PercentCondition { * the hash space. This is a string which can have 0 - 32 characters and can contain ASCII * characters [-_.0-9a-zA-Z].The string is case-sensitive. */ - public PercentCondition(@NonNull int microPercent, + public PercentCondition(@Nullable Integer microPercent, @NonNull PercentConditionOperator percentConditionOperator, @NonNull String seed) { - this.microPercent = microPercent; + this.microPercent = microPercent != null ? microPercent : 0; this.percentConditionOperator = percentConditionOperator; this.seed = seed; } diff --git a/src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java b/src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java new file mode 100644 index 000000000..ea01ee057 --- /dev/null +++ b/src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java @@ -0,0 +1,848 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.junit.Test; + +public class ConditionEvaluatorTest { + + private final ConditionEvaluator conditionEvaluator = new ConditionEvaluator(); + + @Test + public void testEvaluateConditionsEmptyOrConditionToFalse() { + OneOfCondition emptyOneOfConditionOr = createOneOfOrCondition(null); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", emptyOneOfConditionOr); + Map context = new HashMap<>(); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsEmptyOrAndConditionToTrue() { + OneOfCondition emptyOneOfConditionAnd = createOneOfAndCondition(null); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(emptyOneOfConditionAnd); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionOr); + Map context = new HashMap<>(); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsOrAndTrueToTrue() { + OneOfCondition oneOfConditionTrue = createOneOfTrueCondition(); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionTrue); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionOr); + Map context = new HashMap<>(); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsOrAndFalseToFalse() { + OneOfCondition oneOfConditionFalse = createOneOfFalseCondition(); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionFalse); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionOr); + Map context = new HashMap<>(); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsNonOrTopConditionToTrue() { + OneOfCondition oneOfConditionTrue = createOneOfTrueCondition(); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionTrue); + Map context = new HashMap<>(); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsPercentConditionWithInvalidOperatorToFalse() { + OneOfCondition oneOfConditionPercent = createPercentCondition(0, + PercentConditionOperator.UNSPECIFIED, "seed"); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionPercent); + Map context = new HashMap<>(); + context.put("randomizationId", "abc"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsPercentConditionLessOrEqualMaxToTrue() { + OneOfCondition oneOfConditionPercent = createPercentCondition(10_000_0000, + PercentConditionOperator.LESS_OR_EQUAL, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionOr); + Map context = new HashMap<>(); + context.put("randomizationId", "123"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsPercentConditionLessOrEqualMinToFalse() { + OneOfCondition oneOfConditionPercent = createPercentCondition(0, + PercentConditionOperator.LESS_OR_EQUAL, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionOr); + Map context = new HashMap<>(); + context.put("randomizationId", "123"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsPercentConditionUndefinedMicroPercentToFalse() { + OneOfCondition oneOfConditionPercent = createPercentCondition(null, + PercentConditionOperator.LESS_OR_EQUAL, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionOr); + Map context = new HashMap<>(); + context.put("randomizationId", "123"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsUseZeroForUndefinedPercentRange() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(null, null, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionOr); + Map context = new HashMap<>(); + context.put("randomizationId", "123"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsUseZeroForUndefinedUpperBound() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(0, null, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionOr); + Map context = new HashMap<>(); + context.put("randomizationId", "123"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsUseZeroForUndefinedLowerBound() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(null, 10_000_0000, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionOr); + Map context = new HashMap<>(); + context.put("randomizationId", "123"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluatedConditionsGreaterThanMinToTrue() { + OneOfCondition oneOfConditionPercent = createPercentCondition(0, + PercentConditionOperator.GREATER_THAN, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionOr); + Map context = new HashMap<>(); + context.put("randomizationId", "123"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluatedConditionsGreaterThanMaxToFalse() { + OneOfCondition oneOfConditionPercent = createPercentCondition(10_000_0000, + PercentConditionOperator.GREATER_THAN, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionOr); + Map context = new HashMap<>(); + context.put("randomizationId", "123"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluatedConditionsBetweenMinAndMaxToTrue() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(0, 10_000_0000, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionOr); + Map context = new HashMap<>(); + context.put("randomizationId", "123"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluatedConditionsBetweenEqualBoundsToFalse() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(5_000_000, + 5_000_000, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + Map conditions = new HashMap<>(); + conditions.put("is_enabled", oneOfConditionOr); + Map context = new HashMap<>(); + context.put("randomizationId", "123"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsLessOrEqualToApprox() { + OneOfCondition oneOfConditionPerCondition = createPercentCondition(10_000_000, + PercentConditionOperator.LESS_OR_EQUAL, "seed"); + // 284 is 3 standard deviations for 100k trials with 10% probability. + int tolerance = 284; + + int truthyAssignments = evaluateRandomAssignments(oneOfConditionPerCondition, 100000); + + // Evaluate less than or equal 10% to approx 10% + assertTrue(truthyAssignments >= 10_000 - tolerance); + assertTrue(truthyAssignments <= 10_000 + tolerance); + } + + @Test + public void testEvaluateConditionsBetweenApproximateToTrue() { + // Micropercent range is 40% to 60%. + OneOfCondition oneOfConditionPerCondition = createBetweenPercentCondition(40_000_000, + 60_000_000, "seed"); + // 379 is 3 standard deviations for 100k trials with 20% probability. + int tolerance = 379; + + int truthyAssignments = evaluateRandomAssignments(oneOfConditionPerCondition, 100000); + + // Evaluate between 40% to 60% to approx 20% + assertTrue(truthyAssignments >= 20_000 - tolerance); + assertTrue(truthyAssignments <= 20_000 + tolerance); + } + + @Test + public void testEvaluateConditionsInterquartileToFiftyPercent() { + // Micropercent range is 25% to 75%. + OneOfCondition oneOfConditionPerCondition = createBetweenPercentCondition(25_000_000, + 75_000_000, "seed"); + // 474 is 3 standard deviations for 100k trials with 50% probability. + int tolerance = 474; + + int truthyAssignments = evaluateRandomAssignments(oneOfConditionPerCondition, 100000); + + // Evaluate between 25% to 75 to approx 50% + assertTrue(truthyAssignments >= 50_000 - tolerance); + assertTrue(truthyAssignments <= 50_000 + tolerance); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericLessThanToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.NUMERIC_LESS_THAN, ImmutableList.of("-50.0")); + Map context = new HashMap<>(); + context.put("signal_key", "-50.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericLessThanToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.NUMERIC_LESS_THAN, ImmutableList.of("-50.0")); + Map context = new HashMap<>(); + context.put("signal_key", "-50.01"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalInvalidValueNumericOperationToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.NUMERIC_LESS_THAN, ImmutableList.of("non-numeric")); + Map context = new HashMap<>(); + context.put("signal_key", "-50.01"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericLessEqualToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.NUMERIC_LESS_EQUAL, ImmutableList.of("-50.0")); + Map context = new HashMap<>(); + context.put("signal_key", "-50.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericLessEqualToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.NUMERIC_LESS_EQUAL, ImmutableList.of("-50.0")); + Map context = new HashMap<>(); + context.put("signal_key", "-49.9"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericEqualsToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.NUMERIC_EQUAL, ImmutableList.of("50.0")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericEqualsToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.NUMERIC_EQUAL, ImmutableList.of("50.0")); + Map context = new HashMap<>(); + context.put("signal_key", "50.000001"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericNotEqualsToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.NUMERIC_NOT_EQUAL, ImmutableList.of("50.0")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericNotEqualsToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.NUMERIC_NOT_EQUAL, ImmutableList.of("50.0")); + Map context = new HashMap<>(); + context.put("signal_key", "50.000001"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericGreaterEqualToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.NUMERIC_GREATER_EQUAL, ImmutableList.of("-50.0")); + Map context = new HashMap<>(); + context.put("signal_key", "-50.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericGreaterEqualToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.NUMERIC_GREATER_EQUAL, ImmutableList.of("-50.0")); + Map context = new HashMap<>(); + context.put("signal_key", "-50.01"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericgreaterThanToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.NUMERIC_GREATER_THAN, ImmutableList.of("-50.0")); + Map context = new HashMap<>(); + context.put("signal_key", "-50.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericGreaterThanToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.NUMERIC_GREATER_THAN, ImmutableList.of("-50.0")); + Map context = new HashMap<>(); + context.put("signal_key", "-49.09"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.STRING_CONTAINS, ImmutableList.of("One", "hundred")); + Map context = new HashMap<>(); + context.put("signal_key", "Two hundred"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.STRING_CONTAINS, ImmutableList.of("One", "hundred")); + Map context = new HashMap<>(); + context.put("signal_key", "Two hudred"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringDoesNotContainToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.STRING_DOES_NOT_CONTAIN, ImmutableList.of("One", "hundred")); + Map context = new HashMap<>(); + context.put("signal_key", "Two hudred"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringDoesNotContainToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.STRING_DOES_NOT_CONTAIN, ImmutableList.of("One", "hundred")); + Map context = new HashMap<>(); + context.put("signal_key", "Two hundred"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringExactlyMatchesToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.STRING_EXACTLY_MATCHES, ImmutableList.of("hundred")); + Map context = new HashMap<>(); + context.put("signal_key", "hundred"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringExactlyMatchesToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.STRING_EXACTLY_MATCHES, ImmutableList.of("hundred")); + Map context = new HashMap<>(); + context.put("signal_key", "Two hundred"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsRegexToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.STRING_CONTAINS_REGEX, ImmutableList.of(".*hund.*")); + Map context = new HashMap<>(); + context.put("signal_key", "hundred"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsRegexToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.STRING_CONTAINS_REGEX, ImmutableList.of("$hund.*")); + Map context = new HashMap<>(); + context.put("signal_key", "Two ahundred"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessThanToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN, ImmutableList.of("50.0.20")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0.2.0.1"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessThanToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN, ImmutableList.of("50.0.20")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0.20.0.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessThanInvalidVersionToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN, ImmutableList.of("50.0.-20")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0.2.0.1"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessEqualToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_EQUAL, ImmutableList.of("50.0.20.0.0")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0.20.0.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessEqualToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_EQUAL, ImmutableList.of("50.0.2")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0.2.1.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticGreaterThanToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.SEMANTIC_VERSION_GREATER_THAN, ImmutableList.of("50.0.2")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0.20.0.1"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticGreaterThanToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.SEMANTIC_VERSION_GREATER_THAN, ImmutableList.of("50.0.20")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0.20.0.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticGreaterEqualToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.SEMANTIC_VERSION_GREATER_EQUAL, ImmutableList.of("50.0.20")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0.20.0.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticGreaterEqualToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.SEMANTIC_VERSION_GREATER_EQUAL, ImmutableList.of("50.0.20.1")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0.20.0.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticEqualToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.SEMANTIC_VERSION_EQUAL, ImmutableList.of("50.0.20")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0.20.0.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticEqualToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.SEMANTIC_VERSION_EQUAL, ImmutableList.of("50.0.20.1")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0.20.0.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticNotEqualToTrue() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.SEMANTIC_VERSION_NOT_EQUAL, ImmutableList.of("50.0.20.1")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0.20.0.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticNotEqualToFalse() { + Map conditions = createCustomSignalNamedCondition( + CustomSignalOperator.SEMANTIC_VERSION_NOT_EQUAL, ImmutableList.of("50.0.20")); + Map context = new HashMap<>(); + context.put("signal_key", "50.0.20.0.0"); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + assertFalse(result.get("signal_key")); + } + + private Map createCustomSignalNamedCondition( + CustomSignalOperator operator, + ImmutableList targetCustomSignalValues) { + CustomSignalCondition condition = new CustomSignalCondition("signal_key", operator, + targetCustomSignalValues); + OneOfCondition oneOfConditionCustomSignal = new OneOfCondition(); + oneOfConditionCustomSignal.setCustomSignal(condition); + Map conditions = new HashMap<>(); + conditions.put("signal_key", oneOfConditionCustomSignal); + return conditions; + } + + private int evaluateRandomAssignments(OneOfCondition condition, int numOfAssignments) { + int evalTrueCount = 0; + Map conditions = new HashMap<>(); + conditions.put("is_enabled", condition); + for (int i = 0; i < numOfAssignments; i++) { + UUID randomizationId = UUID.randomUUID(); + Map context = new HashMap<>(); + context.put("randomizationId", randomizationId); + + ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, + context); + + if (result.get("is_enabled")) { + evalTrueCount++; + } + } + return evalTrueCount; + } + + private OneOfCondition createOneOfOrCondition(OneOfCondition condition) { + OrCondition orCondition = condition != null ? new OrCondition(ImmutableList.of(condition)) + : new OrCondition(ImmutableList.of()); + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setOrCondition(orCondition); + return oneOfCondition; + } + + private OneOfCondition createOneOfAndCondition(OneOfCondition condition) { + AndCondition andCondition = condition != null ? new AndCondition(ImmutableList.of(condition)) + : new AndCondition(ImmutableList.of()); + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setAndCondition(andCondition); + return oneOfCondition; + } + + private OneOfCondition createOneOfTrueCondition() { + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setTrue(); + return oneOfCondition; + } + + private OneOfCondition createOneOfFalseCondition() { + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setFalse(); + return oneOfCondition; + } + + private OneOfCondition createPercentCondition(Integer microPercent, + PercentConditionOperator operator, String seed) { + PercentCondition percentCondition = new PercentCondition(microPercent, operator, seed); + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setPercent(percentCondition); + return oneOfCondition; + } + + private OneOfCondition createBetweenPercentCondition(Integer lowerBound, Integer upperBound, + String seed) { + MicroPercentRange microPercentRange = new MicroPercentRange(lowerBound, upperBound); + PercentCondition percentCondition = new PercentCondition(microPercentRange, + PercentConditionOperator.BETWEEN, seed); + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setPercent(percentCondition); + return oneOfCondition; + } +} diff --git a/src/test/java/com/google/firebase/remoteconfig/ValueTest.java b/src/test/java/com/google/firebase/remoteconfig/ValueTest.java index 766387b85..257652a85 100644 --- a/src/test/java/com/google/firebase/remoteconfig/ValueTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/ValueTest.java @@ -17,6 +17,8 @@ package com.google.firebase.remoteconfig; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import com.google.firebase.remoteconfig.Value.ValueSource; @@ -80,24 +82,24 @@ public void testAsDoubleReturnsSourceValueAsDouble() { @Test public void testAsBooleanReturnsDefaultValueForStaticSource() { Value value = new Value(ValueSource.STATIC); - assertEquals(value.asBoolean(), false); + assertFalse(value.asBoolean()); } @Test public void testAsBooleanReturnsDefaultValueForInvalidSourceValue() { Value value = new Value(ValueSource.REMOTE, "sample-string"); - assertEquals(value.asBoolean(), false); + assertFalse(value.asBoolean()); } @Test public void testAsBooleanReturnsSourceValueAsBoolean() { Value value = new Value(ValueSource.REMOTE, "1"); - assertEquals(value.asBoolean(), true); + assertTrue(value.asBoolean()); } @Test public void testAsBooleanReturnsSourceValueYesAsBoolean() { Value value = new Value(ValueSource.REMOTE, "YeS"); - assertEquals(value.asBoolean(), true); + assertTrue(value.asBoolean()); } } From 8560befaf215772584158c89e9eb4699ffedd5bf Mon Sep 17 00:00:00 2001 From: Athira M Date: Mon, 9 Dec 2024 15:20:05 +0530 Subject: [PATCH 08/78] Replace map with KeysAndValues --- .../remoteconfig/ConditionEvaluator.java | 15 +- .../firebase/remoteconfig/KeysAndValues.java | 134 ++++++++ .../firebase/remoteconfig/ServerTemplate.java | 3 +- .../remoteconfig/ConditionEvaluatorTest.java | 286 +++++++++--------- 4 files changed, 285 insertions(+), 153 deletions(-) create mode 100644 src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java diff --git a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java index 7774e0ddb..244cc6917 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java +++ b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java @@ -62,21 +62,20 @@ interface CompareNumberFunction { @NonNull public ImmutableMap evaluateConditions( @NonNull Map conditions, - @NonNull Map context) { + @NonNull KeysAndValues context) { ImmutableMap immutableConditions = ImmutableMap.copyOf(conditions); - ImmutableMap immutableContext = ImmutableMap.copyOf(context); ImmutableMap.Builder evaluatedConditions = ImmutableMap.builder(); int nestingLevel = 0; for (ImmutableMap.Entry condition : immutableConditions.entrySet()) { evaluatedConditions.put(condition.getKey(), evaluateCondition(condition.getValue(), - immutableContext, nestingLevel)); + context, nestingLevel)); } return evaluatedConditions.build(); } - private boolean evaluateCondition(OneOfCondition condition, ImmutableMap context, + private boolean evaluateCondition(OneOfCondition condition, KeysAndValues context, int nestingLevel) { if (nestingLevel > MAX_CONDITION_RECURSION_DEPTH) { logger.warn("Maximum condition recursion depth exceeded."); @@ -99,7 +98,7 @@ private boolean evaluateCondition(OneOfCondition condition, ImmutableMap context, + private boolean evaluateOrCondition(OrCondition condition, KeysAndValues context, int nestingLevel) { ImmutableList subConditions = condition.getConditions(); for (OneOfCondition subCondition : subConditions) { @@ -112,7 +111,7 @@ private boolean evaluateOrCondition(OrCondition condition, ImmutableMap context, + KeysAndValues context, int nestingLevel) { ImmutableList subConditions = condition.getConditions(); for (OneOfCondition subCondition : subConditions) { @@ -125,7 +124,7 @@ private boolean evaluateAndCondition(AndCondition condition, } private boolean evaluatePercentCondition(PercentCondition condition, - ImmutableMap context) { + KeysAndValues context) { if (!context.containsKey("randomizationId")) { logger.warn("Percentage operation cannot be performed without randomizationId"); return false; @@ -162,7 +161,7 @@ private boolean evaluatePercentCondition(PercentCondition condition, } private boolean evaluateCustomSignalCondition(CustomSignalCondition condition, - ImmutableMap context) { + KeysAndValues context) { CustomSignalOperator customSignalOperator = condition.getCustomSignalOperator(); String customSignalKey = condition.getCustomSignalKey(); ImmutableList targetCustomSignalValues = condition.getTargetCustomSignalValues(); diff --git a/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java b/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java new file mode 100644 index 000000000..80c735755 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java @@ -0,0 +1,134 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed 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 com.google.firebase.remoteconfig; + +import com.google.common.collect.ImmutableMap; +import com.google.firebase.internal.NonNull; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents data stored in context passed to server-side Remote Config. + */ +public class KeysAndValues { + ImmutableMap.Builder keysAndValuesBuilder = ImmutableMap.builder(); + final ImmutableMap keysAndValues; + + private KeysAndValues(@NonNull Builder builder) { + keysAndValues = keysAndValuesBuilder.putAll(builder.keysAndValues).build(); + } + + /** + * Checks whether a key is present in the context. + * + * @param key The key for data stored in context. + * @return Boolean representing whether the key passed is present in context. + */ + public boolean containsKey(String key) { + return keysAndValues.containsKey(key); + } + + /** + * Gets the value of the data stored in context. + * + * @param key The key for data stored in context. + * @return Value assigned to the key in context. + */ + public String get(String key) { + return keysAndValues.get(key); + } + + /** + * Builder class for KeysAndValues using which values will be assigned to + * private variables. + */ + public static class Builder { + // Holds the converted pairs of custom keys and values. + private Map keysAndValues; + + /** + * Creates an empty Map to save context. + */ + public Builder() { + keysAndValues = new HashMap<>(); + } + + /** + * Adds a context data with string value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, @NonNull String value) { + keysAndValues.put(key, value); + return this; + } + + /** + * Adds a context data with boolean value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, boolean value) { + keysAndValues.put(key, Boolean.toString(value)); + return this; + } + + /** + * Adds a context data with double value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, double value) { + keysAndValues.put(key, Double.toString(value)); + return this; + } + + /** + * Adds a context data with long value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, long value) { + keysAndValues.put(key, Long.toString(value)); + return this; + } + + /** + * Creates an instance of KeysAndValues with the values assigned through + * builder. + * + * @return instance of KeysAndValues + */ + @NonNull + public KeysAndValues build() { + return new KeysAndValues(this); + } + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java index 66614767d..7b5f5d648 100644 --- a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java @@ -19,7 +19,6 @@ import com.google.common.collect.ImmutableMap; import com.google.firebase.ErrorCode; import com.google.firebase.internal.NonNull; -import com.google.firebase.internal.Nullable; import com.google.firebase.remoteconfig.Value.ValueSource; import java.util.HashMap; @@ -60,7 +59,7 @@ public ServerTemplate(@NonNull ConditionEvaluator conditionEvaluator, * @return evaluated server config. * @throws FirebaseRemoteConfigException when there cache is empty. */ - public ServerConfig evaluate(@Nullable ImmutableMap context) + public ServerConfig evaluate(KeysAndValues context) throws FirebaseRemoteConfigException { if (cache == null) { throw new FirebaseRemoteConfigException(ErrorCode.FAILED_PRECONDITION, diff --git a/src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java b/src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java index ea01ee057..4b9317cf3 100644 --- a/src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java @@ -37,7 +37,7 @@ public void testEvaluateConditionsEmptyOrConditionToFalse() { OneOfCondition emptyOneOfConditionOr = createOneOfOrCondition(null); Map conditions = new HashMap<>(); conditions.put("is_enabled", emptyOneOfConditionOr); - Map context = new HashMap<>(); + KeysAndValues context = new KeysAndValues.Builder().build(); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, context); @@ -51,7 +51,7 @@ public void testEvaluateConditionsEmptyOrAndConditionToTrue() { OneOfCondition oneOfConditionOr = createOneOfOrCondition(emptyOneOfConditionAnd); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionOr); - Map context = new HashMap<>(); + KeysAndValues context = new KeysAndValues.Builder().build(); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, context); @@ -66,7 +66,7 @@ public void testEvaluateConditionsOrAndTrueToTrue() { OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionOr); - Map context = new HashMap<>(); + KeysAndValues context = new KeysAndValues.Builder().build(); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, context); @@ -81,7 +81,7 @@ public void testEvaluateConditionsOrAndFalseToFalse() { OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionOr); - Map context = new HashMap<>(); + KeysAndValues context = new KeysAndValues.Builder().build(); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, context); @@ -94,7 +94,7 @@ public void testEvaluateConditionsNonOrTopConditionToTrue() { OneOfCondition oneOfConditionTrue = createOneOfTrueCondition(); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionTrue); - Map context = new HashMap<>(); + KeysAndValues context = new KeysAndValues.Builder().build(); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, context); @@ -108,11 +108,11 @@ public void testEvaluateConditionsPercentConditionWithInvalidOperatorToFalse() { PercentConditionOperator.UNSPECIFIED, "seed"); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionPercent); - Map context = new HashMap<>(); - context.put("randomizationId", "abc"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "abc"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("is_enabled")); } @@ -125,11 +125,11 @@ public void testEvaluateConditionsPercentConditionLessOrEqualMaxToTrue() { OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionOr); - Map context = new HashMap<>(); - context.put("randomizationId", "123"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("is_enabled")); } @@ -142,11 +142,11 @@ public void testEvaluateConditionsPercentConditionLessOrEqualMinToFalse() { OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionOr); - Map context = new HashMap<>(); - context.put("randomizationId", "123"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("is_enabled")); } @@ -159,11 +159,11 @@ public void testEvaluateConditionsPercentConditionUndefinedMicroPercentToFalse() OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionOr); - Map context = new HashMap<>(); - context.put("randomizationId", "123"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("is_enabled")); } @@ -175,11 +175,11 @@ public void testEvaluateConditionsUseZeroForUndefinedPercentRange() { OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionOr); - Map context = new HashMap<>(); - context.put("randomizationId", "123"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("is_enabled")); } @@ -191,11 +191,11 @@ public void testEvaluateConditionsUseZeroForUndefinedUpperBound() { OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionOr); - Map context = new HashMap<>(); - context.put("randomizationId", "123"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("is_enabled")); } @@ -207,11 +207,11 @@ public void testEvaluateConditionsUseZeroForUndefinedLowerBound() { OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionOr); - Map context = new HashMap<>(); - context.put("randomizationId", "123"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("is_enabled")); } @@ -224,11 +224,11 @@ public void testEvaluatedConditionsGreaterThanMinToTrue() { OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionOr); - Map context = new HashMap<>(); - context.put("randomizationId", "123"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("is_enabled")); } @@ -241,11 +241,11 @@ public void testEvaluatedConditionsGreaterThanMaxToFalse() { OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionOr); - Map context = new HashMap<>(); - context.put("randomizationId", "123"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("is_enabled")); } @@ -257,11 +257,11 @@ public void testEvaluatedConditionsBetweenMinAndMaxToTrue() { OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionOr); - Map context = new HashMap<>(); - context.put("randomizationId", "123"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("is_enabled")); } @@ -274,11 +274,11 @@ public void testEvaluatedConditionsBetweenEqualBoundsToFalse() { OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); Map conditions = new HashMap<>(); conditions.put("is_enabled", oneOfConditionOr); - Map context = new HashMap<>(); - context.put("randomizationId", "123"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("is_enabled")); } @@ -331,11 +331,11 @@ public void testEvaluateConditionsInterquartileToFiftyPercent() { public void testEvaluateConditionsCustomSignalNumericLessThanToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.NUMERIC_LESS_THAN, ImmutableList.of("-50.0")); - Map context = new HashMap<>(); - context.put("signal_key", "-50.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -344,11 +344,11 @@ public void testEvaluateConditionsCustomSignalNumericLessThanToFalse() { public void testEvaluateConditionsCustomSignalNumericLessThanToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.NUMERIC_LESS_THAN, ImmutableList.of("-50.0")); - Map context = new HashMap<>(); - context.put("signal_key", "-50.01"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.01"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -357,11 +357,11 @@ public void testEvaluateConditionsCustomSignalNumericLessThanToTrue() { public void testEvaluateConditionsCustomSignalInvalidValueNumericOperationToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.NUMERIC_LESS_THAN, ImmutableList.of("non-numeric")); - Map context = new HashMap<>(); - context.put("signal_key", "-50.01"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.01"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -370,11 +370,11 @@ public void testEvaluateConditionsCustomSignalInvalidValueNumericOperationToFals public void testEvaluateConditionsCustomSignalNumericLessEqualToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.NUMERIC_LESS_EQUAL, ImmutableList.of("-50.0")); - Map context = new HashMap<>(); - context.put("signal_key", "-50.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -383,11 +383,11 @@ public void testEvaluateConditionsCustomSignalNumericLessEqualToFalse() { public void testEvaluateConditionsCustomSignalNumericLessEqualToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.NUMERIC_LESS_EQUAL, ImmutableList.of("-50.0")); - Map context = new HashMap<>(); - context.put("signal_key", "-49.9"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-49.9"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -396,11 +396,11 @@ public void testEvaluateConditionsCustomSignalNumericLessEqualToTrue() { public void testEvaluateConditionsCustomSignalNumericEqualsToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.NUMERIC_EQUAL, ImmutableList.of("50.0")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -409,11 +409,11 @@ public void testEvaluateConditionsCustomSignalNumericEqualsToTrue() { public void testEvaluateConditionsCustomSignalNumericEqualsToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.NUMERIC_EQUAL, ImmutableList.of("50.0")); - Map context = new HashMap<>(); - context.put("signal_key", "50.000001"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.000001"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -422,11 +422,11 @@ public void testEvaluateConditionsCustomSignalNumericEqualsToFalse() { public void testEvaluateConditionsCustomSignalNumericNotEqualsToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.NUMERIC_NOT_EQUAL, ImmutableList.of("50.0")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -435,11 +435,11 @@ public void testEvaluateConditionsCustomSignalNumericNotEqualsToTrue() { public void testEvaluateConditionsCustomSignalNumericNotEqualsToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.NUMERIC_NOT_EQUAL, ImmutableList.of("50.0")); - Map context = new HashMap<>(); - context.put("signal_key", "50.000001"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.000001"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -448,11 +448,11 @@ public void testEvaluateConditionsCustomSignalNumericNotEqualsToFalse() { public void testEvaluateConditionsCustomSignalNumericGreaterEqualToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.NUMERIC_GREATER_EQUAL, ImmutableList.of("-50.0")); - Map context = new HashMap<>(); - context.put("signal_key", "-50.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -461,11 +461,11 @@ public void testEvaluateConditionsCustomSignalNumericGreaterEqualToFalse() { public void testEvaluateConditionsCustomSignalNumericGreaterEqualToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.NUMERIC_GREATER_EQUAL, ImmutableList.of("-50.0")); - Map context = new HashMap<>(); - context.put("signal_key", "-50.01"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.01"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -474,11 +474,11 @@ public void testEvaluateConditionsCustomSignalNumericGreaterEqualToTrue() { public void testEvaluateConditionsCustomSignalNumericgreaterThanToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.NUMERIC_GREATER_THAN, ImmutableList.of("-50.0")); - Map context = new HashMap<>(); - context.put("signal_key", "-50.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -487,11 +487,11 @@ public void testEvaluateConditionsCustomSignalNumericgreaterThanToFalse() { public void testEvaluateConditionsCustomSignalNumericGreaterThanToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.NUMERIC_GREATER_THAN, ImmutableList.of("-50.0")); - Map context = new HashMap<>(); - context.put("signal_key", "-49.09"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-49.09"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -500,11 +500,11 @@ public void testEvaluateConditionsCustomSignalNumericGreaterThanToTrue() { public void testEvaluateConditionsCustomSignalStringContainsToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.STRING_CONTAINS, ImmutableList.of("One", "hundred")); - Map context = new HashMap<>(); - context.put("signal_key", "Two hundred"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hundred"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -513,11 +513,11 @@ public void testEvaluateConditionsCustomSignalStringContainsToTrue() { public void testEvaluateConditionsCustomSignalStringContainsToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.STRING_CONTAINS, ImmutableList.of("One", "hundred")); - Map context = new HashMap<>(); - context.put("signal_key", "Two hudred"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hudred"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -526,11 +526,11 @@ public void testEvaluateConditionsCustomSignalStringContainsToFalse() { public void testEvaluateConditionsCustomSignalStringDoesNotContainToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.STRING_DOES_NOT_CONTAIN, ImmutableList.of("One", "hundred")); - Map context = new HashMap<>(); - context.put("signal_key", "Two hudred"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hudred"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -539,11 +539,11 @@ public void testEvaluateConditionsCustomSignalStringDoesNotContainToTrue() { public void testEvaluateConditionsCustomSignalStringDoesNotContainToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.STRING_DOES_NOT_CONTAIN, ImmutableList.of("One", "hundred")); - Map context = new HashMap<>(); - context.put("signal_key", "Two hundred"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hundred"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -552,11 +552,11 @@ public void testEvaluateConditionsCustomSignalStringDoesNotContainToFalse() { public void testEvaluateConditionsCustomSignalStringExactlyMatchesToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.STRING_EXACTLY_MATCHES, ImmutableList.of("hundred")); - Map context = new HashMap<>(); - context.put("signal_key", "hundred"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "hundred"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -565,11 +565,11 @@ public void testEvaluateConditionsCustomSignalStringExactlyMatchesToTrue() { public void testEvaluateConditionsCustomSignalStringExactlyMatchesToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.STRING_EXACTLY_MATCHES, ImmutableList.of("hundred")); - Map context = new HashMap<>(); - context.put("signal_key", "Two hundred"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hundred"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -578,11 +578,11 @@ public void testEvaluateConditionsCustomSignalStringExactlyMatchesToFalse() { public void testEvaluateConditionsCustomSignalStringContainsRegexToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.STRING_CONTAINS_REGEX, ImmutableList.of(".*hund.*")); - Map context = new HashMap<>(); - context.put("signal_key", "hundred"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "hundred"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -591,11 +591,11 @@ public void testEvaluateConditionsCustomSignalStringContainsRegexToTrue() { public void testEvaluateConditionsCustomSignalStringContainsRegexToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.STRING_CONTAINS_REGEX, ImmutableList.of("$hund.*")); - Map context = new HashMap<>(); - context.put("signal_key", "Two ahundred"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two ahundred"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -604,11 +604,11 @@ public void testEvaluateConditionsCustomSignalStringContainsRegexToFalse() { public void testEvaluateConditionCustomSignalSemanticLessThanToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN, ImmutableList.of("50.0.20")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0.2.0.1"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.2.0.1"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -617,11 +617,11 @@ public void testEvaluateConditionCustomSignalSemanticLessThanToTrue() { public void testEvaluateConditionCustomSignalSemanticLessThanToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN, ImmutableList.of("50.0.20")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0.20.0.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -630,11 +630,11 @@ public void testEvaluateConditionCustomSignalSemanticLessThanToFalse() { public void testEvaluateConditionCustomSignalSemanticLessThanInvalidVersionToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN, ImmutableList.of("50.0.-20")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0.2.0.1"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.2.0.1"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -643,11 +643,11 @@ public void testEvaluateConditionCustomSignalSemanticLessThanInvalidVersionToFal public void testEvaluateConditionCustomSignalSemanticLessEqualToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.SEMANTIC_VERSION_LESS_EQUAL, ImmutableList.of("50.0.20.0.0")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0.20.0.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -656,11 +656,11 @@ public void testEvaluateConditionCustomSignalSemanticLessEqualToTrue() { public void testEvaluateConditionCustomSignalSemanticLessEqualToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.SEMANTIC_VERSION_LESS_EQUAL, ImmutableList.of("50.0.2")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0.2.1.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.2.1.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -669,11 +669,11 @@ public void testEvaluateConditionCustomSignalSemanticLessEqualToFalse() { public void testEvaluateConditionCustomSignalSemanticGreaterThanToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.SEMANTIC_VERSION_GREATER_THAN, ImmutableList.of("50.0.2")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0.20.0.1"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.1"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -682,11 +682,11 @@ public void testEvaluateConditionCustomSignalSemanticGreaterThanToTrue() { public void testEvaluateConditionCustomSignalSemanticGreaterThanToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.SEMANTIC_VERSION_GREATER_THAN, ImmutableList.of("50.0.20")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0.20.0.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -695,11 +695,11 @@ public void testEvaluateConditionCustomSignalSemanticGreaterThanToFalse() { public void testEvaluateConditionCustomSignalSemanticGreaterEqualToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.SEMANTIC_VERSION_GREATER_EQUAL, ImmutableList.of("50.0.20")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0.20.0.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -708,11 +708,11 @@ public void testEvaluateConditionCustomSignalSemanticGreaterEqualToTrue() { public void testEvaluateConditionCustomSignalSemanticGreaterEqualToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.SEMANTIC_VERSION_GREATER_EQUAL, ImmutableList.of("50.0.20.1")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0.20.0.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -721,11 +721,11 @@ public void testEvaluateConditionCustomSignalSemanticGreaterEqualToFalse() { public void testEvaluateConditionCustomSignalSemanticEqualToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.SEMANTIC_VERSION_EQUAL, ImmutableList.of("50.0.20")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0.20.0.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -734,11 +734,11 @@ public void testEvaluateConditionCustomSignalSemanticEqualToTrue() { public void testEvaluateConditionCustomSignalSemanticEqualToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.SEMANTIC_VERSION_EQUAL, ImmutableList.of("50.0.20.1")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0.20.0.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -747,11 +747,11 @@ public void testEvaluateConditionCustomSignalSemanticEqualToFalse() { public void testEvaluateConditionCustomSignalSemanticNotEqualToTrue() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.SEMANTIC_VERSION_NOT_EQUAL, ImmutableList.of("50.0.20.1")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0.20.0.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertTrue(result.get("signal_key")); } @@ -760,11 +760,11 @@ public void testEvaluateConditionCustomSignalSemanticNotEqualToTrue() { public void testEvaluateConditionCustomSignalSemanticNotEqualToFalse() { Map conditions = createCustomSignalNamedCondition( CustomSignalOperator.SEMANTIC_VERSION_NOT_EQUAL, ImmutableList.of("50.0.20")); - Map context = new HashMap<>(); - context.put("signal_key", "50.0.20.0.0"); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); assertFalse(result.get("signal_key")); } @@ -787,11 +787,11 @@ private int evaluateRandomAssignments(OneOfCondition condition, int numOfAssignm conditions.put("is_enabled", condition); for (int i = 0; i < numOfAssignments; i++) { UUID randomizationId = UUID.randomUUID(); - Map context = new HashMap<>(); - context.put("randomizationId", randomizationId); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", randomizationId.toString()); ImmutableMap result = conditionEvaluator.evaluateConditions(conditions, - context); + contextBuilder.build()); if (result.get("is_enabled")) { evalTrueCount++; From 7b4d1fb23545defc0e3417673e54c27a9e1cafa8 Mon Sep 17 00:00:00 2001 From: Athira M Date: Tue, 10 Dec 2024 22:35:33 +0530 Subject: [PATCH 09/78] Branch for syncing with fetch --- .../remoteconfig/FirebaseRemoteConfig.java | 250 +++++++++++++----- .../FirebaseRemoteConfigClient.java | 3 + .../FirebaseRemoteConfigClientImpl.java | 13 + .../firebase/remoteconfig/KeysAndValues.java | 3 +- .../remoteconfig/ServerTemplate-dummy.java | 135 ++++++++++ .../firebase/remoteconfig/ServerTemplate.java | 144 ++-------- .../remoteconfig/ServerTemplateImpl.java | 0 .../internal/ServerTemplateResponse.java | 0 8 files changed, 352 insertions(+), 196 deletions(-) create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerTemplate-dummy.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java index 41a0afbe4..fa8eaa901 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java @@ -27,9 +27,12 @@ import com.google.firebase.internal.NonNull; /** - * This class is the entry point for all server-side Firebase Remote Config actions. + * This class is the entry point for all server-side Firebase Remote Config + * actions. * - *

You can get an instance of {@link FirebaseRemoteConfig} via {@link #getInstance(FirebaseApp)}, + *

+ * You can get an instance of {@link FirebaseRemoteConfig} via + * {@link #getInstance(FirebaseApp)}, * and then use it to manage Remote Config templates. */ public final class FirebaseRemoteConfig { @@ -49,22 +52,26 @@ private FirebaseRemoteConfig(FirebaseApp app) { } /** - * Gets the {@link FirebaseRemoteConfig} instance for the default {@link FirebaseApp}. + * Gets the {@link FirebaseRemoteConfig} instance for the default + * {@link FirebaseApp}. * - * @return The {@link FirebaseRemoteConfig} instance for the default {@link FirebaseApp}. + * @return The {@link FirebaseRemoteConfig} instance for the default + * {@link FirebaseApp}. */ public static FirebaseRemoteConfig getInstance() { return getInstance(FirebaseApp.getInstance()); } /** - * Gets the {@link FirebaseRemoteConfig} instance for the specified {@link FirebaseApp}. + * Gets the {@link FirebaseRemoteConfig} instance for the specified + * {@link FirebaseApp}. * - * @return The {@link FirebaseRemoteConfig} instance for the specified {@link FirebaseApp}. + * @return The {@link FirebaseRemoteConfig} instance for the specified + * {@link FirebaseApp}. */ public static synchronized FirebaseRemoteConfig getInstance(FirebaseApp app) { FirebaseRemoteConfigService service = ImplFirebaseTrampolines.getService(app, SERVICE_ID, - FirebaseRemoteConfigService.class); + FirebaseRemoteConfigService.class); if (service == null) { service = ImplFirebaseTrampolines.addService(app, new FirebaseRemoteConfigService(app)); } @@ -75,7 +82,8 @@ public static synchronized FirebaseRemoteConfig getInstance(FirebaseApp app) { * Gets the current active version of the Remote Config template. * * @return A {@link Template}. - * @throws FirebaseRemoteConfigException If an error occurs while getting the template. + * @throws FirebaseRemoteConfigException If an error occurs while getting the + * template. */ public Template getTemplate() throws FirebaseRemoteConfigException { return getTemplateOp().call(); @@ -85,7 +93,7 @@ public Template getTemplate() throws FirebaseRemoteConfigException { * Similar to {@link #getTemplate()} but performs the operation asynchronously. * * @return An {@code ApiFuture} that completes with a {@link Template} when - * the template is available. + * the template is available. */ public ApiFuture