Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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 a8ef2c6
Show file tree
Hide file tree
Showing 4 changed files with 308 additions and 19 deletions.
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
};
}
}
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -274,6 +315,7 @@ private Optional<String> getInjectionName(Named named) {

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()
Expand All @@ -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)
Expand Down Expand Up @@ -521,6 +580,7 @@ private static class ModuleClass {
final String fqcn;

final List<ProviderMethod> providerMethods = Lists.newArrayList();
final List<AlternativeMethod> alternativeMethods = Lists.newArrayList();
final Element originatingElement;
final String pack;
final String name;
Expand Down Expand Up @@ -553,6 +613,34 @@ private static class ProviderMethod {
}
}

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();
Expand Down

0 comments on commit a8ef2c6

Please sign in to comment.