Bläddra i källkod

PrototypeMap: draft

Signed-off-by: Ranides Atterwim <ranides@gmail.com>
Mariusz Czarnowski 5 år sedan
förälder
incheckning
580c397841

+ 282 - 0
assira/src/main/java/net/ranides/assira/collection/prototype/AbstractPrototype.java

@@ -0,0 +1,282 @@
+package net.ranides.assira.collection.prototype;
+
+import lombok.experimental.UtilityClass;
+import net.ranides.assira.collection.FlatCollection;
+import net.ranides.assira.reflection.IClass;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+@UtilityClass
+public class AbstractPrototype {
+
+    public static abstract class DeclaredMap implements PrototypeMap {
+
+        @Override
+        public PrototypeMap declared() {
+            return this;
+        }
+
+        @Override
+        public Optional<PrototypeEntry<?>> entry(String key) {
+            if(has(key)) {
+                return Optional.of(PrototypeEntry.of(this, key));
+            }
+            return Optional.empty();
+        }
+
+        @Override
+        public <V> Optional<PrototypeEntry<V>> entry(PrototypeField<V> key) {
+            if(has(key)) {
+                return Optional.of(PrototypeEntry.of(this, key));
+            }
+            return Optional.empty();
+        }
+
+        @Override
+        public Collection<PrototypeEntry<?>> entries() {
+            return keys().stream().map(key -> PrototypeEntry.of(this, key)).collect(Collectors.toList());
+        }
+
+        @SuppressWarnings({"unchecked", "rawtypes"})
+        @Override
+        public <V> Collection<PrototypeEntry<V>> entries(IClass<V> type) {
+            return (Collection)entries()
+                .stream()
+                .filter(e -> e.type().isSubclass(type))
+                .collect(Collectors.toList());
+        }
+    }
+
+    public static abstract class BaseMap implements PrototypeMap {
+
+        @Override
+        public Optional<PrototypeEntry<?>> entry(String key) {
+            return apply(scope -> scope.declared().entry(key));
+        }
+
+        @Override
+        public <V> Optional<PrototypeEntry<V>> entry(PrototypeField<V> key) {
+            return apply(scope -> scope.declared().entry(key));
+        }
+
+        @Override
+        public Set<Object> keys() {
+            Set<Object> set = new HashSet<>();
+            accept(scope -> set.addAll(scope.declared().keys()));
+            return set;
+        }
+
+        @Override
+        public Collection<PrototypeEntry<?>> entries() {
+            FlatCollection<PrototypeEntry<?>> flat = new FlatCollection<>();
+            accept(scope -> flat.join(scope.declared().entries()));
+            return flat;
+        }
+
+        @Override
+        public <V> Collection<PrototypeEntry<V>> entries(IClass<V> type) {
+            FlatCollection<PrototypeEntry<V>> flat = new FlatCollection<>();
+            accept(scope -> flat.join(scope.declared().entries(type)));
+            return flat;
+        }
+
+        @Override
+        public boolean has(String key) {
+            return test(scope -> scope.declared().has(key));
+        }
+
+        @Override
+        public boolean has(PrototypeField<?> key) {
+            return test(scope -> scope.declared().has(key));
+        }
+
+        @Override
+        public Optional<Object> get(String key) {
+            return apply(scope -> scope.declared().get(key));
+        }
+
+        @Override
+        public <V> Optional<V> get(PrototypeField<V> key) {
+            return apply(scope -> scope.declared().get(key));
+        }
+
+        @Override
+        public boolean set(String key, Object value) {
+            return test(scope -> scope.declared().has(key) && scope.declared().set(key, value)) || declared().set(key, value);
+        }
+
+        @Override
+        public <V> boolean set(PrototypeField<V> key, V value) {
+            return test(scope -> scope.declared().has(key) && scope.declared().set(key, value)) || declared().set(key, value);
+        }
+
+        protected void accept(Consumer<PrototypeMap> consumer) {
+            for(PrototypeMap scope = this; scope != null; scope = scope.parent()) {
+                consumer.accept(scope);
+            }
+        }
+
+        protected <T> Optional<T> apply(Function<PrototypeMap, Optional<T>> function) {
+            for(PrototypeMap scope = this; scope != null; scope = scope.parent()) {
+                Optional<T> out = function.apply(scope);
+                if(out.isPresent()) {
+                    return out;
+                }
+            }
+            return Optional.empty();
+        }
+
+        protected boolean test(Predicate<PrototypeMap> predicate) {
+            for(PrototypeMap scope = this; scope != null; scope = scope.parent()) {
+                if(predicate.test(scope)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+    }
+
+    public static final class SimpleEntry<V> implements PrototypeEntry<V> {
+        private final PrototypeMap scope;
+        private final PrototypeField<V> field;
+        private final IClass<V> type;
+        private final String name;
+        private final V value;
+
+        public SimpleEntry(PrototypeMap scope, PrototypeField<V> field, V value) {
+            this.scope = scope;
+            this.field = field;
+            this.type = field.type();
+            this.name = field.name();
+            this.value = value;
+        }
+
+        public SimpleEntry(PrototypeMap scope, IClass<V> type, String name, V value) {
+            this.scope = scope;
+            this.field = PrototypeField.of(type, name);
+            this.type = field.type();
+            this.name = field.name();
+            this.value = value;
+        }
+
+        @Override
+        public PrototypeMap scope() {
+            return scope;
+        }
+
+        @Override
+        public PrototypeField<V> key() {
+            return field;
+        }
+
+        @Override
+        public IClass<V> type() {
+            return type;
+        }
+
+        @Override
+        public String name() {
+            return name;
+        }
+
+        @Override
+        public V get() {
+            return value;
+        }
+
+        @Override
+        public boolean set(V value) {
+            throw new UnsupportedOperationException("SimpleEntry is immutable");
+        }
+    }
+
+    public static final class NameEntry implements PrototypeEntry<Object> {
+        private final PrototypeMap scope;
+        private final String name;
+
+        public NameEntry(PrototypeMap scope, String name) {
+            this.scope = scope.declared();
+            this.name = name;
+        }
+
+        @Override
+        public PrototypeMap scope() {
+            return scope;
+        }
+
+        @Override
+        public PrototypeField<Object> key() {
+            return PrototypeField.of(name);
+        }
+
+        @Override
+        public IClass<Object> type() {
+            return IClass.OBJECT;
+        }
+
+        @Override
+        public String name() {
+            return name;
+        }
+
+        @Override
+        public Object get() {
+            return scope.get(name).orElseThrow(NoSuchElementException::new);
+        }
+
+        @Override
+        public boolean set(Object value) {
+            return scope.set(name, value);
+        }
+    }
+
+    public static final class FieldEntry<V> implements PrototypeEntry<V> {
+        private final PrototypeMap scope;
+        private final PrototypeField<V> field;
+
+        public FieldEntry(PrototypeMap scope, PrototypeField<V> field) {
+            this.scope = scope.declared();
+            this.field = field;
+        }
+
+        @Override
+        public PrototypeMap scope() {
+            return scope;
+        }
+
+        @Override
+        public PrototypeField<V> key() {
+            return field;
+        }
+
+        @Override
+        public IClass<V> type() {
+            return field.type();
+        }
+
+        @Override
+        public String name() {
+            return field.name();
+        }
+
+        @Override
+        public V get() {
+            return scope.get(field).orElseThrow(NoSuchElementException::new);
+        }
+
+        @Override
+        public boolean set(V value) {
+            return scope.set(field, value);
+        }
+    }
+}

+ 43 - 0
assira/src/main/java/net/ranides/assira/collection/prototype/PrototypeEntry.java

@@ -0,0 +1,43 @@
+package net.ranides.assira.collection.prototype;
+
+import net.ranides.assira.reflection.IClass;
+
+public interface PrototypeEntry<V> {
+
+    PrototypeMap scope();
+
+    PrototypeField<V> key();
+
+    IClass<V> type();
+
+    String name();
+
+    V get();
+
+    boolean set(V value);
+
+    static PrototypeEntry<?> of(PrototypeMap scope, Object key) {
+        if(key instanceof String) {
+            return PrototypeEntry.of(scope, (String) key);
+        } else {
+            return PrototypeEntry.of(scope, (PrototypeField<?>)key);
+        }
+    }
+
+    static <V> PrototypeEntry<V> of(PrototypeMap scope, PrototypeField<V> field) {
+        return new AbstractPrototype.FieldEntry<>(scope, field);
+    }
+
+    static PrototypeEntry<Object> of(PrototypeMap scope, String name) {
+        return new AbstractPrototype.NameEntry(scope, name);
+    }
+
+    static <V> PrototypeEntry<V> of(PrototypeMap scope, PrototypeField<V> field, V value) {
+        return new AbstractPrototype.SimpleEntry<>(scope, field, value);
+    }
+
+    static PrototypeEntry<Object> of(PrototypeMap scope, String name, Object value) {
+        return new AbstractPrototype.SimpleEntry<>(scope, IClass.OBJECT, name, value);
+    }
+
+}

+ 37 - 0
assira/src/main/java/net/ranides/assira/collection/prototype/PrototypeField.java

@@ -0,0 +1,37 @@
+package net.ranides.assira.collection.prototype;
+
+import lombok.EqualsAndHashCode;
+import net.ranides.assira.reflection.IClass;
+
+@EqualsAndHashCode
+public final class PrototypeField<V> {
+
+    private final IClass<V> type;
+    private final String name;
+
+    private PrototypeField(IClass<V> type, String name) {
+        this.type = type;
+        this.name = name;
+    }
+
+    public IClass<V> type() {
+        return type;
+    }
+
+    public String name() {
+        return name;
+    }
+
+    public static <V> PrototypeField<V> of(Class<V> type, String name) {
+        return new PrototypeField<>(IClass.typeinfo(type), name);
+    }
+
+    public static <V> PrototypeField<V> of(IClass<V> type, String name) {
+        return new PrototypeField<>(type, name);
+    }
+
+    public static PrototypeField<Object> of(String name) {
+        return new PrototypeField<>(IClass.OBJECT, name);
+    }
+
+}

+ 78 - 0
assira/src/main/java/net/ranides/assira/collection/prototype/PrototypeHashMap.java

@@ -0,0 +1,78 @@
+package net.ranides.assira.collection.prototype;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+public class PrototypeHashMap extends AbstractPrototype.BaseMap {
+
+    private final PrototypeMap parent;
+
+    private final PrototypeMap declared = new HashWrapper();
+
+    public PrototypeHashMap() {
+        this(null);
+    }
+
+    public PrototypeHashMap(PrototypeMap parent) {
+        this.parent = parent;
+    }
+
+    @Override
+    public PrototypeMap parent() {
+        return parent;
+    }
+
+    @Override
+    public PrototypeMap declared() {
+        return declared;
+    }
+
+    private class HashWrapper extends AbstractPrototype.DeclaredMap {
+        private final Map<Object, Object> data = new HashMap<>();
+
+        @Override
+        public PrototypeMap parent() {
+            return PrototypeHashMap.this.parent;
+        }
+
+        @Override
+        public Set<Object> keys() {
+            return data.keySet();
+        }
+
+        @Override
+        public boolean has(String key) {
+            return data.containsKey(key);
+        }
+
+        @Override
+        public boolean has(PrototypeField<?> key) {
+            return data.containsKey(key);
+        }
+
+        @Override
+        public Optional<Object> get(String key) {
+            return Optional.ofNullable(data.get(key));
+        }
+
+        @Override
+        public <V> Optional<V> get(PrototypeField<V> key) {
+            return Optional.ofNullable(data.get(key)).map(key.type()::cast);
+        }
+
+        @Override
+        public boolean set(String key, Object value) {
+            data.put(key, value);
+            return true;
+        }
+
+        @Override
+        public <V> boolean set(PrototypeField<V> key, V value) {
+            data.put(key, value);
+            return true;
+        }
+    }
+
+}

+ 37 - 0
assira/src/main/java/net/ranides/assira/collection/prototype/PrototypeMap.java

@@ -0,0 +1,37 @@
+package net.ranides.assira.collection.prototype;
+
+import net.ranides.assira.reflection.IClass;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+public interface PrototypeMap {
+
+    PrototypeMap parent();
+
+    PrototypeMap declared();
+
+    Optional<PrototypeEntry<?>> entry(String key);
+
+    <V> Optional<PrototypeEntry<V>> entry(PrototypeField<V> key);
+
+    Set<Object> keys();
+
+    Collection<PrototypeEntry<?>> entries();
+
+    <V>Collection<PrototypeEntry<V>> entries(IClass<V> type);
+
+    boolean has(String key);
+
+    boolean has(PrototypeField<?> key);
+
+    Optional<Object> get(String key);
+
+    <V> Optional<V> get(PrototypeField<V> key);
+
+    boolean set(String key, Object value);
+
+    <V> boolean set(PrototypeField<V> key, V value);
+
+}

+ 81 - 0
assira/src/test/java/net/ranides/assira/collection/prototype/PrototypeHashMapTest.java

@@ -0,0 +1,81 @@
+package net.ranides.assira.collection.prototype;
+
+import net.ranides.assira.collection.sets.HashSet;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class PrototypeHashMapTest {
+
+    PrototypeHashMap map1;
+    PrototypeHashMap map2;
+    PrototypeHashMap map3;
+    PrototypeHashMap map4;
+
+    @Before
+    public void init() {
+        map1 = new PrototypeHashMap();
+        map2 = new PrototypeHashMap(map1);
+        map3 = new PrototypeHashMap(map2);
+        map4 = new PrototypeHashMap(map3);
+
+        map1.declared().set("a", 11);
+        map1.declared().set("b", 12);
+        map2.declared().set("c", 13);
+        map2.declared().set("d", 14);
+        map3.declared().set("e", 15);
+        map3.declared().set("f", 16);
+    }
+
+    @Test
+    public void basic() {
+        assertEquals(12, map1.get("b").orElse(0));
+        assertEquals(12, map2.get("b").orElse(0));
+        assertEquals(12, map3.get("b").orElse(0));
+        assertEquals(12, map4.get("b").orElse(0));
+
+        assertEquals(16, map3.get("f").orElse(0));
+        assertEquals(16, map4.get("f").orElse(0));
+
+        assertTrue(map1.has("b"));
+        assertTrue(map4.has("b"));
+
+        assertTrue(map3.has("f"));
+        assertTrue(map4.has("f"));
+
+        assertTrue(map1.declared().has("b"));
+        assertFalse(map4.declared().has("b"));
+
+        assertTrue(map3.has("f"));
+        assertFalse(map4.declared().has("f"));
+    }
+
+    @Test
+    public void keys() {
+        Set<String> keys1 = new HashSet<>(Arrays.asList("a", "b"));
+        Set<String> keys2 = new HashSet<>(Arrays.asList("a", "b", "c", "d"));
+        Set<String> keys3 = new HashSet<>(Arrays.asList("a", "b", "c", "d", "e", "f"));
+
+        Set<String> dec1 = new HashSet<>(Arrays.asList("a", "b"));
+        Set<String> dec2 = new HashSet<>(Arrays.asList("c", "d"));
+        Set<String> dec3 = new HashSet<>(Arrays.asList("e", "f"));
+        Set<String> dec4 = Collections.emptySet();
+
+        assertEquals(keys1, map1.keys());
+        assertEquals(keys2, map2.keys());
+        assertEquals(keys3, map3.keys());
+        assertEquals(keys3, map4.keys());
+
+        assertEquals(dec1, map1.declared().keys());
+        assertEquals(dec2, map2.declared().keys());
+        assertEquals(dec3, map3.declared().keys());
+        assertEquals(dec4, map4.declared().keys());
+    }
+}