Skip to content

Commit 05bcab4

Browse files
committed
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.
1 parent ee1545f commit 05bcab4

File tree

3 files changed

+229
-35
lines changed

3 files changed

+229
-35
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package restx.factory.conditional;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static restx.factory.Factory.LocalMachines.overrideComponents;
5+
import static restx.factory.Factory.LocalMachines.threadLocal;
6+
7+
8+
import com.google.common.base.Function;
9+
import com.google.common.collect.Iterables;
10+
import org.junit.AfterClass;
11+
import org.junit.Before;
12+
import org.junit.BeforeClass;
13+
import org.junit.Test;
14+
15+
import java.util.Set;
16+
import restx.factory.Factory;
17+
import restx.factory.Name;
18+
import restx.factory.conditional.components.TestModuleWithConditional;
19+
20+
/**
21+
* Test cases for conditionals.
22+
*
23+
* @author apeyrard
24+
*/
25+
public class ConditionalTest {
26+
27+
/**
28+
* ElementsFromConfig component can not be build, because of module TestMandatoryDependency
29+
* which use a missing dependency.
30+
*/
31+
@BeforeClass
32+
public static void deactivateElementsFromConfig() {
33+
System.setProperty("restx.activation::restx.factory.FactoryMachine::ElementsFromConfig", "false");
34+
}
35+
36+
/**
37+
* cleanup state before each test method
38+
*/
39+
@Before
40+
public void cleanupBefore() {
41+
threadLocal().clear();
42+
}
43+
44+
/**
45+
* cleanup state after this test class execution
46+
*/
47+
@AfterClass
48+
public static void cleanupAfterClass() {
49+
threadLocal().clear();
50+
}
51+
52+
@Test
53+
public void should_provide_component_if_condition_is_verified() {
54+
Factory factory = Factory.newInstance();
55+
Set<TestModuleWithConditional.Pioneer> pioneers = factory.getComponents(TestModuleWithConditional.Pioneer.class);
56+
Iterable<String> names = Iterables.transform(pioneers, new Function<TestModuleWithConditional.Pioneer, String>() {
57+
@Override
58+
public String apply(TestModuleWithConditional.Pioneer pioneer) {
59+
return pioneer.name();
60+
}
61+
});
62+
assertThat(names).containsOnly("Marie Currie", "Charles Babbage");
63+
64+
overrideComponents().set("period", "all");
65+
66+
factory = Factory.newInstance();
67+
pioneers = factory.getComponents(TestModuleWithConditional.Pioneer.class);
68+
names = Iterables.transform(pioneers, new Function<TestModuleWithConditional.Pioneer, String>() {
69+
@Override
70+
public String apply(TestModuleWithConditional.Pioneer pioneer) {
71+
return pioneer.name();
72+
}
73+
});
74+
assertThat(names).containsOnly("Marie Currie", "Charles Babbage", "Alan Turing");
75+
}
76+
77+
@Test
78+
public void should_provide_component_if_condition_is_verified_and_use_name_and_priorities() {
79+
Factory factory = Factory.newInstance();
80+
TestModuleWithConditional.Pioneer pioneer = factory.getComponent(Name.of(TestModuleWithConditional.Pioneer.class, "physics"));
81+
assertThat(pioneer.name()).isEqualTo("Marie Currie");
82+
83+
overrideComponents().set("chauvinist", "true");
84+
85+
factory = Factory.newInstance();
86+
pioneer = factory.getComponent(Name.of(TestModuleWithConditional.Pioneer.class, "physics"));
87+
assertThat(pioneer.name()).isEqualTo("Pierre Currie");
88+
}
89+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package restx.factory.conditional.components;
2+
3+
import javax.inject.Named;
4+
import restx.factory.Module;
5+
import restx.factory.Provides;
6+
import restx.factory.When;
7+
8+
/**
9+
* @author apeyrard
10+
*/
11+
@Module
12+
public class TestModuleWithConditional {
13+
14+
public static interface Pioneer {
15+
String name();
16+
}
17+
18+
@Provides @Named("physics")
19+
public Pioneer currie() {
20+
return new Pioneer() {
21+
@Override
22+
public String name() {
23+
return "Marie Currie";
24+
}
25+
};
26+
}
27+
28+
@Provides(priority = -100) @Named("physics")
29+
@When(name = "chauvinist", value = "true")
30+
public Pioneer pierreCurrie() {
31+
return new Pioneer() {
32+
@Override
33+
public String name() {
34+
return "Pierre Currie";
35+
}
36+
};
37+
}
38+
39+
@Provides
40+
public Pioneer babbage() {
41+
return new Pioneer() {
42+
@Override
43+
public String name() {
44+
return "Charles Babbage";
45+
}
46+
};
47+
}
48+
49+
@Provides
50+
@When(name = "period", value = "all")
51+
public Pioneer turing() {
52+
return new Pioneer() {
53+
@Override
54+
public String name() {
55+
return "Alan Turing";
56+
}
57+
};
58+
}
59+
}

restx-factory/src/main/java/restx/factory/processor/FactoryAnnotationProcessor.java

Lines changed: 81 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -84,73 +84,119 @@ private void processModules(RoundEnvironment roundEnv) throws IOException {
8484

8585
ModuleClass module = new ModuleClass(typeElem.getQualifiedName().toString(), typeElem, mod.priority());
8686
for (Element element : typeElem.getEnclosedElements()) {
87+
8788
// look for Provides or Alternative elements
8889
Provides provides = element.getAnnotation(Provides.class);
8990
Alternative alternative = element.getAnnotation(Alternative.class);
91+
9092
if (element instanceof ExecutableElement
9193
&& element.getKind() == ElementKind.METHOD) {
92-
if (provides != null) {
93-
ExecutableElement exec = (ExecutableElement) element;
94-
95-
ProviderMethod m = new ProviderMethod(
96-
exec.getReturnType().toString(),
97-
exec.getSimpleName().toString(),
98-
provides.priority() == 0 ? mod.priority() : provides.priority(),
99-
getInjectionName(exec.getAnnotation(Named.class)),
100-
exec);
10194

102-
buildInjectableParams(exec, m.parameters);
95+
ExecutableElement exec = (ExecutableElement) element;
96+
When when = exec.getAnnotation(When.class);
10397

104-
buildCheckedExceptions(exec, m.exceptions);
98+
// multiple cases, provides only, provides with when, and alternative
10599

106-
module.providerMethods.add(m);
100+
if (provides != null && when == null) {
101+
// add a provider method to the module
102+
processProviderMethod(mod, module, provides, exec);
103+
} else if (provides != null) {
104+
// we need to create a conditional provider method
105+
processConditionalProviderMethod(
106+
mod,
107+
module,
108+
exec.getReturnType().toString(),
109+
getInjectionName(exec.getAnnotation(Named.class)).or(exec.getSimpleName().toString()),
110+
provides.priority() == 0 ? mod.priority() : provides.priority(),
111+
when,
112+
"Conditional",
113+
exec
114+
);
107115
} else if (alternative != null) {
108-
ExecutableElement exec = (ExecutableElement) element;
109-
110-
When when = exec.getAnnotation(When.class);
116+
// when annotation is required with alternative
111117
if (when == null) {
112118
error("an Alternative MUST be annotated with @When to tell when it must be activated", exec);
113119
continue;
114120
}
115121

116122
TypeElement alternativeTo = null;
117-
if (alternative != null) {
118-
try {
119-
alternative.to();
120-
} catch (MirroredTypeException mte) {
121-
alternativeTo = asTypeElement(mte.getTypeMirror());
122-
}
123+
try {
124+
alternative.to();
125+
} catch (MirroredTypeException mte) {
126+
alternativeTo = asTypeElement(mte.getTypeMirror());
123127
}
124128

125-
// the conditional component name, is the one specified in @Alternative annotation or the simple name of the produced class
126-
String componentName = !alternative.named().isEmpty() ? alternative.named() : alternativeTo.getSimpleName().toString();
129+
String namedAttribute = alternative.named();
130+
Optional<String> injectionName = getInjectionName(alternativeTo.getAnnotation(Named.class));
131+
String componentName;
132+
if (!namedAttribute.isEmpty()) {
133+
// the conditional component name is the one specified in @Alternative annotation
134+
componentName = namedAttribute;
135+
} else if (injectionName.isPresent()) {
136+
// or the Name of the reference class
137+
componentName = injectionName.get();
138+
} else {
139+
// or the simple name of the produced class
140+
componentName = alternativeTo.getSimpleName().toString();
141+
}
127142

128-
ConditionalProviderMethod m = new ConditionalProviderMethod(
143+
// add a conditional provider method to the module
144+
processConditionalProviderMethod(
145+
mod,
146+
module,
129147
alternativeTo.getQualifiedName().toString(),
130148
componentName,
131-
exec.getSimpleName().toString(),
132149
alternative.priority(),
133-
when.name(),
134-
when.value(),
150+
when,
135151
"Alternative",
136-
exec);
137-
138-
buildInjectableParams(exec, m.parameters);
139-
140-
buildCheckedExceptions(exec, m.exceptions);
141-
142-
module.conditionalProviderMethods.add(m);
152+
exec
153+
);
143154
}
144155
}
145156
}
146157

158+
// finally generate the machine with all methods found
147159
generateMachineFile(module);
148160
} catch (IOException e) {
149161
fatalError("error when processing " + annotation, e, annotation);
150162
}
151163
}
152164
}
153165

166+
private void processProviderMethod(Module mod, ModuleClass module, Provides provides, ExecutableElement exec) {
167+
ProviderMethod m = new ProviderMethod(
168+
exec.getReturnType().toString(),
169+
exec.getSimpleName().toString(),
170+
provides.priority() == 0 ? mod.priority() : provides.priority(),
171+
getInjectionName(exec.getAnnotation(Named.class)),
172+
exec);
173+
174+
buildInjectableParams(exec, m.parameters);
175+
176+
buildCheckedExceptions(exec, m.exceptions);
177+
178+
module.providerMethods.add(m);
179+
}
180+
181+
private void processConditionalProviderMethod(Module mod, ModuleClass module, String componentType,
182+
String componentName, int priority, When when, String factoryMachineNameSuffix, ExecutableElement exec) {
183+
ConditionalProviderMethod m = new ConditionalProviderMethod(
184+
componentType,
185+
componentName,
186+
exec.getSimpleName().toString(),
187+
priority == 0 ? mod.priority() : priority,
188+
when.name(),
189+
when.value(),
190+
factoryMachineNameSuffix,
191+
exec);
192+
193+
buildInjectableParams(exec, m.parameters);
194+
195+
buildCheckedExceptions(exec, m.exceptions);
196+
197+
module.conditionalProviderMethods.add(m);
198+
}
199+
154200
private void processMachines(RoundEnvironment roundEnv) throws IOException {
155201
for (Element annotation : roundEnv.getElementsAnnotatedWith(Machine.class)) {
156202
try {
@@ -295,7 +341,7 @@ private ExecutableElement findInjectableConstructor(TypeElement component) {
295341

296342
private void buildCheckedExceptions(ExecutableElement executableElement, List<String> exceptions) {
297343
for (TypeMirror e : executableElement.getThrownTypes()) {
298-
// Assuming Exceptions never have type arguments. Qualified names include type arguments.
344+
// Assuming Exceptions never have type arguments. Qualified names include type arguments.
299345
String exception = ((TypeElement) ((DeclaredType) e).asElement()).getQualifiedName().toString();
300346
exceptions.add(exception);
301347
}

0 commit comments

Comments
 (0)