Skip to content

Commit

Permalink
test: add tests and doc for template engine
Browse files Browse the repository at this point in the history
  • Loading branch information
monperrus committed Dec 24, 2016
1 parent 7a55cd2 commit 5353879
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 199 deletions.
39 changes: 37 additions & 2 deletions doc/template_definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Using method `apply()` enables to get a new block.
Using method `apply()` enables to get a new expression. The core template method must be called `expression` and only contain a return with the expression to be templated.

#### Subclassing `ExtensionTemplate`
Using method `apply()` enables to get a new class where:
Using method `apply()` enables to get a new class where all possible templating in all methods. In addition, the following class level transformations are made:

1) methods and field of the templates are injected in the target class

Expand Down Expand Up @@ -126,7 +126,7 @@ Substitution.insertAll(aCtClass, t);

```

3) method parameters
3) method parameters are replaced

```java
class ATemplate3 extends ExtensionTemplate {
Expand Down Expand Up @@ -171,6 +171,41 @@ public class TryCatchOutOfBoundTemplate extends BlockTemplate {
}
```

Similarly, templated invocations require to declare a template parameter

```java
@Parameter
CtInvocation invocation;
```
and then all `invocation.S()` will be replaced by the actual invocation.

#### Inlining foreach expressions

Foreach expressions can be inlined. They have to be declared as follows:
```java
@Parameter
CtExpression[] intValues;
...
template.intValues = new CtExpression[2];
template.intValues[0] = factory.Code().createLiteral(0);
template.intValues[1] = factory.Code().createLiteral(1);
```

and then,

```java
for(Object x : intValues) {
System.out.println(x);
}
```
is transformed into:

```java
{
java.lang.System.out.println(0);
java.lang.System.out.println(1);
}
```
#### Literal template Parameters

For literals, Spoon provides developers with *literal template parameters*. When the parameter is known to
Expand Down
210 changes: 15 additions & 195 deletions src/main/java/spoon/support/template/SubstitutionVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import spoon.SpoonException;
Expand All @@ -32,47 +31,37 @@
import spoon.reflect.code.CtFieldWrite;
import spoon.reflect.code.CtForEach;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtLiteral;
import spoon.reflect.code.CtReturn;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtSuperAccess;
import spoon.reflect.code.CtThisAccess;
import spoon.reflect.code.CtVariableAccess;
import spoon.reflect.code.CtVariableRead;
import spoon.reflect.code.CtVariableWrite;
import spoon.reflect.declaration.CtAnnotation;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtConstructor;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtNamedElement;
import spoon.reflect.declaration.CtParameter;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtTypeMember;
import spoon.reflect.declaration.CtTypedElement;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtArrayTypeReference;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtFieldReference;
import spoon.reflect.reference.CtReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtInheritanceScanner;
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.Query;
import spoon.reflect.visitor.filter.VariableAccessFilter;
import spoon.template.Local;
import spoon.template.Parameter;
import spoon.template.Template;
import spoon.template.TemplateParameter;

class SkipException extends SpoonException {
class DoNotFurtherTemplateThisElement extends SpoonException {
private static final long serialVersionUID = 1L;

Object skipped;

SkipException(Object e) {
DoNotFurtherTemplateThisElement(Object e) {
super("skipping " + e.toString());
skipped = e;
}
Expand Down Expand Up @@ -171,57 +160,7 @@ private String substituteInDocComment(String docComment) {
return result;
}

/**
* Removes all the elements that are part of the template definitions.
*
* @see Template
* @see TemplateParameter
* @see Local
* @see Parameter
*/
@Override
public <T> void visitCtClass(CtClass<T> ctClass) {
ctClass.removeSuperInterface(factory.Type().createReference(Template.class));
for (CtMethod<?> m : new ArrayList<>(ctClass.getMethods())) { // copy is required for removing while iterating
if (m.getAnnotation(Local.class) != null) {
ctClass.removeMethod(m);
}
}
for (Iterator<CtConstructor<T>> it = ctClass.getConstructors().iterator(); it.hasNext();) {
CtConstructor<?> c = it.next();
if (c.getAnnotation(Local.class) != null) {
it.remove();
}
}
for (CtTypeMember m : new ArrayList<>(ctClass.getTypeMembers())) { // copy is required for removing while iterating
if (!(m instanceof CtField)) {
continue;
}
CtField field = (CtField) m;
if ((field.getAnnotation(Local.class) != null) || Parameters.isParameterSource(field.getReference())) {
ctClass.removeField(field);
continue;
}
// replace fields parameters
String name = field.getSimpleName();
for (String pname : parameterNames) {
if (name.equals(pname)) {
Object value = Parameters.getValue(template, pname, null);
int i = ctClass.getTypeMembers().indexOf(field);
if (value instanceof List) {
List<?> list = (List<?>) value;
for (Object f : list) {
CtField<?> f2 = ((CtField<?>) f).clone();
ctClass.addTypeMemberAt(i++, f2);
}
ctClass.removeTypeMember(field);
}
}
}
}
super.visitCtClass(ctClass);
}

/** statically inline foreach */
@Override
public void visitCtForEach(CtForEach foreach) {
if (foreach.getExpression() instanceof CtFieldAccess) {
Expand All @@ -235,10 +174,13 @@ public void visitCtForEach(CtForEach foreach) {
for (CtVariableAccess<?> va : Query.getElements(b, new VariableAccessFilter<>(foreach.getVariable().getReference()))) {
va.replace((CtExpression) element);
}
if (b instanceof CtBlock && ((CtBlock) b).getStatements().size() == 1) {
b = ((CtBlock) b).getStatement(0);
}
l.addStatement(b);
}
foreach.replace(l);
throw new SkipException(foreach);
throw new DoNotFurtherTemplateThisElement(foreach);
}
}
super.visitCtForEach(foreach);
Expand All @@ -263,7 +205,7 @@ private <T> void visitFieldAccess(CtFieldAccess<T> fieldAccess) {
Object[] value = (Object[]) Parameters.getValue(template, ref.getSimpleName(), null);
fieldAccess.replace((CtExpression) fieldAccess.getFactory().Code().createLiteral(value
.length));
throw new SkipException(fieldAccess);
throw new DoNotFurtherTemplateThisElement(fieldAccess);
}
}
}
Expand Down Expand Up @@ -306,7 +248,7 @@ private <T> void visitFieldAccess(CtFieldAccess<T> fieldAccess) {
toReplace.clone();
}
// do not visit if replaced
throw new SkipException(fieldAccess);
throw new DoNotFurtherTemplateThisElement(fieldAccess);
}
}

Expand All @@ -316,9 +258,6 @@ private <T> void visitFieldAccess(CtFieldAccess<T> fieldAccess) {
@SuppressWarnings("unchecked")
@Override
public <T> void visitCtInvocation(CtInvocation<T> invocation) {
if (invocation.getExecutable().getDeclaringType() == null) {
System.err.println(invocation.getExecutable());
}
if (invocation.getExecutable().isOverriding(S)) {
CtFieldAccess<?> fa = null;
if ((invocation.getTarget() instanceof CtFieldAccess)) {
Expand Down Expand Up @@ -351,7 +290,7 @@ public <T> void visitCtInvocation(CtInvocation<T> invocation) {
}
}
// do not visit the invocation if replaced
throw new SkipException(invocation);
throw new DoNotFurtherTemplateThisElement(invocation);
}
super.visitCtInvocation(invocation);
}
Expand All @@ -375,26 +314,6 @@ public <T> void scanCtExpression(CtExpression<T> expression) {
}
}
}
if (expression instanceof CtLiteral) {
CtLiteral<Object> lit = (CtLiteral<Object>) expression;
if (lit.getValue() instanceof CtTypeReference) {
CtTypeReference<T> t = (CtTypeReference<T>) lit.getValue();
if (parameterNames.contains(t.getSimpleName())) {
// replace type parameters
// TODO: this would probably not work with inner
// classes!!!
Object o = Parameters.getValue(template, t.getSimpleName(), null);
if (o instanceof Class) {
t = factory.Type().createReference(((Class<T>) o));
} else if (o instanceof CtTypeReference) {
t = (CtTypeReference<T>) o;
lit.setValue(t);
} else {
throw new RuntimeException("unsupported reference substitution");
}
}
}
}
super.scanCtExpression(expression);
}

Expand All @@ -407,7 +326,6 @@ public <T> void scanCtTypedElement(CtTypedElement<T> e) {
CtTypeReference<T> t;
Object o = Parameters.getValue(template, e.getType().getSimpleName(), null);
if (o instanceof Class) {
// TODO: CHECK THAT THIS IS STILL WORKING
o = factory.Type().createReference(((Class<T>) o));
}
if (o instanceof CtTypeReference) {
Expand All @@ -430,11 +348,6 @@ public <T> void scanCtTypedElement(CtTypedElement<T> e) {
@Override
public <T> void visitCtExecutableReference(CtExecutableReference<T> reference) {
scanCtReference(reference);
if (reference.getDeclaringType() == null) {
System.err.println("Blabla");
System.err.println(reference);
System.err.println(reference.getParent());
}
visitCtTypeReference(reference.getDeclaringType());
scanCtActualTypeContainer(reference);
}
Expand Down Expand Up @@ -489,70 +402,6 @@ public <T> void visitCtTypeReference(CtTypeReference<T> reference) {
}
super.visitCtTypeReference(reference);
}

@Override
public <T> void visitCtVariableRead(CtVariableRead<T> variableRead) {
if (visitVariableAccess(variableRead)) {
super.visitCtVariableRead(variableRead);
}
}

@Override
public <T> void visitCtVariableWrite(CtVariableWrite<T> variableWrite) {
if (visitVariableAccess(variableWrite)) {
super.visitCtVariableWrite(variableWrite);
}
}

private <T> boolean visitVariableAccess(CtVariableAccess<T> variableAccess) {
if (variableAccess instanceof CtSuperAccess) {
// A CtSuperAccess don't have a variable.
return true;
}
String name = variableAccess.getVariable().getSimpleName();
for (String pname : parameterNames) {
if (name.contains(pname)) {
Object value = Parameters.getValue(template, pname, null);
if ((value instanceof List) && name.equals(pname)) {
// replace list of CtParameter for generic access to the parameters
List<CtParameter<?>> l = (List<CtParameter<?>>) value;
List<CtExpression<?>> vas = factory.Code().createVariableReads(l);
CtAbstractInvocation<?> inv = (CtAbstractInvocation<?>) variableAccess.getParent();
int i = inv.getArguments().indexOf(variableAccess);
inv.getArguments().remove(i);
inv.getExecutable().getActualTypeArguments().remove(i);
for (CtExpression<?> va : vas) {
va.setParent(variableAccess.getParent());
inv.getArguments().add(i, va);
inv.getExecutable().getActualTypeArguments().add(i, va.getType());
i++;
}
throw new SkipException(variableAccess);
}
// replace variable accesses names
if (value instanceof String) {
name = name.replace(pname, (String) value);
variableAccess.getVariable().setSimpleName(name);
}
}
}
CtTypeReference<T> reference = variableAccess.getType();
if ((parameterNames != null) && (reference != null) && parameterNames.contains(reference.getSimpleName()
)) {
CtTypeReference<T> t;
Object o = Parameters.getValue(template, reference.getSimpleName(), null);
if (o instanceof Class) {
t = factory.Type().createReference(((Class<T>) o));
} else if (o instanceof CtTypeReference) {
t = (CtTypeReference<T>) o;
reference.setActualTypeArguments(t.getActualTypeArguments());
} else {
throw new RuntimeException("unsupported reference substitution");
}
variableAccess.setType(t);
}
return true;
}
}

Factory factory;
Expand Down Expand Up @@ -615,42 +464,13 @@ public void scan(Collection<? extends CtElement> elements) {

@Override
public void scan(CtElement element) {
if (element instanceof CtReference) {
try {
// doing the templating of this element
inheritanceScanner.scan(element);
if (!(element instanceof CtTypeReference)) {
// replace parameters in reference names
String name = ((CtReference) element).getSimpleName();
for (String pname : parameterNames) {
if (name.contains(pname)) {
name = name.replace(pname, Parameters.getValue(template, pname, null).toString());
((CtReference) element).setSimpleName(name);
}
}
super.scan(element);
} else {
if (!(parameterNames.contains(((CtReference) element).getSimpleName())
&& (((CtTypeReference<?>) element).getDeclaringType() != null)
&& ((CtTypeReference<?>) element).getDeclaringType().equals(templateRef))) {
super.scan(element);
}
}
} else {
try {
inheritanceScanner.scan(element);
super.scan(element);
} catch (SkipException e) {
// System.out.println(e.getMessage());
} catch (UndefinedParameterException upe) {
removeEnclosingStatement(element);
}
}
}

private void removeEnclosingStatement(CtElement e) {
if (!(e.getParent() instanceof CtBlock)) {
removeEnclosingStatement(e.getParent());
} else {
((CtStatement) e).replace(null);
// and then scan the children for doing the templating as well in them
super.scan(element);
} catch (DoNotFurtherTemplateThisElement ignore) {
}
}
}

0 comments on commit 5353879

Please sign in to comment.