Skip to content

Commit

Permalink
fix(template): relax template parameter constraint (#1535)
Browse files Browse the repository at this point in the history
* test CtTypeReference as Template parameter

* fix: TemplateParemeter proxy can be used for CtTypeReference
  • Loading branch information
pvojtechovsky authored and surli committed Sep 19, 2017
1 parent 494ba6f commit 770e484
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 21 deletions.
44 changes: 23 additions & 21 deletions src/main/java/spoon/template/Substitution.java
Original file line number Diff line number Diff line change
Expand Up @@ -671,29 +671,31 @@ private static <T> void checkTemplateContracts(CtClass<T> c) {
Parameter templateParamAnnotation = f.getAnnotation(Parameter.class);
if (templateParamAnnotation != null && !templateParamAnnotation.value().equals("")) {
String proxyName = templateParamAnnotation.value();
// contract: if value, then the field type must be String
if (!f.getType().equals(c.getFactory().Type().STRING)) {
throw new TemplateException("proxy template parameter must be typed as String " + f.getType().getQualifiedName());
}

// contract: the name of the template parameter must correspond to the name of the field
// as found, by Pavel, this is not good contract because it prevents easy refactoring of templates
// we remove it but keep th commented code in case somebody would come up with this bad idae
// if (!f.getSimpleName().equals("_" + f.getAnnotation(Parameter.class).value())) {
// throw new TemplateException("the field name of a proxy template parameter must be called _" + f.getSimpleName());
// }

// contract: if a proxy parameter is declared and named "x" (@Parameter("x")), then a type member named "x" must exist.
boolean found = false;
for (CtTypeMember member: c.getTypeMembers()) {
if (member.getSimpleName().equals(proxyName)) {
found = true;
// contract: if value, then the field type must be String or CtTypeReference
String fieldTypeQName = f.getType().getQualifiedName();
if (fieldTypeQName.equals(String.class.getName())) {
// contract: the name of the template parameter must correspond to the name of the field
// as found, by Pavel, this is not good contract because it prevents easy refactoring of templates
// we remove it but keep th commented code in case somebody would come up with this bad idae
// if (!f.getSimpleName().equals("_" + f.getAnnotation(Parameter.class).value())) {
// throw new TemplateException("the field name of a proxy template parameter must be called _" + f.getSimpleName());
// }

// contract: if a proxy parameter is declared and named "x" (@Parameter("x")), then a type member named "x" must exist.
boolean found = false;
for (CtTypeMember member: c.getTypeMembers()) {
if (member.getSimpleName().equals(proxyName)) {
found = true;
}
}
if (!found) {
throw new TemplateException("if a proxy parameter is declared and named \"" + proxyName + "\", then a type member named \"\" + proxyName + \"\" must exist.");
}
} else if (fieldTypeQName.equals(CtTypeReference.class.getName())) {
//OK it is CtTypeReference
} else {
throw new TemplateException("proxy template parameter must be typed as String or CtTypeReference, but it is " + fieldTypeQName);
}
if (!found) {
throw new TemplateException("if a proxy parameter is declared and named \"" + proxyName + "\", then a type member named \"\" + proxyName + \"\" must exist.");
}

}
}
}
Expand Down
28 changes: 28 additions & 0 deletions src/test/java/spoon/test/template/TemplateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import spoon.reflect.declaration.CtTypeMember;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtFieldReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.ModelConsistencyChecker;
import spoon.reflect.visitor.filter.NamedElementFilter;
import spoon.support.compiler.FileSystemFile;
Expand All @@ -44,6 +45,7 @@
import spoon.test.template.testclasses.SubStringTemplate;
import spoon.test.template.testclasses.SubstituteLiteralTemplate;
import spoon.test.template.testclasses.SubstituteRootTemplate;
import spoon.test.template.testclasses.TypeReferenceClassAccessTemplate;
import spoon.test.template.testclasses.bounds.CheckBound;
import spoon.test.template.testclasses.bounds.CheckBoundMatcher;
import spoon.test.template.testclasses.bounds.CheckBoundTemplate;
Expand All @@ -62,6 +64,7 @@
import spoon.test.template.testclasses.types.AClassModel;
import spoon.test.template.testclasses.types.AnEnumModel;
import spoon.test.template.testclasses.types.AnIfaceModel;
import spoon.testing.utils.ModelUtils;

import java.io.File;
import java.io.Serializable;
Expand Down Expand Up @@ -1014,4 +1017,29 @@ public void testAnotherFieldAccessNameSubstitution() throws Exception {
assertEquals("java.lang.System.out.println(((x) + (m_x)))", result.getAnonymousExecutables().get(0).getBody().getStatement(0).toString());
}
}

@Test
public void substituteTypeAccessReference() throws Exception {
//contract: the substitution of CtTypeAccess expression ignores actual type arguments if it have to
Launcher spoon = new Launcher();
spoon.addTemplateResource(new FileSystemFile("./src/test/java/spoon/test/template/testclasses/TypeReferenceClassAccessTemplate.java"));
String outputDir = "./target/spooned/test/template/testclasses";
spoon.setSourceOutputDirectory(outputDir);

spoon.buildModel();
Factory factory = spoon.getFactory();

//contract: String value is substituted in substring of literal, named element and reference
CtTypeReference<?> typeRef = factory.Type().createReference(TypeReferenceClassAccessTemplate.Example.class);
typeRef.addActualTypeArgument(factory.Type().DATE);

final CtClass<?> result = (CtClass<?>) new TypeReferenceClassAccessTemplate(typeRef).apply(factory.Class().create("spoon.test.template.TypeReferenceClassAccess"));
spoon.prettyprint();
ModelUtils.canBeBuilt(outputDir, 8);
CtMethod<?> method = result.getMethodsByName("someMethod").get(0);
assertEquals("spoon.test.template.TypeReferenceClassAccess.Example<java.util.Date>", method.getType().toString());
assertEquals("spoon.test.template.TypeReferenceClassAccess.Example<java.util.Date>", method.getParameters().get(0).getType().toString());
assertEquals("o = spoon.test.template.TypeReferenceClassAccess.Example.out", method.getBody().getStatement(0).toString());
assertEquals("spoon.test.template.TypeReferenceClassAccess.Example<java.util.Date> ret = new spoon.test.template.TypeReferenceClassAccess.Example<java.util.Date>()", method.getBody().getStatement(1).toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package spoon.test.template.testclasses;

import spoon.reflect.reference.CtTypeReference;
import spoon.template.ExtensionTemplate;
import spoon.template.Local;
import spoon.template.Parameter;

public class TypeReferenceClassAccessTemplate extends ExtensionTemplate {
Object o;

$Type$ someMethod($Type$ param) {
o = $Type$.out;
$Type$ ret = new $Type$();
return ret;
}

@Local
public TypeReferenceClassAccessTemplate(CtTypeReference<?> typeRef) {
this.typeRef = typeRef;
}

@Parameter("$Type$")
CtTypeReference<?> typeRef;

@Local
static class $Type$ {
static final String out = "";
static long currentTimeMillis(){
return 0;
}
}

public static class Example<T> {
static final String out = "";
static long currentTimeMillis(){
return 0;
}
}
}

0 comments on commit 770e484

Please sign in to comment.