/* Copyright 2013 Jonatan Jönsson
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package se.softhouse.common.collections;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.MapAssert.entry;
import static org.junit.Assert.fail;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import org.junit.Test;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.testing.NullPointerTester;
import com.google.common.testing.NullPointerTester.Visibility;
/**
* Tests for {@link CharacterTrie}
*/
public class CharacterTrieTest
{
static final String FOO = "foo";
static final String BAR = "bar";
static final String ZOO = "zoo";
@Test
public void testTrieTree()
{
String hello = "hello";
String him = "him";
String himmi = "himmi";
String world = "world";
CharacterTrie<String> tree = CharacterTrie.newTrie();
assertThat(tree.toString()).isEqualTo("{}");
assertThat(tree.keySet()).isEmpty();
assertThat(tree.remove("nonexisting key")).isNull();
// Insertion
assertThat(tree.put("hello", hello)).as("Failed to insert hello").isNull();
assertThat(tree.put("himmi", himmi)).as("Failed to insert himmi").isNull();
assertThat(tree.put("him", him)).as("Failed to insert him").isNull();
assertThat(tree.put("world", world)).as("Failed to insert world").isNull();
assertThat(tree.put("world", world)).as("world should already exist in the tree").isEqualTo(world);
assertThat(tree).as("Wrong tree size, insertion in tree must have failed").hasSize(4);
// Removal
// Removing a node which have children
assertThat(tree.remove("him")).as("Failed to delete 'him'").isEqualTo(him);
assertThat(tree.remove("him")).as("Deleted 'him' from: " + tree + ", even though it's part of 'himmi' ").isNull();
// Make sure the removal of 'him' left himmi intact
assertThat(tree).includes(entry("himmi", himmi));
// Clean up parents because they have no children
assertThat(tree.remove("himmi")).as("Failed to delete 'himmi' from " + tree).isEqualTo(himmi);
assertThat(tree.remove("Bye")).as("Deleted non-existing object 'Bye' from " + tree).isNull();
assertThat(tree).includes(entry("hello", hello));
assertThat(tree.containsKey("Bye")).isFalse();
assertThat(tree).as("Wrong tree size, deletion from tree must have failed").hasSize(2);
// Retrieval
assertThat(tree.get("hello")).isEqualTo(hello);
assertThat(tree.get("world")).isEqualTo(world);
assertThat(tree.get("Bye")).isNull();
assertThat(tree.get("hell")).isNull();
assertThat(tree.keySet()).containsOnly("hello", "world");
assertThat(tree.values()).containsOnly(hello, world);
}
@Test
public void testThatEmptyKeysAreSupported() throws Exception
{
CharacterTrie<String> trie = CharacterTrie.newTrie();
trie.put("", FOO);
assertThat(trie.keySet()).contains("");
assertThat(trie.get("")).isEqualTo(FOO);
}
@Test
public void testStartsWith()
{
CharacterTrie<Object> tree = CharacterTrie.newTrie();
Object value = new Object();
tree.put("name=", value);
assertThat(tree.findLongestPrefix("name=value").getValue()).isEqualTo(value);
assertThat(tree.findLongestPrefix("")).isNull();
}
@Test
public void testToStringOnEntry()
{
CharacterTrie<String> trie = CharacterTrie.newTrie();
trie.put("foo", "bar");
assertThat(trie.findLongestPrefix("foo").toString()).isEqualTo("foo=bar");
}
@Test
public void testThatParentsWithoutChildrenAreRemoved()
{
CharacterTrie<String> trie = CharacterTrie.newTrie();
trie.put("foo", BAR);
trie.put("fooo", BAR);
trie.put("fooos", BAR);
// Removing fooo should leave fooos in the trie has it has fooos as a child
assertThat(trie.remove("fooo")).isEqualTo(BAR);
// Removing fooos should remove the left-over fooo node as it no longer has any children
assertThat(trie.remove("fooos")).isEqualTo(BAR);
}
@Test
public void testThatEntrySetIteratesOverAllElements() throws Exception
{
CharacterTrie<String> trie = CharacterTrie.newTrie();
trie.put("foo", FOO);
trie.put("fooo", BAR);
trie.put("fooos", ZOO);
Set<String> actualEntries = valuesInSet(trie.entrySet());
assertThat(actualEntries).containsOnly(FOO, BAR, ZOO);
}
@Test
public void testFindingAllEntriesWithPrefix() throws Exception
{
CharacterTrie<String> trie = CharacterTrie.newTrie();
trie.put("foo", FOO);
trie.put("fooo", BAR);
trie.put("fooos", ZOO);
Set<String> actualEntries = valuesInSet(trie.getEntriesWithPrefix("fooo"));
assertThat(actualEntries).containsOnly(BAR, ZOO);
}
@Test
public void testThatFindingAllEntriesWithNonExistingPrefixReturnsEmptySet() throws Exception
{
CharacterTrie<String> trie = CharacterTrie.newTrie();
trie.put("foo", FOO);
trie.put("fooo", BAR);
trie.put("fooos", ZOO);
assertThat(trie.getEntriesWithPrefix("zoo")).isEmpty();
}
@Test
public void testThatFindingAllEntriesWithEmptyPrefixReturnsTheWholeTrie() throws Exception
{
CharacterTrie<String> trie = CharacterTrie.newTrie();
trie.put("foo", FOO);
trie.put("fooo", BAR);
trie.put("fooos", ZOO);
// Make sure equals of Entry isn't fooling us
CharacterTrie<String> copy = CharacterTrie.newTrie(trie);
assertThat(trie.getEntriesWithPrefix("")).isEqualTo(copy.entrySet());
}
@Test
public void testThatNullKeyOrValueIsNotContained() throws Exception
{
CharacterTrie<Object> trie = CharacterTrie.newTrie();
assertThat(trie.containsKey(null)).isFalse();
assertThat(trie.containsValue(null)).isFalse();
}
@Test
public void testThatSizeForSubsetReturnsSizeOfSubset() throws Exception
{
CharacterTrie<String> trie = CharacterTrie.newTrie();
trie.put("foo", FOO);
trie.put("fooo", BAR);
trie.put("fooos", ZOO);
assertThat(trie.getEntriesWithPrefix("fooo")).hasSize(2);
}
@Test
public void testThatRemovalFromEntrySetIsMadeInOriginalStructure() throws Exception
{
CharacterTrie<String> trie = CharacterTrie.newTrie();
trie.put("foo", FOO);
trie.put("fooo", BAR);
trie.put("fooos", ZOO);
Set<Entry<String, String>> entrySet = trie.entrySet();
Entry<String, String> entryToRemove = Maps.immutableEntry("fooo", BAR);
entrySet.remove(entryToRemove);
assertThat(trie.containsKey("fooo")).isFalse();
Iterator<Entry<String, String>> iterator = entrySet.iterator();
while(iterator.hasNext())
{
iterator.next();
iterator.remove();
}
assertThat(trie).isEmpty();
}
@Test
public void testThatEntrySetIsLexicographicallyOrdered() throws Exception
{
CharacterTrie<String> trie = CharacterTrie.newTrie();
trie.put("one", "January");
trie.put("two", "February");
trie.put("three", "March");
assertThat(trie.entrySet().toString()).isEqualTo("[one=January, three=March, two=February]");
}
@Test
public void testThatPreCreatedEntryHasTheRightKey() throws Exception
{
CharacterTrie<String> trie = CharacterTrie.newTrie();
trie.put("NS", FOO);
trie.put("N", BAR);
assertThat(trie.keySet()).containsOnly("NS", "N");
}
private Set<String> valuesInSet(Set<Entry<String, String>> entries)
{
Set<String> result = Sets.newHashSetWithExpectedSize(entries.size());
for(Entry<String, String> entry : entries)
{
result.add(entry.getValue());
}
return result;
}
@Test
public void testThatModificationDuringIterationIsDetected() throws Exception
{
CharacterTrie<Object> trie = CharacterTrie.newTrie();
trie.put("foo", FOO);
trie.put("bar", BAR);
Iterator<Entry<String, Object>> iterator = trie.entrySet().iterator();
iterator.next();
trie.put("zoo", ZOO);
try
{
iterator.next();
fail("Modifying the trie during an iteration should yield a CME to avoid unpredictable errors further down the line");
}
catch(ConcurrentModificationException expected)
{
}
}
@Test
public void testThatEntriesWithTheSameKeyButWithDifferentValuesAreNonEqual() throws Exception
{
CharacterTrie<Object> trie = CharacterTrie.newTrie();
trie.put("foo", FOO);
Entry<String, Object> foo = trie.entrySet().iterator().next();
CharacterTrie<Object> anotherTrie = CharacterTrie.newTrie();
anotherTrie.put("foo", BAR);
Entry<String, Object> bar = anotherTrie.entrySet().iterator().next();
assertThat(foo).isNotEqualTo(bar);
}
@Test
public void testThatNullContractsAreFollowed()
{
NullPointerTester npeTester = new NullPointerTester();
npeTester.testStaticMethods(CharacterTrie.class, Visibility.PACKAGE);
npeTester.testInstanceMethods(CharacterTrie.newTrie(), Visibility.PACKAGE);
}
}