Skip to content

Commit

Permalink
#27 No longer throw an exception directly if requested class cannot b…
Browse files Browse the repository at this point in the history
…e instantiated

- There may be instantiation providers that know of a subclass to use (e.g. with the newly supported Provider class)
  • Loading branch information
ljacqu committed Aug 15, 2016
1 parent 28c4b32 commit 9416c11
Show file tree
Hide file tree
Showing 14 changed files with 109 additions and 31 deletions.
19 changes: 19 additions & 0 deletions src/main/java/ch/jalu/injector/Injector.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,26 @@ public interface Injector {
*/
<T> void register(Class<? super T> clazz, T object);

/**
* Registers a provider for the given object.
*
* @param clazz the class to register the provider for
* @param provider the provider
* @param <T> the class' type
* @since 0.3
*/
<T> void registerProvider(Class<T> clazz, Provider<? extends T> provider);

/**
* Registers the provider class to instantiate a given class. The first time the {@code clazz} has to
* be instantiated, the {@code providerClass} will be instantiated.
*
* @param clazz the class to register the provider for
* @param providerClass the class of the provider
* @param <T> the class' type
* @param <P> the provider's type
* @since 0.3
*/
<T, P extends Provider<? extends T>> void registerProvider(Class<T> clazz, Class<P> providerClass);

/**
Expand All @@ -34,6 +52,7 @@ public interface Injector {
*
* @param annotation the annotation
* @param object the object
* @since 0.1
*/
void provide(Class<? extends Annotation> annotation, @Nullable Object object);

Expand Down
8 changes: 4 additions & 4 deletions src/main/java/ch/jalu/injector/InjectorImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,6 @@ private <T> T get(Class<T> clazz, Set<Class<?>> traversedClasses) {
}
}

if (!InjectorUtils.canInstantiate(mappedClass)) {
throw new InjectorException("Class " + clazz.getSimpleName() + " cannot be instantiated");
}

// Add the clazz to the list of traversed classes in a new Set, so each path we take has its own Set.
traversedClasses = new HashSet<>(traversedClasses);
traversedClasses.add(mappedClass);
Expand Down Expand Up @@ -199,8 +195,12 @@ private <T> Instantiation<T> getInstantiation(Class<T> clazz) {
}
}

