1818
1919import static com .google .common .base .Preconditions .checkArgument ;
2020import static com .google .common .base .Preconditions .checkNotNull ;
21+ import static com .google .common .collect .ImmutableMap .toImmutableMap ;
2122
2223import com .google .common .collect .ImmutableList ;
24+ import com .google .common .collect .ImmutableMap ;
2325import com .google .firebase .internal .NonNull ;
2426import com .google .firebase .internal .Nullable ;
25-
2627import java .math .BigInteger ;
2728import java .nio .charset .StandardCharsets ;
2829import java .security .MessageDigest ;
3536import java .util .regex .Pattern ;
3637import java .util .regex .PatternSyntaxException ;
3738import java .util .stream .Collectors ;
38-
3939import org .slf4j .Logger ;
4040import org .slf4j .LoggerFactory ;
41-
41+
4242final class ConditionEvaluator {
4343 private static final int MAX_CONDITION_RECURSION_DEPTH = 10 ;
4444 private static final Logger logger = LoggerFactory .getLogger (ConditionEvaluator .class );
4545 private static final BigInteger MICRO_PERCENT_MODULO = BigInteger .valueOf (100_000_000L );
4646
4747 /**
4848 * Evaluates server conditions and assigns a boolean value to each condition.
49- *
49+ *
5050 * @param conditions List of conditions which are to be evaluated.
51- * @param context A map with additional metadata used during evaluation.
51+ * @param context A map with additional metadata used during evaluation.
5252 * @return A map of condition to evaluated value.
5353 */
5454 @ NonNull
5555 Map <String , Boolean > evaluateConditions (
56- @ NonNull List <ServerCondition > conditions ,
57- @ Nullable KeysAndValues context ) {
56+ @ NonNull List <ServerCondition > conditions , @ Nullable KeysAndValues context ) {
5857 checkNotNull (conditions , "List of conditions must not be null." );
5958 checkArgument (!conditions .isEmpty (), "List of conditions must not be empty." );
60- KeysAndValues evaluationContext = context != null
61- ? context
62- : new KeysAndValues .Builder ().build ();
63-
64- Map <String , Boolean > evaluatedConditions = conditions .stream ()
65- .collect (Collectors .toMap (
66- ServerCondition ::getName ,
67- condition ->
68- evaluateCondition (condition .getCondition (), evaluationContext , /* nestingLevel= */ 0 )
69- ));
59+ if (context == null || conditions .isEmpty ()) {
60+ return ImmutableMap .of ();
61+ }
62+ KeysAndValues evaluationContext =
63+ context != null ? context : new KeysAndValues .Builder ().build ();
64+
65+ Map <String , Boolean > evaluatedConditions =
66+ conditions .stream ()
67+ .collect (
68+ toImmutableMap (
69+ ServerCondition ::getName ,
70+ condition ->
71+ evaluateCondition (
72+ condition .getCondition (), evaluationContext , /* nestingLevel= */ 0 )));
7073
7174 return evaluatedConditions ;
7275 }
7376
74- private boolean evaluateCondition (OneOfCondition condition , KeysAndValues context ,
75- int nestingLevel ) {
77+ private boolean evaluateCondition (
78+ OneOfCondition condition , KeysAndValues context , int nestingLevel ) {
7679 if (nestingLevel > MAX_CONDITION_RECURSION_DEPTH ) {
7780 logger .warn ("Maximum condition recursion depth exceeded." );
7881 return false ;
@@ -95,30 +98,30 @@ private boolean evaluateCondition(OneOfCondition condition, KeysAndValues contex
9598 return false ;
9699 }
97100
98-
99- private boolean evaluateOrCondition (OrCondition condition , KeysAndValues context ,
100- int nestingLevel ) {
101+ private boolean evaluateOrCondition (
102+ OrCondition condition , KeysAndValues context , int nestingLevel ) {
101103 return condition .getConditions ().stream ()
102104 .anyMatch (subCondition -> evaluateCondition (subCondition , context , nestingLevel + 1 ));
103105 }
104106
105- private boolean evaluateAndCondition (AndCondition condition , KeysAndValues context ,
106- int nestingLevel ) {
107+ private boolean evaluateAndCondition (
108+ AndCondition condition , KeysAndValues context , int nestingLevel ) {
107109 return condition .getConditions ().stream ()
108110 .allMatch (subCondition -> evaluateCondition (subCondition , context , nestingLevel + 1 ));
109111 }
110112
111- private boolean evaluateCustomSignalCondition (CustomSignalCondition condition ,
112- KeysAndValues context ) {
113+ private boolean evaluateCustomSignalCondition (
114+ CustomSignalCondition condition , KeysAndValues context ) {
113115 CustomSignalOperator customSignalOperator = condition .getCustomSignalOperator ();
114116 String customSignalKey = condition .getCustomSignalKey ();
115- ImmutableList <String > targetCustomSignalValues = ImmutableList . copyOf (
116- condition .getTargetCustomSignalValues ());
117+ ImmutableList <String > targetCustomSignalValues =
118+ ImmutableList . copyOf ( condition .getTargetCustomSignalValues ());
117119
118120 if (targetCustomSignalValues .isEmpty ()) {
119- logger .warn (String .format (
120- "Values must be assigned to all custom signal fields. Operator:%s, Key:%s, Values:%s" ,
121- customSignalOperator , customSignalKey , targetCustomSignalValues ));
121+ logger .warn (
122+ String .format (
123+ "Values must be assigned to all custom signal fields. Operator:%s, Key:%s, Values:%s" ,
124+ customSignalOperator , customSignalKey , targetCustomSignalValues ));
122125 return false ;
123126 }
124127
@@ -130,64 +133,65 @@ private boolean evaluateCustomSignalCondition(CustomSignalCondition condition,
130133 switch (customSignalOperator ) {
131134 // String operations.
132135 case STRING_CONTAINS :
133- return compareStrings (targetCustomSignalValues , customSignalValue ,
136+ return compareStrings (
137+ targetCustomSignalValues ,
138+ customSignalValue ,
134139 (customSignal , targetSignal ) -> customSignal .contains (targetSignal ));
135140 case STRING_DOES_NOT_CONTAIN :
136- return !compareStrings (targetCustomSignalValues , customSignalValue ,
141+ return !compareStrings (
142+ targetCustomSignalValues ,
143+ customSignalValue ,
137144 (customSignal , targetSignal ) -> customSignal .contains (targetSignal ));
138145 case STRING_EXACTLY_MATCHES :
139- return compareStrings (targetCustomSignalValues , customSignalValue ,
146+ return compareStrings (
147+ targetCustomSignalValues ,
148+ customSignalValue ,
140149 (customSignal , targetSignal ) -> customSignal .equals (targetSignal ));
141150 case STRING_CONTAINS_REGEX :
142- return compareStrings (targetCustomSignalValues , customSignalValue ,
151+ return compareStrings (
152+ targetCustomSignalValues ,
153+ customSignalValue ,
143154 (customSignal , targetSignal ) -> compareStringRegex (customSignal , targetSignal ));
144155
145156 // Numeric operations.
146157 case NUMERIC_LESS_THAN :
147- return compareNumbers (targetCustomSignalValues , customSignalValue ,
148- (result ) -> result < 0 );
158+ return compareNumbers (targetCustomSignalValues , customSignalValue , (result ) -> result < 0 );
149159 case NUMERIC_LESS_EQUAL :
150- return compareNumbers (targetCustomSignalValues , customSignalValue ,
151- (result ) -> result <= 0 );
160+ return compareNumbers (targetCustomSignalValues , customSignalValue , (result ) -> result <= 0 );
152161 case NUMERIC_EQUAL :
153- return compareNumbers (targetCustomSignalValues , customSignalValue ,
154- (result ) -> result == 0 );
162+ return compareNumbers (targetCustomSignalValues , customSignalValue , (result ) -> result == 0 );
155163 case NUMERIC_NOT_EQUAL :
156- return compareNumbers (targetCustomSignalValues , customSignalValue ,
157- (result ) -> result != 0 );
164+ return compareNumbers (targetCustomSignalValues , customSignalValue , (result ) -> result != 0 );
158165 case NUMERIC_GREATER_THAN :
159- return compareNumbers (targetCustomSignalValues , customSignalValue ,
160- (result ) -> result > 0 );
166+ return compareNumbers (targetCustomSignalValues , customSignalValue , (result ) -> result > 0 );
161167 case NUMERIC_GREATER_EQUAL :
162- return compareNumbers (targetCustomSignalValues , customSignalValue ,
163- (result ) -> result >= 0 );
168+ return compareNumbers (targetCustomSignalValues , customSignalValue , (result ) -> result >= 0 );
164169
165170 // Semantic operations.
166171 case SEMANTIC_VERSION_EQUAL :
167- return compareSemanticVersions (targetCustomSignalValues , customSignalValue ,
168- (result ) -> result == 0 );
172+ return compareSemanticVersions (
173+ targetCustomSignalValues , customSignalValue , (result ) -> result == 0 );
169174 case SEMANTIC_VERSION_GREATER_EQUAL :
170- return compareSemanticVersions (targetCustomSignalValues , customSignalValue ,
171- (result ) -> result >= 0 );
175+ return compareSemanticVersions (
176+ targetCustomSignalValues , customSignalValue , (result ) -> result >= 0 );
172177 case SEMANTIC_VERSION_GREATER_THAN :
173- return compareSemanticVersions (targetCustomSignalValues , customSignalValue ,
174- (result ) -> result > 0 );
178+ return compareSemanticVersions (
179+ targetCustomSignalValues , customSignalValue , (result ) -> result > 0 );
175180 case SEMANTIC_VERSION_LESS_EQUAL :
176- return compareSemanticVersions (targetCustomSignalValues , customSignalValue ,
177- (result ) -> result <= 0 );
181+ return compareSemanticVersions (
182+ targetCustomSignalValues , customSignalValue , (result ) -> result <= 0 );
178183 case SEMANTIC_VERSION_LESS_THAN :
179- return compareSemanticVersions (targetCustomSignalValues , customSignalValue ,
180- (result ) -> result < 0 );
184+ return compareSemanticVersions (
185+ targetCustomSignalValues , customSignalValue , (result ) -> result < 0 );
181186 case SEMANTIC_VERSION_NOT_EQUAL :
182- return compareSemanticVersions (targetCustomSignalValues , customSignalValue ,
183- (result ) -> result != 0 );
187+ return compareSemanticVersions (
188+ targetCustomSignalValues , customSignalValue , (result ) -> result != 0 );
184189 default :
185190 return false ;
186191 }
187192 }
188193
189- private boolean evaluatePercentCondition (PercentCondition condition ,
190- KeysAndValues context ) {
194+ private boolean evaluatePercentCondition (PercentCondition condition , KeysAndValues context ) {
191195 if (!context .containsKey ("randomizationId" )) {
192196 logger .warn ("Percentage operation must not be performed without randomizationId" );
193197 return false ;
@@ -197,18 +201,16 @@ private boolean evaluatePercentCondition(PercentCondition condition,
197201
198202 // The micro-percent interval to be used with the BETWEEN operator.
199203 MicroPercentRange microPercentRange = condition .getMicroPercentRange ();
200- int microPercentUpperBound = microPercentRange != null
201- ? microPercentRange .getMicroPercentUpperBound ()
202- : 0 ;
203- int microPercentLowerBound = microPercentRange != null
204- ? microPercentRange .getMicroPercentLowerBound ()
205- : 0 ;
204+ int microPercentUpperBound =
205+ microPercentRange != null ? microPercentRange .getMicroPercentUpperBound () : 0 ;
206+ int microPercentLowerBound =
207+ microPercentRange != null ? microPercentRange .getMicroPercentLowerBound () : 0 ;
206208 // The limit of percentiles to target in micro-percents when using the
207209 // LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0
208210 // and 100000000].
209211 int microPercent = condition .getMicroPercent ();
210- BigInteger microPercentile = getMicroPercentile ( condition . getSeed (),
211- context .get ("randomizationId" ));
212+ BigInteger microPercentile =
213+ getMicroPercentile ( condition . getSeed (), context .get ("randomizationId" ));
212214 switch (operator ) {
213215 case LESS_OR_EQUAL :
214216 return microPercentile .compareTo (BigInteger .valueOf (microPercent )) <= 0 ;
@@ -246,10 +248,12 @@ private BigInteger hashSeededRandomizationId(String seededRandomizationId) {
246248 }
247249 }
248250
249- private boolean compareStrings (ImmutableList <String > targetValues , String customSignal ,
250- BiPredicate <String , String > compareFunction ) {
251- return targetValues .stream ().anyMatch (targetValue ->
252- compareFunction .test (customSignal , targetValue ));
251+ private boolean compareStrings (
252+ ImmutableList <String > targetValues ,
253+ String customSignal ,
254+ BiPredicate <String , String > compareFunction ) {
255+ return targetValues .stream ()
256+ .anyMatch (targetValue -> compareFunction .test (customSignal , targetValue ));
253257 }
254258
255259 private boolean compareStringRegex (String customSignal , String targetSignal ) {
@@ -260,12 +264,13 @@ private boolean compareStringRegex(String customSignal, String targetSignal) {
260264 }
261265 }
262266
263- private boolean compareNumbers (ImmutableList < String > targetValues , String customSignal ,
264- IntPredicate compareFunction ) {
267+ private boolean compareNumbers (
268+ ImmutableList < String > targetValues , String customSignal , IntPredicate compareFunction ) {
265269 if (targetValues .size () != 1 ) {
266- logger .warn (String .format (
267- "Target values must contain 1 element for numeric operations. Target Value: %s" ,
268- targetValues ));
270+ logger .warn (
271+ String .format (
272+ "Target values must contain 1 element for numeric operations. Target Value: %s" ,
273+ targetValues ));
269274 return false ;
270275 }
271276
@@ -275,23 +280,22 @@ private boolean compareNumbers(ImmutableList<String> targetValues, String custom
275280 int comparisonResult = Double .compare (customSignalDouble , targetValue );
276281 return compareFunction .test (comparisonResult );
277282 } catch (NumberFormatException e ) {
278- logger .warn ("Error parsing numeric values: customSignal=%s, targetValue=%s" ,
283+ logger .warn (
284+ "Error parsing numeric values: customSignal=%s, targetValue=%s" ,
279285 customSignal , targetValues .get (0 ), e );
280286 return false ;
281287 }
282288 }
283289
284- private boolean compareSemanticVersions (ImmutableList <String > targetValues ,
285- String customSignal ,
286- IntPredicate compareFunction ) {
290+ private boolean compareSemanticVersions (
291+ ImmutableList <String > targetValues , String customSignal , IntPredicate compareFunction ) {
287292 if (targetValues .size () != 1 ) {
288293 logger .warn (String .format ("Target values must contain 1 element for semantic operation." ));
289294 return false ;
290295 }
291296
292297 String targetValueString = targetValues .get (0 );
293- if (!validateSemanticVersion (targetValueString )
294- || !validateSemanticVersion (customSignal )) {
298+ if (!validateSemanticVersion (targetValueString ) || !validateSemanticVersion (customSignal )) {
295299 return false ;
296300 }
297301
@@ -300,7 +304,8 @@ private boolean compareSemanticVersions(ImmutableList<String> targetValues,
300304
301305 int maxLength = 5 ;
302306 if (targetVersion .size () > maxLength || customSignalVersion .size () > maxLength ) {
303- logger .warn ("Semantic version max length(%s) exceeded. Target: %s, Custom Signal: %s" ,
307+ logger .warn (
308+ "Semantic version max length(%s) exceeded. Target: %s, Custom Signal: %s" ,
304309 maxLength , targetValueString , customSignal );
305310 return false ;
306311 }
@@ -316,8 +321,8 @@ private int compareSemanticVersions(List<Integer> version1, List<Integer> versio
316321
317322 for (int i = 0 ; i < maxLength ; i ++) {
318323 // Default to 0 if segment is missing
319- int v1 = i < version1Size ? version1 .get (i ) : 0 ;
320- int v2 = i < version2Size ? version2 .get (i ) : 0 ;
324+ int v1 = i < version1Size ? version1 .get (i ) : 0 ;
325+ int v2 = i < version2Size ? version2 .get (i ) : 0 ;
321326
322327 int comparison = Integer .compare (v1 , v2 );
323328 if (comparison != 0 ) {
@@ -330,8 +335,8 @@ private int compareSemanticVersions(List<Integer> version1, List<Integer> versio
330335
331336 private List <Integer > parseSemanticVersion (String versionString ) {
332337 return Arrays .stream (versionString .split ("\\ ." ))
333- .map (Integer ::parseInt )
334- .collect (Collectors .toList ());
338+ .map (Integer ::parseInt )
339+ .collect (Collectors .toList ());
335340 }
336341
337342 private boolean validateSemanticVersion (String version ) {
0 commit comments