Skip to content

Commit

Permalink
#48 Rename Handler#get to Handler#resolve, merge context classes, ren…
Browse files Browse the repository at this point in the history
…ame Instantiation to Resolution

- Fix postConstruct handlers being called on all objects, not only on recently created ones
  • Loading branch information
ljacqu committed Jun 18, 2017
1 parent 3e38db2 commit a042249
Show file tree
Hide file tree
Showing 46 changed files with 462 additions and 497 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package ch.jalu.injector.extras.handlers;

import ch.jalu.injector.context.ObjectIdentifier;
import ch.jalu.injector.context.UnresolvedContext;
import ch.jalu.injector.context.ResolutionContext;
import ch.jalu.injector.context.ResolutionType;
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.Instantiation;
import ch.jalu.injector.handlers.instantiation.Resolution;
import ch.jalu.injector.utils.InjectorUtils;
import ch.jalu.injector.utils.ReflectionUtils;
import org.reflections.Reflections;
Expand Down Expand Up @@ -37,7 +38,7 @@ protected Class<AllInstances> getAnnotationType() {
}

@Override
public Instantiation<?> resolveValueSafely(UnresolvedContext context, AllInstances annotation) {
public Resolution<?> resolveValueSafely(ResolutionContext context, AllInstances annotation) {
// The raw type, e.g. List or array
final Class<?> rawType = context.getIdentifier().getTypeAsClass();
// The type of the collection, e.g. String for List<String> or String[]
Expand All @@ -50,33 +51,38 @@ public Instantiation<?> resolveValueSafely(UnresolvedContext context, AllInstanc

@SuppressWarnings("unchecked")
Set<Class<?>> subTypes = reflections.getSubTypesOf(genericType);
subTypes.removeIf(type -> !InjectorUtils.canInstantiate(type));
return new AllInstancesInstantiation(rawType, subTypes);
ResolutionType resolutionType = context.getIdentifier().getResolutionType();
List<ObjectIdentifier> dependencies = subTypes.stream()
.filter(InjectorUtils::canInstantiate)
.map(clazz -> new ObjectIdentifier(resolutionType, clazz))
.collect(Collectors.toList());
return new AllInstancesInstantiation(rawType, dependencies);
}

private static final class AllInstancesInstantiation implements Instantiation<Object> {
private static final class AllInstancesInstantiation implements Resolution<Object> {

private final Class<?> rawCollectionType;
private final Set<Class<?>> subtypes;
private final List<ObjectIdentifier> dependencies;

AllInstancesInstantiation(Class<?> rawCollectionType, Set<Class<?>> subtypes) {
AllInstancesInstantiation(Class<?> rawCollectionType, List<ObjectIdentifier> dependencies) {
this.rawCollectionType = rawCollectionType;
this.subtypes = subtypes;
this.dependencies = dependencies;
}

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

@Override
public Object instantiateWith(Object... values) {
// TODO: Revise signature -> creating a Set that will potentially be converted back to an array...
Set<Object> objects = new HashSet<>(Arrays.asList(values));
return ReflectionUtils.toSuitableCollectionType(rawCollectionType, objects);
}

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

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

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

Class<?> rawType = context.getIdentifier().getTypeAsClass();
return new SimpleObjectResolution<>(ReflectionUtils.toSuitableCollectionType(rawType, subTypes));
return new SimpleResolution<>(ReflectionUtils.toSuitableCollectionType(rawType, subTypes));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static List<Handler> createDefaultHandlers(String rootPackage) {
}

/**
* Creates all default handlers implementing {@link Handler#get}. Useful if you want to create your own
* Creates all default handlers implementing {@link Handler#resolve}. Useful if you want to create your own
* preconstruct (etc.) handlers but want to use the default instantiation providers.
* <p>
* Use {@link #createDefaultHandlers(String)} or {@link #addDefaultHandlers(String)} otherwise.
Expand Down
80 changes: 42 additions & 38 deletions injector/src/main/java/ch/jalu/injector/InjectorImpl.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package ch.jalu.injector;

import ch.jalu.injector.context.ObjectIdentifier;
import ch.jalu.injector.context.ResolvedContext;
import ch.jalu.injector.context.ResolutionContext;
import ch.jalu.injector.context.ResolutionType;
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.Instantiation;
import ch.jalu.injector.handlers.instantiation.Resolution;
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 @@ -69,16 +70,12 @@ 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<>());
return resolve(SINGLETON, clazz);
}

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

@Override
Expand All @@ -88,9 +85,7 @@ public <T> T getIfAvailable(Class<T> clazz) {

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

@Override
Expand Down Expand Up @@ -134,48 +129,57 @@ public InjectorConfig getConfig() {
return config;
}

private Object resolveObject(UnresolvedContext context, Set<Class<?>> traversedClasses) {
@SuppressWarnings("unchecked")
private <T> T resolve(ResolutionType resolutionType, Class<?> clazz) {
return (T) resolveObject(
new ResolutionContext(this, new ObjectIdentifier(resolutionType, clazz)),
new HashSet<>());
}

@Nullable
private Object resolveObject(ResolutionContext context, Set<Class<?>> traversedClasses) {
// TODO #49: Convert singleton store to a Handler impl.
if (context.getResolutionType() == StandardResolutionType.SINGLETON) {
if (context.getIdentifier().getResolutionType() == StandardResolutionType.SINGLETON) {
Object knownSingleton = objects.get(context.getIdentifier().getTypeAsClass());
if (knownSingleton != null) {
return knownSingleton;
}
}

Instantiation<?> instantiation = getInstantiation(context);
Resolution<?> resolution = findResolutionOrFail(context);

traversedClasses.add(context.getIdentifier().getTypeAsClass());
validateInjectionHasNoCircularDependencies(instantiation, traversedClasses);
validateInjectionHasNoCircularDependencies(resolution, traversedClasses);

for (ObjectIdentifier identifier : instantiation.getDependencies()) {
for (ObjectIdentifier identifier : resolution.getDependencies()) {
if (traversedClasses.contains(identifier.getTypeAsClass())) {
throw new InjectorException("Found cyclic dependency - already traversed '"
+ identifier.getTypeAsClass() + "' (full traversal list: " + traversedClasses + ")");
}
}

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);
List<ObjectIdentifier> dependencies = resolution.getDependencies();
Object[] resolvedDependencies = dependencies.stream()
.map(identifier -> new ResolutionContext(this, identifier))
.map(dependencyContext -> resolveObject(dependencyContext, new HashSet<>(traversedClasses)))
.toArray();
Object object = resolution.instantiateWith(resolvedDependencies);

if (resolution.isNewlyCreated()) {
object = runPostConstructHandlers(object, context, resolution);
if (context.getIdentifier().getResolutionType() == StandardResolutionType.SINGLETON) {
register((Class) context.getOriginalIdentifier().getTypeAsClass(), object);
}
}
return obj;
return object;
}

private Instantiation<?> getInstantiation(UnresolvedContext context) {
private Resolution<?> findResolutionOrFail(ResolutionContext context) {
try {
for (Handler handler : config.getHandlers()) {
Instantiation<?> instantiation = handler.get(context);
if (instantiation != null) {
return instantiation;
Resolution<?> resolution = handler.resolve(context);
if (resolution != null) {
return resolution;
}
}
} catch (Exception e) {
Expand All @@ -199,11 +203,11 @@ private Instantiation<?> getInstantiation(UnresolvedContext context) {
+ "require the default constructor");
}

private <T> T runPostConstructHandlers(T instance, ResolvedContext resolvedContext) {
private <T> T runPostConstructHandlers(T instance, ResolutionContext context, Resolution<?> resolution) {
T object = instance;
for (Handler handler : config.getHandlers()) {
try {
object = firstNotNull(handler.postProcess(object, resolvedContext), object);
object = firstNotNull(handler.postProcess(object, context, resolution), object);
} catch (Exception e) {
rethrowException(e);
}
Expand All @@ -215,12 +219,12 @@ private <T> T runPostConstructHandlers(T instance, ResolvedContext resolvedConte
* Validates that none of the dependencies' types are present in the given collection
* of traversed classes. This prevents circular dependencies.
*
* @param instantiation the instantiation method to get the dependencies from
* @param resolution the resolution method to get the dependencies from
* @param traversedClasses the collection of traversed classes
*/
private static void validateInjectionHasNoCircularDependencies(Instantiation<?> instantiation,
private static void validateInjectionHasNoCircularDependencies(Resolution<?> resolution,
Set<Class<?>> traversedClasses) {
for (ObjectIdentifier identifier : instantiation.getDependencies()) {
for (ObjectIdentifier identifier : resolution.getDependencies()) {
if (traversedClasses.contains(identifier.getTypeAsClass())) {
throw new InjectorException("Found cyclic dependency - already traversed '"
+ identifier.getTypeAsClass() + "' (full traversal list: " + traversedClasses + ")");
Expand Down
48 changes: 0 additions & 48 deletions injector/src/main/java/ch/jalu/injector/context/ObjectContext.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,44 @@
*/
public class ObjectIdentifier {

private final ResolutionType resolutionType;
private final Type type;
private final List<Annotation> annotations;

public ObjectIdentifier(Type type, Annotation... annotations) {
public ObjectIdentifier(ResolutionType resolutionType, Type type, Annotation... annotations) {
this.resolutionType = resolutionType;
this.type = type;
this.annotations = Arrays.asList(annotations);
}

public ResolutionType getResolutionType() {
return resolutionType;
}

/**
* Returns the type of the requested object. In general this is the type as returned by
* {@link java.lang.reflect.Field#getGenericType()} or similar: it is a {@code Type}, which may be a
* {@link Class} for simple types or types where no generic information is provided, or it may be a
* {@link ParameterizedType} for types with generic information.
*
* Other extensions of Type are not supported by default.
* <p>
* Use {@link #getTypeAsClass()} to get the type as a Class safely.
* See {@link ch.jalu.injector.utils.ReflectionUtils} for performing further operations on the type.
*
* @return the type
*/
public Type getType() {
return type;
}

/**
* Returns the type of the requested object as a Class. If the {@link #getType type} is a type with
* generic information, the returned class is the raw type without the generic information
* (e.g. {@code List} if the type represents {@code List<String>}).
*
* @return the type as class
*/
public Class<?> getTypeAsClass() {
if (type instanceof Class<?>) {
return (Class<?>) type;
Expand Down
Loading

0 comments on commit a042249

Please sign in to comment.