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

Commit

Permalink
Merge pull request #111 from launchdarkly/2.6.0
Browse files Browse the repository at this point in the history
release 2.6.0
  • Loading branch information
eli-darkly authored Feb 13, 2018
2 parents 79a6c13 + 4b60049 commit 1d6896a
Show file tree
Hide file tree
Showing 9 changed files with 464 additions and 27 deletions.
3 changes: 1 addition & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ libraries.shaded = [
"com.google.guava:guava:19.0",
"joda-time:joda-time:2.9.3",
"com.launchdarkly:okhttp-eventsource:1.7.1",
"redis.clients:jedis:2.9.0",
"com.vdurmont:semver4j:2.1.0"
"redis.clients:jedis:2.9.0"
]

libraries.unshaded = [
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
version=2.5.1
version=2.6.0
ossrhUsername=
ossrhPassword=
ossrhPassword=
11 changes: 10 additions & 1 deletion src/main/java/com/launchdarkly/client/LDClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public void track(String eventName, LDUser user) {
}

/**
* Register the user
* Registers the user.
*
* @param user the user to register
*/
Expand Down Expand Up @@ -440,6 +440,15 @@ public String secureModeHash(LDUser user) {
return null;
}

/**
* Returns the current version string of the client library.
* @return a version string conforming to Semantic Versioning (http://semver.org)
*/
@Override
public String version() {
return CLIENT_VERSION;
}

private static String getClientVersion() {
Class clazz = LDConfig.class;
String className = clazz.getSimpleName() + ".class";
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/launchdarkly/client/LDClientInterface.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ public interface LDClientInterface extends Closeable {
boolean isOffline();

String secureModeHash(LDUser user);

String version();
}
13 changes: 2 additions & 11 deletions src/main/java/com/launchdarkly/client/OperandType.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.launchdarkly.client;

import com.google.gson.JsonPrimitive;
import com.vdurmont.semver4j.Semver;
import com.vdurmont.semver4j.Semver.SemverType;
import com.vdurmont.semver4j.SemverException;

/**
* Operator value that can be applied to {@link JsonPrimitive} objects. Incompatible types or other errors
Expand Down Expand Up @@ -36,14 +33,8 @@ public Object getValueAsType(JsonPrimitive value) {
return Util.jsonPrimitiveToDateTime(value);
case semVer:
try {
Semver sv = new Semver(value.getAsString(), SemverType.LOOSE);
// LOOSE means only the major version is required. But comparisons between loose and strictly
// compliant versions don't work properly, so we always convert to a strict version (i.e. fill
// in the minor/patch versions with zeroes if they were absent). Note that if we ever switch
// to a different semver library that doesn't have exactly the same "loose" mode, we will need
// to preprocess the string before parsing to get the same behavior.
return sv.toStrict();
} catch (SemverException e) {
return SemanticVersion.parse(value.getAsString(), true);
} catch (SemanticVersion.InvalidVersionException e) {
return null;
}
default:
Expand Down
176 changes: 176 additions & 0 deletions src/main/java/com/launchdarkly/client/SemanticVersion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package com.launchdarkly.client;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Simple implementation of semantic version parsing and comparison according to the Semantic
* Versions 2.0.0 standard (http://semver.org).
*/
class SemanticVersion implements Comparable<SemanticVersion> {

private static Pattern VERSION_REGEX = Pattern.compile(
"^(?<major>0|[1-9]\\d*)(\\.(?<minor>0|[1-9]\\d*))?(\\.(?<patch>0|[1-9]\\d*))?" +
"(\\-(?<prerel>[0-9A-Za-z\\-\\.]+))?(\\+(?<build>[0-9A-Za-z\\-\\.]+))?$");

@SuppressWarnings("serial")
public static class InvalidVersionException extends Exception {
public InvalidVersionException(String message) {
super(message);
}
}

private final int major;
private final int minor;
private final int patch;
private final String prerelease;
private final String build;

public SemanticVersion(int major, int minor, int patch, String prerelease, String build) {
this.major = major;
this.minor = minor;
this.patch = patch;
this.prerelease = prerelease;
this.build = build;
}

public int getMajor() {
return major;
}

public int getMinor() {
return minor;
}

public int getPatch() {
return patch;
}

public String getPrerelease() {
return prerelease;
}

public String getBuild() {
return build;
}

/**
* Attempts to parse a string as a semantic version according to the Semver 2.0.0 specification.
* @param input the input string
* @return a SemanticVersion instance
* @throws InvalidVersionException if the version could not be parsed
*/
public static SemanticVersion parse(String input) throws InvalidVersionException {
return parse(input, false);
}

/**
* Attempts to parse a string as a semantic version according to the Semver 2.0.0 specification, except that
* the minor and patch versions may optionally be omitted.
* @param input the input string
* @param allowMissingMinorAndPatch true if the parser should tolerate the absence of a minor and/or
* patch version; if absent, they will be treated as zero
* @return a SemanticVersion instance
* @throws InvalidVersionException if the version could not be parsed
*/
public static SemanticVersion parse(String input, boolean allowMissingMinorAndPatch) throws InvalidVersionException {
Matcher matcher = VERSION_REGEX.matcher(input);
if (!matcher.matches()) {
throw new InvalidVersionException("Invalid semantic version");
}
int major, minor, patch;
try {
major = Integer.parseInt(matcher.group("major"));
if (!allowMissingMinorAndPatch) {
if (matcher.group("minor") == null || matcher.group("patch") == null) {
throw new InvalidVersionException("Invalid semantic version");
}
}
minor = matcher.group("minor") == null ? 0 : Integer.parseInt(matcher.group("minor"));
patch = matcher.group("patch") == null ? 0 : Integer.parseInt(matcher.group("patch"));
} catch (NumberFormatException e) {
throw new InvalidVersionException("Invalid semantic version");
}
String prerelease = matcher.group("prerel");
String build = matcher.group("build");
return new SemanticVersion(major, minor, patch, prerelease, build);
}

@Override
public int compareTo(SemanticVersion other) {
return comparePrecedence(other);
}

/**
* Compares this object with another SemanticVersion according to Semver 2.0.0 precedence rules.
* @param other another SemanticVersion
* @return 0 if equal, -1 if the current object has lower precedence, or 1 if the current object has higher precedence
*/
public int comparePrecedence(SemanticVersion other) {
if (other == null) {
return 1;
}
if (major != other.major) {
return Integer.compare(major, other.major);
}
if (minor != other.minor) {
return Integer.compare(minor, other.minor);
}
if (patch != other.patch) {
return Integer.compare(patch, other.patch);
}
if (prerelease == null && other.prerelease == null) {
return 0;
}
// *no* prerelease component always has higher precedence than *any* prerelease component
if (prerelease == null) {
return 1;
}
if (other.prerelease == null) {
return -1;
}
return compareIdentifiers(prerelease.split("\\."), other.prerelease.split("\\."));
}

private int compareIdentifiers(String[] ids1, String[] ids2) {
for (int i = 0; ; i++) {
if (i >= ids1.length)
{
// x.y is always less than x.y.z
return (i >= ids2.length) ? 0 : -1;
}
if (i >= ids2.length)
{
return 1;
}
// each sub-identifier is compared numerically if both are numeric; if both are non-numeric,
// they're compared as strings; otherwise, the numeric one is the lesser one
int n1 = 0, n2 = 0, d;
boolean isNum1, isNum2;
try {
n1 = Integer.parseInt(ids1[i]);
isNum1 = true;
} catch (NumberFormatException e) {
isNum1 = false;
}
try {
n2 = Integer.parseInt(ids2[i]);
isNum2 = true;
} catch (NumberFormatException e) {
isNum2 = false;
}
if (isNum1 && isNum2)
{
d = Integer.compare(n1, n2);
}
else
{
d = isNum1 ? -1 : (isNum2 ? 1 : ids1[i].compareTo(ids2[i]));
}
if (d != 0)
{
return d;
}
}
}
}
34 changes: 23 additions & 11 deletions src/main/java/com/launchdarkly/client/VariationOrRollout.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,35 @@ Integer variationIndexForUser(LDUser user, String key, String salt) {
return null;
}

private float bucketUser(LDUser user, String key, String attr, String salt) {
static float bucketUser(LDUser user, String key, String attr, String salt) {
JsonElement userValue = user.getValueForEvaluation(attr);
String idHash;
if (userValue != null) {
if (userValue.isJsonPrimitive() && userValue.getAsJsonPrimitive().isString()) {
idHash = userValue.getAsString();
if (user.getSecondary() != null) {
idHash = idHash + "." + user.getSecondary().getAsString();
}
String hash = DigestUtils.sha1Hex(key + "." + salt + "." + idHash).substring(0, 15);
long longVal = Long.parseLong(hash, 16);
return (float) longVal / long_scale;
String idHash = getBucketableStringValue(userValue);
if (idHash != null) {
if (user.getSecondary() != null) {
idHash = idHash + "." + user.getSecondary().getAsString();
}
String hash = DigestUtils.sha1Hex(key + "." + salt + "." + idHash).substring(0, 15);
long longVal = Long.parseLong(hash, 16);
return (float) longVal / long_scale;
}
return 0F;
}

private static String getBucketableStringValue(JsonElement userValue) {
if (userValue != null && userValue.isJsonPrimitive()) {
if (userValue.getAsJsonPrimitive().isString()) {
return userValue.getAsString();
}
if (userValue.getAsJsonPrimitive().isNumber()) {
Number n = userValue.getAsJsonPrimitive().getAsNumber();
if (n instanceof Integer) {
return userValue.getAsString();
}
}
}
return null;
}

static class Rollout {
private List<WeightedVariation> variations;
private String bucketBy;
Expand Down
Loading

0 comments on commit 1d6896a

Please sign in to comment.