18
18
19
19
import static com .google .common .base .Preconditions .checkArgument ;
20
20
import static com .google .common .base .Preconditions .checkNotNull ;
21
+ import static com .google .common .collect .ImmutableMap .toImmutableMap ;
21
22
22
23
import com .google .common .collect .ImmutableList ;
24
+ import com .google .common .collect .ImmutableMap ;
23
25
import com .google .firebase .internal .NonNull ;
24
26
import com .google .firebase .internal .Nullable ;
25
-
26
27
import java .math .BigInteger ;
27
28
import java .nio .charset .StandardCharsets ;
28
29
import java .security .MessageDigest ;
35
36
import java .util .regex .Pattern ;
36
37
import java .util .regex .PatternSyntaxException ;
37
38
import java .util .stream .Collectors ;
38
-
39
39
import org .slf4j .Logger ;
40
40
import org .slf4j .LoggerFactory ;
41
-
41
+
42
42
final class ConditionEvaluator {
43
43
private static final int MAX_CONDITION_RECURSION_DEPTH = 10 ;
44
44
private static final Logger logger = LoggerFactory .getLogger (ConditionEvaluator .class );
45
45
private static final BigInteger MICRO_PERCENT_MODULO = BigInteger .valueOf (100_000_000L );
46
46
47
47
/**
48
48
* Evaluates server conditions and assigns a boolean value to each condition.
49
- *
49
+ *
50
50
* @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.
52
52
* @return A map of condition to evaluated value.
53
53
*/
54
54
@ NonNull
55
55
Map <String , Boolean > evaluateConditions (
56
- @ NonNull List <ServerCondition > conditions ,
57
- @ Nullable KeysAndValues context ) {
56
+ @ NonNull List <ServerCondition > conditions , @ Nullable KeysAndValues context ) {
58
57
checkNotNull (conditions , "List of conditions must not be null." );
59
58
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 )));
70
73
71
74
return evaluatedConditions ;
72
75
}
73
76
74
- private boolean evaluateCondition (OneOfCondition condition , KeysAndValues context ,
75
- int nestingLevel ) {
77
+ private boolean evaluateCondition (
78
+ OneOfCondition condition , KeysAndValues context , int nestingLevel ) {
76
79
if (nestingLevel > MAX_CONDITION_RECURSION_DEPTH ) {
77
80
logger .warn ("Maximum condition recursion depth exceeded." );
78
81
return false ;
@@ -95,30 +98,30 @@ private boolean evaluateCondition(OneOfCondition condition, KeysAndValues contex
95
98
return false ;
96
99
}
97
100
98
-
99
- private boolean evaluateOrCondition (OrCondition condition , KeysAndValues context ,
100
- int nestingLevel ) {
101
+ private boolean evaluateOrCondition (
102
+ OrCondition condition , KeysAndValues context , int nestingLevel ) {
101
103
return condition .getConditions ().stream ()
102
104
.anyMatch (subCondition -> evaluateCondition (subCondition , context , nestingLevel + 1 ));
103
105
}
104
106
105
- private boolean evaluateAndCondition (AndCondition condition , KeysAndValues context ,
106
- int nestingLevel ) {
107
+ private boolean evaluateAndCondition (
108
+ AndCondition condition , KeysAndValues context , int nestingLevel ) {
107
109
return condition .getConditions ().stream ()
108
110
.allMatch (subCondition -> evaluateCondition (subCondition , context , nestingLevel + 1 ));
109
111
}
110
112
111
- private boolean evaluateCustomSignalCondition (CustomSignalCondition condition ,
112
- KeysAndValues context ) {
113
+ private boolean evaluateCustomSignalCondition (
114
+ CustomSignalCondition condition , KeysAndValues context ) {
113
115
CustomSignalOperator customSignalOperator = condition .getCustomSignalOperator ();
114
116
String customSignalKey = condition .getCustomSignalKey ();
115
- ImmutableList <String > targetCustomSignalValues = ImmutableList . copyOf (
116
- condition .getTargetCustomSignalValues ());
117
+ ImmutableList <String > targetCustomSignalValues =
118
+ ImmutableList . copyOf ( condition .getTargetCustomSignalValues ());
117
119
118
120
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 ));
122
125
return false ;
123
126
}
124
127
@@ -130,64 +133,65 @@ private boolean evaluateCustomSignalCondition(CustomSignalCondition condition,
130
133
switch (customSignalOperator ) {
131
134
// String operations.
132
135
case STRING_CONTAINS :
133
- return compareStrings (targetCustomSignalValues , customSignalValue ,
136
+ return compareStrings (
137
+ targetCustomSignalValues ,
138
+ customSignalValue ,
134
139
(customSignal , targetSignal ) -> customSignal .contains (targetSignal ));
135
140
case STRING_DOES_NOT_CONTAIN :
136
- return !compareStrings (targetCustomSignalValues , customSignalValue ,
141
+ return !compareStrings (
142
+ targetCustomSignalValues ,
143
+ customSignalValue ,
137
144
(customSignal , targetSignal ) -> customSignal .contains (targetSignal ));
138
145
case STRING_EXACTLY_MATCHES :
139
- return compareStrings (targetCustomSignalValues , customSignalValue ,
146
+ return compareStrings (
147
+ targetCustomSignalValues ,
148
+ customSignalValue ,
140
149
(customSignal , targetSignal ) -> customSignal .equals (targetSignal ));
141
150
case STRING_CONTAINS_REGEX :
142
- return compareStrings (targetCustomSignalValues , customSignalValue ,
151
+ return compareStrings (
152
+ targetCustomSignalValues ,
153
+ customSignalValue ,
143
154
(customSignal , targetSignal ) -> compareStringRegex (customSignal , targetSignal ));
144
155
145
156
// Numeric operations.
146
157
case NUMERIC_LESS_THAN :
147
- return compareNumbers (targetCustomSignalValues , customSignalValue ,
148
- (result ) -> result < 0 );
158
+ return compareNumbers (targetCustomSignalValues , customSignalValue , (result ) -> result < 0 );
149
159
case NUMERIC_LESS_EQUAL :
150
- return compareNumbers (targetCustomSignalValues , customSignalValue ,
151
- (result ) -> result <= 0 );
160
+ return compareNumbers (targetCustomSignalValues , customSignalValue , (result ) -> result <= 0 );
152
161
case NUMERIC_EQUAL :
153
- return compareNumbers (targetCustomSignalValues , customSignalValue ,
154
- (result ) -> result == 0 );
162
+ return compareNumbers (targetCustomSignalValues , customSignalValue , (result ) -> result == 0 );
155
163
case NUMERIC_NOT_EQUAL :
156
- return compareNumbers (targetCustomSignalValues , customSignalValue ,
157
- (result ) -> result != 0 );
164
+ return compareNumbers (targetCustomSignalValues , customSignalValue , (result ) -> result != 0 );
158
165
case NUMERIC_GREATER_THAN :
159
- return compareNumbers (targetCustomSignalValues , customSignalValue ,
160
- (result ) -> result > 0 );
166
+ return compareNumbers (targetCustomSignalValues , customSignalValue , (result ) -> result > 0 );
161
167
case NUMERIC_GREATER_EQUAL :
162
- return compareNumbers (targetCustomSignalValues , customSignalValue ,
163
- (result ) -> result >= 0 );
168
+ return compareNumbers (targetCustomSignalValues , customSignalValue , (result ) -> result >= 0 );
164
169
165
170
// Semantic operations.
166
171
case SEMANTIC_VERSION_EQUAL :
167
- return compareSemanticVersions (targetCustomSignalValues , customSignalValue ,
168
- (result ) -> result == 0 );
172
+ return compareSemanticVersions (
173
+ targetCustomSignalValues , customSignalValue , (result ) -> result == 0 );
169
174
case SEMANTIC_VERSION_GREATER_EQUAL :
170
- return compareSemanticVersions (targetCustomSignalValues , customSignalValue ,
171
- (result ) -> result >= 0 );
175
+ return compareSemanticVersions (
176
+ targetCustomSignalValues , customSignalValue , (result ) -> result >= 0 );
172
177
case SEMANTIC_VERSION_GREATER_THAN :
173
- return compareSemanticVersions (targetCustomSignalValues , customSignalValue ,
174
- (result ) -> result > 0 );
178
+ return compareSemanticVersions (
179
+ targetCustomSignalValues , customSignalValue , (result ) -> result > 0 );
175
180
case SEMANTIC_VERSION_LESS_EQUAL :
176
- return compareSemanticVersions (targetCustomSignalValues , customSignalValue ,
177
- (result ) -> result <= 0 );
181
+ return compareSemanticVersions (
182
+ targetCustomSignalValues , customSignalValue , (result ) -> result <= 0 );
178
183
case SEMANTIC_VERSION_LESS_THAN :
179
- return compareSemanticVersions (targetCustomSignalValues , customSignalValue ,
180
- (result ) -> result < 0 );
184
+ return compareSemanticVersions (
185
+ targetCustomSignalValues , customSignalValue , (result ) -> result < 0 );
181
186
case SEMANTIC_VERSION_NOT_EQUAL :
182
- return compareSemanticVersions (targetCustomSignalValues , customSignalValue ,
183
- (result ) -> result != 0 );
187
+ return compareSemanticVersions (
188
+ targetCustomSignalValues , customSignalValue , (result ) -> result != 0 );
184
189
default :
185
190
return false ;
186
191
}
187
192
}
188
193
189
- private boolean evaluatePercentCondition (PercentCondition condition ,
190
- KeysAndValues context ) {
194
+ private boolean evaluatePercentCondition (PercentCondition condition , KeysAndValues context ) {
191
195
if (!context .containsKey ("randomizationId" )) {
192
196
logger .warn ("Percentage operation must not be performed without randomizationId" );
193
197
return false ;
@@ -197,18 +201,16 @@ private boolean evaluatePercentCondition(PercentCondition condition,
197
201
198
202
// The micro-percent interval to be used with the BETWEEN operator.
199
203
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 ;
206
208
// The limit of percentiles to target in micro-percents when using the
207
209
// LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0
208
210
// and 100000000].
209
211
int microPercent = condition .getMicroPercent ();
210
- BigInteger microPercentile = getMicroPercentile ( condition . getSeed (),
211
- context .get ("randomizationId" ));
212
+ BigInteger microPercentile =
213
+ getMicroPercentile ( condition . getSeed (), context .get ("randomizationId" ));
212
214
switch (operator ) {
213
215
case LESS_OR_EQUAL :
214
216
return microPercentile .compareTo (BigInteger .valueOf (microPercent )) <= 0 ;
@@ -246,10 +248,12 @@ private BigInteger hashSeededRandomizationId(String seededRandomizationId) {
246
248
}
247
249
}
248
250
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 ));
253
257
}
254
258
255
259
private boolean compareStringRegex (String customSignal , String targetSignal ) {
@@ -260,12 +264,13 @@ private boolean compareStringRegex(String customSignal, String targetSignal) {
260
264
}
261
265
}
262
266
263
- private boolean compareNumbers (ImmutableList < String > targetValues , String customSignal ,
264
- IntPredicate compareFunction ) {
267
+ private boolean compareNumbers (
268
+ ImmutableList < String > targetValues , String customSignal , IntPredicate compareFunction ) {
265
269
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 ));
269
274
return false ;
270
275
}
271
276
@@ -275,23 +280,22 @@ private boolean compareNumbers(ImmutableList<String> targetValues, String custom
275
280
int comparisonResult = Double .compare (customSignalDouble , targetValue );
276
281
return compareFunction .test (comparisonResult );
277
282
} 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" ,
279
285
customSignal , targetValues .get (0 ), e );
280
286
return false ;
281
287
}
282
288
}
283
289
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 ) {
287
292
if (targetValues .size () != 1 ) {
288
293
logger .warn (String .format ("Target values must contain 1 element for semantic operation." ));
289
294
return false ;
290
295
}
291
296
292
297
String targetValueString = targetValues .get (0 );
293
- if (!validateSemanticVersion (targetValueString )
294
- || !validateSemanticVersion (customSignal )) {
298
+ if (!validateSemanticVersion (targetValueString ) || !validateSemanticVersion (customSignal )) {
295
299
return false ;
296
300
}
297
301
@@ -300,7 +304,8 @@ private boolean compareSemanticVersions(ImmutableList<String> targetValues,
300
304
301
305
int maxLength = 5 ;
302
306
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" ,
304
309
maxLength , targetValueString , customSignal );
305
310
return false ;
306
311
}
@@ -316,8 +321,8 @@ private int compareSemanticVersions(List<Integer> version1, List<Integer> versio
316
321
317
322
for (int i = 0 ; i < maxLength ; i ++) {
318
323
// 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 ;
321
326
322
327
int comparison = Integer .compare (v1 , v2 );
323
328
if (comparison != 0 ) {
@@ -330,8 +335,8 @@ private int compareSemanticVersions(List<Integer> version1, List<Integer> versio
330
335
331
336
private List <Integer > parseSemanticVersion (String versionString ) {
332
337
return Arrays .stream (versionString .split ("\\ ." ))
333
- .map (Integer ::parseInt )
334
- .collect (Collectors .toList ());
338
+ .map (Integer ::parseInt )
339
+ .collect (Collectors .toList ());
335
340
}
336
341
337
342
private boolean validateSemanticVersion (String version ) {
0 commit comments