Skip to content

Commit

Permalink
#48 Merge Handler#resolveValue into Handler#get, drop DependencyHandl…
Browse files Browse the repository at this point in the history
…er in favor of ObjectIdentifier

(work in progress)
  • Loading branch information
ljacqu committed Jun 17, 2017
1 parent 1836dd2 commit 3e38db2
Show file tree
Hide file tree
Showing 33 changed files with 406 additions and 442 deletions.
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package ch.jalu.injector.extras.handlers;

import ch.jalu.injector.Injector;
import ch.jalu.injector.context.ResolvedContext;
import ch.jalu.injector.context.ObjectIdentifier;
import ch.jalu.injector.context.UnresolvedContext;
import ch.jalu.injector.exceptions.InjectorException;
import ch.jalu.injector.extras.AllInstances;
import ch.jalu.injector.handlers.dependency.TypeSafeAnnotationHandler;
import ch.jalu.injector.handlers.instantiation.DependencyDescription;
import ch.jalu.injector.handlers.instantiation.Instantiation;
import ch.jalu.injector.utils.InjectorUtils;
import ch.jalu.injector.utils.ReflectionUtils;
import org.reflections.Reflections;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
* Handler for {@link AllInstances}. Finds all subtypes of the given dependency,
Expand All @@ -34,30 +37,47 @@ protected Class<AllInstances> getAnnotationType() {
}

