Skip to content

Commit c6da789

Browse files
author
Mike Davis
authored
Add MatchRegistry for custom match implementations. (#390)
* Replace MatchType in favor of MatchRegistry * Add conditionValue to Match#match interface. * Remove the abstract AttributeMatch class. * Remove ExactNumberMatch class. * Add NumberComparator for standardizing Number comparisons * Refactor Semantic Versioning to use static convenience method Incorporating a public MatchRegistry will allow consumers to provide their own implementations of the Match interface.
1 parent 2ffd6d9 commit c6da789

29 files changed

+607
-524
lines changed

core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@
2020
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
2121
import com.fasterxml.jackson.annotation.JsonProperty;
2222
import com.optimizely.ab.config.ProjectConfig;
23-
import com.optimizely.ab.config.audience.match.Match;
24-
import com.optimizely.ab.config.audience.match.MatchType;
25-
import com.optimizely.ab.config.audience.match.UnexpectedValueTypeException;
26-
import com.optimizely.ab.config.audience.match.UnknownMatchTypeException;
23+
import com.optimizely.ab.config.audience.match.*;
2724
import org.slf4j.Logger;
2825
import org.slf4j.LoggerFactory;
2926

@@ -87,35 +84,36 @@ public Boolean evaluate(ProjectConfig config, Map<String, ?> attributes) {
8784
}
8885
// check user attribute value is equal
8986
try {
90-
Match matchType = MatchType.getMatchType(match, value).getMatcher();
91-
Boolean result = matchType.eval(userAttributeValue);
92-
87+
Match matcher = MatchRegistry.getMatch(match);
88+
Boolean result = matcher.eval(value, userAttributeValue);
9389
if (result == null) {
94-
if (!attributes.containsKey(name)) {
95-
//Missing attribute value
96-
logger.debug("Audience condition \"{}\" evaluated to UNKNOWN because no value was passed for user attribute \"{}\"", this, name);
90+
throw new UnknownValueTypeException();
91+
}
92+
93+
return result;
94+
} catch(UnknownValueTypeException e) {
95+
if (!attributes.containsKey(name)) {
96+
//Missing attribute value
97+
logger.debug("Audience condition \"{}\" evaluated to UNKNOWN because no value was passed for user attribute \"{}\"", this, name);
98+
} else {
99+
//if attribute value is not valid
100+
if (userAttributeValue != null) {
101+
logger.warn(
102+
"Audience condition \"{}\" evaluated to UNKNOWN because a value of type \"{}\" was passed for user attribute \"{}\"",
103+
this,
104+
userAttributeValue.getClass().getCanonicalName(),
105+
name);
97106
} else {
98-
//if attribute value is not valid
99-
if (userAttributeValue != null) {
100-
logger.warn(
101-
"Audience condition \"{}\" evaluated to UNKNOWN because a value of type \"{}\" was passed for user attribute \"{}\"",
102-
this,
103-
userAttributeValue.getClass().getCanonicalName(),
104-
name);
105-
} else {
106-
logger.debug(
107-
"Audience condition \"{}\" evaluated to UNKNOWN because a null value was passed for user attribute \"{}\"",
108-
this,
109-
name);
110-
}
107+
logger.debug(
108+
"Audience condition \"{}\" evaluated to UNKNOWN because a null value was passed for user attribute \"{}\"",
109+
this,
110+
name);
111111
}
112112
}
113-
return result;
114-
} catch (UnknownMatchTypeException | UnexpectedValueTypeException ex) {
115-
logger.warn("Audience condition \"{}\" " + ex.getMessage(),
116-
this);
117-
} catch (NullPointerException np) {
118-
logger.error("attribute or value null for match {}", match != null ? match : "legacy condition", np);
113+
} catch (UnknownMatchTypeException | UnexpectedValueTypeException e) {
114+
logger.warn("Audience condition \"{}\" " + e.getMessage(), this);
115+
} catch (NullPointerException e) {
116+
logger.error("attribute or value null for match {}", match != null ? match : "legacy condition", e);
119117
}
120118
return null;
121119
}

core-api/src/main/java/com/optimizely/ab/config/audience/match/DefaultMatchForLegacyAttributes.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2018-2019, Optimizely and contributors
3+
* Copyright 2018-2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -21,17 +21,16 @@
2121
/**
2222
* This is a temporary class. It mimics the current behaviour for
2323
* legacy custom attributes. This will be dropped for ExactMatch and the unit tests need to be fixed.
24-
* @param <T>
2524
*/
26-
class DefaultMatchForLegacyAttributes<T> extends AttributeMatch<T> {
27-
T value;
28-
29-
protected DefaultMatchForLegacyAttributes(T value) {
30-
this.value = value;
31-
}
32-
25+
class DefaultMatchForLegacyAttributes implements Match {
3326
@Nullable
34-
public Boolean eval(Object attributeValue) {
35-
return value.equals(castToValueType(attributeValue, value));
27+
public Boolean eval(Object conditionValue, Object attributeValue) throws UnexpectedValueTypeException {
28+
if (!(conditionValue instanceof String)) {
29+
throw new UnexpectedValueTypeException();
30+
}
31+
if (attributeValue == null) {
32+
return false;
33+
}
34+
return conditionValue.toString().equals(attributeValue.toString());
3635
}
3736
}
Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2018-2019, Optimizely and contributors
3+
* Copyright 2018-2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -18,18 +18,31 @@
1818

1919
import javax.annotation.Nullable;
2020

21-
class ExactMatch<T> extends AttributeMatch<T> {
22-
T value;
23-
24-
protected ExactMatch(T value) {
25-
this.value = value;
26-
}
21+
import static com.optimizely.ab.internal.AttributesUtil.isValidNumber;
2722

23+
/**
24+
* ExactMatch supports matching Numbers, Strings and Booleans. Numbers are first converted to doubles
25+
* before the comparison is evaluated. See {@link NumberComparator} Strings and Booleans are evaulated
26+
* via the Object equals method.
27+
*/
28+
class ExactMatch implements Match {
2829
@Nullable
29-
public Boolean eval(Object attributeValue) {
30-
T converted = castToValueType(attributeValue, value);
31-
if (value != null && converted == null) return null;
30+
public Boolean eval(Object conditionValue, Object attributeValue) throws UnexpectedValueTypeException {
31+
if (isValidNumber(attributeValue)) {
32+
if (isValidNumber(conditionValue)) {
33+
return NumberComparator.compareUnsafe(attributeValue, conditionValue) == 0;
34+
}
35+
return null;
36+
}
37+
38+
if (!(conditionValue instanceof String || conditionValue instanceof Boolean)) {
39+
throw new UnexpectedValueTypeException();
40+
}
41+
42+
if (attributeValue == null || attributeValue.getClass() != conditionValue.getClass()) {
43+
return null;
44+
}
3245

33-
return value == null ? attributeValue == null : value.equals(converted);
46+
return conditionValue.equals(attributeValue);
3447
}
3548
}

core-api/src/main/java/com/optimizely/ab/config/audience/match/ExactNumberMatch.java

Lines changed: 0 additions & 47 deletions
This file was deleted.
Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2018-2019, Optimizely and contributors
3+
* Copyright 2018-2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -16,20 +16,14 @@
1616
*/
1717
package com.optimizely.ab.config.audience.match;
1818

19-
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
20-
2119
import javax.annotation.Nullable;
2220

21+
/**
22+
* ExistsMatch checks that the attribute value is NOT null.
23+
*/
2324
class ExistsMatch implements Match {
24-
@SuppressFBWarnings("URF_UNREAD_FIELD")
25-
Object value;
26-
27-
protected ExistsMatch(Object value) {
28-
this.value = value;
29-
}
30-
3125
@Nullable
32-
public Boolean eval(Object attributeValue) {
26+
public Boolean eval(Object conditionValue, Object attributeValue) {
3327
return attributeValue != null;
3428
}
3529
}

core-api/src/main/java/com/optimizely/ab/config/audience/match/GEMatch.java

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,12 @@
1818

1919
import javax.annotation.Nullable;
2020

21-
import static com.optimizely.ab.internal.AttributesUtil.isValidNumber;
22-
23-
class GEMatch extends AttributeMatch<Number> {
24-
Number value;
25-
26-
protected GEMatch(Number value) {
27-
this.value = value;
28-
}
29-
21+
/**
22+
* GEMatch performs a "greater than or equal to" number comparison via {@link NumberComparator}.
23+
*/
24+
class GEMatch implements Match {
3025
@Nullable
31-
public Boolean eval(Object attributeValue) {
32-
try {
33-
if(isValidNumber(attributeValue)) {
34-
return castToValueType(attributeValue, value).doubleValue() >= value.doubleValue();
35-
}
36-
} catch (Exception e) {
37-
return null;
38-
}
39-
return null;
26+
public Boolean eval(Object conditionValue, Object attributeValue) throws UnknownValueTypeException {
27+
return NumberComparator.compare(attributeValue, conditionValue) >= 0;
4028
}
4129
}
Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2018-2019, Optimizely and contributors
3+
* Copyright 2018-2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -18,24 +18,12 @@
1818

1919
import javax.annotation.Nullable;
2020

21-
import static com.optimizely.ab.internal.AttributesUtil.isValidNumber;
22-
23-
class GTMatch extends AttributeMatch<Number> {
24-
Number value;
25-
26-
protected GTMatch(Number value) {
27-
this.value = value;
28-
}
29-
21+
/**
22+
* GTMatch performs a "greater than" number comparison via {@link NumberComparator}.
23+
*/
24+
class GTMatch implements Match {
3025
@Nullable
31-
public Boolean eval(Object attributeValue) {
32-
try {
33-
if(isValidNumber(attributeValue)) {
34-
return castToValueType(attributeValue, value).doubleValue() > value.doubleValue();
35-
}
36-
} catch (Exception e) {
37-
return null;
38-
}
39-
return null;
26+
public Boolean eval(Object conditionValue, Object attributeValue) throws UnknownValueTypeException {
27+
return NumberComparator.compare(attributeValue, conditionValue) > 0;
4028
}
4129
}

core-api/src/main/java/com/optimizely/ab/config/audience/match/LEMatch.java

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,13 @@
1818

1919
import javax.annotation.Nullable;
2020

21-
import static com.optimizely.ab.internal.AttributesUtil.isValidNumber;
22-
23-
class LEMatch extends AttributeMatch<Number> {
24-
Number value;
25-
26-
protected LEMatch(Number value) {
27-
this.value = value;
28-
}
29-
21+
/**
22+
* GEMatch performs a "less than or equal to" number comparison via {@link NumberComparator}.
23+
*/
24+
class LEMatch implements Match {
3025
@Nullable
31-
public Boolean eval(Object attributeValue) {
32-
try {
33-
if(isValidNumber(attributeValue)) {
34-
return castToValueType(attributeValue, value).doubleValue() <= value.doubleValue();
35-
}
36-
} catch (Exception e) {
37-
return null;
38-
}
39-
return null;
26+
public Boolean eval(Object conditionValue, Object attributeValue) throws UnknownValueTypeException {
27+
return NumberComparator.compare(attributeValue, conditionValue) <= 0;
4028
}
4129
}
4230

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2018-2019, Optimizely and contributors
3+
* Copyright 2018-2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -18,25 +18,12 @@
1818

1919
import javax.annotation.Nullable;
2020

21-
import static com.optimizely.ab.internal.AttributesUtil.isValidNumber;
22-
23-
class LTMatch extends AttributeMatch<Number> {
24-
Number value;
25-
26-
protected LTMatch(Number value) {
27-
this.value = value;
28-
}
29-
21+
/**
22+
* GTMatch performs a "less than" number comparison via {@link NumberComparator}.
23+
*/
24+
class LTMatch implements Match {
3025
@Nullable
31-
public Boolean eval(Object attributeValue) {
32-
try {
33-
if(isValidNumber(attributeValue)) {
34-
return castToValueType(attributeValue, value).doubleValue() < value.doubleValue();
35-
}
36-
} catch (Exception e) {
37-
return null;
38-
}
39-
return null;
26+
public Boolean eval(Object conditionValue, Object attributeValue) throws UnknownValueTypeException {
27+
return NumberComparator.compare(attributeValue, conditionValue) < 0;
4028
}
4129
}
42-

core-api/src/main/java/com/optimizely/ab/config/audience/match/Match.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2018-2019, Optimizely and contributors
3+
* Copyright 2018-2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -20,5 +20,5 @@
2020

2121
public interface Match {
2222
@Nullable
23-
Boolean eval(Object attributeValue);
23+
Boolean eval(Object conditionValue, Object attributeValue) throws UnexpectedValueTypeException, UnknownValueTypeException;
2424
}

0 commit comments

Comments
 (0)