Skip to content

Commit a8ef2c6

Browse files
committed
Allow Alternative to be used in modules
Alternative becomes a synonym of Provides, it was already a synonym of Component, but was not availabe in modules.
1 parent 2963649 commit a8ef2c6

4 files changed

Lines changed: 308 additions & 19 deletions

File tree

restx-factory-testing/src/test/java/restx/factory/alternative/AlternativeTest.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.Properties;
1111
import restx.factory.Factory;
1212
import restx.factory.Name;
13+
import restx.factory.alternative.components.TestAlternativesFromModule;
1314
import restx.factory.alternative.components.TestComponentInterface;
1415
import restx.factory.alternative.components.TestComponentNamed;
1516
import restx.factory.alternative.components.TestComponentSimple;
@@ -130,4 +131,52 @@ public void should_use_alternative_for_provided_named_component_using_same_name(
130131
component = factory.getComponent(Name.of(TestComponentsFromModule.SomeOtherInterface.class, "restx.test.component.productionNamed"));
131132
assertThat(component.mode()).isEqualTo("dev");
132133
}
134+
135+
/*
136+
This test uses an alternative defined in a module.
137+
*/
138+
@Test
139+
public void should_use_alternative_defined_in_modules() {
140+
Factory factory = Factory.builder().addFromServiceLoader().build();
141+
TestAlternativesFromModule.Calculation component = factory.getComponent(TestAlternativesFromModule.Calculation.class);
142+
assertThat(component.calculate(2, 3)).isEqualTo(5);
143+
144+
System.setProperty("restx.test.alternatives", "true");
145+
146+
factory = Factory.builder().addFromServiceLoader().build();
147+
component = factory.getComponent(TestAlternativesFromModule.Calculation.class);
148+
assertThat(component.calculate(2, 3)).isEqualTo(6);
149+
}
150+
151+
/*
152+
This test uses an alternative defined in a module, and the referenced component use a Named annotation
153+
*/
154+
@Test
155+
public void should_use_alternative_defined_in_modules_for_named_components() {
156+
Factory factory = Factory.builder().addFromServiceLoader().build();
157+
TestAlternativesFromModule.Flag component = factory.getComponent(TestAlternativesFromModule.Flag.class);
158+
assertThat(component.value()).isEqualTo(true);
159+
160+
System.setProperty("restx.test.alternatives", "true");
161+
162+
factory = Factory.builder().addFromServiceLoader().build();
163+
component = factory.getComponent(TestAlternativesFromModule.Flag.class);
164+
assertThat(component.value()).isEqualTo(false);
165+
}
166+
167+
/*
168+
This test defines two alternatives, with different priorities, check that the higher is used.
169+
*/
170+
@Test
171+
public void should_use_alternative_with_higher_priority() {
172+
Factory factory = Factory.builder().addFromServiceLoader().build();
173+
TestAlternativesFromModule.Priority component = factory.getComponent(TestAlternativesFromModule.Priority.class);
174+
assertThat(component.value()).isEqualTo(Integer.MAX_VALUE);
175+
176+
System.setProperty("restx.test.alternatives", "true");
177+
178+
factory = Factory.builder().addFromServiceLoader().build();
179+
component = factory.getComponent(TestAlternativesFromModule.Priority.class);
180+
assertThat(component.value()).isEqualTo(Integer.MIN_VALUE);
181+
}
133182
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package restx.factory.alternative.components;
2+
3+
import javax.inject.Named;
4+
import restx.factory.Alternative;
5+
import restx.factory.Component;
6+
import restx.factory.Module;
7+
import restx.factory.Provides;
8+
import restx.factory.When;
9+
10+
/**
11+
* @author apeyrard
12+
*/
13+
@Module
14+
public class TestAlternativesFromModule {
15+
16+
public static interface Calculation {
17+
int calculate(int a, int b);
18+
}
19+
20+
@Provides
21+
public Calculation addition() {
22+
return new Calculation() {
23+
@Override
24+
public int calculate(int a, int b) {
25+
return a + b;
26+
}
27+
};
28+
}
29+
30+
@Alternative(to = Calculation.class, named = "addition")
31+
@When(name = "restx.test.alternatives", value = "true")
32+
public Calculation multiplication() {
33+
return new Calculation() {
34+
@Override
35+
public int calculate(int a, int b) {
36+
return a * b;
37+
}
38+
};
39+
}
40+
41+
public static interface Flag {
42+
boolean value();
43+
}
44+
45+
@Provides
46+
@Named("SomeFlag")
47+
public Flag alwaysTrue() {
48+
return new Flag() {
49+
@Override
50+
public boolean value() {
51+
return true;
52+
}
53+
};
54+
}
55+
56+
@Alternative(to = Flag.class, named = "SomeFlag")
57+
@When(name = "restx.test.alternatives", value = "true")
58+
public Flag alwaysFalse() {
59+
return new Flag() {
60+
@Override
61+
public boolean value() {
62+
return false;
63+
}
64+
};
65+
}
66+
67+
public static interface Priority {
68+
int value();
69+
}
70+
71+
@Provides
72+
public Priority priority() {
73+
return new Priority() {
74+
@Override
75+
public int value() {
76+
return Integer.MAX_VALUE;
77+
}
78+
};
79+
}
80+
81+
@Alternative(to = Priority.class, named = "priority", priority = -2000)
82+
@When(name = "restx.test.alternatives", value = "true")
83+
public Priority nilPriority() {
84+
return new Priority() {
85+
@Override
86+
public int value() {
87+
return 0;
88+
}
89+
};
90+
}
91+
92+
93+
@Alternative(to = Priority.class, named = "priority", priority = -3000)
94+
@When(name = "restx.test.alternatives", value = "true")
95+
public Priority minPriority() {
96+
return new Priority() {
97+
@Override
98+
public int value() {
99+
return Integer.MIN_VALUE;
100+
}
101+
};
102+
}
103+
}

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

