package com.dplm.simpleDI;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.HashSet;

import com.dplm.simpleDI.exceptions.CircularDependencyException;
import com.dplm.simpleDI.exceptions.EmptyConstructorNotFoundException;
import com.dplm.simpleDI.exceptions.UnexpectedInstantiationException;

public class Injector {

	private static Injector singleton;	

	private HashMap<Class<?>, Object> instanceMap;
	
	public Injector(){
		instanceMap = new HashMap<Class<?>, Object>();
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T inject(Class<T> classObj){
		
		if(singleton == null){
			singleton = new Injector();
		}
		
		return (T)singleton.checkCacheOrInject(classObj, new HashSet<Class<?>>());
	}
	
	private Object checkCacheOrInject(Class<?> classObj, HashSet<Class<?>> processingStack){
		if(instanceMap.containsKey(classObj)){
			return instanceMap.get(classObj);
		}
		
		circularDependencyChecking(classObj, processingStack);
		
		Object result = resolveInjection(classObj, processingStack);
		instanceMap.put(classObj, result);
		return result;
	}

	private void circularDependencyChecking(Class<?> classObj, HashSet<Class<?>> processingStack){
		if(processingStack.contains(classObj)){
			throw new CircularDependencyException();
		}else{
			processingStack.add(classObj);
		}		
	}
	
	private Object resolveInjection(Class<?> classObj, HashSet<Class<?>> processingStack){
		Constructor<?> constructor = findConstructor(classObj);
		if(constructor.getParameterCount() == 0){
			return resolveEmptyConstructor(constructor);
		}else{
			return resolveInjectedConstructor(constructor, processingStack);
		}
	}
		
	private Constructor<?> findConstructor(Class<?> classObj){
		Constructor<?> emptyConstructor = null;
		for(Constructor<?> constructor : classObj.getConstructors()){
			if(constructor.isAnnotationPresent(AutoInject.class)){
				return constructor;
			}else if(constructor.getParameterCount() == 0){
				emptyConstructor = constructor;
			}
		}
		
		if(emptyConstructor == null){
			throw new EmptyConstructorNotFoundException();			
		}else{
			return emptyConstructor;
		}
	}	
	
	private Object resolveEmptyConstructor(Constructor<?> constructor){
		try {
			return constructor.newInstance();
		} catch (Exception e) {
			throw new UnexpectedInstantiationException();
		}
	}
	
	private Object resolveInjectedConstructor(Constructor<?> constructor, HashSet<Class<?>> processingStack){
		Object[] params = buildConstructorParameter(constructor, processingStack);
		
		try {
			return constructor.newInstance(params);
		} catch (Exception e) {
			throw new UnexpectedInstantiationException();
		}
	}
	
	private Object[] buildConstructorParameter(Constructor<?> constructor, HashSet<Class<?>> processingStack){
		Object[] params = new Object[constructor.getParameterCount()];
		int id = 0;
		for(Class<?> classObj : constructor.getParameterTypes()){
			params[id++] = checkCacheOrInject(classObj, processingStack);
		}
		
		return params;
	}
}

@@ -88,34 +88,8 @@
 Map;
-%0Aimport java.util.HashSet;
 %0A%0Aim
@@ -676,16 +676,22 @@
 lassObj,
+ null,
  new Has
@@ -687,28 +687,38 @@
 ll, new Hash
-Set%3C
+Map%3CClass%3C?%3E, 
 Class%3C?%3E%3E())
@@ -782,41 +782,74 @@
 bj, 
-HashSet%3CClass%3C?%3E%3E processingStack
+Class%3C?%3E parentClassObj, HashMap%3CClass%3C?%3E, Class%3C?%3E%3E dependencyMap
 )%7B%0A%09
@@ -968,38 +968,52 @@
 (classObj, p
-rocessingStack
+arentClassObj, dependencyMap
 );%0A%09%09%0A%09%09Obje
@@ -1047,39 +1047,37 @@
 n(classObj, 
-processingStack
+dependencyMap
 );%0A%09%09instanc
@@ -1189,64 +1189,95 @@
 bj, 
-HashSet%3CClass%3C?%3E%3E processingStack)%7B%0A%09%09if(processingStack
+Class%3C?%3E parentClassObj, HashMap%3CClass%3C?%3E, Class%3C?%3E%3E dependencyMap)%7B%0A%09%09if(dependencyMap
 .con
@@ -1281,16 +1281,19 @@
 contains
+Key
 (classOb
@@ -1357,29 +1357,43 @@
 %0A%09%09%09
-processingStack.add(c
+dependencyMap.put(classObj, parentC
 lass
@@ -1401,18 +1401,16 @@
 bj);%0A%09%09%7D
-%09%09
 %0A%09%7D%0A%09%0A%09p
@@ -1459,27 +1459,27 @@
 assObj, Hash
-Set
+Map
 %3CClass%3C?%3E%3E p
@@ -1475,33 +1475,41 @@
 Class%3C?%3E
-%3E processingStack
+, Class%3C?%3E%3E dependencyMap
 )%7B%0A%09%09Con
@@ -1708,39 +1708,37 @@
 onstructor, 
-processingStack
+dependencyMap
 );%0A%09%09%7D%0A%09%7D%0A%09%09
@@ -2482,35 +2482,35 @@
 onstructor, Hash
-Set
+Map
 %3CClass%3C?%3E%3E proce
@@ -2502,33 +2502,41 @@
 Class%3C?%3E
-%3E processingStack
+, Class%3C?%3E%3E dependencyMap
 )%7B%0A%09%09Obj
@@ -2589,31 +2589,29 @@
 ructor, 
-processingStack
+dependencyMap
 );%0A%09%09%0A%09%09
@@ -2823,11 +2823,11 @@
 Hash
-Set
+Map
 %3CCla
@@ -2835,25 +2835,33 @@
 s%3C?%3E
-%3E processingStack
+, Class%3C?%3E%3E dependencyMap
 )%7B%0A%09
@@ -3049,23 +3049,54 @@
 bj, 
-processingStack
+constructor.getDeclaringClass(), dependencyMap
 );%0A%09

Store dependency information during the injection