Permalink
Browse files

Allow conditional components in module

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 05bcab4600df7e9a4e4f17e52d7b6f85f29ec341
@@ -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");
+ }
+}
@@ -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";
+ }
+ };
+ }
+}
@@ -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 {
@@ -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);
}

0 comments on commit 05bcab4

Please sign in to comment.