فهرست منبع

new: DynamicInvoker - draft

Mariusz Sieroń 6 سال پیش
والد
کامیت
b66fd29109

+ 0 - 37
assira/src/main/java/net/ranides/assira/collection/quotient/QuotientMap.java

@@ -1,37 +0,0 @@
-package net.ranides.assira.collection.quotient;
-
-import net.ranides.assira.annotations.Meta;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-
-@Meta.Experimental
-public interface QuotientMap<K,V> extends Map<K,V> {
-
-    Entry<K,V> root();
-
-    Entry<K,V> find(K key);
-
-    Entry<K,V> findExact(K key);
-
-    V removeExact(K key);
-
-    Collection<Entry<K,V>> findAll(K key);
-
-    Set<Entry<K, V>> quotientEntrySet();
-
-    interface Comparator<K> {
-
-        boolean isSuccessor(K predecessor, K successor);
-
-    }
-
-    interface Entry<K,V> extends Map.Entry<K, V> {
-
-        Entry<K,V> getPredecessor();
-
-        Collection<Entry<K,V>> getSuccessors();
-
-    }
-}

+ 0 - 488
assira/src/main/java/net/ranides/assira/collection/quotient/QuotientTreeMap.java

@@ -1,488 +0,0 @@
-package net.ranides.assira.collection.quotient;
-
-import lombok.EqualsAndHashCode;
-import net.ranides.assira.annotations.Meta;
-import net.ranides.assira.collection.ACollection;
-import net.ranides.assira.collection.iterators.IteratorUtils;
-
-import java.io.Serializable;
-import java.util.*;
-
-@Meta.Experimental
-public class QuotientTreeMap<K, V> implements QuotientMap<K, V> {
-
-    protected final Comparator<K> comparator;
-
-    protected final Node<K, V> root = new Node<>(null, null, null);
-
-    protected int size;
-
-    public QuotientTreeMap(Comparator<K> comparator) {
-        this.comparator = comparator;
-    }
-
-    @Override
-    public Entry<K, V> root() {
-        return root;
-    }
-
-    @Override
-    public Entry<K, V> find(K key) {
-        return find(root, key);
-    }
-
-    private Node<K, V> find(Node<K, V> node, K key) {
-        for (Node<K, V> c : node.succ) {
-            if (isSuccessor(c.key, key)) {
-                return find(c, key);
-            }
-        }
-        return node.value != null ? node : null;
-    }
-
-    @Override
-    public Entry<K, V> findExact(K key) {
-        return findExact(root, key);
-    }
-
-    private Node<K, V> findExact(Node<K, V> node, K key) {
-        if (Objects.equals(node.key, key)) {
-            return node.value != null ? node : null;
-        }
-
-        for (Node<K, V> c : node.succ) {
-            if (isSuccessor(c.key, key)) {
-                Node<K, V> ret = findExact(c, key);
-                if (ret != null) {
-                    return ret;
-                }
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public V get(Object key) {
-        //noinspection unchecked
-        Node<K, V> node = find(root, (K) key);
-        return node == null ? null : node.value;
-    }
-
-    @Override
-    public Collection<Entry<K, V>> findAll(K key) {
-        Node<K, V> found = find(root, key);
-        if (found != null) {
-            return new Predecessors<>(found);
-        }
-        return Collections.emptySet();
-    }
-
-    protected boolean isSuccessor(K parent, K child) {
-        return parent == null || (child != null && comparator.isSuccessor(parent, child));
-    }
-
-    @Override
-    public int size() {
-        return size;
-    }
-
-    @Override
-    public V put(K key, V value) {
-        return insertAt(root, key, value, true);
-    }
-
-    @Override
-    public V putIfAbsent(K key, V value) {
-        return insertAt(root, key, value, false);
-    }
-
-    @Override
-    public V replace(K key, V value) {
-        Node<K, V> node = findExact(root, key);
-        if (node == null) {
-            return null;
-        }
-        V prev = node.value;
-        node.value = value;
-        return prev;
-    }
-
-    protected V insertAt(Node<K, V> node, K key, V value, boolean overwrite) {
-        if (Objects.equals(node.key, key)) {
-            return replaceAt(node, key, value, overwrite);
-        }
-        if (!isSuccessor(node.key, key)) {
-            throw new IllegalStateException("Insert " + key + " at " + node.key);
-        }
-        for (Node<K, V> c : node.succ) {
-            if (isSuccessor(c.key, key)) {
-                return insertAt(c, key, value, overwrite);
-            } else if (isSuccessor(key, c.key)) {
-                size++;
-                return insertAbove(c, key, value);
-            }
-        }
-        size++;
-        return insertBelow(node, key, value);
-    }
-
-    protected V insertBelow(Node<K, V> parent, K key, V value) {
-        Node<K, V> node = new Node<>(parent, key, value);
-        parent.succ.add(node);
-        return null;
-    }
-
-    protected V insertAbove(Node<K, V> lower, K key, V value) {
-        Node<K, V> upper = lower.pred;
-        Node<K, V> node = new Node<>(upper, key, value);
-
-        // replace relations
-        node.succ.add(lower);
-        lower.pred = node;
-
-        // create new relations
-        upper.succ.remove(lower);
-        upper.succ.add(node);
-
-        // fix neighbours
-        Iterator<Node<K, V>> iterator = upper.succ.iterator();
-        while (iterator.hasNext()) {
-            Node<K, V> c = iterator.next();
-            if (!node.key.equals(c.key) && isSuccessor(node.key, c.key)) {
-                iterator.remove();
-                node.succ.add(c);
-            }
-        }
-        return null;
-    }
-
-    protected V replaceAt(Node<K, V> node, K key, V value, boolean overwrite) {
-        if (!overwrite && node.value != null) {
-            return null;
-        }
-        V prev = node.value;
-        node.value = value;
-        return prev;
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return size == 0;
-    }
-
-    @Override
-    public boolean containsKey(Object key) {
-        //noinspection unchecked
-        return findExact(root, (K) key) != null;
-    }
-
-    @Override
-    public boolean containsValue(Object value) {
-        return containsValue(root, value);
-    }
-
-    private boolean containsValue(Node<K, V> node, Object value) {
-        if (value.equals(node.value)) {
-            return true;
-        }
-        for (Node<K, V> c : node.succ) {
-            if (containsValue(c, value)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public V remove(Object key) {
-        //noinspection unchecked
-        Node<K, V> node = find(root, (K) key);
-        if (node == null) {
-            return null;
-        }
-        removeNode(node);
-        return node.value;
-    }
-
-    @Override
-    public V removeExact(K key) {
-        Node<K, V> node = findExact(root, key);
-        if (node == null) {
-            return null;
-        }
-        removeNode(node);
-        return node.value;
-    }
-
-    private void removeNode(Node<K, V> node) {
-        size--;
-        node.pred.succ.remove(node);
-        node.pred.succ.addAll(node.succ);
-    }
-
-    @Override
-    public void putAll(Map<? extends K, ? extends V> m) {
-        for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
-            put(entry.getKey(), entry.getValue());
-        }
-    }
-
-    @Override
-    public void clear() {
-        root.succ.clear();
-        size = 0;
-    }
-
-    @Override
-    public Set<K> keySet() {
-        return new KeyCollection();
-    }
-
-    @Override
-    public Collection<V> values() {
-        return new ValueCollection();
-    }
-
-    @Override
-    public Set<Map.Entry<K, V>> entrySet() {
-
-        return new EntryCollection(root);
-    }
-
-    @Override
-    public Set<Entry<K, V>> quotientEntrySet() {
-        //noinspection unchecked
-        return (Set)new EntryCollection(root);
-    }
-
-    private class EntryCollection extends ACollection<Map.Entry<K, V>> implements Set<Map.Entry<K, V>>, Serializable {
-
-        private static final long serialVersionUID = 1L;
-
-        private final Node<K, V> head;
-
-        public EntryCollection(Node<K, V> head) {
-            this.head = head;
-        }
-
-        @Override
-        public Iterator<Map.Entry<K, V>> iterator() {
-            return new NodeIterator<>(head);
-        }
-
-        @Override
-        public int size() {
-            return QuotientTreeMap.this.size();
-        }
-
-        @Override
-        public boolean remove(Object value) {
-            //noinspection unchecked
-            Map.Entry<K, V> entry = (Map.Entry<K, V>) value;
-            Node<K, V> found = findExact(head, entry.getKey());
-            if (found != null && Objects.equals(found.getValue(), entry.getValue())) {
-                removeNode(found);
-                return true;
-            } else {
-                return false;
-            }
-
-        }
-
-        @Override
-        public boolean contains(Object value) {
-            //noinspection unchecked
-            Map.Entry<K, V> entry = (Map.Entry<K, V>) value;
-            Node<K, V> found = findExact(head, entry.getKey());
-            return found != null && Objects.equals(found.getValue(), entry.getValue());
-        }
-
-        @Override
-        public boolean add(Map.Entry<K, V> entry) {
-            return null != insertAt(head, entry.getKey(), entry.getValue(), true);
-        }
-    }
-
-    private class KeyCollection extends ACollection<K> implements Set<K>, Serializable {
-
-        private static final long serialVersionUID = 1L;
-
-        @Override
-        public Iterator<K> iterator() {
-            return IteratorUtils.map(entrySet().iterator(), Map.Entry::getKey);
-        }
-
-        @Override
-        public int size() {
-            return QuotientTreeMap.this.size();
-        }
-
-        @Override
-        public boolean remove(Object value) {
-            //noinspection unchecked
-            return null != QuotientTreeMap.this.removeExact((K)value);
-        }
-
-        @Override
-        public boolean contains(Object value) {
-            return QuotientTreeMap.this.containsKey(value);
-        }
-
-    }
-
-    private class ValueCollection extends ACollection<V> implements Serializable {
-
-        private static final long serialVersionUID = 1L;
-
-        @Override
-        public Iterator<V> iterator() {
-            return IteratorUtils.map(entrySet().iterator(), Map.Entry::getValue);
-        }
-
-        @Override
-        public int size() {
-            return QuotientTreeMap.this.size();
-        }
-
-        @Override
-        public boolean contains(Object value) {
-            return QuotientTreeMap.this.containsValue(value);
-        }
-
-    }
-
-    private static class NodeIterator<K, V> implements Iterator<Map.Entry<K, V>> {
-
-        private final LinkedList<Entry<K, V>> parents = new LinkedList<>();
-
-        private Iterator<Entry<K, V>> children;
-
-        public NodeIterator(Entry<K, V> node) {
-            if (node.getValue() != null) {
-                children = Collections.singletonList(node).iterator();
-            } else {
-                children = node.getSuccessors().iterator();
-            }
-        }
-
-        @Override
-        public boolean hasNext() {
-            return children.hasNext() || !parents.isEmpty();
-        }
-
-        @Override
-        public Entry<K, V> next() {
-            if (children.hasNext()) {
-                Entry<K, V> current = children.next();
-                if (!current.getSuccessors().isEmpty()) {
-                    parents.addLast(current);
-                }
-                return current;
-            }
-            if (!parents.isEmpty()) {
-                children = parents.removeFirst().getSuccessors().iterator();
-                return next();
-            }
-            throw new NoSuchElementException();
-        }
-    }
-
-    @EqualsAndHashCode(of={"key", "value"})
-    protected static class Node<K, V> implements Entry<K, V> {
-
-        Node<K, V> pred;
-
-        K key;
-
-        V value;
-
-        Collection<Node<K, V>> succ = new HashSet<>();
-
-        Node(Node<K, V> pred, K key, V value) {
-            this.pred = pred;
-            this.key = key;
-            this.value = value;
-        }
-
-        @Override
-        public K getKey() {
-            return key;
-        }
-
-        @Override
-        public V getValue() {
-            return value;
-        }
-
-        @Override
-        public V setValue(V value) {
-            return value;
-        }
-
-        @Override
-        public Entry<K, V> getPredecessor() {
-            return pred;
-        }
-
-        @Override
-        public Collection<Entry<K, V>> getSuccessors() {
-            //noinspection unchecked
-            return (Collection) succ;
-        }
-
-        @Override
-        public String toString() {
-            return "{" + key + "=" + value + "}";
-        }
-
-    }
-
-    private static class Predecessors<K, V> extends ACollection<Entry<K, V>> {
-
-        private final Entry<K, V> head;
-
-        private int size = -1;
-
-        public Predecessors(Node<K, V> head) {
-            this.head = head;
-        }
-
-        @Override
-        public Iterator<Entry<K, V>> iterator() {
-            return new PredecessorIterator<>(head);
-        }
-
-        @Override
-        public int size() {
-            if (size != -1) {
-                return size;
-            }
-            int r = 0;
-            for (Entry<K, V> e = head; e.getPredecessor() != null; e = e.getPredecessor()) {
-                r++;
-            }
-            return size = r;
-        }
-    }
-
-    private static class PredecessorIterator<K, V> implements Iterator<Entry<K, V>> {
-
-        private Entry<K, V> node;
-
-        public PredecessorIterator(Entry<K, V> node) {
-            this.node = node;
-        }
-
-        @Override
-        public boolean hasNext() {
-            return node.getPredecessor() != null;
-        }
-
-        @Override
-        public Entry<K, V> next() {
-            Entry<K, V> curr = this.node;
-            node = node.getPredecessor();
-            return curr;
-        }
-    }
-}

+ 111 - 29
assira/src/main/java/net/ranides/assira/generic/DynamicInvoker.java

@@ -1,22 +1,18 @@
 package net.ranides.assira.generic;
 
-import lombok.Data;
+import lombok.EqualsAndHashCode;
 import net.ranides.assira.annotations.Meta;
-import net.ranides.assira.collection.quotient.QuotientMap;
-import net.ranides.assira.collection.quotient.QuotientTreeMap;
+import net.ranides.assira.collection.sets.HashSet;
 import net.ranides.assira.reflection.IAttribute;
 import net.ranides.assira.reflection.IClass;
 import net.ranides.assira.reflection.util.ClassUtils;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
+import java.util.*;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
 /**
- * Very naive, buth hopefully correct and predictable implementation of dynamic dispatch.
+ * Very naive, but hopefully correct and predictable implementation of dynamic dispatch.
  * It should select the best matching overload of method.
  * Optionally, class offers double-dispatch, basing on argument and return type. It can be used to implement
  * rather sophisticated casting.
@@ -72,12 +68,10 @@ public class DynamicInvoker {
 
     public static final class Builder {
 
-        // altough we use QuotientMap, it is so bad, that we should remove it as fast, as possible
-        private final QuotientMap<Class<?>, List<RFunction>> map = new QuotientTreeMap<>(Class::isAssignableFrom);
+        private final TypeList<RFunction> map = new TypeList<>();
 
         private final List<RFunction> list = new ArrayList<>();
 
-
         public <T,R> Builder append(Class<T> argument, Class<R> returns, Function<T, R> function) {
             return append0(argument, returns, function);
         }
@@ -121,31 +115,15 @@ public class DynamicInvoker {
         }
 
         private <T,R> Builder append0(Class<?> argument, Class<?> returns, Function function) {
-            RFunction v = new RFunction(argument, returns, function);
-
-            QuotientMap.Entry<Class<?>, List<RFunction>> entry = map.findExact(argument);
-            if(entry != null) {
-                entry.getValue().add(v);
-            } else {
-                map.put(argument, new ArrayList<>(Arrays.asList(v)));
-            }
+            map.append(argument, new RFunction(argument, returns, function));
             return this;
         }
 
         public DynamicInvoker build() {
-            revdump(list, map.root());
-
+            map.toList(list);
             return new DynamicInvoker(this);
         }
 
-        private static void revdump(List<RFunction> target, QuotientMap.Entry<Class<?>, List<RFunction>> node) {
-            for (QuotientMap.Entry<Class<?>, List<RFunction>> n : node.getSuccessors()) {
-                revdump(target, n);
-            }
-            if(null != node.getKey()) {
-                target.addAll(node.getValue());
-            }
-        }
     }
 
     private static final class RFunction {
@@ -163,6 +141,110 @@ public class DynamicInvoker {
         }
     }
 
+    private static class TypeList<V> {
+
+        private final Node<V> root = new Node<>(null, null, null);
+
+        public void toList(List<V> target) {
+            toList(target, root);
+        }
+
+        private void toList(List<V> target, Node<V> node) {
+            for (Node<V> n : node.succ) {
+                toList(target, n);
+            }
+            if(null != node.type) {
+                target.addAll(node.values);
+            }
+        }
+
+        public void append(Class<?> type, V value) {
+            Node<V> entry = find(root, type);
+            if(entry != null) {
+                entry.values.add(value);
+            } else {
+                insertAt(root, type, value);
+            }
+        }
+
+        private Node<V> find(Node<V> node, Class<?> key) {
+            if (Objects.equals(node.type, key)) {
+                return node;
+            }
+
+            for (Node<V> c : node.succ) {
+                if (isSuccessor(c.type, key)) {
+                    Node<V> ret = find(c, key);
+                    if (ret != null) {
+                        return ret;
+                    }
+                }
+            }
+            return null;
+        }
+
+        private boolean isSuccessor(Class<?> parent, Class<?> child) {
+            return parent == null || (child != null && parent.isAssignableFrom(child));
+        }
+
+        private void insertAt(Node<V> node, Class<?> key, V value) {
+            for (Node<V> c : node.succ) {
+                if (isSuccessor(c.type, key)) {
+                    insertAt(c, key, value);
+                    return;
+                } else if (isSuccessor(key, c.type)) {
+                    insertAbove(c, key, value);
+                    return;
+                }
+            }
+            insertBelow(node, key, value);
+        }
+
+        private void insertBelow(Node<V> parent, Class<?> key, V value) {
+            Node<V> node = new Node<>(parent, key, value);
+            parent.succ.add(node);
+        }
+
+        private void insertAbove(Node<V> lower, Class<?> key, V value) {
+            Node<V> upper = lower.pred;
+            Node<V> node = new Node<>(upper, key, value);
+
+            // replace relations
+            node.succ.add(lower);
+            lower.pred = node;
+
+            // create new relations
+            upper.succ.remove(lower);
+            upper.succ.add(node);
+
+            // fix neighbours
+            Iterator<Node<V>> iterator = upper.succ.iterator();
+            while (iterator.hasNext()) {
+                Node<V> c = iterator.next();
+                if (!node.type.equals(c.type) && isSuccessor(node.type, c.type)) {
+                    iterator.remove();
+                    node.succ.add(c);
+                }
+            }
+        }
+
+        static class Node<V> {
+
+            Node<V> pred;
+
+            final Class<?> type;
 
+            final List<V> values;
 
+            final Collection<Node<V>> succ = new HashSet<>();
+
+            Node(Node<V> predecessor, Class<?> type, V value) {
+                this.pred = predecessor;
+                this.type = type;
+                this.values = new ArrayList<>(Collections.singletonList(value));
+            }
+
+        }
+
+    }
 }

+ 0 - 152
assira/src/test/java/net/ranides/assira/collection/quotient/QuotientMapHelper.java

@@ -1,152 +0,0 @@
-package net.ranides.assira.collection.quotient;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-public class QuotientMapHelper {
-
-    public static final QuotientMap.Comparator<String> CMP = (a, b) -> {
-        String ai[] = a.split(":");
-        String bi[] = b.split(":");
-        if(ai.length != 3) {
-            throw new IllegalArgumentException("Invalid key " + a);
-        }
-        if(bi.length !=3) {
-            throw new IllegalArgumentException("Invalid key " + b);
-        }
-        if(!ai[0].equals(bi[0])) {
-            return false;
-        }
-        if(!ai[1].equals(bi[1])) {
-            return false;
-        }
-        return ai[2].compareTo(bi[2]) <= 0;
-    };
-
-    public static String NODES = "[{1:1:02=1102}, {1:5:13=1513}, {1:5:54=1554}, {1:5:61=1561}, {1:5:72=1572}, {1:8:13=1813}, {2:2:14=2214}, {2:3:15=2315}, {2:3:73=2313}, {2:6:22=2622}, {3:4:51=3451}, {3:4:70=3470}, {3:9:52=3952}, {6:4:11=6411}, {6:4:12=6412}, {6:4:15=6415}, {6:4:20=6420}, {6:4:24=6424}, {6:4:30=6430}, {6:4:42=6442}]";
-
-    public static QuotientMap<String, Integer> fill(QuotientMap<String, Integer> map) {
-        map.put("3:4:51", 3451);
-        map.put("3:4:70", 3470);
-        map.put("3:9:52", 3952);
-
-        map.put("2:2:14", 2214);
-        map.put("2:3:15", 2315);
-        map.put("2:3:73", 2313);
-        map.put("2:6:22", 2622);
-
-        map.put("1:1:02", 1102);
-        map.put("1:5:13", 1513);
-        map.put("1:5:54", 1554);
-        map.put("1:5:61", 1561);
-        map.put("1:5:72", 1572);
-        map.put("1:8:13", 1813);
-
-        map.put("6:4:42", 6442);
-        map.put("6:4:30", 6430);
-        map.put("6:4:24", 6424);
-        map.put("6:4:20", 6420);
-        map.put("6:4:15", 6415);
-        map.put("6:4:12", 6412);
-        map.put("6:4:11", 6411);
-
-        return map;
-    }
-
-    public static List<String> zip(Collection<? extends Map.Entry<?,?>> nodes) {
-        List<String> strings = new ArrayList<>();
-        for (Map.Entry<?,?> node : nodes) {
-            strings.add("{" + asText(node.getKey())+"="+node.getValue()+"}");
-        }
-        strings.sort(String::compareTo);
-        return strings;
-    }
-
-    public static void print(QuotientMap<?,?> tree) {
-        for (Object line : dump(tree)) {
-            System.out.printf("\"%s\"%n", line);
-        }
-        System.out.println("--------------");
-    }
-
-    public static List<String> revdump(QuotientMap<?,?> tree) {
-        ArrayList<String> target = new ArrayList<>();
-        revdump(target, "", tree.root());
-        return target;
-    }
-
-    private static void revdump(List<String> target, String indent, QuotientMap.Entry<?,?> node) {
-        String key;
-        if(null != node.getKey()) {
-            key = indent + "/" + asText(node.getKey());
-        } else {
-            key = indent;
-        }
-        for (QuotientMap.Entry<?,?> n : node.getSuccessors()) {
-            revdump(target, key, n);
-        }
-        if(null != node.getKey()) {
-            target.add(key + "=" + node.getValue());
-        }
-    }
-
-    public static List<String> dump(QuotientMap<?,?> tree) {
-        ArrayList<String> target = new ArrayList<>();
-        dump(target, "", tree.root());
-        return target;
-    }
-
-    private static void dump(List<String> target, String indent, QuotientMap.Entry<?,?> node) {
-        String key;
-        if(null != node.getKey()) {
-            key = indent + "/" + asText(node.getKey());
-            target.add(key + "=" + node.getValue());
-        } else {
-            key = indent;
-        }
-        for (QuotientMap.Entry<?,?> n : node.getSuccessors()) {
-            dump(target, key, n);
-        }
-    }
-
-    private static String asText(Object value) {
-        if(value instanceof Class<?>) {
-            return ((Class<?>)value).getSimpleName();
-        } else {
-            return String.valueOf(value);
-        }
-    }
-
-    static class A1 {    }
-
-    static class A1P extends A1 {    }
-
-    static class A1Q extends A1 {    }
-
-    static class A1R extends A1 {    }
-
-    static class A1P1 extends A1P {    }
-
-    static class A1P2 extends A1P {    }
-
-    static class A1Q3 extends A1Q {    }
-
-    static class A1P1S extends A1P1 {    }
-
-    static class A1P1D extends A1P1 {    }
-
-    static class B1 {    }
-
-    static class B2 extends B1 {    }
-
-    static class B3 extends B2 {    }
-
-    interface D1 {    }
-
-    interface D2 {    }
-
-    interface E extends D1, D2 {    }
-
-}

+ 0 - 350
assira/src/test/java/net/ranides/assira/collection/quotient/QuotientTreeMapTest.java

@@ -1,350 +0,0 @@
-package net.ranides.assira.collection.quotient;
-
-import net.ranides.assira.collection.iterators.IteratorUtils;
-import org.junit.Test;
-
-import java.util.AbstractMap.SimpleEntry;
-import java.util.*;
-
-import static net.ranides.assira.collection.quotient.QuotientMapHelper.*;
-import static org.junit.Assert.*;
-
-public class QuotientTreeMapTest {
-
-    private QuotientMap<String, Integer> makeMap() {
-        return fill(new QuotientTreeMap<>(CMP));
-    }
-
-
-    @Test
-    public void create() {
-        QuotientMap<String, Integer> map = makeMap();
-//        print(map);
-
-        assertEquals(NODES, zip(map.entrySet()).toString());
-        assertEquals(20, IteratorUtils.size(map.quotientEntrySet().iterator()));
-        assertEquals(20, map.quotientEntrySet().size());
-        assertEquals(20, map.size());
-    }
-
-
-    @Test
-    public void find() {
-        QuotientMap<String, Integer> map = makeMap();
-
-        assertNull(map.get("1:5:10"));
-        assertEquals(1513, map.get("1:5:15").intValue());
-        assertEquals(1554, map.get("1:5:54").intValue());
-        assertEquals(1572, map.get("1:5:99").intValue());
-        assertNull(map.get("2:6:12"));
-        assertEquals(2622, map.get("2:6:22").intValue());
-        assertEquals(2622, map.get("2:6:30").intValue());
-
-        assertNull(map.find("1:5:10"));
-        assertEquals(1513, map.find("1:5:15").getValue().intValue());
-        assertEquals(1554, map.find("1:5:54").getValue().intValue());
-        assertEquals(1572, map.find("1:5:99").getValue().intValue());
-        assertNull(map.find("2:6:12"));
-        assertEquals(2622, map.find("2:6:22").getValue().intValue());
-        assertEquals(2622, map.find("2:6:30").getValue().intValue());
-    }
-
-    @Test
-    public void findExact() {
-        QuotientMap<String, Integer> map = makeMap();
-
-        assertNull(map.findExact("1:5:10"));
-        assertNull(map.findExact("1:5:15"));
-        assertEquals(1554, map.findExact("1:5:54").getValue().intValue());
-        assertNull(map.findExact("1:5:99"));
-        assertNull(map.findExact("2:6:12"));
-        assertEquals(2622, map.findExact("2:6:22").getValue().intValue());
-        assertNull(map.findExact("2:6:30"));
-
-        assertEquals(4, map.findAll("1:5:99").size());
-        assertEquals(2, map.findAll("1:5:54").size());
-        assertEquals(1, map.findAll("1:5:13").size());
-        assertEquals(0, map.findAll("1:5:00").size());
-        assertEquals(7, map.findAll("6:4:42").size());
-        assertEquals(4, map.findAll("6:4:20").size());
-        assertEquals(0, map.findAll("7:3:10").size());
-
-        assertEquals("[{1:5:13=1513}, {1:5:54=1554}, {1:5:61=1561}, {1:5:72=1572}]", zip(map.findAll("1:5:99")).toString());
-        assertEquals("[{1:5:13=1513}, {1:5:54=1554}]", zip(map.findAll("1:5:54")).toString());
-        assertEquals("[{1:5:13=1513}]", zip(map.findAll("1:5:13")).toString());
-        assertEquals("[]", zip(map.findAll("1:5:00")).toString());
-        assertEquals("[{6:4:11=6411}, {6:4:12=6412}, {6:4:15=6415}, {6:4:20=6420}, {6:4:24=6424}, {6:4:30=6430}, {6:4:42=6442}]", zip(map.findAll("6:4:42")).toString());
-        assertEquals("[{6:4:11=6411}, {6:4:12=6412}, {6:4:15=6415}, {6:4:20=6420}]", zip(map.findAll("6:4:20")).toString());
-        assertEquals("[]", zip(map.findAll("7:3:10")).toString());
-    }
-
-    @Test
-    public void findAll_unique() {
-        QuotientMap<String, Integer> map = makeMap();
-
-        String nodes5s4 = "[{1:5:13=1513}, {1:5:54=1554}, {1:5:61=1561}, {1:5:72=1572}]";
-        String nodes5s2 = "[{1:5:13=1513}, {1:5:54=1554}]";
-        String nodes5s1 = "[{1:5:13=1513}]";
-        String nodes5s0 = "[]";
-
-        String nodes6s7 = "[{6:4:11=6411}, {6:4:12=6412}, {6:4:15=6415}, {6:4:20=6420}, {6:4:24=6424}, {6:4:30=6430}, {6:4:42=6442}]";
-        String nodes6s4 = "[{6:4:11=6411}, {6:4:12=6412}, {6:4:15=6415}, {6:4:20=6420}]";
-        String nodes6s0 = "[]";
-
-        assertEquals(4, map.findAll("1:5:99").size());
-        assertEquals(2, map.findAll("1:5:54").size());
-        assertEquals(1, map.findAll("1:5:13").size());
-        assertEquals(0, map.findAll("1:5:00").size());
-
-        assertEquals(7, map.findAll("6:4:42").size());
-        assertEquals(4, map.findAll("6:4:20").size());
-        assertEquals(0, map.findAll("7:3:10").size());
-
-        assertEquals(nodes5s4, zip(map.findAll("1:5:99")).toString());
-        assertEquals(nodes5s2, zip(map.findAll("1:5:54")).toString());
-        assertEquals(nodes5s1, zip(map.findAll("1:5:13")).toString());
-        assertEquals(nodes5s0, zip(map.findAll("1:5:00")).toString());
-
-        assertEquals(nodes6s7, zip(map.findAll("6:4:42")).toString());
-        assertEquals(nodes6s4, zip(map.findAll("6:4:20")).toString());
-        assertEquals(nodes6s0, zip(map.findAll("7:3:10")).toString());
-    }
-
-
-
-    @Test
-    public void duplicates() {
-        QuotientMap<String, Integer> map = makeMap();
-
-        String exp1 = "[{1:5:13=1513}, {1:5:54=1554}, {1:5:61=1561}, {1:5:72=1572}]";
-        String exp2 = "[{1:5:13=151300}, {1:5:54=1554}, {1:5:61=1561}, {1:5:72=1572}]";
-        String exp3 = "[{1:5:13=151300}, {1:5:54=155400}, {1:5:61=1561}, {1:5:72=1572}]";
-        String exp4 = "[{1:5:13=151300}, {1:5:54=155400}, {1:5:61=1561}, {1:5:72=1572}]";
-
-        assertEquals(exp1, zip(map.findAll("1:5:99")).toString());
-
-        assertEquals(1513, map.put("1:5:13", 151300).intValue());
-        assertEquals(exp2, zip(map.findAll("1:5:99")).toString());
-
-        assertEquals(1554, map.replace("1:5:54", 155400).intValue());
-        assertEquals(exp3, zip(map.findAll("1:5:99")).toString());
-
-        assertNull(map.replace("1:5:58", 155800));
-        assertEquals(exp4, zip(map.findAll("1:5:99")).toString());
-    }
-
-    @Test
-    public void putNeighbour() {
-        {
-            QuotientMap<Class<?>, Integer> tree = new QuotientTreeMap<>(Class::isAssignableFrom);
-            tree.put(A1.class, 1);
-            tree.put(A1Q.class, 2);
-            tree.put(A1R.class, 3);
-            tree.put(A1P.class, 4);
-            tree.put(A1P1.class, 5);
-            tree.put(A1P1S.class, 6);
-            tree.put(A1P1D.class, 7);
-//            print(tree);
-
-            Collection<String> expected = new HashSet<>(Arrays.asList(
-                    "/A1=1",
-                    "/A1/A1Q=2",
-                    "/A1/A1R=3",
-                    "/A1/A1P=4",
-                    "/A1/A1P/A1P1=5",
-                    "/A1/A1P/A1P1/A1P1S=6",
-                    "/A1/A1P/A1P1/A1P1D=7"
-            ));
-            assertEquals(expected, new HashSet<>(dump(tree)));
-        }
-
-
-        {
-            QuotientMap<Class<?>, Integer> tree = new QuotientTreeMap<>(Class::isAssignableFrom);
-            tree.put(A1.class, 1);
-            tree.put(A1Q.class, 2);
-            tree.put(A1R.class, 3);
-            tree.put(A1P1S.class, 4);
-            tree.put(A1P1D.class, 5);
-            tree.put(A1P.class, 6);
-            tree.put(A1P1.class, 7);
-            //print(zip(tree));
-
-            Collection<String> expected = new HashSet<>(Arrays.asList(
-                    "/A1=1",
-                    "/A1/A1Q=2",
-                    "/A1/A1R=3",
-                    "/A1/A1P=6",
-                    "/A1/A1P/A1P1=7",
-                    "/A1/A1P/A1P1/A1P1S=4",
-                    "/A1/A1P/A1P1/A1P1D=5"
-            ));
-            assertEquals(expected, new HashSet<>(dump(tree)));
-        }
-
-        {
-            QuotientMap<Class<?>, Integer> tree = new QuotientTreeMap<>(Class::isAssignableFrom);
-            tree.put(A1.class, 1);
-            tree.put(A1Q.class, 2);
-            tree.put(A1R.class, 3);
-            tree.put(A1P1S.class, 4);
-            tree.put(A1P1D.class, 5);
-            tree.put(A1P1.class, 6);
-            tree.put(A1P.class, 7);
-//            print(zip(tree));
-
-            Collection<String> expected = new HashSet<>(Arrays.asList(
-                    "/A1=1",
-                    "/A1/A1Q=2",
-                    "/A1/A1R=3",
-                    "/A1/A1P=7",
-                    "/A1/A1P/A1P1=6",
-                    "/A1/A1P/A1P1/A1P1S=4",
-                    "/A1/A1P/A1P1/A1P1D=5"
-            ));
-            List<String> olist = dump(tree);
-            List<String> rlist = revdump(tree);
-
-            assertEquals(expected, new HashSet<>(olist));
-            assertEquals(expected, new HashSet<>(rlist));
-
-            System.out.println(olist);
-            System.out.println("====");
-            System.out.println(rlist);
-
-            assertTrue(olist.indexOf("/A1=1") < olist.indexOf("/A1/A1R=3"));
-            assertTrue(olist.indexOf("/A1/A1P/A1P1=6") < olist.indexOf("/A1/A1P/A1P1/A1P1S=4"));
-
-            assertTrue(rlist.indexOf("/A1=1") > rlist.indexOf("/A1/A1R=3"));
-            assertTrue(rlist.indexOf("/A1/A1P/A1P1=6") > rlist.indexOf("/A1/A1P/A1P1/A1P1S=4"));
-        }
-
-    }
-
-    @Test
-    public void removeClear() {
-        QuotientMap<String, Integer> map = makeMap();
-
-        assertEquals(20, map.size());
-
-        assertEquals(1561, map.get("1:5:61").intValue());
-        assertEquals(1561, map.remove("1:5:61").intValue());
-        assertEquals(1554, map.get("1:5:61").intValue());
-
-        assertEquals(1554, map.remove("1:5:55").intValue());
-        assertEquals(1513, map.get("1:5:61").intValue());
-
-        assertEquals(6420, map.removeExact("6:4:20").intValue());
-        assertEquals(6415, map.get("6:4:20").intValue());
-
-        assertNull(map.removeExact("6:4:32"));
-        assertEquals(6430, map.get("6:4:32").intValue());
-
-        assertEquals(6430, map.removeExact("6:4:30").intValue());
-        assertEquals(6424, map.get("6:4:30").intValue());
-
-        assertNull(map.remove("9:0:00"));
-
-        assertEquals(16, map.size());
-        assertEquals(16, IteratorUtils.size(map.entrySet().iterator()));
-
-        map.clear();
-        assertEquals(0, map.size());
-        assertEquals(0, IteratorUtils.size(map.entrySet().iterator()));
-
-        assertEquals("[]", zip(map.entrySet()).toString());
-
-        fill(map);
-        assertEquals(NODES, zip(map.entrySet()).toString());
-    }
-
-    @Test
-    public void contains() {
-        QuotientMap<String, Integer> map = makeMap();
-
-        assertTrue(map.containsKey("6:4:30"));
-        assertFalse(map.containsKey("6:4:32"));
-        assertFalse(map.containsKey("9:0:00"));
-
-        assertTrue(map.containsValue(6430));
-        assertFalse(map.containsValue(6432));
-        assertFalse(map.containsValue("?"));
-    }
-
-    @Test
-    public void entrySet() {
-        QuotientMap<String, Integer> map = makeMap();
-        Set<Map.Entry<String, Integer>> entries = map.entrySet();
-
-        assertEquals(20, entries.size());
-        assertTrue(entries.contains(new SimpleEntry<>("2:6:22", 2622)));
-        assertFalse(entries.contains(new SimpleEntry<>("2:6:50", 2622)));
-        assertFalse(entries.contains(new SimpleEntry<>("2:6:22", 2600)));
-
-        assertTrue(entries.remove(new SimpleEntry<>("1:8:13", 1813)));
-        assertFalse(entries.remove(new SimpleEntry<>("1:5:54", 1500)));
-        assertFalse(entries.remove(new SimpleEntry<>("1:5:55", 1554)));
-
-        assertEquals(19, entries.size());
-
-        entries.add(new SimpleEntry<>("1:5:17", 1517));
-        entries.add(new SimpleEntry<>("6:4:11", 641100));
-
-        String content = "[{1:1:02=1102}, {1:5:13=1513}, {1:5:17=1517}, {1:5:54=1554}, {1:5:61=1561}, {1:5:72=1572}, {2:2:14=2214}, {2:3:15=2315}, {2:3:73=2313}, {2:6:22=2622}, {3:4:51=3451}, {3:4:70=3470}, {3:9:52=3952}, {6:4:11=641100}, {6:4:12=6412}, {6:4:15=6415}, {6:4:20=6420}, {6:4:24=6424}, {6:4:30=6430}, {6:4:42=6442}]";
-        assertEquals(content, zip(map.entrySet()).toString());
-
-        TreeSet<String> collected = IteratorUtils.collect(IteratorUtils.map(entries.iterator(), Object::toString), new TreeSet<>());
-        assertEquals(content, collected.toString());
-    }
-
-
-
-    @Test
-    public void keySet() {
-        QuotientMap<String, Integer> map = makeMap();
-        Set<String> keys = map.keySet();
-
-        print(map);
-
-        assertEquals(20, keys.size());
-        assertTrue(keys.contains("2:6:22"));
-        assertFalse(keys.contains("2:6:50"));
-
-        assertTrue(keys.remove("1:8:13"));
-        assertFalse(keys.remove("1:5:55"));
-
-        assertEquals(19, keys.size());
-
-        String content = "[{1:1:02=1102}, {1:5:13=1513}, {1:5:54=1554}, {1:5:61=1561}, {1:5:72=1572}, {2:2:14=2214}, {2:3:15=2315}, {2:3:73=2313}, {2:6:22=2622}, {3:4:51=3451}, {3:4:70=3470}, {3:9:52=3952}, {6:4:11=6411}, {6:4:12=6412}, {6:4:15=6415}, {6:4:20=6420}, {6:4:24=6424}, {6:4:30=6430}, {6:4:42=6442}]";
-        assertEquals(content, zip(map.entrySet()).toString());
-
-        String keyset = "[1:1:02, 1:5:13, 1:5:54, 1:5:61, 1:5:72, 2:2:14, 2:3:15, 2:3:73, 2:6:22, 3:4:51, 3:4:70, 3:9:52, 6:4:11, 6:4:12, 6:4:15, 6:4:20, 6:4:24, 6:4:30, 6:4:42]";
-        TreeSet<String> collected = IteratorUtils.collect(keys.iterator(), new TreeSet<>());
-        assertEquals(keyset, collected.toString());
-    }
-
-    @Test
-    public void values() {
-        QuotientMap<String, Integer> map = makeMap();
-        Collection<Integer> values = map.values();
-
-        print(map);
-
-        assertEquals(20, values.size());
-        assertTrue(values.contains(2622));
-        assertFalse(values.contains(2650));
-
-        String keyset = "[1102, 1513, 1554, 1561, 1572, 1813, 2214, 2313, 2315, 2622, 3451, 3470, 3952, 6411, 6412, 6415, 6420, 6424, 6430, 6442]";
-        TreeSet<Integer> collected = IteratorUtils.collect(values.iterator(), new TreeSet<>());
-        assertEquals(keyset, collected.toString());
-    }
-
-    // TODO: putAll
-
-    // TODO: entrySet: iterator+, size+, contains+, remove+, add+
-
-    // TODO: keySet:   iterator, size, contains, remove
-
-    // TODO: values:   iterator, size+, contains+
-
-}