cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From skolbac...@apache.org
Subject [1/3] cayenne git commit: CAY-2029 | Allow out-of-order insertion into DI lists
Date Thu, 22 Oct 2015 09:32:25 GMT
Repository: cayenne
Updated Branches:
  refs/heads/master 7d10d7a1b -> 2df3c91a5


CAY-2029 | Allow out-of-order insertion into DI lists


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/3cdd4872
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/3cdd4872
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/3cdd4872

Branch: refs/heads/master
Commit: 3cdd4872e7efc8cfdcd36d21bbcdf208af930c9d
Parents: 7d10d7a
Author: Savva Kolbachev <s.kolbachev@gmail.com>
Authored: Fri Oct 16 16:18:01 2015 +0300
Committer: Savva Kolbachev <s.kolbachev@gmail.com>
Committed: Fri Oct 16 16:19:18 2015 +0300

----------------------------------------------------------------------
 .../java/org/apache/cayenne/di/DIGraph.java     | 163 +++++++++++++++++++
 .../java/org/apache/cayenne/di/ListBuilder.java |  29 ++--
 .../apache/cayenne/di/UnorderedListBuilder.java |  42 +++++
 .../cayenne/di/spi/DefaultListBuilder.java      |  57 +++++--
 .../org/apache/cayenne/di/spi/ListProvider.java |  53 ++++--
 .../di/spi/DefaultInjectorInjectionTest.java    |  57 +++++++
 6 files changed, 370 insertions(+), 31 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/3cdd4872/cayenne-di/src/main/java/org/apache/cayenne/di/DIGraph.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/main/java/org/apache/cayenne/di/DIGraph.java b/cayenne-di/src/main/java/org/apache/cayenne/di/DIGraph.java
