Skip to content

Commit

Permalink
Fix bug Lazies & Providers injected through constructor or method
Browse files Browse the repository at this point in the history
  • Loading branch information
dlemures committed Aug 19, 2016
1 parent 276565f commit a78b0e9
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 30 deletions.
Expand Up @@ -214,7 +214,7 @@ protected boolean isValidInjectAnnotatedMethod(ExecutableElement methodElement)
}

protected boolean isValidInjectedType(VariableElement injectedTypeElement) {
if (!isValidInjectedKind(injectedTypeElement)) {
if (!isValidInjectedElementKind(injectedTypeElement)) {
return false;
}
if (isProviderOrLazy(injectedTypeElement) && !isValidProviderOrLazy(injectedTypeElement)) {
Expand All @@ -223,7 +223,7 @@ protected boolean isValidInjectedType(VariableElement injectedTypeElement) {
return true;
}

private boolean isValidInjectedKind(VariableElement injectedTypeElement) {
private boolean isValidInjectedElementKind(VariableElement injectedTypeElement) {
Element typeElement = typeUtils.asElement(injectedTypeElement.asType());
if (typeElement.getKind() != ElementKind.CLASS //
&& typeElement.getKind() != ElementKind.INTERFACE //
Expand All @@ -234,7 +234,9 @@ private boolean isValidInjectedKind(VariableElement injectedTypeElement) {
Element enclosingElement = injectedTypeElement.getEnclosingElement();
if (enclosingElement instanceof TypeElement) {
error(injectedTypeElement, "Field %s#%s is of type %s which is not supported by Toothpick.",
((TypeElement) enclosingElement).getQualifiedName(), injectedTypeElement.getSimpleName(), typeElement);
((TypeElement) enclosingElement).getQualifiedName(),
injectedTypeElement.getSimpleName(),
typeElement);
} else {
Element methodOrConstructorElement = enclosingElement;
enclosingElement = enclosingElement.getEnclosingElement();
Expand All @@ -256,13 +258,16 @@ private boolean isValidProviderOrLazy(Element element) {
if (declaredType.getTypeArguments().isEmpty()) {
Element enclosingElement = element.getEnclosingElement();
if (enclosingElement instanceof TypeElement) {
error(element, "Field %s#%s is not a valid Lazy or Provider.",
((TypeElement) enclosingElement).getQualifiedName(), element.getSimpleName());
error(element, "Field %s#%s is not a valid %s.",
((TypeElement) enclosingElement).getQualifiedName(),
element.getSimpleName(),
declaredType);
} else {
error(element, "Parameter %s in method/constructor %s#%s is not a valid Lazy or Provider.",
error(element, "Parameter %s in method/constructor %s#%s is not a valid %s.",
element.getSimpleName(), //
((TypeElement) enclosingElement.getEnclosingElement()).getQualifiedName(), //
enclosingElement.getSimpleName());
enclosingElement.getSimpleName(),
declaredType);
}
return false;
}
Expand Down
Expand Up @@ -2,6 +2,8 @@

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import toothpick.compiler.common.generators.targets.ParamInjectionTarget;
Expand All @@ -20,44 +22,53 @@ public abstract class CodeGenerator {
*/
public abstract String brewJava();

public CodeBlock getInvokeScopeGetMethodWithNameCodeBlock(ParamInjectionTarget memberInjectionTarget) {
protected CodeBlock getInvokeScopeGetMethodWithNameCodeBlock(ParamInjectionTarget paramInjectionTarget) {
final String scopeGetMethodName;
final String injectionName;
if (memberInjectionTarget.name == null) {
if (paramInjectionTarget.name == null) {
injectionName = "";
} else {
injectionName = ", \"" + memberInjectionTarget.name.toString() + "\"";
injectionName = ", \"" + paramInjectionTarget.name.toString() + "\"";
}
final ClassName className;
switch (memberInjectionTarget.kind) {
switch (paramInjectionTarget.kind) {
case INSTANCE:
scopeGetMethodName = "getInstance";
className = ClassName.get(memberInjectionTarget.memberClass);
className = ClassName.get(paramInjectionTarget.memberClass);
break;
case PROVIDER:
scopeGetMethodName = "getProvider";
className = ClassName.get(memberInjectionTarget.kindParamClass);
className = ClassName.get(paramInjectionTarget.kindParamClass);
break;
case LAZY:
scopeGetMethodName = "getLazy";
className = ClassName.get(memberInjectionTarget.kindParamClass);
className = ClassName.get(paramInjectionTarget.kindParamClass);
break;
default:
throw new IllegalStateException("The kind can't be null.");
}
return CodeBlock.builder().add("$L($T.class$L)", scopeGetMethodName, className, injectionName).build();
}

protected TypeName getParamType(ParamInjectionTarget paramInjectionTarget) {
if (paramInjectionTarget.kind == ParamInjectionTarget.Kind.INSTANCE) {
return TypeName.get(paramInjectionTarget.memberClass.asType());
} else {
return ParameterizedTypeName.get(ClassName.get(paramInjectionTarget.memberClass),
ClassName.get(paramInjectionTarget.kindParamClass));
}
}

/**
* @returns the FQN of the code generated by this {@link CodeGenerator}.
*/
public abstract String getFqcn();

public static String getGeneratedFQNClassName(TypeElement typeElement) {
protected static String getGeneratedFQNClassName(TypeElement typeElement) {
return getGeneratedPackageName(typeElement) + "." + getGeneratedSimpleClassName(typeElement);
}

public static String getGeneratedSimpleClassName(TypeElement typeElement) {
protected static String getGeneratedSimpleClassName(TypeElement typeElement) {
String result = typeElement.getSimpleName().toString();
//deals with inner classes
while (typeElement.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
Expand All @@ -67,7 +78,7 @@ public static String getGeneratedSimpleClassName(TypeElement typeElement) {
return result;
}

public static String getSimpleClassName(ClassName className) {
protected static String getSimpleClassName(ClassName className) {
String result = "";
java.util.List<String> simpleNames = className.simpleNames();
for (int i = 0; i < simpleNames.size(); i++) {
Expand All @@ -80,7 +91,7 @@ public static String getSimpleClassName(ClassName className) {
return result;
}

public static String getGeneratedPackageName(TypeElement typeElement) {
protected static String getGeneratedPackageName(TypeElement typeElement) {
//deals with inner classes
while (typeElement.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
typeElement = (TypeElement) typeElement.getEnclosingElement();
Expand Down
Expand Up @@ -95,8 +95,7 @@ private void emitCreateInstance(TypeSpec.Builder builder) {
for (ParamInjectionTarget paramInjectionTarget : constructorInjectionTarget.parameters) {
CodeBlock invokeScopeGetMethodWithNameCodeBlock = getInvokeScopeGetMethodWithNameCodeBlock(paramInjectionTarget);
String paramName = "param" + counter++;
TypeName paramType = TypeName.get(paramInjectionTarget.memberClass.asType());
createInstanceBuilder.addCode("$T $L = scope.", paramType, paramName);
createInstanceBuilder.addCode("$T $L = scope.", getParamType(paramInjectionTarget), paramName);
createInstanceBuilder.addCode(invokeScopeGetMethodWithNameCodeBlock);
createInstanceBuilder.addCode(";");
createInstanceBuilder.addCode(LINE_SEPARATOR);
Expand Down
Expand Up @@ -107,8 +107,7 @@ private void emitInjectMethods(List<MethodInjectionTarget> methodInjectionTarget
for (ParamInjectionTarget paramInjectionTarget : methodInjectionTarget.parameters) {
CodeBlock invokeScopeGetMethodWithNameCodeBlock = getInvokeScopeGetMethodWithNameCodeBlock(paramInjectionTarget);
String paramName = "param" + counter++;
TypeName paramType = TypeName.get(paramInjectionTarget.memberClass.asType());
injectMethodBuilder.addCode("$T $L = scope.", paramType, paramName);
injectMethodBuilder.addCode("$T $L = scope.", getParamType(paramInjectionTarget), paramName);
injectMethodBuilder.addCode(invokeScopeGetMethodWithNameCodeBlock);
injectMethodBuilder.addCode(";");
injectMethodBuilder.addCode(LINE_SEPARATOR);
Expand Down
Expand Up @@ -305,6 +305,146 @@ public void testNonEmptyConstructor() {
.generatesSources(expectedSource);
}

@Test
public void testNonEmptyConstructorWithLazy() {
JavaFileObject source = JavaFileObjects.forSourceString("test.TestNonEmptyConstructor", Joiner.on('\n').join(//
"package test;", //
"import javax.inject.Inject;", //
"import toothpick.Lazy;", //
"public class TestNonEmptyConstructor {", //
" @Inject public TestNonEmptyConstructor(Lazy<String> str, Integer n) {}", //
"}" //
));

JavaFileObject expectedSource = JavaFileObjects.forSourceString("test/TestNonEmptyConstructor$$Factory", Joiner.on('\n').join(//
"package test;", //
"import java.lang.Integer;", //
"import java.lang.Override;", //
"import java.lang.String;", //
"import toothpick.Factory;", //
"import toothpick.Lazy;", //
"import toothpick.Scope;", //
"", //
"public final class TestNonEmptyConstructor$$Factory implements Factory<TestNonEmptyConstructor> {", //
" @Override", //
" public TestNonEmptyConstructor createInstance(Scope scope) {", //
" scope = getTargetScope(scope);", //
" Lazy<String> param1 = scope.getLazy(String.class);", //
" Integer param2 = scope.getInstance(Integer.class);", //
" TestNonEmptyConstructor testNonEmptyConstructor = new TestNonEmptyConstructor(param1, param2);", //
" return testNonEmptyConstructor;", //
" }", //
" @Override", //
" public Scope getTargetScope(Scope scope) {", //
" return scope;", //
" }", //
" @Override", //
" public boolean hasScopeAnnotation() {", //
" return false;", //
" }", //
" @Override", //
" public boolean hasScopeInstancesAnnotation() {", //
" return false;", //
" }", //
"}" //
));

assert_().about(javaSource())
.that(source)
.processedWith(ProcessorTestUtilities.factoryProcessors())
.compilesWithoutError()
.and()
.generatesSources(expectedSource);
}

@Test
public void testNonEmptyConstructorWithProvider() {
JavaFileObject source = JavaFileObjects.forSourceString("test.TestNonEmptyConstructor", Joiner.on('\n').join(//
"package test;", //
"import javax.inject.Inject;", //
"import javax.inject.Provider;", //
"public class TestNonEmptyConstructor {", //
" @Inject public TestNonEmptyConstructor(Provider<String> str, Integer n) {}", //
"}" //
));

JavaFileObject expectedSource = JavaFileObjects.forSourceString("test/TestNonEmptyConstructor$$Factory", Joiner.on('\n').join(//
"package test;", //
"import java.lang.Integer;", //
"import java.lang.Override;", //
"import java.lang.String;", //
"import javax.inject.Provider;", //
"import toothpick.Factory;", //
"import toothpick.Scope;", //
"", //
"public final class TestNonEmptyConstructor$$Factory implements Factory<TestNonEmptyConstructor> {", //
" @Override", //
" public TestNonEmptyConstructor createInstance(Scope scope) {", //
" scope = getTargetScope(scope);", //
" Provider<String> param1 = scope.getProvider(String.class);", //
" Integer param2 = scope.getInstance(Integer.class);", //
" TestNonEmptyConstructor testNonEmptyConstructor = new TestNonEmptyConstructor(param1, param2);", //
" return testNonEmptyConstructor;", //
" }", //
" @Override", //
" public Scope getTargetScope(Scope scope) {", //
" return scope;", //
" }", //
" @Override", //
" public boolean hasScopeAnnotation() {", //
" return false;", //
" }", //
" @Override", //
" public boolean hasScopeInstancesAnnotation() {", //
" return false;", //
" }", //
"}" //
));

assert_().about(javaSource())
.that(source)
.processedWith(ProcessorTestUtilities.factoryProcessors())
.compilesWithoutError()
.and()
.generatesSources(expectedSource);
}

@Test
public void testNonEmptyConstructor_shouldFail_whenContainsInvalidLazyParameter() {
JavaFileObject source = JavaFileObjects.forSourceString("test.TestNonEmptyConstructor", Joiner.on('\n').join(//
"package test;", //
"import javax.inject.Inject;", //
"import toothpick.Lazy;", //
"public class TestNonEmptyConstructor {", //
" @Inject public TestNonEmptyConstructor(Lazy lazy, Integer n) {}", //
"}" //
));

assert_().about(javaSource())
.that(source)
.processedWith(ProcessorTestUtilities.factoryProcessors())
.failsToCompile()
.withErrorContaining("Parameter lazy in method/constructor test.TestNonEmptyConstructor#<init> is not a valid toothpick.Lazy.");
}

@Test
public void testNonEmptyConstructor_shouldFail_whenContainsInvalidProviderParameter() {
JavaFileObject source = JavaFileObjects.forSourceString("test.TestNonEmptyConstructor", Joiner.on('\n').join(//
"package test;", //
"import javax.inject.Inject;", //
"import javax.inject.Provider;", //
"public class TestNonEmptyConstructor {", //
" @Inject public TestNonEmptyConstructor(Provider provider, Integer n) {}", //
"}" //
));

assert_().about(javaSource())
.that(source)
.processedWith(ProcessorTestUtilities.factoryProcessors())
.failsToCompile()
.withErrorContaining("Parameter provider in method/constructor test.TestNonEmptyConstructor#<init> is not a valid javax.inject.Provider.");
}

@Test
public void testAbstractClassWithInjectedConstructor() {
JavaFileObject source = JavaFileObjects.forSourceString("test.TestInvalidClassConstructor", Joiner.on('\n').join(//
Expand Down
Expand Up @@ -111,7 +111,7 @@ public void testRelaxedFactoryCreationForInjectedField_shouldFail_WhenFieldIsInv
.that(source)
.processedWith(ProcessorTestUtilities.factoryAndMemberInjectorProcessors())
.failsToCompile()
.withErrorContaining("Field test.TestRelaxedFactoryCreationForInjectField#foo is not a valid Lazy or Provider.");
.withErrorContaining("Field test.TestRelaxedFactoryCreationForInjectField#foo is not a valid toothpick.Lazy.");
}

@Test
Expand All @@ -129,7 +129,7 @@ public void testRelaxedFactoryCreationForInjectedField_shouldFail_WhenFieldIsInv
.that(source)
.processedWith(ProcessorTestUtilities.factoryAndMemberInjectorProcessors())
.failsToCompile()
.withErrorContaining("Field test.TestRelaxedFactoryCreationForInjectField#foo is not a valid Lazy or Provider.");
.withErrorContaining("Field test.TestRelaxedFactoryCreationForInjectField#foo is not a valid javax.inject.Provider.");
}

@Test
Expand Down
Expand Up @@ -111,7 +111,7 @@ public void testRelaxedFactoryCreationForInjectedMethod_shouldFail_WhenMethodPar
.that(source)
.processedWith(ProcessorTestUtilities.factoryAndMemberInjectorProcessors())
.failsToCompile()
.withErrorContaining("Parameter foo in method/constructor test.TestRelaxedFactoryCreationForInjectMethod#m is not a valid Lazy or Provider.");
.withErrorContaining("Parameter foo in method/constructor test.TestRelaxedFactoryCreationForInjectMethod#m is not a valid toothpick.Lazy.");
}

@Test
Expand All @@ -129,7 +129,7 @@ public void testRelaxedFactoryCreationForInjectedMethod_shouldFail_WhenMethodPar
.that(source)
.processedWith(ProcessorTestUtilities.factoryAndMemberInjectorProcessors())
.failsToCompile()
.withErrorContaining("Parameter foo in method/constructor test.TestRelaxedFactoryCreationForInjectMethod#m is not a valid Lazy or Provider.");
.withErrorContaining("Parameter foo in method/constructor test.TestRelaxedFactoryCreationForInjectMethod#m is not a valid javax.inject.Provider.");
}

@Test
Expand Down
Expand Up @@ -466,7 +466,7 @@ public void testFieldInjection_shouldFail_WhenFieldIsInvalidLazy() {
.that(source)
.processedWith(memberInjectorProcessors())
.failsToCompile()
.withErrorContaining("Field test.TestFieldInjection#foo is not a valid Lazy or Provider.");
.withErrorContaining("Field test.TestFieldInjection#foo is not a valid toothpick.Lazy.");
}

@Test
Expand All @@ -486,7 +486,7 @@ public void testFieldInjection_shouldFail_WhenFieldIsInvalidProvider() {
.that(source)
.processedWith(memberInjectorProcessors())
.failsToCompile()
.withErrorContaining("Field test.TestFieldInjection#foo is not a valid Lazy or Provider.");
.withErrorContaining("Field test.TestFieldInjection#foo is not a valid javax.inject.Provider.");
}

@Test
Expand Down

0 comments on commit a78b0e9

Please sign in to comment.