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 bagClass) { + return assertDoesNotThrow(() -> bagClass.getDeclaredConstructor().newInstance()); + } + + @ParameterizedTest + @ValueSource(classes = { ArrayCharBag.class }) + public void testCharBagAdd(Class 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 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 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 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 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()); + } + +}