new file mode 100644
index 0000000..4c04ced
--- /dev/null
+++ b/cayenne-di/src/main/java/org/apache/cayenne/di/DIGraph.java
@@ -0,0 +1,163 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.di;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The implementation here is basically an adjacency list, but a {@link Map}
+ * is used to map each vertex to its list of adjacent vertices.
+ *
+ * @param <V> A type of a vertex.
+ * @since 4.0.M3
+ */
+public class DIGraph<V> {
+
+    /**
+     * Note: {@link LinkedHashMap} is used for supporting insertion order.
+     */
+    private Map<V, List<V>> neighbors = new LinkedHashMap<>();
+
+    public DIGraph() {
+    }
+
+    /**
+     * Add a vertex to the graph. Nothing happens if vertex is already in graph.
+     */
+    public void add(V vertex) {
+        if (neighbors.containsKey(vertex)) {
+            return;
+        }
+
+        neighbors.put(vertex, new ArrayList<V>());
+    }
+
+    /**
+     * Add an edge to the graph; if either vertex does not exist, it's added.
+     * This implementation allows the creation of multi-edges and self-loops.
+     */
+    public void add(V from, V to) {
+        this.add(from);
+        this.add(to);
+        neighbors.get(from).add(to);
+    }
+
+    /**
+     * True iff graph contains vertex.
+     */
+    public boolean contains(V vertex) {
+        return neighbors.containsKey(vertex);
+    }
+
+    /**
+     * Remove an edge from the graph. Nothing happens if no such edge.
+     *
+     * @throws IllegalArgumentException if either vertex doesn't exist.
+     */
+    public void remove(V from, V to) {
+        if (!(this.contains(from) && this.contains(to)))
+            throw new IllegalArgumentException("Nonexistent vertex");
+
+        neighbors.get(from).remove(to);
+    }
+
+    /**
+     * Return (as a Map) the out-degree of each vertex.
+     */
+    public Map<V, Integer> outDegree() {
+        Map<V, Integer> result = new LinkedHashMap<>();
+
+        for (Map.Entry<V, List<V>> entry : neighbors.entrySet()) {
+            result.put(entry.getKey(), entry.getValue().size());
+        }
+
+        return result;
+    }
+
+    /**
+     * Return (as a Map) the in-degree of each vertex.
+     */
+    public Map<V, Integer> inDegree() {
+        Map<V, Integer> result = new LinkedHashMap<>();
+
+        for (Map.Entry<V, List<V>> entry : neighbors.entrySet()) {
+            result.putIfAbsent(entry.getKey(), 0);
+            for (V to : entry.getValue()) {
+                result.put(to, result.getOrDefault(to, 0) + 1);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Return (as a List) the topological sort of the vertices; null for no such sort.
+     */
+    public List<V> topSort() {
+        Map<V, Integer> degree = inDegree();
+        Deque<V> zeroDegree = new ArrayDeque<>();
+        LinkedList<V> result = new LinkedList<>();
+
+        for (Map.Entry<V, Integer> entry : degree.entrySet()) {
+            if (entry.getValue() == 0) {
+                zeroDegree.push(entry.getKey());
+            }
+        }
+
+        while (!zeroDegree.isEmpty()) {
+            V v = zeroDegree.pop();
+            result.push(v);
+
+            for (V neighbor : neighbors.get(v)) {
+                degree.put(neighbor, degree.get(neighbor) - 1);
+                if (degree.get(neighbor) == 0) {
+                    zeroDegree.push(neighbor);
+                }
+            }
+        }
+
+        // Check that we have used the entire graph (if not, there was a cycle)
+        if (result.size() != neighbors.size()) {
+            return null;
+        }
+
+        return result;
+    }
+
+    /**
+     * String representation of graph.
+     */
+    public String toString() {
+        StringBuffer s = new StringBuffer();
+
+        for (Map.Entry<V, List<V>> entry : neighbors.entrySet()) {
+            s.append("\n    " + entry.getKey() + " -> " + entry.getValue());
+        }
+
+        return s.toString();
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3cdd4872/cayenne-di/src/main/java/org/apache/cayenne/di/ListBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/main/java/org/apache/cayenne/di/ListBuilder.java b/cayenne-di/src/main/java/org/apache/cayenne/di/ListBuilder.java
index fb1f7f6..3724484 100644
--- a/cayenne-di/src/main/java/org/apache/cayenne/di/ListBuilder.java
+++ b/cayenne-di/src/main/java/org/apache/cayenne/di/ListBuilder.java
@@ -18,23 +18,32 @@
  ****************************************************************/
 package org.apache.cayenne.di;
 
-import java.util.Collection;
-
-import org.apache.cayenne.di.DIRuntimeException;
-
 /**
  * A binding builder for list configurations.
  * 
  * @param <T> A type of list values.
  * @since 3.1
  */
-public interface ListBuilder<T> {
+public interface ListBuilder<T> extends UnorderedListBuilder<T> {
+
+    /**
+     * @since 4.0.M3
+     */
+    UnorderedListBuilder<T> after(Class<?> type);
+
+    /**
+     * @since 4.0.M3
+     */
+    UnorderedListBuilder<T> after(Key<?> key);
 
-    ListBuilder<T> add(Class<? extends T> interfaceType) throws DIRuntimeException;
+    /**
+     * @since 4.0.M3
+     */
+    UnorderedListBuilder<T> before(Class<?> type);
 
-    ListBuilder<T> add(T value) throws DIRuntimeException;
-    
-    ListBuilder<T> addAll(Collection<T> values) throws DIRuntimeException;
+    /**
+     * @since 4.0.M3
+     */
+    UnorderedListBuilder<T> before(Key<?> key);
 
-    void in(Scope scope);
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3cdd4872/cayenne-di/src/main/java/org/apache/cayenne/di/UnorderedListBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/main/java/org/apache/cayenne/di/UnorderedListBuilder.java b/cayenne-di/src/main/java/org/apache/cayenne/di/UnorderedListBuilder.java
new file mode 100644
index 0000000..39c3de2
--- /dev/null
+++ b/cayenne-di/src/main/java/org/apache/cayenne/di/UnorderedListBuilder.java
@@ -0,0 +1,42 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.di;
+
+import java.util.Collection;
+
+/**
+ * A binding builder for unordered list configurations.
+ *
+ * @param <T> A type of list values.
+ * @since 4.0.M3
+ */
+public interface UnorderedListBuilder<T> {
+
+    ListBuilder<T> add(Class<? extends T> interfaceType) throws DIRuntimeException;
+
+    ListBuilder<T> add(T value) throws DIRuntimeException;
+
+    ListBuilder<T> add(Key<T> key, T object) throws DIRuntimeException;
+
+    ListBuilder<T> addAll(Collection<T> values) throws DIRuntimeException;
+
+    void in(Scope scope);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3cdd4872/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultListBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultListBuilder.java b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultListBuilder.java
index cac9729..f337681 100644
--- a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultListBuilder.java
+++ b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultListBuilder.java
@@ -18,15 +18,16 @@
  ****************************************************************/
 package org.apache.cayenne.di.spi;
 
-import java.util.Collection;
-import java.util.List;
-
 import org.apache.cayenne.di.DIRuntimeException;
 import org.apache.cayenne.di.Key;
 import org.apache.cayenne.di.ListBuilder;
+import org.apache.cayenne.di.UnorderedListBuilder;
 import org.apache.cayenne.di.Provider;
 import org.apache.cayenne.di.Scope;
 
+import java.util.Collection;
+import java.util.List;
+
 /**
  * @since 3.1
  */
@@ -47,30 +48,64 @@ class DefaultListBuilder<T> implements ListBuilder<T> {
     @Override
     public ListBuilder<T> add(Class<? extends T> interfaceType)
             throws DIRuntimeException {
-        getListProvider().add(injector.getProvider(interfaceType));
+
+        Key key = Key.get(interfaceType);
+        getListProvider().add(key, injector.getProvider(key));
+        return this;
+    }
+
+    @Override
+    public ListBuilder<T> add(T object) throws DIRuntimeException {
+
+        Provider<T> provider0 = new InstanceProvider<T>(object);
+        Provider<T> provider1 = new FieldInjectingProvider<T>(provider0, injector);
+
+        getListProvider().add(Key.get(object.getClass(), String.valueOf(object.hashCode())),
provider1);
         return this;
     }
 
     @Override
-    public ListBuilder<T> add(T value) throws DIRuntimeException {
+    public ListBuilder<T> add(Key<T> key, T object) throws DIRuntimeException
{
 
-        Provider<T> provider0 = new InstanceProvider<T>(value);
+        Provider<T> provider0 = new InstanceProvider<T>(object);
         Provider<T> provider1 = new FieldInjectingProvider<T>(provider0, injector);
 
-        getListProvider().add(provider1);
+        getListProvider().add(key, provider1);
+        return this;
+    }
+
+    @Override
+    public UnorderedListBuilder<T> after(Class<?> type) {
+        return after(Key.get(type));
+    }
+
+    @Override
+    public UnorderedListBuilder<T> after(Key<?> key) {
+        getListProvider().after(key);
+        return this;
+    }
+
+    @Override
+    public UnorderedListBuilder<T> before(Class<?> type) {
+        return before(Key.get(type));
+    }
+
+    @Override
+    public UnorderedListBuilder<T> before(Key<?> key) {
+        getListProvider().before(key);
         return this;
     }
 
     @Override
-    public ListBuilder<T> addAll(Collection<T> values) throws DIRuntimeException
{
+    public ListBuilder<T> addAll(Collection<T> objects) throws DIRuntimeException
{
 
         ListProvider listProvider = getListProvider();
 
-        for (T value : values) {
-            Provider<T> provider0 = new InstanceProvider<T>(value);
+        for (T object : objects) {
+            Provider<T> provider0 = new InstanceProvider<T>(object);
             Provider<T> provider1 = new FieldInjectingProvider<T>(provider0,
injector);
 
-            listProvider.add(provider1);
+            listProvider.add(Key.get(object.getClass(), String.valueOf(object.hashCode())),
provider1);
         }
 
         return this;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3cdd4872/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ListProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ListProvider.java b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ListProvider.java
index baed20d..1e366bf 100644
--- a/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ListProvider.java
+++ b/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ListProvider.java
@@ -18,36 +18,69 @@
  ****************************************************************/
 package org.apache.cayenne.di.spi;
 
-import java.util.ArrayList;
-import java.util.List;
-
+import org.apache.cayenne.di.DIGraph;
 import org.apache.cayenne.di.DIRuntimeException;
+import org.apache.cayenne.di.Key;
 import org.apache.cayenne.di.Provider;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 /**
  * @since 3.1
  */
 class ListProvider implements Provider<List<?>> {
 
-    private List<Provider<?>> providers;
+    private Map<Key<?>, Provider<?>> providers;
+    private DIGraph graph;
+    private Key<?> lastKey;
 
     public ListProvider() {
-        this.providers = new ArrayList<Provider<?>>();
+        this.providers = new HashMap<>();
+        this.graph = new DIGraph();
     }
 
     @Override
     public List<?> get() throws DIRuntimeException {
-        List<Object> list = new ArrayList<Object>(providers.size());
+        List<Key<?>> insertOrder = graph.topSort();
+
+        if (insertOrder == null)
+            throw new DIRuntimeException("Dependency cycle detected in DI container");
+
+        if (insertOrder.size() != providers.size()) {
+            List<Key<?>> emptyKeys = new ArrayList<>();
 
-        for (Provider<?> provider : providers) {
-            list.add(provider.get());
+            for (Key<?> key : insertOrder) {
+                if (!providers.containsKey(key)) {
+                    emptyKeys.add(key);
+                }
+            }
+
+            throw new DIRuntimeException("DI list has no providers for keys: %s", emptyKeys);
+        }
+
+        List<Object> list = new ArrayList<>(insertOrder.size());
+        for (Key<?> key : insertOrder) {
+            list.add(providers.get(key).get());
         }
 
         return list;
     }
 
-    void add(Provider<?> provider) {
-        providers.add(provider);
+    void add(Key<?> key, Provider<?> provider) {
+        providers.put(key, provider);
+        graph.add(key);
+        lastKey = key;
+    }
+
+    void after(Key<?> key) {
+        graph.add(lastKey, key);
+    }
+
+    void before(Key<?> key) {
+        graph.add(key, lastKey);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3cdd4872/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DefaultInjectorInjectionTest.java
----------------------------------------------------------------------
diff --git a/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DefaultInjectorInjectionTest.java
b/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DefaultInjectorInjectionTest.java
index 4b72cdf..26f44b6 100644
--- a/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DefaultInjectorInjectionTest.java
+++ b/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DefaultInjectorInjectionTest.java
@@ -272,6 +272,32 @@ public class DefaultInjectorInjectionTest {
     }
 
     @Test
+    public void testListInjection_addOrderedValues() {
+        Module module = new Module() {
+            @Override
+            public void configure(Binder binder) {
+                binder.bind(MockInterface1.class).to(
+                        MockImplementation1_ListConfiguration.class);
+
+                binder.bindList("xyz")
+                        .add("1value")
+                        .add("2value")
+                        .add(Key.get(Object.class, "5value"), "5value")
+                        .after(Key.get(Object.class, "4value"))
+                        .add("3value")
+                        .before(Key.get(Object.class, "4value"))
+                        .add(Key.get(Object.class, "4value"), "4value");
+            }
+        };
+
+        DefaultInjector injector = new DefaultInjector(module);
+
+        MockInterface1 service = injector.getInstance(MockInterface1.class);
+        assertNotNull(service);
+        assertEquals(";1value;2value;3value;4value;5value", service.getName());
+    }
+
+    @Test
     public void testListInjection_addType() {
         Module module = new Module() {
 
@@ -293,6 +319,37 @@ public class DefaultInjectorInjectionTest {
     }
 
     @Test
+    public void testListInjection_addOrderedTypes() {
+        Module module = new Module() {
+
+            public void configure(Binder binder) {
+                binder.bind(MockInterface1.class).to(
+                        MockImplementation1_ListConfiguration.class);
+
+                binder.bind(MockInterface5.class).to(MockImplementation5.class);
+
+                binder.bindList("xyz")
+                        .add("1value")
+                        .add("5value")
+                        .before(MockInterface5.class)
+                        .add("2value")
+                        .add(Key.get(Object.class, "4value"), "4value")
+                        .add("6value")
+                        .after(MockInterface5.class)
+                        .add("3value")
+                        .before(Key.get(Object.class, "4value"))
+                        .add(MockInterface5.class);
+            }
+        };
+
+        DefaultInjector injector = new DefaultInjector(module);
+
+        MockInterface1 service = injector.getInstance(MockInterface1.class);
+        assertNotNull(service);
+        assertEquals(";1value;2value;3value;4value;5value;xyz;6value", service.getName());
+    }
+
+    @Test
     public void testListInjection_empty() {
         Module module = new Module() {
 


Mime
View raw message