Преглед на файлове

new: IClassLoader#defineClass(Path)
new: QuotientMap - draft
new: DynamicInvoker - draft

Mariusz Sieroń преди 6 години
родител
ревизия
be498e7991

+ 16 - 3
assira/src/main/java/net/ranides/assira/annotations/Meta.java

@@ -6,9 +6,8 @@
  */
 package net.ranides.assira.annotations;
 
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
+import java.lang.annotation.*;
+
 import static java.lang.annotation.ElementType.*;
 import static java.lang.annotation.RetentionPolicy.*;
 import java.util.ServiceLoader;
@@ -123,4 +122,18 @@ public final class Meta {
         String value() default "";
 
     }
+
+    @Target(METHOD)
+    @Retention(RUNTIME)
+    public @interface DynamicDispatch {
+    }
+
+    /**
+     * Annotated type or method can be drastically changed or removed at all in future versions
+     */
+    @Target({METHOD, TYPE})
+    @Retention(SOURCE)
+    public @interface Experimental {
+    }
+
 }

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

@@ -0,0 +1,37 @@
+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();
+
+    }
+}

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

@@ -0,0 +1,488 @@
+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;
+        }
+    }
+}

+ 168 - 0
assira/src/main/java/net/ranides/assira/generic/DynamicInvoker.java

@@ -0,0 +1,168 @@
+package net.ranides.assira.generic;
+
+import lombok.Data;
+import net.ranides.assira.annotations.Meta;
+import net.ranides.assira.collection.quotient.QuotientMap;
+import net.ranides.assira.collection.quotient.QuotientTreeMap;
+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.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Very naive, buth 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.
+ *
+ */
+public class DynamicInvoker {
+
+    private final List<RFunction> list;
+
+    private DynamicInvoker(Builder src) {
+        this.list = src.list;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public Object call(Object value) {
+        Function<Object, Object> func = match(value)
+            .orElseThrow(() -> new IllegalArgumentException("No method found for: " + value.getClass()));
+        return func.apply(value);
+    }
+
+    @SuppressWarnings("unchecked")
+    public Optional<Function<Object, Object>> match(Object value) {
+        for (RFunction r : list) {
+            if (r.arg.isInstance(value)) {
+                return Optional.of(r.func);
+            }
+        }
+        return Optional.empty();
+    }
+
+    public <T,R> R call(Class<R> returns, T value) {
+        Function<T, R> func = match(returns, value)
+                .orElseThrow(() -> new IllegalArgumentException("No method found for: " + value.getClass() + "=>" + ClassUtils.box(returns)));
+        return func.apply(value);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T, R> Optional<Function<T,R>> match(Class<R> returns, T value) {
+        Class<?> ir = ClassUtils.box(returns);
+        for (RFunction r : list) {
+            if (r.arg.isInstance(value)) {
+                if(ir.isAssignableFrom(r.ret)) {
+                    return Optional.of(r.func);
+                }
+
+            }
+        }
+        return Optional.empty();
+    }
+
+    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 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);
+        }
+
+        public <T> Builder append(Class<T> argument, Consumer<T> consumer) {
+            return append(argument, void.class, v -> { consumer.accept(v); return null; });
+        }
+
+        public Builder appendHelper(Class<?> helper) {
+            IClass.typeinfo(helper).methods()
+                    .require(IAttribute.STATIC)
+                    .require(IAttribute.ANY)
+                    .require(Meta.DynamicDispatch.class)
+                    .require(1)
+                    .stream()
+                    .each(m -> {
+                        Class<?> a = m.arguments().first().get().type().raw();
+                        Class<?> r = m.returns().raw();
+                        append0(a, r, v -> m.call(v));
+                    });
+            return this;
+        }
+
+        public Builder appendDelegate(Object delegate) {
+            IClass.typefor(delegate).methods()
+                    .require(IAttribute.ANY)
+                    .require(Meta.DynamicDispatch.class)
+                    .require(1)
+                    .stream()
+                    .each(m -> {
+                        Class<?> a = m.arguments().first().get().type().raw();
+                        Class<?> r = m.returns().raw();
+
+                        if (m.attributes().has(IAttribute.STATIC)) {
+                            append0(a, r, v -> m.call(v));
+                        } else {
+                            append0(a, r, v -> m.apply(delegate, new Object[]{v}));
+                        }
+                    });
+            return this;
+        }
+
+        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)));
+            }
+            return this;
+        }
+
+        public DynamicInvoker build() {
+            revdump(list, map.root());
+
+            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 {
+
+        private final Class<?> arg;
+
+        private final Class<?> ret;
+
+        private final Function func;
+
+        public RFunction(Class<?> arg, Class<?> ret, Function func) {
+            this.arg = ClassUtils.box(arg);
+            this.ret = ClassUtils.box(ret);
+            this.func = func;
+        }
+    }
+
+
+
+}

+ 7 - 0
assira/src/main/java/net/ranides/assira/reflection/IClassLoader.java

