Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Commit 1d6896a

Browse files
authored
Merge pull request #111 from launchdarkly/2.6.0
release 2.6.0
2 parents 79a6c13 + 4b60049 commit 1d6896a

File tree

9 files changed

+464
-27
lines changed

9 files changed

+464
-27
lines changed

build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ libraries.shaded = [
3232
"com.google.guava:guava:19.0",
3333
"joda-time:joda-time:2.9.3",
3434
"com.launchdarkly:okhttp-eventsource:1.7.1",
35-
"redis.clients:jedis:2.9.0",
36-
"com.vdurmont:semver4j:2.1.0"
35+
"redis.clients:jedis:2.9.0"
3736
]
3837

3938
libraries.unshaded = [

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
version=2.5.1
1+
version=2.6.0
22
ossrhUsername=
3-
ossrhPassword=
3+
ossrhPassword=

src/main/java/com/launchdarkly/client/LDClient.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public void track(String eventName, LDUser user) {
155155
}
156156

157157
/**
158-
* Register the user
158+
* Registers the user.
159159
*
160160
* @param user the user to register
161161
*/
@@ -440,6 +440,15 @@ public String secureModeHash(LDUser user) {
440440
return null;
441441
}
442442

443+
/**
444+
* Returns the current version string of the client library.
445+
* @return a version string conforming to Semantic Versioning (http://semver.org)
446+
*/
447+
@Override
448+
public String version() {
449+
return CLIENT_VERSION;
450+
}
451+
443452
private static String getClientVersion() {
444453
Class clazz = LDConfig.class;
445454
String className = clazz.getSimpleName() + ".class";

src/main/java/com/launchdarkly/client/LDClientInterface.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,6 @@ public interface LDClientInterface extends Closeable {
4040
boolean isOffline();
4141

4242
String secureModeHash(LDUser user);
43+
44+
String version();
4345
}

src/main/java/com/launchdarkly/client/OperandType.java

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package com.launchdarkly.client;
22

33
import com.google.gson.JsonPrimitive;
4-
import com.vdurmont.semver4j.Semver;
5-
import com.vdurmont.semver4j.Semver.SemverType;
6-
import com.vdurmont.semver4j.SemverException;
74

85
/**
96
* Operator value that can be applied to {@link JsonPrimitive} objects. Incompatible types or other errors
@@ -36,14 +33,8 @@ public Object getValueAsType(JsonPrimitive value) {
3633
return Util.jsonPrimitiveToDateTime(value);
3734
case semVer:
3835
try {
39-
Semver sv = new Semver(value.getAsString(), SemverType.LOOSE);
40-
// LOOSE means only the major version is required. But comparisons between loose and strictly
41-
// compliant versions don't work properly, so we always convert to a strict version (i.e. fill
42-
// in the minor/patch versions with zeroes if they were absent). Note that if we ever switch
43-
// to a different semver library that doesn't have exactly the same "loose" mode, we will need
44-
// to preprocess the string before parsing to get the same behavior.
45-
return sv.toStrict();
46-
} catch (SemverException e) {
36+
return SemanticVersion.parse(value.getAsString(), true);
37+
} catch (SemanticVersion.InvalidVersionException e) {
4738
return null;
4839
}
4940
default:
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package com.launchdarkly.client;
2+
3+
import java.util.regex.Matcher;
4+
import java.util.regex.Pattern;
5+
6+
/**
7+
* Simple implementation of semantic version parsing and comparison according to the Semantic
8+
* Versions 2.0.0 standard (http://semver.org).
9+
*/
10+
class SemanticVersion implements Comparable<SemanticVersion> {
11+
12+
private static Pattern VERSION_REGEX = Pattern.compile(
13+
"^(?<major>0|[1-9]\\d*)(\\.(?<minor>0|[1-9]\\d*))?(\\.(?<patch>0|[1-9]\\d*))?" +
14+
"(\\-(?<prerel>[0-9A-Za-z\\-\\.]+))?(\\+(?<build>[0-9A-Za-z\\-\\.]+))?$");
15+
16+
@SuppressWarnings("serial")
17+
public static class InvalidVersionException extends Exception {
18+
public InvalidVersionException(String message) {
19+
super(message);
20+
}
21+
}
22+
23+
private final int major;
24+
private final int minor;
25+
private final int patch;
26+
private final String prerelease;
27+
private final String build;
28+
29+
public SemanticVersion(int major, int minor, int patch, String prerelease, String build) {
30+
this.major = major;
31+
this.minor = minor;
32+
this.patch = patch;
33+
this.prerelease = prerelease;
34+
this.build = build;
35+
}
36+
37+
public int getMajor() {
38+
return major;
39+
}
40+
41+
public int getMinor() {
42+
return minor;
43+
}
44+
45+
public int getPatch() {
46+
return patch;
47+
}
48+
49+
public String getPrerelease() {
50+
return prerelease;
51+
}
52+
53+
public String getBuild() {
54+
return build;
55+
}
56+
57+
/**
58+
* Attempts to parse a string as a semantic version according to the Semver 2.0.0 specification.
59+
* @param input the input string
60+
* @return a SemanticVersion instance
61+
* @throws InvalidVersionException if the version could not be parsed
62+
*/
63+
public static SemanticVersion parse(String input) throws InvalidVersionException {
64+
return parse(input, false);
65+
}
66+
67+
/**
68+
* Attempts to parse a string as a semantic version according to the Semver 2.0.0 specification, except that
69+
* the minor and patch versions may optionally be omitted.
70+
* @param input the input string
71+
* @param allowMissingMinorAndPatch true if the parser should tolerate the absence of a minor and/or
72+
* patch version; if absent, they will be treated as zero
73+
* @return a SemanticVersion instance
74+
* @throws InvalidVersionException if the version could not be parsed
75+
*/
76+
public static SemanticVersion parse(String input, boolean allowMissingMinorAndPatch) throws InvalidVersionException {
77+
Matcher matcher = VERSION_REGEX.matcher(input);
78+
if (!matcher.matches()) {
79+
throw new InvalidVersionException("Invalid semantic version");
80+
}
81+
int major, minor, patch;
82+
try {
83+
major = Integer.parseInt(matcher.group("major"));
84+
if (!allowMissingMinorAndPatch) {
85+
if (matcher.group("minor") == null || matcher.group("patch") == null) {
86+
throw new InvalidVersionException("Invalid semantic version");
87+
}
88+
}
89+
minor = matcher.group("minor") == null ? 0 : Integer.parseInt(matcher.group("minor"));
90+
patch = matcher.group("patch") == null ? 0 : Integer.parseInt(matcher.group("patch"));
91+
} catch (NumberFormatException e) {
92+
throw new InvalidVersionException("Invalid semantic version");
93+
}
94+
String prerelease = matcher.group("prerel");
95+
String build = matcher.group("build");
96+
return new SemanticVersion(major, minor, patch, prerelease, build);
97+
}
98+
99+
@Override
100+
public int compareTo(SemanticVersion other) {
101+
return comparePrecedence(other);
102+
}
103+
104+
/**
105+
* Compares this object with another SemanticVersion according to Semver 2.0.0 precedence rules.
106+
* @param other another SemanticVersion
107+
* @return 0 if equal, -1 if the current object has lower precedence, or 1 if the current object has higher precedence
108+
*/
109+
public int comparePrecedence(SemanticVersion other) {
110+
if (other == null) {
111+
return 1;
112+
}
113+
if (major != other.major) {
114+
return Integer.compare(major, other.major);
115+
}
116+
if (minor != other.minor) {
117+
return Integer.compare(minor, other.minor);
118+
}
119+
if (patch != other.patch) {
120+
return Integer.compare(patch, other.patch);
121+
}
122+
if (prerelease == null && other.prerelease == null) {
123+
return 0;
124+
}
125+
// *no* prerelease component always has higher precedence than *any* prerelease component
126+
if (prerelease == null) {
127+
return 1;
128+
}
129+
if (other.prerelease == null) {
130+
return -1;
131+
}
132+
return compareIdentifiers(prerelease.split("\\."), other.prerelease.split("\\."));
133+
}
134+
135+
private int compareIdentifiers(String[] ids1, String[] ids2) {
136+
for (int i = 0; ; i++) {
137+
if (i >= ids1.length)
138+
{
139+
// x.y is always less than x.y.z
140+
return (i >= ids2.length) ? 0 : -1;
141+
}
142+
if (i >= ids2.length)
143+
{
144+
return 1;
145+
}
146+
// each sub-identifier is compared numerically if both are numeric; if both are non-numeric,
147+
// they're compared as strings; otherwise, the numeric one is the lesser one
148+
int n1 = 0, n2 = 0, d;
149+
boolean isNum1, isNum2;
150+
try {
151+
n1 = Integer.parseInt(ids1[i]);
152+
isNum1 = true;
153+
} catch (NumberFormatException e) {
154+
isNum1 = false;
155+
}
156+
try {
157+
n2 = Integer.parseInt(ids2[i]);
158+
isNum2 = true;
159+
} catch (NumberFormatException e) {
160+
isNum2 = false;
161+
}
162+
if (isNum1 && isNum2)
163+
{
164+
d = Integer.compare(n1, n2);
165+
}
166+
else
167+
{
168+
d = isNum1 ? -1 : (isNum2 ? 1 : ids1[i].compareTo(ids2[i]));
169+
}
170+
if (d != 0)
171+
{
172+
return d;
173+
}
174+
}
175+
}
176+
}

src/main/java/com/launchdarkly/client/VariationOrRollout.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,35 @@ Integer variationIndexForUser(LDUser user, String key, String salt) {
4141
return null;
4242
}
4343

44-
private float bucketUser(LDUser user, String key, String attr, String salt) {
44+
static float bucketUser(LDUser user, String key, String attr, String salt) {
4545
JsonElement userValue = user.getValueForEvaluation(attr);
46-
String idHash;
47-
if (userValue != null) {
48-
if (userValue.isJsonPrimitive() && userValue.getAsJsonPrimitive().isString()) {
49-
idHash = userValue.getAsString();
50-
if (user.getSecondary() != null) {
51-
idHash = idHash + "." + user.getSecondary().getAsString();
52-
}
53-
String hash = DigestUtils.sha1Hex(key + "." + salt + "." + idHash).substring(0, 15);
54-
long longVal = Long.parseLong(hash, 16);
55-
return (float) longVal / long_scale;
46+
String idHash = getBucketableStringValue(userValue);
47+
if (idHash != null) {
48+
if (user.getSecondary() != null) {
49+
idHash = idHash + "." + user.getSecondary().getAsString();
5650
}
51+
String hash = DigestUtils.sha1Hex(key + "." + salt + "." + idHash).substring(0, 15);
52+
long longVal = Long.parseLong(hash, 16);
53+
return (float) longVal / long_scale;
5754
}
5855
return 0F;
5956
}
6057

58+
private static String getBucketableStringValue(JsonElement userValue) {
59+
if (userValue != null && userValue.isJsonPrimitive()) {
60+
if (userValue.getAsJsonPrimitive().isString()) {
61+
return userValue.getAsString();
62+
}
63+
if (userValue.getAsJsonPrimitive().isNumber()) {
64+
Number n = userValue.getAsJsonPrimitive().getAsNumber();
65+
if (n instanceof Integer) {
66+
return userValue.getAsString();
67+
}
68+
}
69+
}
70+
return null;
71+
}
72+
6173
static class Rollout {
6274
private List<WeightedVariation> variations;
6375
private String bucketBy;

0 commit comments

Comments
 (0)