Skip to content

Commit 312a1d5

Browse files
feature: add semantic version types and test (#386)
* semantic versioning * Fix * added comments and refact * comments fix * Added tests and refact logic * refact * Refactored entire logic of Semantic version comparision and added new tests for preRelease * Nit fix * Added Additional tests for Semantic Versioning * Allowed numbers to parse * Revert "Allowed numbers to parse" This reverts commit 6af5312. * Refactored Logic and made it similar to other sdks * SpotBug bug fix * Removed static functions * remove string field and use proprety version Co-authored-by: Tom Zurkan <thomas.zurkan@optimizely.com>
1 parent 12aceba commit 312a1d5

File tree

12 files changed

+1136
-2
lines changed

12 files changed

+1136
-2
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
*
3+
* Copyright 2020, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.config.audience.match;
18+
19+
import javax.annotation.Nullable;
20+
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+
30+
@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;
40+
}
41+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
*
3+
* Copyright 2020, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.config.audience.match;
18+
19+
import javax.annotation.Nullable;
20+
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+
30+
@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;
40+
}
41+
}
42+

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

Lines changed: 36 additions & 1 deletion
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.
@@ -50,11 +50,21 @@ public static MatchType getMatchType(String matchType, Object conditionValue) th
5050
return new MatchType(matchType, new SubstringMatch((String) conditionValue));
5151
}
5252
break;
53+
case "ge":
54+
if (isValidNumber(conditionValue)) {
55+
return new MatchType(matchType, new GEMatch((Number) conditionValue));
56+
}
57+
break;
5358
case "gt":
5459
if (isValidNumber(conditionValue)) {
5560
return new MatchType(matchType, new GTMatch((Number) conditionValue));
5661
}
5762
break;
63+
case "le":
64+
if (isValidNumber(conditionValue)) {
65+
return new MatchType(matchType, new LEMatch((Number) conditionValue));
66+
}
67+
break;
5868
case "lt":
5969
if (isValidNumber(conditionValue)) {
6070
return new MatchType(matchType, new LTMatch((Number) conditionValue));
@@ -65,6 +75,31 @@ public static MatchType getMatchType(String matchType, Object conditionValue) th
6575
return new MatchType(matchType, new DefaultMatchForLegacyAttributes<String>((String) conditionValue));
6676
}
6777
break;
78+
case "semver_eq":
79+
if (conditionValue instanceof String) {
80+
return new MatchType(matchType, new SemanticVersionEqualsMatch((String) conditionValue));
81+
}
82+
break;
83+
case "semver_ge":
84+
if (conditionValue instanceof String) {
85+
return new MatchType(matchType, new SemanticVersionGEMatch((String) conditionValue));
86+
}
87+
break;
88+
case "semver_gt":
89+
if (conditionValue instanceof String) {
90+
return new MatchType(matchType, new SemanticVersionGTMatch((String) conditionValue));
91+
}
92+
break;
93+
case "semver_le":
94+
if (conditionValue instanceof String) {
95+
return new MatchType(matchType, new SemanticVersionLEMatch((String) conditionValue));
96+
}
97+
break;
98+
case "semver_lt":
99+
if (conditionValue instanceof String) {
100+
return new MatchType(matchType, new SemanticVersionLTMatch((String) conditionValue));
101+
}
102+
break;
68103
default:
69104
throw new UnknownMatchTypeException();
70105
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
*
3+
* Copyright 2020, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.config.audience.match;
18+
19+
import java.util.ArrayList;
20+
import java.util.Collections;
21+
import java.util.List;
22+
23+
import static com.optimizely.ab.internal.AttributesUtil.parseNumeric;
24+
import static com.optimizely.ab.internal.AttributesUtil.stringIsNullOrEmpty;
25+
26+
public final class SemanticVersion {
27+
28+
private static final String BUILD_SEPERATOR = "+";
29+
private static final String PRE_RELEASE_SEPERATOR = "-";
30+
31+
private final String version;
32+
33+
public SemanticVersion(String version) {
34+
this.version = version;
35+
}
36+
37+
public int compare(SemanticVersion targetedVersion) throws Exception {
38+
39+
if (targetedVersion == null || stringIsNullOrEmpty(targetedVersion.version)) {
40+
return 0;
41+
}
42+
43+
String[] targetedVersionParts = targetedVersion.splitSemanticVersion();
44+
String[] userVersionParts = splitSemanticVersion();
45+
46+
for (int index = 0; index < targetedVersionParts.length; index++) {
47+
48+
if (userVersionParts.length <= index) {
49+
return targetedVersion.isPreRelease() ? 1 : -1;
50+
}
51+
Integer targetVersionPartInt = parseNumeric(targetedVersionParts[index]);
52+
Integer userVersionPartInt = parseNumeric(userVersionParts[index]);
53+
54+
if (userVersionPartInt == null) {
55+
// Compare strings
56+
int result = userVersionParts[index].compareTo(targetedVersionParts[index]);
57+
if (result != 0) {
58+
return result;
59+
}
60+
} else if (targetVersionPartInt != null) {
61+
if (!userVersionPartInt.equals(targetVersionPartInt)) {
62+
return userVersionPartInt < targetVersionPartInt ? -1 : 1;
63+
}
64+
} else {
65+
return -1;
66+
}
67+
}
68+
69+
if (!targetedVersion.isPreRelease() &&
70+
isPreRelease()) {
71+
return -1;
72+
}
73+
74+
return 0;
75+
}
76+
77+
public boolean isPreRelease() {
78+
return version.contains(PRE_RELEASE_SEPERATOR);
79+
}
80+
81+
public boolean isBuild() {
82+
return version.contains(BUILD_SEPERATOR);
83+
}
84+
85+
public String[] splitSemanticVersion() throws Exception {
86+
List<String> versionParts = new ArrayList<>();
87+
// pre-release or build.
88+
String versionSuffix = "";
89+
// for example: beta.2.1
90+
String[] preVersionParts;
91+
92+
// Contains white spaces
93+
if (version.contains(" ")) { // log and throw error
94+
throw new Exception("Semantic version contains white spaces. Invalid Semantic Version.");
95+
}
96+
97+
if (isBuild() || isPreRelease()) {
98+
String[] partialVersionParts = version.split(isPreRelease() ?
99+
PRE_RELEASE_SEPERATOR : BUILD_SEPERATOR);
100+
101+
if (partialVersionParts.length <= 1) {
102+
// throw error
103+
throw new Exception("Invalid Semantic Version.");
104+
}
105+
// major.minor.patch
106+
String versionPrefix = partialVersionParts[0];
107+
108+
versionSuffix = partialVersionParts[1];
109+
110+
preVersionParts = versionPrefix.split("\\.");
111+
} else {
112+
preVersionParts = version.split("\\.");
113+
}
114+
115+
if (preVersionParts.length > 3) {
116+
// Throw error as pre version should only contain major.minor.patch version
117+
throw new Exception("Invalid Semantic Version.");
118+
}
119+
120+
for (String preVersionPart : preVersionParts) {
121+
if (parseNumeric(preVersionPart) == null) {
122+
throw new Exception("Invalid Semantic Version.");
123+
}
124+
}
125+
126+
Collections.addAll(versionParts, preVersionParts);
127+
if (!stringIsNullOrEmpty(versionSuffix)) {
128+
versionParts.add(versionSuffix);
129+
}
130+
131+
return versionParts.toArray(new String[0]);
132+
}
133+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
*
3+
* Copyright 2020, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.config.audience.match;
18+
19+
import javax.annotation.Nullable;
20+
21+
class SemanticVersionEqualsMatch implements Match {
22+
String value;
23+
24+
protected SemanticVersionEqualsMatch(String value) {
25+
this.value = value;
26+
}
27+
28+
@Nullable
29+
public Boolean eval(Object attributeValue) {
30+
try {
31+
if (this.value != null && attributeValue instanceof String) {
32+
SemanticVersion conditionalVersion = new SemanticVersion(value);
33+
SemanticVersion userSemanticVersion = new SemanticVersion((String) attributeValue);
34+
return userSemanticVersion.compare(conditionalVersion) == 0;
35+
}
36+
} catch (Exception e) {
37+
return null;
38+
}
39+
return null;
40+
}
41+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
*
3+
* Copyright 2020, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.config.audience.match;
18+
19+
import javax.annotation.Nullable;
20+
21+
class SemanticVersionGEMatch implements Match {
22+
String value;
23+
24+
protected SemanticVersionGEMatch(String value) {
25+
this.value = value;
26+
}
27+
28+
@Nullable
29+
public Boolean eval(Object attributeValue) {
30+
try {
31+
if (this.value != null && attributeValue instanceof String) {
32+
SemanticVersion conditionalVersion = new SemanticVersion(value);
33+
SemanticVersion userSemanticVersion = new SemanticVersion((String) attributeValue);
34+
return userSemanticVersion.compare(conditionalVersion) >= 0;
35+
}
36+
} catch (Exception e) {
37+
return null;
38+
}
39+
return null;
40+
}
41+
}

0 commit comments

Comments
 (0)