diff --git a/pom.xml b/pom.xml
index 5ef7e21..7f5e4fa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,6 +61,12 @@
5.10.1
test
+
+ org.junit.jupiter
+ junit-jupiter-params
+ 5.10.1
+ test
+
org.java-websocket
Java-WebSocket
diff --git a/src/main/java/me/mrletsplay/shareclientcore/document/ArrayCharBag.java b/src/main/java/me/mrletsplay/shareclientcore/document/ArrayCharBag.java
index fb0e162..15bee35 100644
--- a/src/main/java/me/mrletsplay/shareclientcore/document/ArrayCharBag.java
+++ b/src/main/java/me/mrletsplay/shareclientcore/document/ArrayCharBag.java
@@ -27,15 +27,40 @@ public class ArrayCharBag implements CharBag {
this(Collections.emptyList());
}
+ /**
+ * Finds the index of a character if it is contained within the bag, or the index it would be inserted at if it is not contained in the bag.
+ * @param character The character to find
+ * @return The index the character was found or would be inserted at
+ */
+ private int indexOf(Char character) {
+ int lo = 0, hi = chars.size() - 1;
+
+ while(hi >= lo) {
+ int mid = (lo + hi) / 2;
+ Char c = get(mid);
+
+ int comp = Util.compareChars(c, character);
+ if(comp == 0) return mid;
+
+ if(comp < 0) {
+ lo = mid + 1;
+ }else {
+ hi = mid - 1;
+ }
+ }
+
+ return lo;
+ }
+
@Override
public int add(Char character) {
- int i = 0;
- // TODO: use binary search
- while(i < chars.size() && Util.compareChars(chars.get(i), character) < 0) i++;
+ int i = indexOf(character);
+
if(i < chars.size() && Util.compareChars(chars.get(i), character) == 0) {
debugValues.increment(DEBUG_INSERTIONS_DROPPED);
return -1;
}
+
chars.add(i, character);
debugValues.increment(DEBUG_INSERTIONS);
return i;
@@ -43,13 +68,12 @@ public class ArrayCharBag implements CharBag {
@Override
public int remove(Char character) {
- int i = 0;
- // TODO: use binary search
- while(i < chars.size() && Util.compareChars(chars.get(i), character) < 0) i++;
+ int i = indexOf(character);
if(i == chars.size() || Util.compareChars(chars.get(i), character) != 0) {
debugValues.increment(DEBUG_DELETIONS_DROPPED);
return -1;
}
+
chars.remove(i);
debugValues.increment(DEBUG_DELETIONS);
return i;
diff --git a/src/test/java/me/mrletsplay/shareclientcore/CharBagTest.java b/src/test/java/me/mrletsplay/shareclientcore/CharBagTest.java
new file mode 100644
index 0000000..2f54e46
--- /dev/null
+++ b/src/test/java/me/mrletsplay/shareclientcore/CharBagTest.java
@@ -0,0 +1,95 @@
+package me.mrletsplay.shareclientcore;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import me.mrletsplay.shareclientcore.document.ArrayCharBag;
+import me.mrletsplay.shareclientcore.document.Char;
+import me.mrletsplay.shareclientcore.document.CharBag;
+import me.mrletsplay.shareclientcore.document.Identifier;
+
+public class CharBagTest {
+
+ private CharBag createBag(Class extends CharBag> bagClass) {
+ return assertDoesNotThrow(() -> bagClass.getDeclaredConstructor().newInstance());
+ }
+
+ @ParameterizedTest
+ @ValueSource(classes = { ArrayCharBag.class })
+ public void testCharBagAdd(Class extends CharBag> bagClass) {
+ CharBag bag = createBag(bagClass);
+ Char c = new Char(new Identifier[] { new Identifier(0, 0) }, 0, (byte) 0);
+ assertEquals(0, bag.add(c));
+ assertEquals(List.of(c), bag.toList());
+ }
+
+ @ParameterizedTest
+ @ValueSource(classes = { ArrayCharBag.class })
+ public void testCharBagAddBetween(Class extends CharBag> bagClass) {
+ CharBag bag = createBag(bagClass);
+ Char c = new Char(new Identifier[] { new Identifier(0, 0) }, 0, (byte) 0);
+ Char c2 = new Char(new Identifier[] { new Identifier(1, 0) }, 1, (byte) 0);
+ Char cBetween = new Char(new Identifier[] { new Identifier(0, 1) }, 2, (byte) 0);
+
+ bag.add(c);
+ bag.add(c2);
+
+ assertEquals(List.of(c, c2), bag.toList());
+
+ assertEquals(1, bag.add(cBetween));
+
+ assertEquals(List.of(c, cBetween, c2), bag.toList());
+ }
+
+ @ParameterizedTest
+ @ValueSource(classes = { ArrayCharBag.class })
+ public void testCharBagRemove(Class extends CharBag> bagClass) {
+ CharBag bag = createBag(bagClass);
+ Char c = new Char(new Identifier[] { new Identifier(0, 0) }, 0, (byte) 0);
+ bag.add(c);
+ assertEquals(0, bag.remove(c));
+ assertEquals(Collections.emptyList(), bag.toList());
+ }
+
+ @ParameterizedTest
+ @ValueSource(classes = { ArrayCharBag.class })
+ public void testCharBagGet(Class extends CharBag> bagClass) {
+ CharBag bag = createBag(bagClass);
+ Char c = new Char(new Identifier[] { new Identifier(0, 0) }, 0, (byte) 0);
+ Char c2 = new Char(new Identifier[] { new Identifier(0, 1) }, 2, (byte) 0);
+ Char c3 = new Char(new Identifier[] { new Identifier(1, 0) }, 1, (byte) 0);
+
+ bag.add(c);
+ bag.add(c3);
+ bag.add(c2);
+
+ assertEquals(List.of(c, c2, c3), bag.toList());
+ assertEquals(c2, bag.get(1));
+ }
+
+ @ParameterizedTest
+ @ValueSource(classes = { ArrayCharBag.class })
+ public void testCharBagClear(Class extends CharBag> bagClass) {
+ CharBag bag = createBag(bagClass);
+ Char c = new Char(new Identifier[] { new Identifier(0, 0) }, 0, (byte) 0);
+ Char c2 = new Char(new Identifier[] { new Identifier(0, 1) }, 2, (byte) 0);
+ Char c3 = new Char(new Identifier[] { new Identifier(1, 0) }, 1, (byte) 0);
+
+ bag.add(c);
+ bag.add(c3);
+ bag.add(c2);
+
+ assertEquals(List.of(c, c2, c3), bag.toList());
+
+ bag.clear();
+
+ assertEquals(Collections.emptyList(), bag.toList());
+ }
+
+}