// No instantiation method was found, handle error with most appropriate message
if (config.getInstantiationProviders().isEmpty()) {
throw new InjectorException("You did not register any instantiation methods!");
} else if (!InjectorUtils.canInstantiate(clazz)) {
throw new InjectorException("Did not find instantiation method for '" + clazz + "'. This class cannot "
+ "be instantiated directly, please check the class or your handlers.");
}
throw new InjectorException("Did not find instantiation method for '" + clazz + "'. Make sure your class "
+ "conforms to one of the registered instantiations. If default: make sure you have "
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/ch/jalu/injector/handlers/Handler.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
* create an object.</li>
* <li>{@link ch.jalu.injector.handlers.dependency.DependencyHandler}: Offers a way to resolve a given dependency
* required to instantiate a class, e.g. to implement custom behavior for annotations.</li>
* <li>{@link ch.jalu.injector.handlers.postconstruct.PostConstructHandler}: Perform an action on an object that
* <li>{@link ch.jalu.injector.handlers.postconstruct.PostConstructHandler}: Performs an action on an object that
* has been created with the injector. You can support {@code @PostConstruct} methods this way or perform some
* form of validation.</li>
* <li>{@link ch.jalu.injector.handlers.provider.ProviderHandler}: Processes the provider object and classes that are
* supplied to the injector.</li>
* </ul>
*
* Handlers are executed in the order that they are given to {@link ch.jalu.injector.InjectorBuilder}, so more important
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
/**
* Provider for {@link ConstructorInjection}.
*/
public class ConstructorInjectionProvider implements InstantiationProvider {
public class ConstructorInjectionProvider extends DirectInstantiationProvider {

@Override
public <T> ConstructorInjection<T> get(Class<T> clazz) {
protected <T> ConstructorInjection<T> safeGet(Class<T> clazz) {
Constructor<T> constructor = getInjectionConstructor(clazz);
if (constructor == null) {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ch.jalu.injector.handlers.instantiation;

import ch.jalu.injector.utils.InjectorUtils;

import javax.annotation.Nullable;

/**
* Instantiation provider for instantiations that happen on the class directly,
* i.e. limited to classes which are instantiable.
*/
public abstract class DirectInstantiationProvider implements InstantiationProvider {

@Override
public final <T> Instantiation<T> get(Class<T> clazz) {
if (InjectorUtils.canInstantiate(clazz)) {
return safeGet(clazz);
}
return null;
}

/**
* Gets the instantiation for the class or null if unavailable. This method
* is only called with classes which can be instantiated.
*
* @param clazz the class to process
* @param <T> the class' type
* @return the instantiation, or null if not applicable
*/
@Nullable
protected abstract <T> Instantiation<T> safeGet(Class<T> clazz);

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
/**
* Provider for {@link FieldInjection}.
*/
public class FieldInjectionProvider implements InstantiationProvider {
public class FieldInjectionProvider extends DirectInstantiationProvider {

@Override
public <T> FieldInjection<T> get(Class<T> clazz) {
protected <T> FieldInjection<T> safeGet(Class<T> clazz) {
Constructor<T> constructor = getNoArgsConstructor(clazz);
if (constructor == null) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public interface Instantiation<T> {
* @return list of dependencies
* @see #instantiateWith
*/
List<? extends DependencyDescription> getDependencies();
List<DependencyDescription> getDependencies();

/**
* Creates a new instance with the given values as dependencies. The given values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
/**
* Provider for {@link InstantiationFallback}.
*/
public class InstantiationFallbackProvider implements InstantiationProvider {
public class InstantiationFallbackProvider extends DirectInstantiationProvider {

@Override
public <T> InstantiationFallback<T> get(Class<T> clazz) {
protected <T> InstantiationFallback<T> safeGet(Class<T> clazz) {
Constructor<T> noArgsConstructor = getNoArgsConstructor(clazz);
// Return fallback only if we have no args constructor and no @Inject annotation anywhere
if (noArgsConstructor != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,29 @@
import javax.inject.Provider;

/**
* .
* Handles {@link Provider} objects and classes supplied to the injector.
*/
public interface ProviderHandler extends Handler {

/**
* Processes the given provider.
*
* @param clazz the class to associate the provider with
* @param provider the provider
* @param <T> the class' type
* @throws Exception for unsuccessful validation, etc.
*/
<T> void onProvider(Class<T> clazz, Provider<? extends T> provider) throws Exception;

/**
* Processes the given provider class.
*
* @param clazz the class to associate the provider class with
* @param providerClass the provider class
* @param <T> the class' type
* @param <P> the provider class' type
* @throws Exception for unsuccessful validation, etc.
*/
<T, P extends Provider<? extends T>> void onProviderClass(Class<T> clazz, Class<P> providerClass) throws Exception;

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public <T> Instantiation<T> get(Class<T> clazz) {
return (Instantiation<T>) providers.get(clazz);
}

/**
* Simple instantiation that creates an object with the known provider.
*
* @param <T> the type of the class to create
*/
private static final class ProviderInstantiation<T> implements Instantiation<T> {

private final Provider<? extends T> provider;
Expand All @@ -58,6 +63,13 @@ public T instantiateWith(Object... values) {
}
}

/**
* Instantiation that internally creates the required provider first. This is triggered by
* declaring the provider class as a dependency, making the injector create the provider
* class first.
*
* @param <T> the type of the class to create
*/
private final class UninitializedProviderInstantiation<T> implements Instantiation<T> {

private final Class<T> clazz;
Expand All @@ -80,6 +92,7 @@ public T instantiateWith(Object... values) {
@SuppressWarnings("unchecked")
Provider<? extends T> provider = (Provider<? extends T>) values[0];
T object = provider.get();
// The injector passed us the provider, so save it in the map for future uses
providers.put(clazz, new ProviderInstantiation<>(provider));
return object;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class ConstructorInjectionTest {
@Test
public void shouldReturnDependencies() {
// given
ConstructorInjection<ClassWithAnnotations> injection = provider.get(ClassWithAnnotations.class);
Instantiation<ClassWithAnnotations> injection = provider.get(ClassWithAnnotations.class);

// when
List<DependencyDescription> dependencies = injection.getDependencies();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class FieldInjectionTest {
@Test
public void shouldReturnDependencies() {
// given
FieldInjection<FieldInjectionWithAnnotations> injection =
Instantiation<FieldInjectionWithAnnotations> injection =
provider.get(FieldInjectionWithAnnotations.class);

// when
Expand All @@ -58,7 +58,7 @@ public void shouldReturnDependencies() {
@Test
public void shouldInstantiateClass() {
// given
FieldInjection<BetaManager> injection = provider.get(BetaManager.class);
Instantiation<BetaManager> injection = provider.get(BetaManager.class);
ProvidedClass providedClass = new ProvidedClass("");
AlphaService alphaService = AlphaService.newInstance(providedClass);
GammaService gammaService = new GammaService(alphaService);
Expand All @@ -74,7 +74,7 @@ public void shouldInstantiateClass() {
@Test
public void shouldProvideNullForImpossibleFieldInjection() {
// given / when
FieldInjection<BadFieldInjection> injection = provider.get(BadFieldInjection.class);
Instantiation<BadFieldInjection> injection = provider.get(BadFieldInjection.class);

// then
assertThat(injection, nullValue());
Expand All @@ -83,7 +83,7 @@ public void shouldProvideNullForImpossibleFieldInjection() {
@Test(expected = InjectorException.class)
public void shouldForwardExceptionDuringInstantiation() {
// given
FieldInjection<ThrowingConstructor> injection = provider.get(ThrowingConstructor.class);
Instantiation<ThrowingConstructor> injection = provider.get(ThrowingConstructor.class);

// when / when
injection.instantiateWith(new ProvidedClass(""));
Expand All @@ -95,7 +95,7 @@ public void shouldThrowForInvalidFieldValue() {
ProvidedClass providedClass = new ProvidedClass("");
AlphaService alphaService = AlphaService.newInstance(providedClass);
GammaService gammaService = new GammaService(alphaService);
FieldInjection<BetaManager> injection = provider.get(BetaManager.class);
Instantiation<BetaManager> injection = provider.get(BetaManager.class);

// when / then
// Correct order is provided, gamma, alpha
Expand All @@ -107,7 +107,7 @@ public void shouldThrowForNullValue() {
// given
ProvidedClass providedClass = new ProvidedClass("");
AlphaService alphaService = AlphaService.newInstance(providedClass);
FieldInjection<BetaManager> injection = provider.get(BetaManager.class);
Instantiation<BetaManager> injection = provider.get(BetaManager.class);

// when / then
// Correct order is provided, gamma, alpha
Expand All @@ -132,7 +132,7 @@ public void shouldNotReturnFieldInjectionForZeroInjectFields() {
@Test
public void shouldNotScanClassWithNoFieldScan() {
// given / when
FieldInjection<NoFieldScanClass> injection = provider.get(NoFieldScanClass.class);
Instantiation<NoFieldScanClass> injection = provider.get(NoFieldScanClass.class);

// then
assertThat(injection, nullValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void shouldInstantiateClass() {
@Test
public void shouldHaveEmptyDependenciesAndAnnotations() {
// given
InstantiationFallback<InstantiationFallbackClasses.FallbackClass> instantiation =
Instantiation<InstantiationFallbackClasses.FallbackClass> instantiation =
provider.get(InstantiationFallbackClasses.FallbackClass.class);

// when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
import ch.jalu.injector.handlers.provider.impl.Delta2Provider;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;

import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
Expand All @@ -29,7 +27,6 @@
/**
* Test for {@link ProviderHandlerImpl}.
*/
@RunWith(MockitoJUnitRunner.class)
public class ProviderHandlerImplTest {

private ProviderHandlerImpl providerHandler = new ProviderHandlerImpl();
Expand Down Expand Up @@ -131,34 +128,32 @@ public void shouldThrowForInvalidArgument() {
}

@Test
// TODO #27: Change test to use interface instead of impl. class
public void shouldInstantiateClassWithProvider() {
// given
Injector injector = new InjectorBuilder()
.addDefaultHandlers("ch.jalu.injector")
.create();
Charlie charlie = injector.getSingleton(Charlie.class);
injector.registerProvider(Delta2.class, new Delta2Provider(charlie));
injector.registerProvider(Delta.class, new Delta2Provider(charlie));

// when
Delta delta = injector.getSingleton(Delta2.class);
Delta delta = injector.getSingleton(Delta.class);

// then
assertThat(delta, instanceOf(Delta2.class));
assertThat(delta.getName(), equalTo("pre_BRAVO_CHARLIE"));
}

@Test
// TODO #27: Change test to use interface instead of impl. class
public void shouldInstantiateClassWithProviderClass() {
// given
Injector injector = new InjectorBuilder()
.addDefaultHandlers("ch.jalu.injector")
.create();
injector.registerProvider(Delta1.class, Delta1Provider.class);
injector.registerProvider(Delta.class, Delta1Provider.class);

// when
Delta delta = injector.getSingleton(Delta1.class);
Delta delta = injector.getSingleton(Delta.class);

// then
assertThat(delta, instanceOf(Delta1.class));
Expand Down

0 comments on commit 9416c11

Please sign in to comment.