/* * Copyright 2013 Samppa Saarela * * 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 org.javersion.util; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertThat; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.javersion.util.AbstractHashTrie.ArrayNode; import org.javersion.util.AbstractHashTrie.HashNode; import org.javersion.util.AbstractHashTrie.Node; import org.junit.Test; import com.google.common.collect.*; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; public class PersistentHashMapTest extends AbstractPersistentMapTest<PersistentHashMap<Integer,Integer>>{ private static <K, V> Merger<Map.Entry<K, V>> vetoMerger() { return new Merger<Map.Entry<K, V>>() { @Override public boolean merge(Map.Entry<K, V> oldEntry, Map.Entry<K, V> newEntry) { return false; } @Override public boolean insert(Map.Entry<K, V> newEntry) { return false; } @Override public boolean delete(Map.Entry<K, V> oldEntry) { return false; } }; } static class HashKey { public final int hash; public HashKey(int hash) { this.hash = hash; } @Override public int hashCode() { return hash; } public String toString() { return "" + hash + "@" + System.identityHashCode(this); } } // @Test // public void Add_Values() { // PersistentHashMap<String, String> map = PersistentHashMap.empty(); // PersistentHashMap<String, String> otherMap = map.assoc("key", "value"); // assertThat(otherMap.get("key"), equalTo("value")); // assertThat(map.get("key"), nullValue()); // // map = otherMap.assoc("key", "value2"); // assertThat(map.get("key"), equalTo("value2")); // assertThat(otherMap.get("key"), equalTo("value")); // // map = map.assoc("key2", "value"); // assertThat(map.get("key2"), equalTo("value")); // assertThat(otherMap.get("key2"), nullValue()); // // map = map.assoc("null", null); // assertThat(map.get("null"), nullValue()); // assertThat(map.containsKey("null"), equalTo(true)); // // assertThat(map.containsKey(null), equalTo(false)); // map = map.assoc(null, "not-null"); // assertThat(map.get(null), equalTo("not-null")); // } @Test public void Size_With_Collisions() { HashKey k1 = new HashKey(1); HashKey k2 = new HashKey(1); HashKey k3 = new HashKey(1); PersistentHashMap<Object, Object> map = PersistentHashMap.empty(); assertThat(map.size(), equalTo(0)); map = map.assoc(k1, k1); assertThat(map.size(), equalTo(1)); // Same key and value map = map.assoc(k1, k1); assertThat(map.size(), equalTo(1)); // Same key, different value map = map.assoc(k1, k2); assertThat(map.size(), equalTo(1)); // Colliding key map = map.assoc(k2, k2); assertThat(map.size(), equalTo(2)); // Same colliding key and value map = map.assoc(k2, k2); assertThat(map.size(), equalTo(2)); // Same colliding key, different value map = map.assoc(k2, k1); assertThat(map.size(), equalTo(2)); // Another colliding key map = map.assoc(k3, k3); assertThat(map.size(), equalTo(3)); } @Test public void Size_With_Deep_Collision() { HashKey k0 = new HashKey(0); HashKey k1 = new HashKey(0); PersistentHashMap<Object, Object> map = PersistentHashMap.empty(); map = map.assoc(k0, k0); map = map.assoc(k1, k1); assertThat(map.size(), equalTo(2)); for (int i=1; i < 32; i++) { map = map.assoc(i, i); assertThat(map.size(), equalTo(i + 2)); } assertThat(map.size(), equalTo(33)); map = map.assoc(32, 32); assertThat(map.size(), equalTo(34)); } @Test public void Collision_Dissoc() { HashKey k0 = new HashKey(0); HashKey k1 = new HashKey(0); HashKey k2 = new HashKey(0); PersistentHashMap<Object, Object> map = PersistentHashMap.empty(); map = map.assoc(k0, k0); map = map.assoc(k1, k1); assertThat(map.dissoc(0).size(), equalTo(2)); assertThat(map.dissoc(k1).size(), equalTo(1)); assertThat(map.dissoc(k1).get(k0), equalTo((Object) k0)); assertThat(map.dissoc(k0).size(), equalTo(1)); assertThat(map.dissoc(k0).get(k0), nullValue()); map = map.assoc(k2, k2); assertThat(map.dissoc(k0).size(), equalTo(2)); assertThat(map.dissoc(k0).get(k2), equalTo((Object) k2)); assertThat(map.dissoc(k0).get(k1), equalTo((Object) k1)); assertThat(map.dissoc(k1).size(), equalTo(2)); assertThat(map.dissoc(k1).get(k0), equalTo((Object) k0)); assertThat(map.dissoc(k1).get(k2), equalTo((Object) k2)); assertThat(map.dissoc(k2).size(), equalTo(2)); assertThat(map.dissoc(k2).get(k0), equalTo((Object) k0)); assertThat(map.dissoc(k2).get(k1), equalTo((Object) k1)); assertThat(map.dissoc(0), sameInstance(map)); } @Test public void ArrayNode_insert() { PersistentMap<Integer, Integer> map = emptyMap(), result; for (int i=0; i < 32; i++) { map = map.assoc(i, i); } map = map.dissoc(7); map = map.assoc(7, 7); assertThat(map.containsKey(7), equalTo(true)); map = map.dissoc(13); result = map.merge(13, 13, vetoMerger()); assertThat(result, sameInstance(map)); } @Test public void collisions() { HashKey k1 = new HashKey(1); HashKey k2 = new HashKey(1); HashKey k3 = new HashKey(1); PersistentHashMap<HashKey, HashKey> map = PersistentHashMap.empty(); map = map.assoc(k1, k1); map = map.assoc(k2, k1); map = map.assoc(k2, k2); map = map.assoc(k3, k3); assertThat(map.get(k1), equalTo(k1)); assertThat(map.get(k2), equalTo(k2)); assertThat(map.get(k3), equalTo(k3)); assertThat(map.get(new HashKey(1)), nullValue()); Map<HashKey, HashKey> hashMap = ImmutableMap.of(k1, k1, k2, k2, k3, k3); assertThat(map.asMap(), equalTo(hashMap)); map = map.assocAll(hashMap); assertThat(map.asMap(), equalTo(hashMap)); map = map.dissoc(k1); assertThat(map.containsKey(k1), equalTo(false)); assertThat(map.containsKey(k2), equalTo(true)); assertThat(map.containsKey(k3), equalTo(true)); map = map.dissoc(k2); map = map.dissoc(k2); assertThat(map.get(k2), nullValue()); map = map.dissoc(k3); assertThat(map.get(k3), nullValue()); assertThat(map.size(), equalTo(0)); } @Test public void collisions_veto() { Merger<Map.Entry<HashKey, HashKey>> merger = PersistentHashMapTest.<HashKey, HashKey>vetoMerger(); HashKey k1 = new HashKey(1); HashKey k2 = new HashKey(1); HashKey k3 = new HashKey(1); PersistentHashMap<HashKey, HashKey> map = PersistentHashMap.empty(), result; map = map.assoc(k1, k1); result = map.merge(k2, k2, merger); assertThat(result, sameInstance(map)); assertThat(result.size(), equalTo(1)); map = map.assoc(k2, k2); result = map.merge(k3, k3, merger); assertThat(result, sameInstance(map)); assertThat(result.size(), equalTo(2)); result = map.merge(k2, k3, merger); assertThat(result, sameInstance(map)); assertThat(result.size(), equalTo(2)); result = map.dissoc(k1, merger); assertThat(result, sameInstance(map)); assertThat(result.size(), equalTo(2)); } /** * */ @Test public void Collisions_Incremental() { PersistentHashMap<HashKey, HashKey> map = PersistentHashMap.<HashKey, HashKey>empty(); List<HashKey> keys = Lists.newArrayList(); for (int i=0; i < 4097; i++) { HashKey key = new HashKey(i); keys.add(key); map = map.assoc(key, key); key = new HashKey(i); keys.add(key); map = map.assoc(key, key); } assertThat(map.size(), equalTo(keys.size())); for (HashKey key : keys) { assertThat(map.get(key), equalTo(key)); } assertThat(map.get(new HashKey(5)), nullValue()); int size = map.size(); for (HashKey key : keys) { map = map.dissoc(key); map = map.dissoc(key); assertThat(map.size(), equalTo(size-1)); size--; } } @Test public void Assoc_All_Map() { Map<Integer, Integer> ints = ImmutableMap.of(1, 1, 2, 2); Map<Integer, Integer> map = PersistentHashMap.copyOf(ints).asMap(); assertThat(map, equalTo(ints)); } @Test public void Assoc_All_PersistentMap() { PersistentHashMap<Integer, Integer> map = PersistentHashMap.of(1, 1); PersistentHashMap<Integer, Integer> ints = PersistentHashMap.of(2, 2, 3, 3); Map<Integer, Integer> expected = ImmutableMap.of(1, 1, 2, 2, 3, 3); assertThat(map.assocAll(ints).asMap(), equalTo(expected)); } @Override protected PersistentHashMap<Integer, Integer> emptyMap() { return PersistentHashMap.empty(); } @Test public void iterate_deepest_possible_tree() { int k1 = 0b00_11111_11111_11111_11111_11111_11111, k2 = 0b01_11111_11111_11111_11111_11111_11111, k3 = 0b10_11111_11111_11111_11111_11111_11111, k4 = 0b11_11111_11111_11111_11111_11111_11111; PersistentHashMap<Integer, Integer> map = PersistentHashMap.<Integer, Integer>empty() .assoc(k1, 1) .assoc(k2, 2) .assoc(k3, 3) .assoc(k4, 4); Set<Integer> results = new HashSet<>(); for (Iterator<Map.Entry<Integer, Integer>> iter = map.iterator(); iter.hasNext(); ) { Map.Entry<Integer, Integer> entry = iter.next(); results.add(entry.getKey()); results.add(entry.getValue()); } assertThat(results, equalTo(ImmutableSet.of(k1, k2, k3, k4, 1, 2, 3, 4))); } @SuppressWarnings("rawtypes") @Override protected void assertMapProperties(PersistentMap<Integer, Integer> map) { assertThat(map, instanceOf(PersistentHashMap.class)); PersistentHashMap<Integer, Integer> hashMap = (PersistentHashMap<Integer, Integer>) map; Node root = hashMap.root(); if (root instanceof HashNode) { assertThat(((HashNode) root).updateContext.isCommitted(), equalTo(true)); } if (root instanceof ArrayNode) { assertThat(((ArrayNode) root).updateContext.isCommitted(), equalTo(true)); } } @Override protected void assertEmptyMap(PersistentMap<Integer, Integer> map) { assertThat(map.size(), equalTo(0)); assertThat(((PersistentHashMap<Integer, Integer>) map).root(), notNullValue()); } }