Skip to content

Commit

Permalink
iss 32. add support for Provides annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
ljacqu committed Nov 3, 2016
1 parent 9354ec3 commit 7766c28
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 0 deletions.
9 changes: 9 additions & 0 deletions core/src/main/java/ch/jalu/injector/Injector.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ch.jalu.injector;

import ch.jalu.injector.config.InjectorConfiguration;

import javax.annotation.Nullable;
import javax.inject.Provider;
import java.lang.annotation.Annotation;
Expand Down Expand Up @@ -56,6 +58,13 @@ public interface Injector {
*/
void provide(Class<? extends Annotation> annotation, @Nullable Object object);

/**
* Processes the given configuration class.
*
* @param configuration the configuration
*/
void addConfiguration(InjectorConfiguration configuration);

/**
* Retrieves or instantiates an object of the given type (singleton scope).
*
Expand Down
14 changes: 14 additions & 0 deletions core/src/main/java/ch/jalu/injector/InjectorImpl.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package ch.jalu.injector;

import ch.jalu.injector.config.InjectorConfiguration;
import ch.jalu.injector.config.ProviderByMethod;
import ch.jalu.injector.config.ProviderMethodsUtil;
import ch.jalu.injector.exceptions.InjectorException;
import ch.jalu.injector.handlers.annotationvalues.AnnotationValueHandler;
import ch.jalu.injector.handlers.dependency.DependencyHandler;
Expand All @@ -14,6 +17,7 @@
import javax.annotation.Nullable;
import javax.inject.Provider;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
Expand Down Expand Up @@ -109,6 +113,16 @@ public <T> Collection<T> retrieveAllOfType(Class<T> clazz) {
return instances;
}

@Override
public void addConfiguration(InjectorConfiguration configuration) {
for (Method method : configuration.getClass().getDeclaredMethods()) {
if (ProviderMethodsUtil.isProviderMethod(method)) {
Class providerType = ProviderMethodsUtil.getProviderMethodType(method);
registerProvider(providerType, new ProviderByMethod<>(method, configuration));
}
}
}

@Override
public <T> void registerProvider(Class<T> clazz, Provider<? extends T> provider) {
checkNotNull(clazz, "Class may not be null");
Expand Down
16 changes: 16 additions & 0 deletions core/src/main/java/ch/jalu/injector/annotations/Provides.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ch.jalu.injector.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks that a method provides a certain type. The return value of the method
* is used as the type.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Provides {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ch.jalu.injector.config;

/**
* Marker interface for an injector configuration class.
*/
public interface InjectorConfiguration {
}
28 changes: 28 additions & 0 deletions core/src/main/java/ch/jalu/injector/config/ProviderByMethod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package ch.jalu.injector.config;

import ch.jalu.injector.utils.InjectorUtils;
import ch.jalu.injector.utils.ReflectionUtils;

import javax.inject.Provider;
import java.lang.reflect.Method;

/**
* Wraps a method call as a Provider.
*/
public class ProviderByMethod<T> implements Provider<T> {

private final Method method;
private final Object instance;

public ProviderByMethod(Method method, Object instance) {
InjectorUtils.checkArgument(method.getParameterTypes().length == 0,
"Provider method may not take any arguments");
this.method = method;
this.instance = instance;
}

@Override
public T get() {
return (T) ReflectionUtils.invokeMethod(method, instance);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package ch.jalu.injector.config;

import ch.jalu.injector.annotations.Provides;
import ch.jalu.injector.exceptions.InjectorException;

import java.lang.reflect.Method;

import static ch.jalu.injector.utils.InjectorUtils.checkArgument;

/**
* Has methods for {@link Provides} methods.
*/
public final class ProviderMethodsUtil {

private ProviderMethodsUtil() {
}

public static boolean isProviderMethod(Method method) {
if (!method.isAnnotationPresent(Provides.class)) {
return false;
}

checkArgument(method.getParameterTypes().length == 0, "@Provides method '" + method
+ "' is not a no-argument method");
return true;
}

public static Class<?> getProviderMethodType(Method method) {
Class<?> returnType = method.getReturnType();
if (returnType.equals(Void.TYPE)) {
throw new InjectorException("@Provides method '" + method + "' has void return type");
}
return returnType;
}

}
79 changes: 79 additions & 0 deletions core/src/test/java/ch/jalu/injector/InjectorImplTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ch.jalu.injector;

import ch.jalu.injector.TestUtils.ExceptionCatcher;
import ch.jalu.injector.exceptions.InjectorException;
import ch.jalu.injector.handlers.Handler;
import ch.jalu.injector.handlers.annotationvalues.AnnotationValueHandler;
import ch.jalu.injector.handlers.instantiation.InstantiationProvider;
Expand All @@ -21,6 +22,8 @@
import ch.jalu.injector.samples.Reloadable;
import ch.jalu.injector.samples.SampleInstantiationImpl;
import ch.jalu.injector.samples.Size;
import ch.jalu.injector.samples.configurations.InvalidInjectorConfigurations;
import ch.jalu.injector.samples.configurations.SampleConfiguration;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
Expand All @@ -34,13 +37,15 @@
import java.util.Iterator;
import java.util.List;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -394,6 +399,80 @@ public void shouldNotInstantiateForMissingDependencies() {
assertThat(gammaService, nullValue());
}

@Test
public void shouldGetProvidersFromConfigurationClass() {
// given
Injector injector = new InjectorBuilder()
.addDefaultHandlers("ch.jalu")
.create();
SampleConfiguration sampleConfiguration = new SampleConfiguration();
injector.addConfiguration(sampleConfiguration);

// when
ProvidedClass providedClass = injector.getSingleton(ProvidedClass.class);
BetaManager betaManager = injector.getSingleton(BetaManager.class);
AlphaService alphaService = injector.getSingleton(AlphaService.class);

// then
assertThat(providedClass, sameInstance(sampleConfiguration.getProvidedClass()));
assertThat(betaManager, sameInstance(sampleConfiguration.initBetaManager()));
assertThat(alphaService.getProvidedClass(), equalTo(providedClass));
}

@Test
public void shouldThrowForMultipleProviders() {
// given
Injector injector = new InjectorBuilder()
.addDefaultHandlers("ch.jalu")
.create();
injector.registerProvider(ProvidedClass.class, new Provider<ProvidedClass>() {
@Override
public ProvidedClass get() {
return new ProvidedClass("");
}
});

// when / then
try {
injector.addConfiguration(new SampleConfiguration());
fail("Expected exception to be thrown");
} catch (InjectorException e) {
assertThat(e.getMessage(), containsString("Provider already registered"));
}
}

@Test
public void shouldThrowForProvidesMethodWithArguments() {
// given
Injector injector = new InjectorBuilder()
.addDefaultHandlers("ch.jalu")
.create();

// when/then
try {
injector.addConfiguration(new InvalidInjectorConfigurations.ConfigurationWithParameters());
fail("Expected exception to be thrown");
} catch (InjectorException e) {
assertThat(e.getMessage(), containsString("is not a no-argument method"));
}
}

@Test
public void shouldThrowForProvidesMethodWithVoidType() {
// given
Injector injector = new InjectorBuilder()
.addDefaultHandlers("ch.jalu")
.create();

// when / then
try {
injector.addConfiguration(new InvalidInjectorConfigurations.ConfigurationWithVoidReturnType());
fail("Expected exception to be thrown");
} catch (InjectorException e) {
assertThat(e.getMessage(), containsString("has void return type"));
}
}

private static List<Handler> getAllHandlersExceptInstantiationProviders() {
List<Handler> handlers = InjectorBuilder.createDefaultHandlers("");
Iterator<Handler> iterator = handlers.iterator();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ch.jalu.injector.samples.configurations;

import ch.jalu.injector.annotations.Provides;
import ch.jalu.injector.config.InjectorConfiguration;
import ch.jalu.injector.samples.ProvidedClass;

/**
* Collection of invalid configurations.
*/
public class InvalidInjectorConfigurations {

public static final class ConfigurationWithVoidReturnType implements InjectorConfiguration {
@Provides
public ProvidedClass initProvidedClass() {
return new ProvidedClass("");
}

@Provides
public void initBetaManager() {
// invalid return type
}
}

public static final class ConfigurationWithParameters implements InjectorConfiguration {
@Provides
public ProvidedClass initProvidedClass(boolean b) {
return new ProvidedClass("");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package ch.jalu.injector.samples.configurations;

import ch.jalu.injector.annotations.Provides;
import ch.jalu.injector.config.InjectorConfiguration;
import ch.jalu.injector.samples.BetaManager;
import ch.jalu.injector.samples.ProvidedClass;

/**
* Sample configuration class.
*/
public class SampleConfiguration implements InjectorConfiguration {

private BetaManager betaManager = new BetaManager();
private ProvidedClass providedClass = new ProvidedClass("");

@Provides
public BetaManager initBetaManager() {
return betaManager;
}

@Provides
private ProvidedClass initProvidedClass() {
return providedClass;
}

public ProvidedClass getProvidedClass() {
return providedClass;
}
}

0 comments on commit 7766c28

Please sign in to comment.