Author: aadamchik
Date: Sat Nov 21 18:22:39 2009
New Revision: 882956

URL: http://svn.apache.org/viewvc?rev=882956&view=rev
Log:
playing with DI ideas

* configuration binding to object interface instead of implementation
* circular dependency detection
* provider injection support

Added:
    
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIUtil.java
      - copied, changed from r882933, 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java
    
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/InjectionStack.java
    
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation1_DepOn2.java
      - copied, changed from r882933, 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java
    
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation1_DepOn2Provider.java
      - copied, changed from r882933, 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java
    
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2_ConstructorProvider.java
      - copied, changed from r882933, 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java
    
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation5.java
      - copied, changed from r882933, 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface2.java
    
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface5.java
      - copied, changed from r882933, 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface2.java
Modified:
    cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/Binder.java
    cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/Injector.java
    
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/ListBuilder.java
    
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/MapBuilder.java
    
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ConstructorProvider.java
    
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultBindingBuilder.java
    
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultInjector.java
    
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultListBuilder.java
    
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultMapBuilder.java
    
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/FieldInjectingProvider.java
    
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java
    
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface2.java
    
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DefaultInjectorTest.java

Modified: 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/Binder.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/Binder.java?rev=882956&r1=882955&r2=882956&view=diff
==============================================================================
--- cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/Binder.java 
(original)
+++ cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/Binder.java 
Sat Nov 21 18:22:39 2009
@@ -28,17 +28,17 @@
 
     /**
      * Starts a binding of a map "configuration" that will be injected into an
-     * implementation class identified via the "implementationType" parameter.
-     * Configurations can only be injected via a constructor. Each type can 
take at most
-     * one configuration object via a constructor.
+     * implementation class for the specified "interfaceType" parameter. 
Configurations
+     * can only be injected via a constructor. An object can take at most one
+     * configuration object via a constructor.
      */
-    <T> MapBuilder<T> bindMap(Class<T> implementationType);
+    <T> MapBuilder<T> bindMap(Class<T> interfaceType);
 
     /**
      * Starts a binding of a list "configuration" that will be injected into an
-     * implementation class identified via the "implementationType" parameter.
-     * Configurations can only be injected via a constructor. Each type can 
take at most
-     * one configuration object via a constructor.
+     * implementation class for the specified "interfaceType" parameter. 
Configurations
+     * can only be injected via a constructor. An object can take at most one
+     * configuration object via a constructor.
      */
-    <T> ListBuilder<T> bindList(Class<T> implementationType);
+    <T> ListBuilder<T> bindList(Class<T> interfaceType);
 }

Modified: 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/Injector.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/Injector.java?rev=882956&r1=882955&r2=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/Injector.java 
(original)
+++ 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/Injector.java 
Sat Nov 21 18:22:39 2009
@@ -28,6 +28,8 @@
      * {...@link DIException} if the type is not bound, or an instance can not 
be created.
      */
     <T> T getInstance(Class<T> type) throws DIException;
+    
+    <T> Provider<T> getProvider(Class<T> type) throws DIException;
 
     <T> Map<String, ?> getMapConfiguration(Class<T> type);
 

Modified: 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/ListBuilder.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/ListBuilder.java?rev=882956&r1=882955&r2=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/ListBuilder.java 
(original)
+++ 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/ListBuilder.java 
Sat Nov 21 18:22:39 2009
@@ -25,7 +25,7 @@
  */
 public interface ListBuilder<T> {
 
-    <E> ListBuilder<T> add(Class<? extends E> valueType) throws DIException;
+    <E> ListBuilder<T> add(Class<? extends E> interfaceType) throws 
DIException;
 
     <E> ListBuilder<T> add(E value) throws DIException;
 }

