package querqy.trie; import static org.hamcrest.Matchers.contains; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.junit.Assert.*; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; import org.junit.Test; public class TrieMapTest { @Test public void testThatEmptyMapAlwaysReturnsUnknownState() { TrieMap<Integer> map = new TrieMap<>(); State<Integer> state = map.get("abc").getStateForCompleteSequence(); assertNotNull(state); assertFalse(state.isKnown()); assertFalse(state.isFinal()); assertEquals(-1, state.getIndex()); } @Test public void testThatBlankLookupSequenceAlwaysReturnsUnknownState() { TrieMap<Integer> map = new TrieMap<>(); map.put("abc", 1); State<Integer> state = map.get("").getStateForCompleteSequence(); assertNotNull(state); assertFalse(state.isKnown()); assertFalse(state.isFinal()); assertEquals(-1, state.getIndex()); } @Test public void testThatSubsequenceOfEntryReturnsKnownAndNonFinalState() { TrieMap<Integer> map = new TrieMap<>(); map.put("abc", 1); State<Integer> state = map.get("a").getStateForCompleteSequence(); assertNotNull(state); assertTrue(state.isKnown()); assertFalse(state.isFinal()); assertEquals(0, state.getIndex()); state = map.get("ab").getStateForCompleteSequence(); assertNotNull(state); assertTrue(state.isKnown()); assertFalse(state.isFinal()); assertEquals(1, state.getIndex()); } @Test public void testThatSequenceOfEntryReturnsKnownAndFinalStateAndValue() { TrieMap<Integer> map = new TrieMap<>(); map.put("abc", 1); State<Integer> state = map.get("abc").getStateForCompleteSequence(); assertNotNull(state); assertTrue(state.isKnown()); assertTrue(state.isFinal()); assertEquals((Integer) 1, state.getValue()); assertEquals(2, state.getIndex()); } @Test public void testThatOverlappingSequencesMatchCorrectly() { TrieMap<Integer> map = new TrieMap<>(); map.put("abc", 1); map.put("ab", 2); State<Integer> state = map.get("abc").getStateForCompleteSequence(); assertNotNull(state); assertTrue(state.isKnown()); assertTrue(state.isFinal()); assertEquals((Integer) 1, state.getValue()); assertEquals(2, state.getIndex()); state = map.get("ab").getStateForCompleteSequence(); assertNotNull(state); assertTrue(state.isKnown()); assertTrue(state.isFinal()); assertEquals((Integer) 2, state.getValue()); assertEquals(1, state.getIndex()); } @Test public void testResumingFromKnwonNonFinalState() throws Exception { TrieMap<Integer> map = new TrieMap<>(); map.put("abc", 1); State<Integer> state = map.get("ab").getStateForCompleteSequence(); assertNotNull(state); assertTrue(state.isKnown()); assertFalse(state.isFinal()); assertEquals(1, state.getIndex()); State<Integer> state2 = map.get("c", state).getStateForCompleteSequence(); assertNotNull(state2); assertTrue(state2.isKnown()); assertTrue(state2.isFinal()); assertEquals((Integer) 1, state2.getValue()); assertEquals(0, state2.getIndex()); } @Test public void testResumingFromKnwonAndFinalState() throws Exception { TrieMap<Integer> map = new TrieMap<>(); map.put("abc", 1); map.put("ab", 2); State<Integer> state = map.get("ab").getStateForCompleteSequence(); assertNotNull(state); assertTrue(state.isKnown()); assertTrue(state.isFinal()); assertEquals((Integer) 2, state.getValue()); assertEquals(1, state.getIndex()); State<Integer> state2 = map.get("c", state).getStateForCompleteSequence(); assertNotNull(state2); assertTrue(state2.isKnown()); assertTrue(state2.isFinal()); assertEquals((Integer) 1, state2.getValue()); assertEquals(0, state2.getIndex()); } @Test public void testThatUnknownSequenceReturnsUnknownAndNonFinal() throws Exception { TrieMap<Integer> map = new TrieMap<>(); map.put("abc", 1); State<Integer> state = map.get("ak").getStateForCompleteSequence(); assertNotNull(state); assertFalse(state.isKnown()); assertFalse(state.isFinal()); assertEquals(-1, state.getIndex()); } @Test public void testThatResumingFromUnknownStateThrowsException() throws Exception { TrieMap<Integer> map = new TrieMap<>(); map.put("abc", 1); State<Integer> state = map.get("k").getStateForCompleteSequence(); assertFalse(state.isKnown()); assertFalse(state.isFinal()); try { map.get("abc", state); fail("get() must not resume from unknown state"); } catch (IllegalArgumentException e) { // expected } } @Test public void testThatMapDoesNotAcceptEmptySequence() throws Exception { TrieMap<Integer> map = new TrieMap<>(); try { map.put("", 1); fail("put() must not accept an empty char sequence"); } catch (IllegalArgumentException e) { // expected } } @Test public void testGetPrefixIfAloneInMap() throws Exception { TrieMap<Integer> map = new TrieMap<>(); map.putPrefix("a", 1); States<Integer> states = map.get("ab"); State<Integer> completeSequenceState = states.getStateForCompleteSequence(); assertFalse(completeSequenceState.isFinal()); assertFalse(completeSequenceState.isKnown()); List<State<Integer>> prefixes = states.getPrefixes(); assertNotNull(prefixes); assertThat(prefixes, contains(state(true, true, 0, 1))); } @Test public void testGetPrefixAsSubstringOfOther() throws Exception { TrieMap<Integer> map = new TrieMap<>(); map.putPrefix("a", 1); map.put("abc", 2); States<Integer> states = map.get("ab"); State<Integer> completeSequenceState = states.getStateForCompleteSequence(); assertFalse(completeSequenceState.isFinal()); assertTrue(completeSequenceState.isKnown()); List<State<Integer>> prefixes = states.getPrefixes(); assertNotNull(prefixes); assertThat(prefixes, contains(state(true, true, 0, 1))); } @Test public void testGetPrefixWithLongerCompleteSequence() throws Exception { TrieMap<Integer> map = new TrieMap<>(); map.putPrefix("ab", 1); map.put("abc", 2); States<Integer> states = map.get("abc"); State<Integer> completeSequenceState = states.getStateForCompleteSequence(); assertThat(completeSequenceState, state(true, true, 2, 2)); List<State<Integer>> prefixes = states.getPrefixes(); assertNotNull(prefixes); assertThat(prefixes, contains(state(true, true, 1, 1))); } @Test public void testGetPrefixWithOtherOfSameLength() throws Exception { TrieMap<Integer> map = new TrieMap<>(); map.putPrefix("ab", 1); map.put("ab", 2); States<Integer> states = map.get("ab"); State<Integer> completeSequenceState = states.getStateForCompleteSequence(); assertThat(completeSequenceState, state(true, true, 1, 2)); List<State<Integer>> prefixes = states.getPrefixes(); assertNull(prefixes); } @Test public void testValueIteratorEmptyMap() throws Exception { TrieMap<Integer> map = new TrieMap<Integer>(); Iterator<Integer> it = map.iterator(); assertFalse(it.hasNext()); try { it.next(); fail("NoSuchElementException expected"); } catch (NoSuchElementException e) { // expected } } @Test public void testValueIteratorOneChar() throws Exception { TrieMap<Integer> map = new TrieMap<Integer>(); map.put("1", 1); Iterator<Integer> it = map.iterator(); assertTrue(it.hasNext()); assertEquals((Integer) 1, it.next()); try { it.next(); fail("NoSuchElementException expected"); } catch (NoSuchElementException e) { // expected } } @Test public void testValueIteratorTwoValuesOneCharDeep() throws Exception { TrieMap<Integer> map = new TrieMap<Integer>(); map.put("1", 1); map.put("2", 2); List<Integer> values = new LinkedList<Integer>(); for (Integer v: map) { values.add(v); } assertThat(values, containsInAnyOrder((Integer) 1, (Integer) 2)); } @Test public void testValueIteratorOneValueTwoCharsDeep() throws Exception { TrieMap<Integer> map = new TrieMap<Integer>(); map.put("12", 1); Iterator<Integer> it = map.iterator(); assertTrue(it.hasNext()); assertEquals((Integer) 1, it.next()); try { it.next(); fail("NoSuchElementException expected"); } catch (NoSuchElementException e) { // expected } } @Test public void testValueIteratorTwoValuesTwoCharsDeepOverlap() throws Exception { TrieMap<Integer> map = new TrieMap<Integer>(); map.put("11", 1); map.put("12", 2); List<Integer> values = new LinkedList<Integer>(); for (Integer v: map) { values.add(v); } assertThat(values, containsInAnyOrder((Integer) 1, (Integer) 2)); } @Test public void testValueIteratorValuesAndPrefix() throws Exception { TrieMap<Integer> map = new TrieMap<Integer>(); map.put("11", 1); map.put("12", 2); map.putPrefix("1", 3); map.put("2459", 4); map.putPrefix("245", 5); List<Integer> values = new LinkedList<Integer>(); for (Integer v: map) { values.add(v); } assertThat(values, containsInAnyOrder((Integer) 1, (Integer) 2, (Integer) 3, (Integer) 4, (Integer) 5)); } public static <T> StateMatcher<T> state(boolean isKnown, boolean isFinal, int index, T value) { return new StateMatcher<T>(isKnown, isFinal, index, value); } public static class StateMatcher<T> extends TypeSafeMatcher<State<T>> { final boolean isKnown; final boolean isFinal; final int index; final T value; public StateMatcher(boolean isKnown, boolean isFinal, int index, T value) { this.isKnown = isKnown; this.isFinal = isFinal; this.index = index; this.value = value; } @Override public void describeTo(Description description) { description.appendText("isKnown=" + isKnown + ", isFinal=" + isFinal + ", index=" + index + ", value=" + value ); } @Override protected boolean matchesSafely(State<T> item) { if (index != item.getIndex()) return false; if (isFinal != item.isFinal()) return false; if (isKnown != item.isKnown()) return false; if (value == null) { if (item.getValue() != null) return false; } else if (!value.equals(item.getValue())) return false; return true; } } }