Lines changed: 106 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import com.samskivert.mustache.Template;
88
import restx.common.processor.RestxAbstractProcessor;
99
import restx.factory.*;
10-
import restx.factory.Name;
1110

1211
import javax.annotation.processing.RoundEnvironment;
1312
import javax.annotation.processing.SupportedAnnotationTypes;
@@ -85,24 +84,61 @@ private void processModules(RoundEnvironment roundEnv) throws IOException {
8584

8685
ModuleClass module = new ModuleClass(typeElem.getQualifiedName().toString(), typeElem, mod.priority());
8786
for (Element element : typeElem.getEnclosedElements()) {
87+
// look for Provides or Alternative elements
8888
Provides provides = element.getAnnotation(Provides.class);
89+
Alternative alternative = element.getAnnotation(Alternative.class);
8990
if (element instanceof ExecutableElement
90-
&& element.getKind() == ElementKind.METHOD
91-
&& provides != null) {
92-
ExecutableElement exec = (ExecutableElement) element;
93-
94-
ProviderMethod m = new ProviderMethod(
95-
exec.getReturnType().toString(),
96-
exec.getSimpleName().toString(),
97-
provides.priority() == 0 ? mod.priority() : provides.priority(),
98-
getInjectionName(exec.getAnnotation(Named.class)),
99-
exec);
100-
101-
buildInjectableParams(exec, m.parameters);
102-
103-
buildCheckedExceptions(exec, m.exceptions);
104-
105-
module.providerMethods.add(m);
91+
&& 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);
101+
102+
buildInjectableParams(exec, m.parameters);
103+
104+
buildCheckedExceptions(exec, m.exceptions);
105+
106+
module.providerMethods.add(m);
107+
} else if (alternative != null) {
108+
ExecutableElement exec = (ExecutableElement) element;
109+
110+
When when = exec.getAnnotation(When.class);
111+
if (when == null) {
112+
error("an Alternative MUST be annotated with @When to tell when it must be activated", exec);
113+
continue;
114+
}
115+
116+
TypeElement alternativeTo = null;
117+
if (alternative != null) {
118+
try {
119+
alternative.to();
120+
} catch (MirroredTypeException mte) {
121+
alternativeTo = asTypeElement(mte.getTypeMirror());
122+
}
123+
}
124+
125+
AlternativeMethod m = new AlternativeMethod(
126+
exec.getReturnType().toString(),
127+
alternativeTo.getQualifiedName().toString(),
128+
alternativeTo.getSimpleName().toString(),
129+
exec.getSimpleName().toString(),
130+
alternative.priority(),
131+
!alternative.named().isEmpty() ? Optional.of(alternative.named()) : Optional.<String>absent(),
132+
when.name(),
133+
when.value(),
134+
exec);
135+
136+
buildInjectableParams(exec, m.parameters);
137+
138+
buildCheckedExceptions(exec, m.exceptions);
139+
140+
module.alternativeMethods.add(m);
141+
}
106142
}
107143
}
108144