Modified: 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/MapBuilder.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/MapBuilder.java?rev=882956&r1=882955&r2=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/MapBuilder.java 
(original)
+++ 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/MapBuilder.java 
Sat Nov 21 18:22:39 2009
@@ -25,7 +25,7 @@
  */
 public interface MapBuilder<T> {
 
-    <E> MapBuilder<T> put(String key, Class<? extends E> valueType) throws 
DIException;
+    <E> MapBuilder<T> put(String key, Class<? extends E> interfaceType) throws 
DIException;
 
     <E> MapBuilder<T> put(String key, E value) throws DIException;
 }

Modified: 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ConstructorProvider.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ConstructorProvider.java?rev=882956&r1=882955&r2=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ConstructorProvider.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/ConstructorProvider.java
 Sat Nov 21 18:22:39 2009
@@ -20,6 +20,7 @@
 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Type;
 import java.util.List;
 import java.util.Map;
 
@@ -30,10 +31,12 @@
 
 class ConstructorProvider<T> implements Provider<T> {
 
+    private Class<T> interfaceType;
     private Constructor<? extends T> constructor;
     private Injector injector;
 
-    ConstructorProvider(Class<? extends T> implementation, Injector injector) {
+    ConstructorProvider(Class<T> interfaceType, Class<? extends T> 
implementation,
+            Injector injector) {
 
         initConstructor(implementation);
 
@@ -45,6 +48,7 @@
 
         this.constructor.setAccessible(true);
         this.injector = injector;
+        this.interfaceType = interfaceType;
     }
 
     private void initConstructor(Class<? extends T> implementation) {
@@ -100,6 +104,7 @@
     public T get() {
 
         Class<?>[] constructorParameters = constructor.getParameterTypes();
+        Type[] genericTypes = constructor.getGenericParameterTypes();
         Object[] args = new Object[constructorParameters.length];
 
         for (int i = 0; i < constructorParameters.length; i++) {
@@ -110,13 +115,27 @@
             // implementations in the declaration will make the injection much 
more
             // complicated
             if (Map.class.equals(parameter)) {
-                args[i] = 
injector.getMapConfiguration(constructor.getDeclaringClass());
+                args[i] = injector.getMapConfiguration(interfaceType);
             }
             // parameter must be declared as "java.util.List". Use of specific 
list
             // implementations in the declaration will make the injection much 
more
             // complicated
             else if (List.class.equals(parameter)) {
-                args[i] = 
injector.getListConfiguration(constructor.getDeclaringClass());
+                args[i] = injector.getListConfiguration(interfaceType);
+            }
+            else if (Provider.class.equals(parameter)) {
+
+                Class<?> objectClass = 
DIUtil.getParameterClass(genericTypes[i]);
+
+                if (objectClass == null) {
+                    String message = String.format(
+                            "Constrcutor provider parameter %s must be "
+                                    + "parameterized to be usable for 
injection",
+                            parameter.getName());
+                    throw new DIException(message);
+                }
+
+                args[i] = injector.getProvider(objectClass);
             }
             else {
                 args[i] = injector.getInstance(parameter);

Copied: 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIUtil.java 
(from r882933, 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java)
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIUtil.java?p2=cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIUtil.java&p1=cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java&r1=882933&r2=882956&rev=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DIUtil.java 
Sat Nov 21 18:22:39 2009
@@ -16,17 +16,24 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-package org.apache.cayenne.di.mock;
+package org.apache.cayenne.di.spi;
 
-import org.apache.cayenne.di.Inject;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 
-public class MockImplementation2 implements MockInterface2 {
+class DIUtil {
 
-    @Inject
-    private MockInterface1 service;
+    static Class<?> getParameterClass(Type type) {
 
-    public String getAlteredName() {
-        return "altered_" + service.getName();
-    }
+        if (type instanceof ParameterizedType) {
+            ParameterizedType parameterizedType = (ParameterizedType) type;
+            Type[] parameters = parameterizedType.getActualTypeArguments();
+
+            if (parameters.length == 1) {
+                return (Class<?>) parameters[0];
+            }
+        }
 
+        return null;
+    }
 }

Modified: 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultBindingBuilder.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultBindingBuilder.java?rev=882956&r1=882955&r2=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultBindingBuilder.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultBindingBuilder.java
 Sat Nov 21 18:22:39 2009
@@ -38,11 +38,13 @@
 
         // creating a provider chain for lazy resolution of singletons...
         ConstructorProvider<T> provider0 = new ConstructorProvider<T>(
+                interfaceType,
                 implementation,
                 injector);
         FieldInjectingProvider<T> provider1 = new FieldInjectingProvider<T>(
                 provider0,
-                injector);
+                injector,
+                key);
         SingletonProvider<T> provider2 = new SingletonProvider<T>(provider1);
 
         // TODO: andrus 11/15/2009 - report overriding the binding??
@@ -57,7 +59,8 @@
                 providerType);
         FieldInjectingProvider<T> provider1 = new FieldInjectingProvider<T>(
                 provider0,
-                injector);
+                injector,
+                key);
         SingletonProvider<T> provider2 = new SingletonProvider<T>(provider1);
 
         // TODO: andrus 11/15/2009 - report overriding existing binding??

Modified: 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultInjector.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultInjector.java?rev=882956&r1=882955&r2=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultInjector.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultInjector.java
 Sat Nov 21 18:22:39 2009
@@ -32,12 +32,14 @@
     private Map<String, Provider<?>> bindings;
     private Map<String, MapProvider> mapConfigurations;
     private Map<String, ListProvider> listConfigurations;
+    private InjectionStack injectionStack;
 
     public DefaultInjector(Module... modules) throws DIException {
 
         this.bindings = new HashMap<String, Provider<?>>();
         this.mapConfigurations = new HashMap<String, MapProvider>();
         this.listConfigurations = new HashMap<String, ListProvider>();
+        this.injectionStack = new InjectionStack();
 
         if (modules != null && modules.length > 0) {
             DefaultBinder binder = new DefaultBinder(this);
@@ -47,6 +49,10 @@
         }
     }
 
+    InjectionStack getInjectionStack() {
+        return injectionStack;
+    }
+
     Map<String, Provider<?>> getBindings() {
         return bindings;
     }
@@ -60,19 +66,7 @@
     }
 
     public <T> T getInstance(Class<T> type) throws DIException {
-        if (type == null) {
-            throw new NullPointerException("Null type");
-        }
-
-        String key = KeyGenerator.toKey(type);
-        Provider<T> provider = (Provider<T>) bindings.get(key);
-
-        if (provider == null) {
-
-            throw new DIException("Type is not bound in the  registry: " + 
type.getName());
-        }
-
-        return provider.get();
+        return getProvider(type).get();
     }
 
     public <T> List<?> getListConfiguration(Class<T> type) {
@@ -110,4 +104,19 @@
         return provider.get();
     }
 
+    public <T> Provider<T> getProvider(Class<T> type) throws DIException {
+        if (type == null) {
+            throw new NullPointerException("Null type");
+        }
+
+        String key = KeyGenerator.toKey(type);
+        Provider<T> provider = (Provider<T>) bindings.get(key);
+
+        if (provider == null) {
+            throw new DIException("Type is not bound in the registry: " + 
type.getName());
+        }
+
+        return provider;
+    }
+
 }

Modified: 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultListBuilder.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultListBuilder.java?rev=882956&r1=882955&r2=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultListBuilder.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultListBuilder.java
 Sat Nov 21 18:22:39 2009
@@ -31,14 +31,7 @@
         implementationTypeKey = KeyGenerator.toKey(implementationType);
     }
 
-    public <E> ListBuilder<T> add(Class<? extends E> valueType) throws 
DIException {
-
-        // creating a provider chain for lazy resolution of singletons...
-        ConstructorProvider<E> provider0 = new 
ConstructorProvider<E>(valueType, injector);
-        FieldInjectingProvider<E> provider1 = new FieldInjectingProvider<E>(
-                provider0,
-                injector);
-        SingletonProvider<E> provider2 = new SingletonProvider<E>(provider1);
+    public <E> ListBuilder<T> add(Class<? extends E> interfaceType) throws 
DIException {
 
         ListProvider listProvider = injector.getListConfigurations().get(
                 implementationTypeKey);
@@ -47,7 +40,7 @@
             injector.getListConfigurations().put(implementationTypeKey, 
listProvider);
         }
 
-        listProvider.add(provider2);
+        listProvider.add(injector.getProvider(interfaceType));
         return this;
     }
 

Modified: 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultMapBuilder.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultMapBuilder.java?rev=882956&r1=882955&r2=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultMapBuilder.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/DefaultMapBuilder.java
 Sat Nov 21 18:22:39 2009
@@ -31,16 +31,9 @@
         implementationTypeKey = KeyGenerator.toKey(implementationType);
     }
 