@@ -2,6 +2,9 @@ package net.ranides.assira.reflection;
 
 import net.ranides.asm.ClassReader;
 
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.concurrent.ConcurrentHashMap;
@@ -61,4 +64,8 @@ public class IClassLoader extends ClassLoader {
         return defineClass(classname, bytecode);
     }
 
+    public IClass<?> defineClass(Path path) throws IOException {
+        return defineClass(Files.readAllBytes(path));
+    }
+
 }

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

@@ -0,0 +1,152 @@
+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 {    }
+
+}

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

@@ -0,0 +1,350 @@
+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+
+
+}

+ 81 - 0
assira/src/test/java/net/ranides/assira/generic/DynamicInvokerTest.java

@@ -0,0 +1,81 @@
+package net.ranides.assira.generic;
+
+import lombok.RequiredArgsConstructor;
+import net.ranides.assira.annotations.Meta;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class DynamicInvokerTest {
+
+    @Test
+    public void basic() {
+
+        DynamicInvoker di = DynamicInvoker.builder()
+                .append(List.class, String.class, v -> "List")
+                .append(ArrayList.class, String.class, v -> "ArrayList")
+                .append(List.class, int.class, v -> v.size())
+                .append(ArrayList.class, int.class, v -> 1000 + v.size())
+                .build();
+
+        assertNotNull(di.call(Arrays.asList("a","b","c")));
+        assertNotNull(di.call(new ArrayList<>(Arrays.asList("a","b","c"))));
+
+        assertEquals("List", di.call(String.class, Arrays.asList("a","b","c")));
+        assertEquals("ArrayList", di.call(String.class, new ArrayList<>(Arrays.asList("a","b","c"))));
+
+        assertEquals((Integer)3, di.call(int.class, Arrays.asList("a","b","c")));
+        assertEquals((Integer)1003, di.call(int.class, new ArrayList<>(Arrays.asList("a","b","c"))));
+
+    }
+
+    @Test
+    public void reflective() {
+
+        DynamicInvoker di = DynamicInvoker.builder()
+                .appendDelegate(new InvokeHelper(2000))
+                .build();
+
+        assertNotNull(di.call(Arrays.asList("a","b","c")));
+        assertNotNull(di.call(new ArrayList<>(Arrays.asList("a","b","c"))));
+
+        assertEquals("List", di.call(String.class, Arrays.asList("a","b","c")));
+        assertEquals("ArrayList", di.call(String.class, new ArrayList<>(Arrays.asList("a","b","c"))));
+
+        assertEquals((Integer)3, di.call(int.class, Arrays.asList("a","b","c")));
+        assertEquals((Integer)2003, di.call(int.class, new ArrayList<>(Arrays.asList("a","b","c"))));
+
+    }
+
+    @RequiredArgsConstructor
+    public static class InvokeHelper {
+
+        private final int v;
+
+        @Meta.DynamicDispatch
+        protected static String asText(List<?> values) {
+            return "List";
+        }
+
+        @Meta.DynamicDispatch
+        protected String asText(ArrayList<?> values) {
+            return "ArrayList";
+        }
+
+        @Meta.DynamicDispatch
+        private static int asInt(List<?> values) {
+            return values.size();
+        }
+
+        @Meta.DynamicDispatch
+        private int asInt(ArrayList<?> values) {
+            return v + values.size();
+        }
+
+    }
+}

+ 52 - 0
assira/src/test/java/net/ranides/assira/reflection/IClassLoaderTest.java

@@ -0,0 +1,52 @@
+package net.ranides.assira.reflection;
+
+import net.ranides.assira.reflection.util.ReflectUtils;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.*;
+
+public class IClassLoaderTest {
+
+    private static final String TYPE1_CLASS = "..\\assira.test\\target\\classes\\net\\ranides\\assira\\test\\Type1.class";
+
+    @Test
+    public void system() throws IOException {
+        IClassLoader loader = IClassLoader.getSystem();
+
+        Supplier<Integer> obj1 = (Supplier<Integer>)loader.defineClass(Paths.get(TYPE1_CLASS)).construct();
+        assertEquals(11, (int)obj1.get());
+
+        Supplier<Integer> obj2 = (Supplier<Integer>)loader.defineClass(ReflectUtils.getByteCode(SmartProducer.class)).construct();
+
+        assertEquals(49, (int)obj2.get());
+        assertFalse(obj2 instanceof SmartProducer);
+    }
+
+    @Test
+    public void named() throws IOException {
+        IClassLoader loader = IClassLoader.getInstance("custom", IClassLoader.getSystem());
+
+        Supplier<Integer> obj1 = (Supplier<Integer>)loader.defineClass(Paths.get(TYPE1_CLASS)).construct();
+        assertEquals(11, (int)obj1.get());
+
+        Supplier<Integer> obj2 = (Supplier<Integer>)loader.defineClass(ReflectUtils.getByteCode(SmartProducer.class)).construct();
+
+        assertEquals(49, (int)obj2.get());
+        assertFalse(obj2 instanceof SmartProducer);
+    }
+
+    public static class SmartProducer implements Supplier<Integer> {
+
+        private int v = 7;
+
+        @Override
+        public Integer get() {
+            return (v = v*7);
+        }
+
+    }
+}