Skip to content

Commit 25bfe59

Browse files
add key-by-key tests
fix adoption not propagating upward
1 parent c371b88 commit 25bfe59

File tree

2 files changed

+87
-6
lines changed

2 files changed

+87
-6
lines changed

enigma/src/main/java/org/quiltmc/enigma/util/multi_trie/MutableMapNode.java

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public boolean clearLeaves() {
7474
/**
7575
* Removes the branch node associated with the passed {@code key} if that node is empty.
7676
*
77-
* @implNote This should only be passed one of <em>this</em> node's branches.
77+
* @implNote This should only be passed one of <em>this</em> node's {@linkplain #getBranches() branches}.
7878
*
7979
* @return {@code true} if the branch was pruned, or {@code false otherwise}
8080
*/
@@ -88,6 +88,26 @@ protected boolean pruneIfEmpty(Branch<K, V, B> branch) {
8888
}
8989
}
9090

91+
/**
92+
* If the passed {@code branch} is an {@linkplain #orphans orphan},
93+
* removes it from {@linkplain #orphans} and puts it in {@linkplain #getBranches() branches}.
94+
*
95+
* @implNote only non-empty branches should be passed to this method;
96+
* it's called when a node may have changed from empty to non-empty
97+
*
98+
* @return {@code true} if the passed {@code branch} was an orphan, or {@code false} otherwise
99+
*/
100+
protected boolean tryAdopt(Branch<K, V, B> branch) {
101+
final boolean wasOrphan = this.orphans.remove(branch.getKey()) != null;
102+
if (wasOrphan) {
103+
this.getBranches().put(branch.getKey(), branch.getSelf());
104+
105+
return true;
106+
} else {
107+
return false;
108+
}
109+
}
110+
91111
/**
92112
* @return a new, empty branch node instance
93113
*/
@@ -128,10 +148,7 @@ protected abstract static class Branch<K, V, B extends Branch<K, V, B>> extends
128148
@Override
129149
public void put(V value) {
130150
super.put(value);
131-
final boolean wasOrphan = this.getParent().orphans.remove(this.getKey()) != null;
132-
if (wasOrphan) {
133-
this.getParent().getBranches().put(this.getKey(), this.getSelf());
134-
}
151+
this.getParent().tryAdopt(this);
135152
}
136153

137154
@Override
@@ -166,5 +183,16 @@ protected boolean pruneIfEmpty(Branch<K, V, B> branch) {
166183
return false;
167184
}
168185
}
186+
187+
@Override
188+
protected boolean tryAdopt(Branch<K, V, B> branch) {
189+
if (super.tryAdopt(branch)) {
190+
this.getParent().tryAdopt(this);
191+
192+
return true;
193+
} else {
194+
return false;
195+
}
196+
}
169197
}
170198
}

enigma/src/test/java/org/quiltmc/enigma/util/multi_trie/CompositeStringMultiTrieTest.java

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,65 @@
1515
import static org.junit.jupiter.api.Assertions.assertEquals;
1616
import static org.junit.jupiter.api.Assertions.assertTrue;
1717

18-
// TODO key-by-key access tests
1918
public class CompositeStringMultiTrieTest {
2019
private static final String VALUES = "values";
2120
private static final String LEAVES = "leaves";
2221
private static final String BRANCHES = "branches";
2322

23+
private static final String KEY_BY_KEY_SUBJECT = "key-by-key subject";
24+
25+
// test key-by-key put's orphan logic
26+
@Test
27+
void testPutKeyByKeyRootDown() {
28+
final CompositeStringMultiTrie<Integer> trie = CompositeStringMultiTrie.createHashed();
29+
30+
for (int depth = 0; depth < KEY_BY_KEY_SUBJECT.length(); depth++) {
31+
MutableMapNode<Character, Integer, ?> node = trie.getRoot();
32+
for (int iKey = 0; iKey <= depth; iKey++) {
33+
node = node.next(KEY_BY_KEY_SUBJECT.charAt(iKey));
34+
}
35+
36+
node.put(depth);
37+
38+
assertOneLeaf(node);
39+
40+
assertTrieSize(trie, depth + 1);
41+
}
42+
}
43+
44+
@Test
45+
void testPutKeyByKeyStemUp() {
46+
final CompositeStringMultiTrie<Integer> trie = CompositeStringMultiTrie.createHashed();
47+
48+
for (int depth = KEY_BY_KEY_SUBJECT.length() - 1; depth >= 0; depth--) {
49+
MutableMapNode<Character, Integer, ?> node = trie.getRoot();
50+
for (int iKey = 0; iKey <= depth; iKey++) {
51+
node = node.next(KEY_BY_KEY_SUBJECT.charAt(iKey));
52+
}
53+
54+
node.put(depth);
55+
56+
assertOneLeaf(node);
57+
58+
assertTrieSize(trie, KEY_BY_KEY_SUBJECT.length() - depth);
59+
}
60+
}
61+
62+
private static void assertOneLeaf(MutableMapNode<Character, Integer, ?> node) {
63+
assertEquals(
64+
1, node.streamLeaves().count(),
65+
() -> "Expected node to have only one leaf, but had the following: " + node.streamLeaves().toList()
66+
);
67+
}
68+
69+
private static void assertTrieSize(CompositeStringMultiTrie<Integer> trie, int expectedSize) {
70+
assertEquals(
71+
expectedSize, trie.getSize(),
72+
() -> "Expected node to have %s values, but had the following: %s"
73+
.formatted(expectedSize, trie.getRoot().streamValues().toList())
74+
);
75+
}
76+
2477
@Test
2578
void testPut() {
2679
final CompositeStringMultiTrie<Association> trie = Association.createAndPopulateTrie();

0 commit comments

Comments
 (0)