Permalink
Browse files

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.
  • Loading branch information...
a-peyrard committed Jan 27, 2015
1 parent 2963649 commit a8ef2c6d1dbb440af961e7f78ccd4e8b93e9f655
@@ -10,6 +10,7 @@
import java.util.Properties;
import restx.factory.Factory;
import restx.factory.Name;
+import restx.factory.alternative.components.TestAlternativesFromModule;
import restx.factory.alternative.components.TestComponentInterface;
import restx.factory.alternative.components.TestComponentNamed;
import restx.factory.alternative.components.TestComponentSimple;
@@ -130,4 +131,52 @@ public void should_use_alternative_for_provided_named_component_using_same_name(
component = factory.getComponent(Name.of(TestComponentsFromModule.SomeOtherInterface.class, "restx.test.component.productionNamed"));
assertThat(component.mode()).isEqualTo("dev");
}
+
+ /*
+ This test uses an alternative defined in a module.
+ */
+ @Test
+ public void should_use_alternative_defined_in_modules() {
+ Factory factory = Factory.builder().addFromServiceLoader().build();
+ TestAlternativesFromModule.Calculation component = factory.getComponent(TestAlternativesFromModule.Calculation.class);
+ assertThat(component.calculate(2, 3)).isEqualTo(5);
+
+ System.setProperty("restx.test.alternatives", "true");
+
+ factory = Factory.builder().addFromServiceLoader().build();
+ component = factory.getComponent(TestAlternativesFromModule.Calculation.class);
+ assertThat(component.calculate(2, 3)).isEqualTo(6);
+ }
+
+ /*
+ This test uses an alternative defined in a module, and the referenced component use a Named annotation
+ */
+ @Test
+ public void should_use_alternative_defined_in_modules_for_named_components() {
+ Factory factory = Factory.builder().addFromServiceLoader().build();
+ TestAlternativesFromModule.Flag component = factory.getComponent(TestAlternativesFromModule.Flag.class);
+ assertThat(component.value()).isEqualTo(true);
+
+ System.setProperty("restx.test.alternatives", "true");
+
+ factory = Factory.builder().addFromServiceLoader().build();
+ component = factory.getComponent(TestAlternativesFromModule.Flag.class);
+ assertThat(component.value()).isEqualTo(false);
+ }
+
+ /*
+ This test defines two alternatives, with different priorities, check that the higher is used.
+ */
+ @Test
+ public void should_use_alternative_with_higher_priority() {
+ Factory factory = Factory.builder().addFromServiceLoader().build();
+ TestAlternativesFromModule.Priority component = factory.getComponent(TestAlternativesFromModule.Priority.class);
+ assertThat(component.value()).isEqualTo(Integer.MAX_VALUE);
+
+ System.setProperty("restx.test.alternatives", "true");
+
+ factory = Factory.builder().addFromServiceLoader().build();
+ component = factory.getComponent(TestAlternativesFromModule.Priority.class);
+ assertThat(component.value()).isEqualTo(Integer.MIN_VALUE);
+ }
}
@@ -0,0 +1,103 @@
+package restx.factory.alternative.components;
+
+import javax.inject.Named;
+import restx.factory.Alternative;
+import restx.factory.Component;
+import restx.factory.Module;
+import restx.factory.Provides;
+import restx.factory.When;
+
+/**
+ * @author apeyrard
+ */
+@Module
+public class TestAlternativesFromModule {
+
+ public static interface Calculation {
+ int calculate(int a, int b);
+ }
+
+ @Provides
+ public Calculation addition() {
+ return new Calculation() {
+ @Override
+ public int calculate(int a, int b) {
+ return a + b;
+ }
+ };
+ }
+
+ @Alternative(to = Calculation.class, named = "addition")
+ @When(name = "restx.test.alternatives", value = "true")
+ public Calculation multiplication() {
+ return new Calculation() {
+ @Override
+ public int calculate(int a, int b) {
+ return a * b;
+ }
+ };
+ }
+
+ public static interface Flag {
+ boolean value();
+ }
+
+ @Provides
+ @Named("SomeFlag")
+ public Flag alwaysTrue() {
+ return new Flag() {
+ @Override
+ public boolean value() {
+ return true;
+ }
+ };
+ }
+
+ @Alternative(to = Flag.class, named = "SomeFlag")
+ @When(name = "restx.test.alternatives", value = "true")
+ public Flag alwaysFalse() {
+ return new Flag() {
+ @Override
+ public boolean value() {
+ return false;
+ }
+ };
+ }
+
+ public static interface Priority {
+ int value();
+ }
+
+ @Provides
+ public Priority priority() {
+ return new Priority() {
+ @Override
+ public int value() {
+ return Integer.MAX_VALUE;
+ }
+ };
+ }
+
+ @Alternative(to = Priority.class, named = "priority", priority = -2000)
+ @When(name = "restx.test.alternatives", value = "true")
+ public Priority nilPriority() {
+ return new Priority() {
+ @Override
+ public int value() {
+ return 0;
+ }
+ };
+ }
+
+
+ @Alternative(to = Priority.class, named = "priority", priority = -3000)
+ @When(name = "restx.test.alternatives", value = "true")
+ public Priority minPriority() {
+ return new Priority() {
+ @Override
+ public int value() {
+ return Integer.MIN_VALUE;
+ }
+ };
+ }
+}
@@ -7,7 +7,6 @@
import com.samskivert.mustache.Template;
import restx.common.processor.RestxAbstractProcessor;
import restx.factory.*;
-import restx.factory.Name;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
@@ -85,24 +84,61 @@ 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
- && 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);
-
- buildCheckedExceptions(exec, m.exceptions);
-
- module.providerMethods.add(m);
+ && 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);
+
+ buildCheckedExceptions(exec, m.exceptions);
+
+ module.providerMethods.add(m);
+ } else if (alternative != null) {
+ ExecutableElement exec = (ExecutableElement) element;
+
+ When when = exec.getAnnotation(When.class);
+ 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());
+ }
+ }
+
+ AlternativeMethod m = new AlternativeMethod(
+ exec.getReturnType().toString(),
+ alternativeTo.getQualifiedName().toString(),
+ alternativeTo.getSimpleName().toString(),
+ exec.getSimpleName().toString(),
+ alternative.priority(),
+ !alternative.named().isEmpty() ? Optional.of(alternative.named()) : Optional.<String>absent(),
+ when.name(),
+ when.value(),
+ exec);
+
+ buildInjectableParams(exec, m.parameters);
+
+ buildCheckedExceptions(exec, m.exceptions);
+
+ module.alternativeMethods.add(m);
+ }
}
}
@@ -166,6 +202,11 @@ private void processComponents(RoundEnvironment roundEnv) throws IOException {
private void processAlternatives(RoundEnvironment roundEnv) throws IOException {
for (Element elem : roundEnv.getElementsAnnotatedWith(Alternative.class)) {
try {
+ if (elem instanceof ExecutableElement && elem.getKind() == ElementKind.METHOD) {
+ // skip this annotation, if it is in a module, it will been managed by processModules
+ continue;
+ }
+
if (!(elem instanceof TypeElement)) {
error("annotating element " + elem + " of type " + elem.getKind().name()
+ " with @Alternative is not supported", elem);
@@ -274,6 +315,7 @@ private void buildInjectableParams(ExecutableElement executableElement, List<Inj
private void generateMachineFile(ModuleClass moduleClass) throws IOException {
List<ImmutableMap<String, Object>> engines = Lists.newArrayList();
+ List<ImmutableMap<String, Object>> alternativesEngines = Lists.newArrayList();
for (ProviderMethod method : moduleClass.providerMethods) {
engines.add(ImmutableMap.<String, Object>builder()
@@ -289,20 +331,37 @@ private void generateMachineFile(ModuleClass moduleClass) throws IOException {
.build());
}
+ for (AlternativeMethod method : moduleClass.alternativeMethods) {
+ alternativesEngines.add(ImmutableMap.<String, Object>builder()
+ .put("componentType", method.componentType)
+ .put("alternativeToComponentType", method.alternativeType)
+ .put("alternativeToComponentName", method.injectionName.or(method.alternativeName))
+ .put("alternativeToComponentSimpleName", method.alternativeName)
+ .put("whenName", method.whenName)
+ .put("whenValue", method.whenValue)
+ .put("priority", method.priority)
+ .put("queriesDeclarations", Joiner.on("\n").join(buildQueriesDeclarationsCode(method.parameters)))
+ .put("methodName", method.methodName)
+ .put("queries", Joiner.on(",\n").join(buildQueriesNames(method.parameters)))
+ .put("parameters", Joiner.on(",\n").join(buildParamFromSatisfiedBomCode(method.parameters)))
+ .put("exceptions", method.exceptions.isEmpty() ? false : Joiner.on("|").join(method.exceptions))
+ .build());
+ }
+
ImmutableMap<String, Object> ctx = ImmutableMap.<String, Object>builder()
.put("package", moduleClass.pack)
.put("machine", moduleClass.name + "FactoryMachine")
.put("moduleFqcn", moduleClass.fqcn)
.put("moduleType", moduleClass.name)
.put("priority", moduleClass.priority)
.put("engines", engines)
+ .put("alternativesEngines", alternativesEngines)
.build();
generateJavaClass(moduleClass.fqcn + "FactoryMachine", moduleMachineTpl, ctx,
Collections.singleton(moduleClass.originatingElement));
}
-
private void generateMachineFile(ComponentClass componentClass, ComponentClass alternativeTo, When when) throws IOException {
ImmutableMap<String, String> ctx = ImmutableMap.<String, String>builder()
.put("package", componentClass.pack)
@@ -521,6 +580,7 @@ private boolean isMultiType(TypeMirror type) {
final String fqcn;
final List<ProviderMethod> providerMethods = Lists.newArrayList();
+ final List<AlternativeMethod> alternativeMethods = Lists.newArrayList();
final Element originatingElement;
final String pack;
final String name;
@@ -553,6 +613,34 @@ private boolean isMultiType(TypeMirror type) {
}
}
+ private static class AlternativeMethod {
+ final Element originatingElement;
+ final String componentType;
+ final String alternativeType;
+ final String alternativeName;
+ final String methodName;
+ final int priority;
+ final Optional<String> injectionName;
+ final String whenName;
+ final String whenValue;
+ final List<InjectableParameter> parameters = Lists.newArrayList();
+ final List<String> exceptions = Lists.newArrayList();
+
+ AlternativeMethod(String componentType, String alternativeType,
+ String alternativeName, String methodName, int priority, Optional<String> injectionName,
+ String whenName, String whenValue, Element originatingElement) {
+ this.componentType = componentType;
+ this.alternativeType = alternativeType;
+ this.alternativeName = alternativeName;
+ this.methodName = methodName;
+ this.priority = priority;
+ this.injectionName = injectionName;
+ this.whenName = whenName;
+ this.whenValue = whenValue;
+ this.originatingElement = originatingElement;
+ }
+ }
+
private class ServicesDeclaration {
private final Set<String> declaredServices = Sets.newHashSet();
Oops, something went wrong.

0 comments on commit a8ef2c6

Please sign in to comment.