From 41572f6cbad34181bcf974b76953331052237460 Mon Sep 17 00:00:00 2001
From: jonessha <108424630+jonessha@users.noreply.github.com>
Date: Mon, 20 Nov 2023 17:04:04 -0700
Subject: [PATCH] =?UTF-8?q?Adding=20optional=20Configuration=20to=20Machin?=
=?UTF-8?q?e=20that=20can=20be=20used=20to=20enable=20a=E2=80=A6=20(#125)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Adding optional Configuration to Machine that can be used to enable additional NameState re-use where each key subsequence has a single NameState
* Updating readme, performing rename, adding benchmark test
* Moving Configuration builder into GenericMachine/Machine
* Bumping version
---
README.md | 38 +++++++++++++
pom.xml | 2 +-
.../amazon/event/ruler/GenericMachine.java | 57 ++++++++++++++++++-
.../ruler/GenericMachineConfiguration.java | 18 ++++++
.../software/amazon/event/ruler/Machine.java | 17 ++++++
.../amazon/event/ruler/NameState.java | 21 +++++++
.../amazon/event/ruler/Benchmarks.java | 46 +++++++++++++++
.../GenericMachineConfigurationTest.java | 19 +++++++
.../amazon/event/ruler/MachineTest.java | 42 ++++++++++++++
.../amazon/event/ruler/NameStateTest.java | 20 +++++++
10 files changed, 277 insertions(+), 3 deletions(-)
create mode 100644 src/main/software/amazon/event/ruler/GenericMachineConfiguration.java
create mode 100644 src/test/software/amazon/event/ruler/GenericMachineConfigurationTest.java
diff --git a/README.md b/README.md
index 3eae67c..0de2103 100644
--- a/README.md
+++ b/README.md
@@ -550,6 +550,44 @@ the strings it stored and returned were thought of as rule names.
For safety, the type used to "name" rules should be immutable. If you change the content of an object while
it's being used as a rule name, this may break the operation of Ruler.
+### Configuration
+
+The GenericMachine and Machine constructors optionally accept a GenericMachineConfiguration object, which exposes the
+following configuration options.
+
+#### additionalNameStateReuse
+Default: false
+Normally, NameStates are re-used for a given key subsequence and pattern if this key subsequence and pattern have been
+previously added, or if a pattern has already been added for the given key subsequence. Hence, by default, NameState
+re-use is opportunistic. But by setting this flag to true, NameState re-use will be forced for a key subsequence. This
+means that the first pattern being added for a key subsequence will re-use a NameState if that key subsequence has been
+added before. Meaning each key subsequence has a single NameState. This improves memory utilization exponentially in
+some cases but does lead to more sub-rules being stored in individual NameStates, which Ruler sometimes iterates over,
+which can cause a modest runtime performance regression. This defaults to false for backwards compatibility, but likely,
+all but the most latency sensitive of applications would benefit from setting this to true.
+
+Here's a simple example. Consider:
+
+```javascript
+machine.addRule("0", "{\"key1\": [\"a\", \"b\", \"c\"]}");
+```
+
+The pattern "a" creates a NameState, and then, even with additionalNameStateReuse=false, the second pattern ("b") and
+third pattern ("c") re-use that same NameState. But consider the following instead:
+
+```javascript
+machine.addRule("0", "{\"key1\": [\"a\"]}");
+machine.addRule("1", "{\"key1\": [\"b\"]}");
+machine.addRule("2", "{\"key1\": [\"c\"]}");
+```
+
+Now, with additionalNameStateReuse=false, we end up with three NameStates, because the first pattern encountered for a
+key subsequence on each rule addition will create a new NameState. So, "a", "b", and "c" all get their own NameStates.
+However, with additionalNameStateReuse=true, "a" will create a new NameState, then "b" and "c" will reuse this same
+NameState. This is accomplished by storing that we already have a NameState for the key subsequence "key1".
+
+Note that it doesn't matter if each addRule uses a different rule name or the same rule name.
+
### addRule()
All forms of this method have the same first argument, a String which provides
diff --git a/pom.xml b/pom.xml
index 049a4bc..1ac3c9b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
software.amazon.event.ruler
event-ruler
Event Ruler
- 1.5.0
+ 1.6.0
Event Ruler is a Java library that allows matching Rules to Events. An event is a list of fields,
which may be given as name/value pairs or as a JSON object. A rule associates event field names with lists of
possible values. There are two reasons to use Ruler: 1/ It's fast; the time it takes to match Events doesn't
diff --git a/src/main/software/amazon/event/ruler/GenericMachine.java b/src/main/software/amazon/event/ruler/GenericMachine.java
index d12db45..3bf8fac 100644
--- a/src/main/software/amazon/event/ruler/GenericMachine.java
+++ b/src/main/software/amazon/event/ruler/GenericMachine.java
@@ -37,6 +37,11 @@ public class GenericMachine {
*/
private static final int MAXIMUM_RULE_SIZE = 256;
+ /**
+ * Configuration for the Machine.
+ */
+ private final GenericMachineConfiguration configuration;
+
/**
* The start state of matching and adding rules.
*/
@@ -56,7 +61,14 @@ public class GenericMachine {
*/
private final SubRuleContext.Generator subRuleContextGenerator = new SubRuleContext.Generator();
- public GenericMachine() {}
+ @Deprecated
+ public GenericMachine() {
+ this(builder().buildConfig());
+ }
+
+ protected GenericMachine(GenericMachineConfiguration configuration) {
+ this.configuration = configuration;
+ }
/**
* Return any rules that match the fields in the event in a way that is Array-Consistent (thus trailing "AC" on
@@ -322,6 +334,7 @@ private Set deleteStep(final NameState state,
if (!doesNameStateContainPattern(nextNameState, pattern) &&
deletePattern(state, key, pattern)) {
deletedKeys.add(key);
+ state.removeNextNameState(key, configuration);
}
}
}
@@ -340,6 +353,7 @@ private Set deleteStep(final NameState state,
// does not transition to the next NameState.
if (!doesNameStateContainPattern(nextNameState, pattern) && deletePattern(state, key, pattern)) {
deletedKeys.add(key);
+ state.removeNextNameState(key, configuration);
}
}
}
@@ -545,6 +559,15 @@ private boolean addStep(final NameState state,
// for each pattern, we'll provisionally add it to the BMC, which may already have it. Pass the states
// list in in case the BMC doesn't already have a next-step for this pattern and needs to make a new one
NameState lastNextState = null;
+
+ if (configuration.isAdditionalNameStateReuse()) {
+ lastNextState = state.getNextNameState(key);
+ if (lastNextState == null) {
+ lastNextState = new NameState();
+ state.addNextNameState(key, lastNextState, configuration);
+ }
+ }
+
Set nameStates = new HashSet<>();
if (nameStatesForEachKey[keyIndex] == null) {
nameStatesForEachKey[keyIndex] = new HashSet<>();
@@ -553,7 +576,6 @@ private boolean addStep(final NameState state,
if (isNamePattern(pattern)) {
lastNextState = nameMatcher.addPattern(pattern, lastNextState == null ? new NameState() : lastNextState);
} else {
- assert byteMachine != null;
lastNextState = byteMachine.addPattern(pattern, lastNextState);
}
nameStates.add(lastNextState);
@@ -678,5 +700,36 @@ public String toString() {
", fieldStepsUsedRefCount=" + fieldStepsUsedRefCount +
'}';
}
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ protected static class Builder {
+
+ /**
+ * Normally, NameStates are re-used for a given key subsequence and pattern if this key subsequence and pattern have
+ * been previously added, or if a pattern has already been added for the given key subsequence. Hence by default,
+ * NameState re-use is opportunistic. But by setting this flag to true, NameState re-use will be forced for a key
+ * subsequence. This means that the first pattern being added for a key subsequence will re-use a NameState if that
+ * key subsequence has been added before. Meaning each key subsequence has a single NameState. This improves memory
+ * utilization exponentially in some cases but does lead to more sub-rules being stored in individual NameStates,
+ * which Ruler sometimes iterates over, which can cause a modest runtime performance regression.
+ */
+ private boolean additionalNameStateReuse = false;
+
+ public Builder withAdditionalNameStateReuse(boolean additionalNameStateReuse) {
+ this.additionalNameStateReuse = additionalNameStateReuse;
+ return this;
+ }
+
+ public T build() {
+ return (T) new GenericMachine(buildConfig());
+ }
+
+ protected GenericMachineConfiguration buildConfig() {
+ return new GenericMachineConfiguration(additionalNameStateReuse);
+ }
+ }
}
diff --git a/src/main/software/amazon/event/ruler/GenericMachineConfiguration.java b/src/main/software/amazon/event/ruler/GenericMachineConfiguration.java
new file mode 100644
index 0000000..2385a2c
--- /dev/null
+++ b/src/main/software/amazon/event/ruler/GenericMachineConfiguration.java
@@ -0,0 +1,18 @@
+package software.amazon.event.ruler;
+
+/**
+ * Configuration for a GenericMachine. For descriptions of the options, see GenericMachine.Builder.
+ */
+class GenericMachineConfiguration {
+
+ private final boolean additionalNameStateReuse;
+
+ GenericMachineConfiguration(boolean additionalNameStateReuse) {
+ this.additionalNameStateReuse = additionalNameStateReuse;
+ }
+
+ boolean isAdditionalNameStateReuse() {
+ return additionalNameStateReuse;
+ }
+}
+
diff --git a/src/main/software/amazon/event/ruler/Machine.java b/src/main/software/amazon/event/ruler/Machine.java
index dc01e13..52f929a 100644
--- a/src/main/software/amazon/event/ruler/Machine.java
+++ b/src/main/software/amazon/event/ruler/Machine.java
@@ -13,6 +13,23 @@
*/
public class Machine extends GenericMachine {
+ @Deprecated
public Machine() {
+ super();
+ }
+
+ private Machine(GenericMachineConfiguration configuration) {
+ super(configuration);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ protected static class Builder extends GenericMachine.Builder {
+ @Override
+ public Machine build() {
+ return new Machine(buildConfig());
+ }
}
}
\ No newline at end of file
diff --git a/src/main/software/amazon/event/ruler/NameState.java b/src/main/software/amazon/event/ruler/NameState.java
index 67453f8..12bc0e8 100644
--- a/src/main/software/amazon/event/ruler/NameState.java
+++ b/src/main/software/amazon/event/ruler/NameState.java
@@ -28,6 +28,10 @@ class NameState {
// while add/delete Rule is active in another thread, without any locks.
private final Map> mustNotExistMatchers = new ConcurrentHashMap<>(1);
+ // Maps a key to the next NameState accessible via either valueTransitions or mustNotExistMatchers.
+ // Only used when Configuration is set for additionalNameStateReuse.
+ private final Map keyToNextNameState = new ConcurrentHashMap<>();
+
// All rules, both terminal and non-terminal, keyed by pattern, that led to this NameState.
private final Map> patternToRules = new ConcurrentHashMap<>();
@@ -153,6 +157,12 @@ void removeKeyTransition(String name) {
mustNotExistMatchers.remove(name);
}
+ void removeNextNameState(String key, GenericMachineConfiguration configuration) {
+ if (configuration.isAdditionalNameStateReuse()) {
+ keyToNextNameState.remove(key);
+ }
+ }
+
boolean isEmpty() {
return valueTransitions.isEmpty() &&
mustNotExistMatchers.isEmpty() &&
@@ -215,6 +225,12 @@ void addKeyTransition(final String key, final NameMatcher to) {
mustNotExistMatchers.put(key, to);
}
+ void addNextNameState(final String key, final NameState nextNameState, final GenericMachineConfiguration configuration) {
+ if (configuration.isAdditionalNameStateReuse()) {
+ keyToNextNameState.put(key, nextNameState);
+ }
+ }
+
NameMatcher getKeyTransitionOn(final String token) {
return mustNotExistMatchers.get(token);
}
@@ -284,6 +300,10 @@ Set getNameTransitions(final Event event, final ArrayMembership membe
return nextNameStates;
}
+ public NameState getNextNameState(String key) {
+ return keyToNextNameState.get(key);
+ }
+
public int evaluateComplexity(MachineComplexityEvaluator evaluator) {
int maxComplexity = evaluator.getMaxComplexity();
int complexity = 0;
@@ -321,6 +341,7 @@ public String toString() {
return "NameState{" +
"valueTransitions=" + valueTransitions +
", mustNotExistMatchers=" + mustNotExistMatchers +
+ ", keyToNextNameState=" + keyToNextNameState +
", patternToRules=" + patternToRules +
", patternToTerminalSubRuleIds=" + patternToTerminalSubRuleIds +
", patternToNonTerminalSubRuleIds=" + patternToNonTerminalSubRuleIds +
diff --git a/src/test/software/amazon/event/ruler/Benchmarks.java b/src/test/software/amazon/event/ruler/Benchmarks.java
index eb9be40..35387c6 100644
--- a/src/test/software/amazon/event/ruler/Benchmarks.java
+++ b/src/test/software/amazon/event/ruler/Benchmarks.java
@@ -546,6 +546,52 @@ public void exactRuleMemoryBenchmark() throws Exception {
rules.clear();
}
+ @Test
+ public void lowNameStateReuseMemoryBenchmark() throws Exception {
+ Machine machine = new Machine();
+ System.out.println("Low NameState Reuse Memory Benchmark");
+ nameStateReuseMemoryBenchmark(machine);
+ }
+
+ @Test
+ public void highNameStateReuseMemoryBenchmark() throws Exception {
+ Machine machine = Machine.builder().withAdditionalNameStateReuse(true).build();
+ System.out.println("High NameState Reuse Memory Benchmark");
+ nameStateReuseMemoryBenchmark(machine);
+ }
+
+ private void nameStateReuseMemoryBenchmark(Machine machine) throws Exception {
+ int maxKeys = 256;
+ System.gc();
+ long memBefore = Runtime.getRuntime().freeMemory();
+ int sizeBefore = machine.approximateObjectCount();
+ System.out.printf("Before: %.1f (%d)\n", 1.0 * memBefore / 1000000, sizeBefore);
+
+ // For a readable version with a similar setup to the rules being added here, see
+ // MachineTest.testApproximateObjectCountEachKeyHasThreePatternsAddedOneAtATime. By adding one pattern at a time
+ // for each key, we create three different branches in the low NameState reuse test, but a single branch in the
+ // high NameState reuse test. So with low NameState reuse, Machine size grows exponentially with number of keys.
+ for (int i = 0; i < maxKeys; i++) {
+ StringBuilder prefix = new StringBuilder();
+ for (int j = 0; j < i; j++) {
+ int k = 3 * j;
+ prefix.append("\"key" + k + "\": [\"" + k + "\", \"" + (k + 1) + "\", \"" + (k + 2) + "\"], ");
+ }
+ int k = 3 * i;
+ machine.addRule("" + k, "{" + prefix + "\"key" + i + "\": [\"" + k + "\"]}");
+ machine.addRule("" + k + 1, "{" + prefix + "\"key" + i + "\": [\"" + (k + 1) + "\"]}");
+ machine.addRule("" + k + 2, "{" + prefix + "\"key" + i + "\": [\"" + (k + 2) + "\"]}");
+ }
+
+ System.gc();
+ long memAfter = Runtime.getRuntime().freeMemory();
+ int sizeAfter = machine.approximateObjectCount();
+ System.out.printf("After: %.1f (%d)\n", 1.0 * memAfter / 1000000, sizeAfter);
+ int perRuleMem = (int) ((1.0 * (memAfter - memBefore)) / (maxKeys * 3));
+ int perRuleSize = (int) ((1.0 * (sizeAfter - sizeBefore)) / (maxKeys * 3));
+ System.out.println("Per rule: " + perRuleMem + " (" + perRuleSize + ")");
+ }
+
@Test
public void AnythingButPerformanceBenchmark() throws Exception {
readCityLots2();
diff --git a/src/test/software/amazon/event/ruler/GenericMachineConfigurationTest.java b/src/test/software/amazon/event/ruler/GenericMachineConfigurationTest.java
new file mode 100644
index 0000000..3dc2292
--- /dev/null
+++ b/src/test/software/amazon/event/ruler/GenericMachineConfigurationTest.java
@@ -0,0 +1,19 @@
+package software.amazon.event.ruler;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class GenericMachineConfigurationTest {
+
+ @Test
+ public void testAdditionalNameStateReuseTrue() {
+ assertTrue(new GenericMachineConfiguration(true).isAdditionalNameStateReuse());
+ }
+
+ @Test
+ public void testAdditionalNameStateReuseFalse() {
+ assertFalse(new GenericMachineConfiguration(false).isAdditionalNameStateReuse());
+ }
+}
diff --git a/src/test/software/amazon/event/ruler/MachineTest.java b/src/test/software/amazon/event/ruler/MachineTest.java
index dabdd02..344c078 100644
--- a/src/test/software/amazon/event/ruler/MachineTest.java
+++ b/src/test/software/amazon/event/ruler/MachineTest.java
@@ -2621,4 +2621,46 @@ public void testLargeArrayRulesVsOR() throws Exception {
"}");
assertEquals(608, machine.approximateObjectCount(10000));
}
+
+ @Test
+ public void testApproximateObjectCountEachKeyHasThreePatternsAddedOneAtATime() throws Exception {
+ Machine machine = new Machine();
+ testApproximateObjectCountEachKeyHasThreePatternsAddedOneAtATime(machine);
+ assertEquals(72216, machine.approximateObjectCount(500000));
+ }
+
+ @Test
+ public void testApproximateObjectCountEachKeyHasThreePatternsAddedOneAtATimeWithAdditionalNameStateReuse() throws Exception {
+ Machine machine = Machine.builder().withAdditionalNameStateReuse(true).build();
+ testApproximateObjectCountEachKeyHasThreePatternsAddedOneAtATime(machine);
+ assertEquals(136, machine.approximateObjectCount(500000));
+ }
+
+ private void testApproximateObjectCountEachKeyHasThreePatternsAddedOneAtATime(Machine machine) throws Exception {
+ machine.addRule("0", "{\"key1\": [\"a\"]}");
+ machine.addRule("1", "{\"key1\": [\"b\"]}");
+ machine.addRule("2", "{\"key1\": [\"c\"]}");
+ machine.addRule("3", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\"]}");
+ machine.addRule("4", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"e\"]}");
+ machine.addRule("5", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"f\"]}");
+ machine.addRule("6", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\"]}");
+ machine.addRule("7", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"h\"]}");
+ machine.addRule("8", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"i\"]}");
+ machine.addRule("9", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\"]}");
+ machine.addRule("10", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"k\"]}");
+ machine.addRule("11", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"l\"]}");
+ machine.addRule("12", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\", \"k\", \"l\"], \"key5\": [\"m\"]}");
+ machine.addRule("13", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\", \"k\", \"l\"], \"key5\": [\"n\"]}");
+ machine.addRule("14", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\", \"k\", \"l\"], \"key5\": [\"o\"]}");
+ machine.addRule("15", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\", \"k\", \"l\"], \"key5\": [\"m\", \"n\", \"o\"], \"key6\": [\"p\"]}");
+ machine.addRule("16", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\", \"k\", \"l\"], \"key5\": [\"m\", \"n\", \"o\"], \"key6\": [\"q\"]}");
+ machine.addRule("17", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\", \"k\", \"l\"], \"key5\": [\"m\", \"n\", \"o\"], \"key6\": [\"r\"]}");
+ machine.addRule("18", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\", \"k\", \"l\"], \"key5\": [\"m\", \"n\", \"o\"], \"key6\": [\"p\", \"q\", \"r\"], \"key7\": [\"s\"]}");
+ machine.addRule("19", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\", \"k\", \"l\"], \"key5\": [\"m\", \"n\", \"o\"], \"key6\": [\"p\", \"q\", \"r\"], \"key7\": [\"t\"]}");
+ machine.addRule("20", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\", \"k\", \"l\"], \"key5\": [\"m\", \"n\", \"o\"], \"key6\": [\"p\", \"q\", \"r\"], \"key7\": [\"u\"]}");
+ machine.addRule("21", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\", \"k\", \"l\"], \"key5\": [\"m\", \"n\", \"o\"], \"key6\": [\"p\", \"q\", \"r\"], \"key7\": [\"s\", \"t\", \"u\"], \"key8\": [\"v\"]}");
+ machine.addRule("22", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\", \"k\", \"l\"], \"key5\": [\"m\", \"n\", \"o\"], \"key6\": [\"p\", \"q\", \"r\"], \"key7\": [\"s\", \"t\", \"u\"], \"key8\": [\"w\"]}");
+ machine.addRule("23", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\", \"k\", \"l\"], \"key5\": [\"m\", \"n\", \"o\"], \"key6\": [\"p\", \"q\", \"r\"], \"key7\": [\"s\", \"t\", \"u\"], \"key8\": [\"x\"]}");
+ machine.addRule("24", "{\"key1\": [\"a\", \"b\", \"c\"], \"key2\": [\"d\", \"e\", \"f\"], \"key3\": [\"g\", \"h\", \"i\"], \"key4\": [\"j\", \"k\", \"l\"], \"key5\": [\"m\", \"n\", \"o\"], \"key6\": [\"p\", \"q\", \"r\"], \"key7\": [\"s\", \"t\", \"u\"], \"key8\": [\"v\", \"w\", \"x\"], \"key9\": [\"y\"]}");
+ }
}
diff --git a/src/test/software/amazon/event/ruler/NameStateTest.java b/src/test/software/amazon/event/ruler/NameStateTest.java
index 4a86419..f97ede4 100644
--- a/src/test/software/amazon/event/ruler/NameStateTest.java
+++ b/src/test/software/amazon/event/ruler/NameStateTest.java
@@ -115,4 +115,24 @@ public void testContainsRule() {
assertFalse(nameState.containsRule("rule3", Patterns.exactMatch("a")));
assertFalse(nameState.containsRule("rule1", Patterns.exactMatch("c")));
}
+
+ @Test
+ public void testNextNameStateWithoutAdditionalNameStateReuse() {
+ NameState nameState = new NameState();
+ NameState nextNameState = new NameState();
+ GenericMachineConfiguration withoutAdditionalNameStateReuse = new GenericMachineConfiguration(false);
+ nameState.addNextNameState("key", nextNameState, withoutAdditionalNameStateReuse);
+ assertNull(nameState.getNextNameState("key"));
+ }
+
+ @Test
+ public void testNextNameStateWithAdditionalNameStateReuse() {
+ NameState nameState = new NameState();
+ NameState nextNameState = new NameState();
+ GenericMachineConfiguration withAdditionalNameStateReuse = new GenericMachineConfiguration(true);
+ nameState.addNextNameState("key", nextNameState, withAdditionalNameStateReuse);
+ assertEquals(nextNameState, nameState.getNextNameState("key"));
+ nameState.removeNextNameState("key", withAdditionalNameStateReuse);
+ assertNull(nameState.getNextNameState("key"));
+ }
}