@@ -166,6 +202,11 @@ private void processComponents(RoundEnvironment roundEnv) throws IOException {
166202
private void processAlternatives(RoundEnvironment roundEnv) throws IOException {
167203
for (Element elem : roundEnv.getElementsAnnotatedWith(Alternative.class)) {
168204
try {
205+
if (elem instanceof ExecutableElement && elem.getKind() == ElementKind.METHOD) {
206+
// skip this annotation, if it is in a module, it will been managed by processModules
207+
continue;
208+
}
209+
169210
if (!(elem instanceof TypeElement)) {
170211
error("annotating element " + elem + " of type " + elem.getKind().name()
171212
+ " with @Alternative is not supported", elem);
@@ -274,6 +315,7 @@ private Optional<String> getInjectionName(Named named) {
274315

275316
private void generateMachineFile(ModuleClass moduleClass) throws IOException {
276317
List<ImmutableMap<String, Object>> engines = Lists.newArrayList();
318+
List<ImmutableMap<String, Object>> alternativesEngines = Lists.newArrayList();
277319

278320
for (ProviderMethod method : moduleClass.providerMethods) {
279321
engines.add(ImmutableMap.<String, Object>builder()
@@ -289,20 +331,37 @@ private void generateMachineFile(ModuleClass moduleClass) throws IOException {
289331
.build());
290332
}
291333

334+
for (AlternativeMethod method : moduleClass.alternativeMethods) {
335+
alternativesEngines.add(ImmutableMap.<String, Object>builder()
336+
.put("componentType", method.componentType)
337+
.put("alternativeToComponentType", method.alternativeType)
338+
.put("alternativeToComponentName", method.injectionName.or(method.alternativeName))
339+
.put("alternativeToComponentSimpleName", method.alternativeName)
340+
.put("whenName", method.whenName)
341+
.put("whenValue", method.whenValue)
342+
.put("priority", method.priority)
343+
.put("queriesDeclarations", Joiner.on("\n").join(buildQueriesDeclarationsCode(method.parameters)))
344+
.put("methodName", method.methodName)
345+
.put("queries", Joiner.on(",\n").join(buildQueriesNames(method.parameters)))
346+
.put("parameters", Joiner.on(",\n").join(buildParamFromSatisfiedBomCode(method.parameters)))
347+
.put("exceptions", method.exceptions.isEmpty() ? false : Joiner.on("|").join(method.exceptions))
348+
.build());
349+
}
350+
292351
ImmutableMap<String, Object> ctx = ImmutableMap.<String, Object>builder()
293352
.put("package", moduleClass.pack)
294353
.put("machine", moduleClass.name + "FactoryMachine")
295354
.put("moduleFqcn", moduleClass.fqcn)
296355
.put("moduleType", moduleClass.name)
297356
.put("priority", moduleClass.priority)
298357
.put("engines", engines)
358+
.put("alternativesEngines", alternativesEngines)
299359
.build();
300360

301361
generateJavaClass(moduleClass.fqcn + "FactoryMachine", moduleMachineTpl, ctx,
302362
Collections.singleton(moduleClass.originatingElement));
303363
}
304364

305-
306365
private void generateMachineFile(ComponentClass componentClass, ComponentClass alternativeTo, When when) throws IOException {
307366
ImmutableMap<String, String> ctx = ImmutableMap.<String, String>builder()
308367
.put("package", componentClass.pack)
@@ -521,6 +580,7 @@ private static class ModuleClass {
521580
final String fqcn;
522581

523582
final List<ProviderMethod> providerMethods = Lists.newArrayList();
583+
final List<AlternativeMethod> alternativeMethods = Lists.newArrayList();
524584
final Element originatingElement;
525585
final String pack;
526586
final String name;
@@ -553,6 +613,34 @@ private static class ProviderMethod {
553613
}
554614
}
555615

616+
private static class AlternativeMethod {
617+
final Element originatingElement;
618+
final String componentType;
619+
final String alternativeType;
620+
final String alternativeName;
621+
final String methodName;
622+
final int priority;
623+
final Optional<String> injectionName;
624+
final String whenName;
625+
final String whenValue;
626+
final List<InjectableParameter> parameters = Lists.newArrayList();
627+
final List<String> exceptions = Lists.newArrayList();
628+
629+
AlternativeMethod(String componentType, String alternativeType,
630+
String alternativeName, String methodName, int priority, Optional<String> injectionName,
631+
String whenName, String whenValue, Element originatingElement) {
632+
this.componentType = componentType;
633+
this.alternativeType = alternativeType;
634+
this.alternativeName = alternativeName;
635+
this.methodName = methodName;
636+
this.priority = priority;
637+
this.injectionName = injectionName;
638+
this.whenName = whenName;
639+
this.whenValue = whenValue;
640+
this.originatingElement = originatingElement;
641+
}
642+
}
643+
556644

557645
private class ServicesDeclaration {
558646
private final Set<String> declaredServices = Sets.newHashSet();

0 commit comments

Comments
 (0)