@Override
public Object resolveValueSafely(ResolvedContext context, AllInstances annotation,
DependencyDescription dependencyDescription) {
public Instantiation<?> resolveValueSafely(UnresolvedContext context, AllInstances annotation) {
// The raw type, e.g. List or array
final Class<?> rawType = dependencyDescription.getTypeAsClass();
final Class<?> rawType = context.getIdentifier().getTypeAsClass();
// The type of the collection, e.g. String for List<String> or String[]
final Class genericType = ReflectionUtils.getCollectionType(rawType, dependencyDescription.getType());
final Class genericType = ReflectionUtils.getCollectionType(rawType, context.getIdentifier().getType());

if (genericType == null) {
throw new InjectorException("Unsupported dependency of type '" + rawType
+ "' annotated with @AllInstances. (Or did you forget the generic type?)");
}

// TODO: Implement detection of cyclic dependencies
@SuppressWarnings("unchecked")
Set<Class<?>> subTypes = reflections.getSubTypesOf(genericType);
Set<Object> instances = new HashSet<>(subTypes.size());
subTypes.removeIf(type -> !InjectorUtils.canInstantiate(type));
return new AllInstancesInstantiation(rawType, subTypes);
}

private static final class AllInstancesInstantiation implements Instantiation<Object> {

private final Class<?> rawCollectionType;
private final Set<Class<?>> subtypes;

final Injector injector = context.getInjector();
for (Class<?> type : subTypes) {
if (InjectorUtils.canInstantiate(type)) {
instances.add(injector.getSingleton(type));
}
AllInstancesInstantiation(Class<?> rawCollectionType, Set<Class<?>> subtypes) {
this.rawCollectionType = rawCollectionType;
this.subtypes = subtypes;
}

@Override
public List<ObjectIdentifier> getDependencies() {
return subtypes.stream().map(type -> new ObjectIdentifier(type)).collect(Collectors.toList());
}

@Override
public Object instantiateWith(Object... values) {
Set<Object> objects = new HashSet<>(Arrays.asList(values));
return ReflectionUtils.toSuitableCollectionType(rawCollectionType, objects);
}
return ReflectionUtils.toSuitableCollectionType(rawType, instances);
}

@Override
public boolean saveIfSingleton() {
return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package ch.jalu.injector.extras.handlers;

import ch.jalu.injector.context.ResolvedContext;
import ch.jalu.injector.context.UnresolvedContext;
import ch.jalu.injector.extras.AllTypes;
import ch.jalu.injector.handlers.dependency.TypeSafeAnnotationHandler;
import ch.jalu.injector.handlers.instantiation.DependencyDescription;
import ch.jalu.injector.handlers.instantiation.Instantiation;
import ch.jalu.injector.handlers.instantiation.SimpleObjectResolution;
import ch.jalu.injector.utils.InjectorUtils;
import ch.jalu.injector.utils.ReflectionUtils;
import org.reflections.Reflections;
Expand Down Expand Up @@ -31,12 +32,11 @@ protected Class<AllTypes> getAnnotationType() {
}

@Override
public Object resolveValueSafely(ResolvedContext context, AllTypes annotation,
DependencyDescription dependencyDescription) {
public Instantiation<?> resolveValueSafely(UnresolvedContext context, AllTypes annotation) {
InjectorUtils.checkNotNull(annotation.value(), "Annotation value may not be null");
Set<?> subTypes = reflections.getSubTypesOf(annotation.value());

Class<?> rawType = dependencyDescription.getTypeAsClass();
return ReflectionUtils.toSuitableCollectionType(rawType, subTypes);
Class<?> rawType = context.getIdentifier().getTypeAsClass();
return new SimpleObjectResolution<>(ReflectionUtils.toSuitableCollectionType(rawType, subTypes));
}
}
155 changes: 53 additions & 102 deletions injector/src/main/java/ch/jalu/injector/InjectorImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

import ch.jalu.injector.context.ObjectIdentifier;
import ch.jalu.injector.context.ResolvedContext;
import ch.jalu.injector.context.StandardResolutionType;
import ch.jalu.injector.context.UnresolvedContext;
import ch.jalu.injector.exceptions.InjectorException;
import ch.jalu.injector.handlers.Handler;
import ch.jalu.injector.handlers.instantiation.DependencyDescription;
import ch.jalu.injector.handlers.instantiation.Instantiation;
import ch.jalu.injector.utils.InjectorUtils;

import javax.annotation.Nullable;
import javax.inject.Provider;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
Expand Down Expand Up @@ -47,11 +46,6 @@ protected InjectorImpl(InjectorConfig config) {
this.objects.put(Injector.class, this);
}

@Override
public <T> T getSingleton(Class<T> clazz) {
return get(clazz, new HashSet<>());
}

@Override
public <T> void register(Class<? super T> clazz, T object) {
if (objects.containsKey(clazz)) {
Expand All @@ -73,9 +67,16 @@ public void provide(Class<? extends Annotation> clazz, Object object) {
}
}

@Override
public <T> T getSingleton(Class<T> clazz) {
return (T) resolveObject(
new UnresolvedContext(this, SINGLETON, new ObjectIdentifier(clazz)),
new HashSet<>());
}

@Override
public <T> T newInstance(Class<T> clazz) {
return (T) instantiate(
return (T) resolveObject(
new UnresolvedContext(this, REQUEST_SCOPED, new ObjectIdentifier(clazz)),
new HashSet<>());
}
Expand All @@ -87,7 +88,7 @@ public <T> T getIfAvailable(Class<T> clazz) {

@Override
public <T> T createIfHasDependencies(Class<T> clazz) {
return (T) instantiate(
return (T) resolveObject(
new UnresolvedContext(this, REQUEST_SCOPED_IF_HAS_DEPENDENCIES, new ObjectIdentifier(clazz)),
new HashSet<>());
}
Expand Down Expand Up @@ -133,88 +134,55 @@ public InjectorConfig getConfig() {
return config;
}

/**
* Returns an instance of the given class by retrieving it or by instantiating it if not yet present.
*
* @param clazz the class to retrieve the singleton instance for
* @param traversedClasses the list of traversed classes
* @param <T> the class' type
* @return instance or associated value (for annotations)
*/
private <T> T get(Class<T> clazz, Set<Class<?>> traversedClasses) {
if (objects.containsKey(clazz)) {
return clazz.cast(objects.get(clazz));
private Object resolveObject(UnresolvedContext context, Set<Class<?>> traversedClasses) {
// TODO #49: Convert singleton store to a Handler impl.
if (context.getResolutionType() == StandardResolutionType.SINGLETON) {
Object knownSingleton = objects.get(context.getIdentifier().getTypeAsClass());
if (knownSingleton != null) {
return knownSingleton;
}
}

UnresolvedContext context = new UnresolvedContext(this, SINGLETON, new ObjectIdentifier(clazz));
// Add the clazz to the list of traversed classes in a new Set, so each path we take has its own Set.
T object = (T) instantiate(context, new HashSet<>(traversedClasses));
register(clazz, object);
return object;
}

/**
* Instantiates the given class by locating its @Inject elements and retrieving
* or instantiating the required instances.
*
* @param context the instantiation context
* @param traversedClasses collection of classes already traversed
* @return the instantiated object, or {@code null} if dependency lookup returned {@code null}
*/
@Nullable
private Object instantiate(UnresolvedContext context, Set<Class<?>> traversedClasses) {
Instantiation<?> instantiation = getInstantiation(context);
traversedClasses.add(context.getIdentifier().getType());
validateInjectionHasNoCircularDependencies(instantiation.getDependencies(), traversedClasses);

ResolvedContext resolvedContext = context.buildResolvedContext(instantiation);
Object[] dependencies = resolveDependencies(resolvedContext, traversedClasses);
if (dependencies == null) {
return null;
}
Object object = instantiation.instantiateWith(dependencies);
return runPostConstructHandlers(object, resolvedContext);
}
traversedClasses.add(context.getIdentifier().getTypeAsClass());
validateInjectionHasNoCircularDependencies(instantiation, traversedClasses);

/**
* Resolves the dependencies for the given class instantiation, i.e. returns a collection that satisfy
* the class' dependencies by retrieving elements or instantiating them where necessary.
*
* @param resolvedContext the initialization context
* @param traversedClasses collection of traversed classes
* @return array with the parameters to use in the constructor, {@code null} if a dependency is not available
* and {@link ch.jalu.injector.context.StandardResolutionType#REQUEST_SCOPED_IF_HAS_DEPENDENCIES} is the
* resolution type
*/
@Nullable
private Object[] resolveDependencies(ResolvedContext resolvedContext,
Set<Class<?>> traversedClasses) {
List<? extends DependencyDescription> dependencies = resolvedContext.getInstantiation().getDependencies();
Object[] values = new Object[dependencies.size()];
for (int i = 0; i < dependencies.size(); ++i) {
DependencyDescription dependency = dependencies.get(i);
Object object = resolveDependency(resolvedContext, dependency);
if (object == null) {
if (REQUEST_SCOPED_IF_HAS_DEPENDENCIES == resolvedContext.getResolutionType()
&& objects.get(dependency.getTypeAsClass()) == null) {
return null;
}
object = get(dependency.getTypeAsClass(), traversedClasses);
for (ObjectIdentifier identifier : instantiation.getDependencies()) {
if (traversedClasses.contains(identifier.getTypeAsClass())) {
throw new InjectorException("Found cyclic dependency - already traversed '"
+ identifier.getTypeAsClass() + "' (full traversal list: " + traversedClasses + ")");
}
values[i] = object;
}
return values;

List<ObjectIdentifier> dependencies = instantiation.getDependencies();
List<Object> resolvedDependencies = new ArrayList<>(dependencies.size());
for (ObjectIdentifier id : dependencies) {
UnresolvedContext ctx = new UnresolvedContext(this, StandardResolutionType.SINGLETON, id);
resolvedDependencies.add(resolveObject(ctx, new HashSet<>(traversedClasses)));
}
Object obj = instantiation.instantiateWith(resolvedDependencies.toArray());
obj = runPostConstructHandlers(obj, context.buildResolvedContext(instantiation));

if (context.getResolutionType() == StandardResolutionType.SINGLETON && instantiation.saveIfSingleton()) {
register((Class) context.getOriginalIdentifier().getTypeAsClass(), obj);
}
return obj;
}

private Instantiation<?> getInstantiation(UnresolvedContext context) {
for (Handler handler : config.getHandlers()) {
Instantiation<?> instantiation = handler.get(context);
if (instantiation != null) {
return instantiation;
try {
for (Handler handler : config.getHandlers()) {
Instantiation<?> instantiation = handler.get(context);
if (instantiation != null) {
return instantiation;
}
}
} catch (Exception e) {
rethrowException(e);
}

final Class<?> clazz = context.getIdentifier().getType();
final Class<?> clazz = context.getIdentifier().getTypeAsClass();
if (!InjectorUtils.canInstantiate(clazz)) {
String hint = clazz.isPrimitive()
? "Primitive types must be provided by default. "
Expand All @@ -225,7 +193,7 @@ private Instantiation<?> getInstantiation(UnresolvedContext context) {
+ " %sThis class cannot be instantiated; please check the class or your handlers.",
clazz, hint));
}
throw new InjectorException("Did not find instantiation method for '" + context.getIdentifier().getType()
throw new InjectorException("Did not find instantiation method for '" + context.getIdentifier().getTypeAsClass()
+ "'. Make sure your class conforms to one of the registered instantiations. If default: "
+ "make sure you have a constructor with @Inject or fields with @Inject. Fields with @Inject "
+ "require the default constructor");
Expand All @@ -243,36 +211,19 @@ private <T> T runPostConstructHandlers(T instance, ResolvedContext resolvedConte
return object;
}

@Nullable
private Object resolveDependency(ResolvedContext resolvedContext,
DependencyDescription dependencyDescription) {
Object o;
for (Handler handler : config.getHandlers()) {
try {
if ((o = handler.resolveValue(resolvedContext, dependencyDescription)) != null) {
return o;
}
} catch (Exception e) {
rethrowException(e);
}
}
return null;
}

/**
* Validates that none of the dependencies' types are present in the given collection
* of traversed classes. This prevents circular dependencies.
*
* @param dependencies the dependencies of the class
* @param instantiation the instantiation method to get the dependencies from
* @param traversedClasses the collection of traversed classes
*/
private static void validateInjectionHasNoCircularDependencies(List<? extends DependencyDescription> dependencies,
private static void validateInjectionHasNoCircularDependencies(Instantiation<?> instantiation,
Set<Class<?>> traversedClasses) {
for (DependencyDescription dependency : dependencies) {
Class<?> clazz = dependency.getTypeAsClass();
if (traversedClasses.contains(clazz)) {
throw new InjectorException("Found cyclic dependency - already traversed '" + clazz
+ "' (full traversal list: " + traversedClasses + ")");
for (ObjectIdentifier identifier : instantiation.getDependencies()) {
if (traversedClasses.contains(identifier.getTypeAsClass())) {
throw new InjectorException("Found cyclic dependency - already traversed '"
+ identifier.getTypeAsClass() + "' (full traversal list: " + traversedClasses + ")");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package ch.jalu.injector.context;

import ch.jalu.injector.exceptions.InjectorException;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;

Expand All @@ -9,18 +13,33 @@
*/
public class ObjectIdentifier {

private final Class<?> type;
private final Type type;
private final List<Annotation> annotations;

public ObjectIdentifier(Class<?> type, Annotation... annotations) {
public ObjectIdentifier(Type type, Annotation... annotations) {
this.type = type;
this.annotations = Arrays.asList(annotations);
}

public Class<?> getType() {
public Type getType() {
return type;
}

public Class<?> getTypeAsClass() {
if (type instanceof Class<?>) {
return (Class<?>) type;
} else if (type instanceof ParameterizedType) {
Type rawType = ((ParameterizedType) type).getRawType();
if (rawType instanceof Class<?>) {
return (Class<?>) rawType;
} else {
throw new InjectorException("Parameterized type '" + type + "' does not have a Class as its raw type");
}
}
throw new InjectorException("Unknown Type '" + type + "' (" + type.getClass()
+ ") cannot be converted to Class");
}

public List<Annotation> getAnnotations() {
return annotations;
}
Expand Down
Loading

0 comments on commit 3e38db2

Please sign in to comment.