Skip to content

Commit 0a24b56

Browse files
committed
Targeting rule evaluator
AI-Session-Id: 095253e8-7e1c-4578-9779-bf96395021cf AI-Tool: claude-code AI-Model: unknown
1 parent 68731e9 commit 0a24b56

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+3190
-0
lines changed

targeting-engine/pom.xml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?xml version="1.0"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>io.split.client</groupId>
7+
<artifactId>java-client-parent</artifactId>
8+
<version>4.18.3</version>
9+
</parent>
10+
11+
<version>4.18.3</version>
12+
<artifactId>targeting-engine</artifactId>
13+
<packaging>jar</packaging>
14+
<name>Targeting Engine</name>
15+
<description>A generic, zero-dependency targeting rules engine extracted from the Split Java SDK</description>
16+
17+
<dependencies>
18+
<dependency>
19+
<groupId>junit</groupId>
20+
<artifactId>junit</artifactId>
21+
<version>4.13.1</version>
22+
<scope>test</scope>
23+
</dependency>
24+
<dependency>
25+
<groupId>org.mockito</groupId>
26+
<artifactId>mockito-core</artifactId>
27+
<version>5.14.2</version>
28+
<scope>test</scope>
29+
</dependency>
30+
</dependencies>
31+
32+
<build>
33+
<plugins>
34+
<plugin>
35+
<groupId>org.apache.maven.plugins</groupId>
36+
<artifactId>maven-compiler-plugin</artifactId>
37+
<version>3.3</version>
38+
<configuration>
39+
<source>${maven.compiler.source}</source>
40+
<target>${maven.compiler.target}</target>
41+
</configuration>
42+
</plugin>
43+
<plugin>
44+
<groupId>org.apache.maven.plugins</groupId>
45+
<artifactId>maven-surefire-plugin</artifactId>
46+
<version>3.2.5</version>
47+
</plugin>
48+
</plugins>
49+
</build>
50+
</project>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package io.split.rules.bucketing;
2+
3+
import io.split.rules.model.Partition;
4+
5+
import java.util.List;
6+
7+
/**
8+
* Hashes keys into buckets and selects treatments from partition lists.
9+
*/
10+
public final class Bucketer {
11+
private static final int ALGO_LEGACY = 1;
12+
private static final int ALGO_MURMUR = 2;
13+
private static final String CONTROL = "control";
14+
15+
/**
16+
* Returns the treatment for the given key, seed, partitions, and algorithm.
17+
* Returns "control" if no partition matches.
18+
*/
19+
public static String getTreatment(String key, int seed, List<Partition> partitions, int algo) {
20+
if (partitions.isEmpty()) {
21+
return CONTROL;
22+
}
23+
if (hundredPercentOneTreatment(partitions)) {
24+
return partitions.get(0).treatment;
25+
}
26+
return selectTreatment(bucket(hash(key, seed, algo)), partitions);
27+
}
28+
29+
/**
30+
* Returns a bucket between 1 and 100, inclusive.
31+
*/
32+
public static int getBucket(String key, int seed, int algo) {
33+
return bucket(hash(key, seed, algo));
34+
}
35+
36+
static long hash(String key, int seed, int algo) {
37+
switch (algo) {
38+
case ALGO_MURMUR:
39+
return murmurHash(key, seed);
40+
case ALGO_LEGACY:
41+
default:
42+
return legacyHash(key, seed);
43+
}
44+
}
45+
46+
static long murmurHash(String key, int seed) {
47+
return MurmurHash3.murmurhash3_x86_32(key, 0, key.length(), seed);
48+
}
49+
50+
static int legacyHash(String key, int seed) {
51+
int h = 0;
52+
for (int i = 0; i < key.length(); i++) {
53+
h = 31 * h + key.charAt(i);
54+
}
55+
return h ^ seed;
56+
}
57+
58+
static int bucket(long hash) {
59+
return (int) (Math.abs(hash % 100) + 1);
60+
}
61+
62+
private static String selectTreatment(int bucket, List<Partition> partitions) {
63+
int covered = 0;
64+
for (Partition partition : partitions) {
65+
covered += partition.size;
66+
if (covered >= bucket) {
67+
return partition.treatment;
68+
}
69+
}
70+
return CONTROL;
71+
}
72+
73+
private static boolean hundredPercentOneTreatment(List<Partition> partitions) {
74+
return partitions.size() == 1 && partitions.get(0).size == 100;
75+
}
76+
77+
private Bucketer() {}
78+
}

0 commit comments

Comments
 (0)