Skip to content

Commit

Permalink
Allow conditional components in module
Browse files Browse the repository at this point in the history
Conditional component in a module, is a method annotated with @provides
and @when.
The factory machine for this component will only be built if the condition is
satisfied.
  • Loading branch information
a-peyrard committed Feb 8, 2015
1 parent ee1545f commit 05bcab4
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package restx.factory.conditional;

import static org.assertj.core.api.Assertions.assertThat;
import static restx.factory.Factory.LocalMachines.overrideComponents;
import static restx.factory.Factory.LocalMachines.threadLocal;


import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.Set;
import restx.factory.Factory;
import restx.factory.Name;
import restx.factory.conditional.components.TestModuleWithConditional;

/**
* Test cases for conditionals.
*
* @author apeyrard
*/
public class ConditionalTest {

/**
* ElementsFromConfig component can not be build, because of module TestMandatoryDependency
* which use a missing dependency.
*/
@BeforeClass
public static void deactivateElementsFromConfig() {
System.setProperty("restx.activation::restx.factory.FactoryMachine::ElementsFromConfig", "false");
}

/**
* cleanup state before each test method
*/
@Before
public void cleanupBefore() {
threadLocal().clear();
}

/**
* cleanup state after this test class execution
*/
@AfterClass
public static void cleanupAfterClass() {
threadLocal().clear();
}

@Test
public void should_provide_component_if_condition_is_verified() {
Factory factory = Factory.newInstance();
Set<TestModuleWithConditional.Pioneer> pioneers = factory.getComponents(TestModuleWithConditional.Pioneer.class);
Iterable<String> names = Iterables.transform(pioneers, new Function<TestModuleWithConditional.Pioneer, String>() {
@Override
public String apply(TestModuleWithConditional.Pioneer pioneer) {
return pioneer.name();
}
});
assertThat(names).containsOnly("Marie Currie", "Charles Babbage");

overrideComponents().set("period", "all");

factory = Factory.newInstance();
pioneers = factory.getComponents(TestModuleWithConditional.Pioneer.class);
names = Iterables.transform(pioneers, new Function<TestModuleWithConditional.Pioneer, String>() {
@Override
public String apply(TestModuleWithConditional.Pioneer pioneer) {
return pioneer.name();
}
});
assertThat(names).containsOnly("Marie Currie", "Charles Babbage", "Alan Turing");
}

@Test
public void should_provide_component_if_condition_is_verified_and_use_name_and_priorities() {
Factory factory = Factory.newInstance();
TestModuleWithConditional.Pioneer pioneer = factory.getComponent(Name.of(TestModuleWithConditional.Pioneer.class, "physics"));
assertThat(pioneer.name()).isEqualTo("Marie Currie");

overrideComponents().set("chauvinist", "true");

factory = Factory.newInstance();
pioneer = factory.getComponent(Name.of(TestModuleWithConditional.Pioneer.class, "physics"));
assertThat(pioneer.name()).isEqualTo("Pierre Currie");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package restx.factory.conditional.components;

import javax.inject.Named;
import restx.factory.Module;
import restx.factory.Provides;
import restx.factory.When;

/**
* @author apeyrard
*/
@Module
public class TestModuleWithConditional {

public static interface Pioneer {
String name();
}

@Provides @Named("physics")
public Pioneer currie() {
return new Pioneer() {
@Override
public String name() {
return "Marie Currie";
}
};
}

@Provides(priority = -100) @Named("physics")
@When(name = "chauvinist", value = "true")
public Pioneer pierreCurrie() {
return new Pioneer() {
@Override
public String name() {
return "Pierre Currie";
}
};
}

@Provides
public Pioneer babbage() {
return new Pioneer() {
@Override
public String name() {
return "Charles Babbage";
}
};
}

@Provides
@When(name = "period", value = "all")
public Pioneer turing() {
return new Pioneer() {
@Override
public String name() {
return "Alan Turing";
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,73 +84,119 @@ private void processModules(RoundEnvironment roundEnv) throws IOException {

ModuleClass module = new ModuleClass(typeElem.getQualifiedName().toString(), typeElem, mod.priority());
for (Element element : typeElem.getEnclosedElements()) {

// look for Provides or Alternative elements
Provides provides = element.getAnnotation(Provides.class);
Alternative alternative = element.getAnnotation(Alternative.class);

if (element instanceof ExecutableElement
&& element.getKind() == ElementKind.METHOD) {
if (provides != null) {
ExecutableElement exec = (ExecutableElement) element;

ProviderMethod m = new ProviderMethod(
exec.getReturnType().toString(),
exec.getSimpleName().toString(),
provides.priority() == 0 ? mod.priority() : provides.priority(),
getInjectionName(exec.getAnnotation(Named.class)),
exec);

buildInjectableParams(exec, m.parameters);
ExecutableElement exec = (ExecutableElement) element;
When when = exec.getAnnotation(When.class);

buildCheckedExceptions(exec, m.exceptions);
// multiple cases, provides only, provides with when, and alternative

module.providerMethods.add(m);
if (provides != null && when == null) {
// add a provider method to the module
processProviderMethod(mod, module, provides, exec);
} else if (provides != null) {
// we need to create a conditional provider method
processConditionalProviderMethod(
mod,
module,
exec.getReturnType().toString(),
getInjectionName(exec.getAnnotation(Named.class)).or(exec.getSimpleName().toString()),
provides.priority() == 0 ? mod.priority() : provides.priority(),
when,
"Conditional",
exec
);
} else if (alternative != null) {
ExecutableElement exec = (ExecutableElement) element;

When when = exec.getAnnotation(When.class);
// when annotation is required with alternative
if (when == null) {
error("an Alternative MUST be annotated with @When to tell when it must be activated", exec);
continue;
}

TypeElement alternativeTo = null;
if (alternative != null) {
try {
alternative.to();
} catch (MirroredTypeException mte) {
alternativeTo = asTypeElement(mte.getTypeMirror());
}
try {
alternative.to();
} catch (MirroredTypeException mte) {
alternativeTo = asTypeElement(mte.getTypeMirror());
}

// the conditional component name, is the one specified in @Alternative annotation or the simple name of the produced class
String componentName = !alternative.named().isEmpty() ? alternative.named() : alternativeTo.getSimpleName().toString();
String namedAttribute = alternative.named();
Optional<String> injectionName = getInjectionName(alternativeTo.getAnnotation(Named.class));
String componentName;
if (!namedAttribute.isEmpty()) {
// the conditional component name is the one specified in @Alternative annotation
componentName = namedAttribute;
} else if (injectionName.isPresent()) {
// or the Name of the reference class
componentName = injectionName.get();
} else {
// or the simple name of the produced class
componentName = alternativeTo.getSimpleName().toString();
}

ConditionalProviderMethod m = new ConditionalProviderMethod(
// add a conditional provider method to the module
processConditionalProviderMethod(
mod,
module,
alternativeTo.getQualifiedName().toString(),
componentName,
exec.getSimpleName().toString(),
alternative.priority(),
when.name(),
when.value(),
when,
"Alternative",
exec);

buildInjectableParams(exec, m.parameters);

buildCheckedExceptions(exec, m.exceptions);

module.conditionalProviderMethods.add(m);
exec
);
}
}
}

// finally generate the machine with all methods found
generateMachineFile(module);
} catch (IOException e) {
fatalError("error when processing " + annotation, e, annotation);
}
}
}

private void processProviderMethod(Module mod, ModuleClass module, Provides provides, ExecutableElement exec) {
ProviderMethod m = new ProviderMethod(
exec.getReturnType().toString(),
exec.getSimpleName().toString(),
provides.priority() == 0 ? mod.priority() : provides.priority(),
getInjectionName(exec.getAnnotation(Named.class)),
exec);

buildInjectableParams(exec, m.parameters);

buildCheckedExceptions(exec, m.exceptions);

module.providerMethods.add(m);
}

private void processConditionalProviderMethod(Module mod, ModuleClass module, String componentType,
String componentName, int priority, When when, String factoryMachineNameSuffix, ExecutableElement exec) {
ConditionalProviderMethod m = new ConditionalProviderMethod(
componentType,
componentName,
exec.getSimpleName().toString(),
priority == 0 ? mod.priority() : priority,
when.name(),
when.value(),
factoryMachineNameSuffix,
exec);

buildInjectableParams(exec, m.parameters);

buildCheckedExceptions(exec, m.exceptions);

module.conditionalProviderMethods.add(m);
}

private void processMachines(RoundEnvironment roundEnv) throws IOException {
for (Element annotation : roundEnv.getElementsAnnotatedWith(Machine.class)) {
try {
Expand Down Expand Up @@ -295,7 +341,7 @@ private ExecutableElement findInjectableConstructor(TypeElement component) {

private void buildCheckedExceptions(ExecutableElement executableElement, List<String> exceptions) {
for (TypeMirror e : executableElement.getThrownTypes()) {
// Assuming Exceptions never have type arguments. Qualified names include type arguments.
// Assuming Exceptions never have type arguments. Qualified names include type arguments.
String exception = ((TypeElement) ((DeclaredType) e).asElement()).getQualifiedName().toString();
exceptions.add(exception);
}
Expand Down

0 comments on commit 05bcab4

Please sign in to comment.