-    public <E> MapBuilder<T> put(String key, Class<? extends E> valueType)
+    public <E> MapBuilder<T> put(String key, Class<? extends E> interfaceType)
             throws DIException {
 
-        // creating a provider chain for lazy resolution of singletons...
-        ConstructorProvider<E> provider0 = new 
ConstructorProvider<E>(valueType, injector);
-        FieldInjectingProvider<E> provider1 = new FieldInjectingProvider<E>(
-                provider0,
-                injector);
-        SingletonProvider<E> provider2 = new SingletonProvider<E>(provider1);
-
         MapProvider mapProvider = injector.getMapConfigurations().get(
                 implementationTypeKey);
         if (mapProvider == null) {
@@ -49,7 +42,7 @@
         }
 
         // TODO: andrus 11/15/2009 - report overriding the key??
-        mapProvider.put(key, provider2);
+        mapProvider.put(key, injector.getProvider(interfaceType));
         return this;
     }
 

Modified: 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/FieldInjectingProvider.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/FieldInjectingProvider.java?rev=882956&r1=882955&r2=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/FieldInjectingProvider.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/FieldInjectingProvider.java
 Sat Nov 21 18:22:39 2009
@@ -24,17 +24,19 @@
 
 import org.apache.cayenne.di.DIException;
 import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.di.Provider;
 
 class FieldInjectingProvider<T> implements Provider<T> {
 
-    private Injector injector;
+    private String bindingKey;
+    private DefaultInjector injector;
     private Provider<T> delegate;
 
-    FieldInjectingProvider(Provider<T> delegate, Injector injector) {
+    FieldInjectingProvider(Provider<T> delegate, DefaultInjector injector,
+            String bindingKey) {
         this.delegate = delegate;
         this.injector = injector;
+        this.bindingKey = bindingKey;
     }
 
     private Collection<Field> initInjectionPoints(
@@ -69,19 +71,51 @@
                 object.getClass(),
                 new ArrayList<Field>());
 
-        for (Field field : injectableFields) {
-            Object value = injector.getInstance(field.getType());
-            try {
-                field.set(object, value);
-            }
-            catch (Exception e) {
-                String message = String.format(
-                        "Error injecting into field %s.%s of type %s",
-                        field.getDeclaringClass().getName(),
-                        field.getName(),
-                        field.getType().getName());
-                throw new DIException(message, e);
+        injector.getInjectionStack().push(bindingKey);
+
+        try {
+
+            for (Field field : injectableFields) {
+                Object value;
+                Class<?> fieldType = field.getType();
+
+                if (Provider.class.equals(fieldType)) {
+
+                    Class<?> objectClass = DIUtil.getParameterClass(field
+                            .getGenericType());
+
+                    if (objectClass == null) {
+                        String message = String.format(
+                                "Provider field %s.%s of type %s must be "
+                                        + "parameterized to be usable for 
injection",
+                                field.getDeclaringClass().getName(),
+                                field.getName(),
+                                fieldType.getName());
+                        throw new DIException(message);
+                    }
+
+                    value = injector.getProvider(objectClass);
+                }
+                else {
+                    value = injector.getInstance(fieldType);
+                }
+
+                try {
+                    field.set(object, value);
+                }
+                catch (Exception e) {
+                    String message = String.format(
+                            "Error injecting into field %s.%s of type %s",
+                            field.getDeclaringClass().getName(),
+                            field.getName(),
+                            fieldType.getName());
+                    throw new DIException(message, e);
+                }
             }
         }
+        finally {
+            injector.getInjectionStack().pop();
+        }
     }
+
 }

Added: 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/InjectionStack.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/InjectionStack.java?rev=882956&view=auto
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/InjectionStack.java
 (added)
+++ 
cayenne/sandbox/cayenne-di/src/main/java/org/apache/cayenne/di/spi/InjectionStack.java
 Sat Nov 21 18:22:39 2009
@@ -0,0 +1,69 @@
+/*****************************************************************
+ *   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.spi;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.cayenne.di.DIException;
+
+/**
+ * A helper object that tracks the injection stack to prevent circular 
dependencies.
+ */
+class InjectionStack {
+
+    private ThreadLocal<LinkedList<String>> stack;
+
+    InjectionStack() {
+        this.stack = new ThreadLocal<LinkedList<String>>();
+    }
+
+    void reset() {
+        List<String> localStack = stack.get();
+        if (localStack != null) {
+            localStack.clear();
+        }
+    }
+
+    void push(String bindingKey) throws DIException {
+        LinkedList<String> localStack = stack.get();
+        if (localStack == null) {
+            localStack = new LinkedList<String>();
+            stack.set(localStack);
+        }
+
+        if (localStack.contains(bindingKey)) {
+            throw new DIException("Circular dependency detected: "
+                    + localStack
+                    + ". You should try injecting a provider instead.");
+        }
+
+        localStack.add(bindingKey);
+    }
+
+    void pop() {
+        LinkedList<String> localStack = stack.get();
+        if (localStack != null) {
+            localStack.removeLast();
+        }
+        else {
+            throw new IndexOutOfBoundsException("0");
+        }
+    }
+}

Copied: 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation1_DepOn2.java
 (from r882933, 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java)
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation1_DepOn2.java?p2=cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation1_DepOn2.java&p1=cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java&r1=882933&r2=882956&rev=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation1_DepOn2.java
 Sat Nov 21 18:22:39 2009
@@ -20,13 +20,15 @@
 
 import org.apache.cayenne.di.Inject;
 
-public class MockImplementation2 implements MockInterface2 {
+public class MockImplementation1_DepOn2 implements MockInterface1 {
 
+    // this creates a circular dependency when MockImplementation2 is bound to
+    // MockInterface2.
     @Inject
-    private MockInterface1 service;
+    private MockInterface2 interface2;
 
-    public String getAlteredName() {
-        return "altered_" + service.getName();
+    public String getName() {
+        return interface2.getName();
     }
 
 }

Copied: 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation1_DepOn2Provider.java
 (from r882933, 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java)
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation1_DepOn2Provider.java?p2=cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation1_DepOn2Provider.java&p1=cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java&r1=882933&r2=882956&rev=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation1_DepOn2Provider.java
 Sat Nov 21 18:22:39 2009
@@ -19,14 +19,17 @@
 package org.apache.cayenne.di.mock;
 
 import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.di.Provider;
 
-public class MockImplementation2 implements MockInterface2 {
+public class MockImplementation1_DepOn2Provider implements MockInterface1 {
 
+    // this creates a circular dependency when MockImplementation2 is bound to
+    // MockInterface2, however injecting a provider should prevent errors...
     @Inject
-    private MockInterface1 service;
+    private Provider<MockInterface2> interface2Provider;
 
-    public String getAlteredName() {
-        return "altered_" + service.getName();
+    public String getName() {
+        return interface2Provider.get().getName();
     }
 
 }

Modified: 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java?rev=882956&r1=882955&r2=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java
 Sat Nov 21 18:22:39 2009
@@ -28,5 +28,9 @@
     public String getAlteredName() {
         return "altered_" + service.getName();
     }
+    
+    public String getName() {
+        return "MockImplementation2Name";
+    }
 
 }

Copied: 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2_ConstructorProvider.java
 (from r882933, 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java)
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2_ConstructorProvider.java?p2=cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2_ConstructorProvider.java&p1=cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java&r1=882933&r2=882956&rev=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation2_ConstructorProvider.java
 Sat Nov 21 18:22:39 2009
@@ -19,14 +19,23 @@
 package org.apache.cayenne.di.mock;
 
 import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.di.Provider;
 
-public class MockImplementation2 implements MockInterface2 {
+public class MockImplementation2_ConstructorProvider implements MockInterface2 
{
 
-    @Inject
-    private MockInterface1 service;
+    private Provider<MockInterface1> provider;
+
+    public MockImplementation2_ConstructorProvider(
+            @Inject Provider<MockInterface1> provider) {
+        this.provider = provider;
+    }
 
     public String getAlteredName() {
-        return "altered_" + service.getName();
+        return "altered_" + provider.get().getName();
+    }
+
+    public String getName() {
+        return "MockImplementation2Name";
     }
 
 }

Copied: 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation5.java
 (from r882933, 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface2.java)
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation5.java?p2=cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation5.java&p1=cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface2.java&r1=882933&r2=882956&rev=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface2.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockImplementation5.java
 Sat Nov 21 18:22:39 2009
@@ -18,7 +18,10 @@
  ****************************************************************/
 package org.apache.cayenne.di.mock;
 
-public interface MockInterface2 {
+public class MockImplementation5 implements MockInterface5 {
 
-    String getAlteredName();
+    @Override
+    public String toString() {
+        return "xyz";
+    }
 }

Modified: 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface2.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface2.java?rev=882956&r1=882955&r2=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface2.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface2.java
 Sat Nov 21 18:22:39 2009
@@ -21,4 +21,6 @@
 public interface MockInterface2 {
 
     String getAlteredName();
+    
+    String getName();
 }

Copied: 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface5.java
 (from r882933, 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface2.java)
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface5.java?p2=cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface5.java&p1=cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface2.java&r1=882933&r2=882956&rev=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface2.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/mock/MockInterface5.java
 Sat Nov 21 18:22:39 2009
@@ -18,7 +18,6 @@
  ****************************************************************/
 package org.apache.cayenne.di.mock;
 
-public interface MockInterface2 {
+public interface MockInterface5 {
 
-    String getAlteredName();
 }

Modified: 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DefaultInjectorTest.java
URL: 
http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DefaultInjectorTest.java?rev=882956&r1=882955&r2=882956&view=diff
==============================================================================
--- 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DefaultInjectorTest.java
 (original)
+++ 
cayenne/sandbox/cayenne-di/src/test/java/org/apache/cayenne/di/spi/DefaultInjectorTest.java
 Sat Nov 21 18:22:39 2009
@@ -21,20 +21,26 @@
 import junit.framework.TestCase;
 
 import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.DIException;
 import org.apache.cayenne.di.Module;
 import org.apache.cayenne.di.mock.MockImplementation1;
 import org.apache.cayenne.di.mock.MockImplementation1Alt;
+import org.apache.cayenne.di.mock.MockImplementation1_DepOn2;
+import org.apache.cayenne.di.mock.MockImplementation1_DepOn2Provider;
 import org.apache.cayenne.di.mock.MockImplementation1_ListConfiguration;
 import org.apache.cayenne.di.mock.MockImplementation1_MapConfiguration;
 import org.apache.cayenne.di.mock.MockImplementation2;
 import org.apache.cayenne.di.mock.MockImplementation2Sub1;
+import org.apache.cayenne.di.mock.MockImplementation2_ConstructorProvider;
 import org.apache.cayenne.di.mock.MockImplementation3;
 import org.apache.cayenne.di.mock.MockImplementation4;
+import org.apache.cayenne.di.mock.MockImplementation5;
 import org.apache.cayenne.di.mock.MockInterface1;
 import org.apache.cayenne.di.mock.MockInterface1Provider;
 import org.apache.cayenne.di.mock.MockInterface2;
 import org.apache.cayenne.di.mock.MockInterface3;
 import org.apache.cayenne.di.mock.MockInterface4;
+import org.apache.cayenne.di.mock.MockInterface5;
 
 public class DefaultInjectorTest extends TestCase {
 
@@ -189,9 +195,11 @@
                 binder.bind(MockInterface1.class).to(
                         MockImplementation1_MapConfiguration.class);
 
-                binder.bindMap(MockImplementation1_MapConfiguration.class).put(
-                        "x",
-                        "xvalue").put("y", "yvalue").put("x", "xvalue1");
+                binder
+                        .bindMap(MockInterface1.class)
+                        .put("x", "xvalue")
+                        .put("y", "yvalue")
+                        .put("x", "xvalue1");
             }
         };
 
@@ -202,17 +210,14 @@
         assertEquals(";x=xvalue1;y=yvalue", service.getName());
     }
 
-    public void testListInjection() {
+    public void testListInjection_addValue() {
         Module module = new Module() {
 
             public void configure(Binder binder) {
                 binder.bind(MockInterface1.class).to(
                         MockImplementation1_ListConfiguration.class);
 
-                binder
-                        .bindList(MockImplementation1_ListConfiguration.class)
-                        .add("xvalue")
-                        .add("yvalue");
+                
binder.bindList(MockInterface1.class).add("xvalue").add("yvalue");
             }
         };
 
@@ -222,4 +227,83 @@
         assertNotNull(service);
         assertEquals(";xvalue;yvalue", service.getName());
     }
+
+    public void testListInjection_addType() {
+        Module module = new Module() {
+
+            public void configure(Binder binder) {
+                
binder.bind(MockInterface5.class).to(MockImplementation5.class);
+
+                binder.bind(MockInterface1.class).to(
+                        MockImplementation1_ListConfiguration.class);
+
+                
binder.bindList(MockInterface1.class).add(MockInterface5.class).add(
+                        "yvalue");
+            }
+        };
+
+        DefaultInjector injector = new DefaultInjector(module);
+
+        MockInterface1 service = injector.getInstance(MockInterface1.class);
+        assertNotNull(service);
+        assertEquals(";xyz;yvalue", service.getName());
+    }
+
+    public void testFieldInjection_CircularDependency() {
+
+        Module module = new Module() {
+
+            public void configure(Binder binder) {
+                
binder.bind(MockInterface1.class).to(MockImplementation1_DepOn2.class);
+                
binder.bind(MockInterface2.class).to(MockImplementation2.class);
+            }
+        };
+
+        DefaultInjector injector = new DefaultInjector(module);
+
+        try {
+            injector.getInstance(MockInterface1.class);
+            fail("Circular dependency is not detected.");
+        }
+        catch (DIException e) {
+            // expected
+        }
+        catch (StackOverflowError e) {
+            fail("Circular dependency is not detected, causing stack 
overflow");
+        }
+    }
+
+    public void testProviderInjection_Constructor() {
+
+        Module module = new Module() {
+
+            public void configure(Binder binder) {
+                
binder.bind(MockInterface1.class).to(MockImplementation1.class);
+                binder.bind(MockInterface2.class).to(
+                        MockImplementation2_ConstructorProvider.class);
+            }
+        };
+
+        DefaultInjector injector = new DefaultInjector(module);
+
+        MockInterface2 service = injector.getInstance(MockInterface2.class);
+        assertEquals("altered_MyName", service.getAlteredName());
+    }
+
+    public void testProviderInjection_CircularDependency() {
+
+        Module module = new Module() {
+
+            public void configure(Binder binder) {
+                binder.bind(MockInterface1.class).to(
+                        MockImplementation1_DepOn2Provider.class);
+                
binder.bind(MockInterface2.class).to(MockImplementation2.class);
+            }
+        };
+
+        DefaultInjector injector = new DefaultInjector(module);
+
+        MockInterface1 service = injector.getInstance(MockInterface1.class);
+        assertEquals("MockImplementation2Name", service.getName());
+    }
 }


Reply via email to