
com.cloudbees.groovy.cps.CpsTransformer.groovy Maven / Gradle / Ivy
package com.cloudbees.groovy.cps
import com.cloudbees.groovy.cps.impl.CpsCallableInvocation
import com.cloudbees.groovy.cps.impl.CpsFunction
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.ast.expr.*
import org.codehaus.groovy.ast.stmt.*
import org.codehaus.groovy.classgen.AsmClassGenerator
import org.codehaus.groovy.classgen.BytecodeExpression
import org.codehaus.groovy.classgen.GeneratorContext
import org.codehaus.groovy.classgen.Verifier
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.Janitor
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.control.customizers.CompilationCustomizer
import org.codehaus.groovy.runtime.powerassert.SourceText
import org.codehaus.groovy.syntax.Token
import javax.annotation.Nonnull
import java.lang.annotation.Annotation
import java.lang.reflect.Modifier
import static org.codehaus.groovy.syntax.Types.*
/**
* Performs CPS transformation of Groovy methods.
*
*
* Every method not annotated with {@link NonCPS} gets rewritten. The general strategy of CPS transformation is
* as follows:
*
*
* Before:
*
* Object foo(int x, int y) {
* return x+y;
* }
*
*
*
* After:
*
* Object foo(int x, int y) {
* // the first part is AST of the method body
* // the rest (including implicit receiver argument) is actual value of arguments
* throw new CpsCallableInvocation(___cps___N, this, new Object[] {x, y});
* }
*
* private static CpsFunction ___cps___N = ___cps___N();
*
* private static final CpsFunction ___cps___N() {
* Builder b = new Builder(...);
* return new CpsFunction(['x','y'], b.plus(b.localVariable("x"), b.localVariable("y"))
* }
*
*
*
* That is, we transform a Groovy AST of the method body into a tree of {@link Block}s by using {@link Builder},
* then the method just returns this function object and expect the caller to evaluate it, instead of executing the method
* synchronously before it returns.
*
*
* This class achieves this transformation by implementing {@link GroovyCodeVisitor} and traverse Groovy AST tree
* in the in-order. As we traverse this tree, we produce another Groovy AST tree that invokes {@link Builder}.
* Note that we aren't calling Builder directly here; that's supposed to happen when the Groovy code under transformation
* actually runs.
*
*
* Groovy AST that calls {@link Builder} is a tree of function call, so we build {@link MethodCallExpression}s
* in the top-down manner. We do this by {@link CpsTransformer#makeNode(String)}, which creates a call to {@code Builder.xxx(...)},
* then supply the closure that fills in the arguments to this call by walking down the original Groovy AST tree.
* This walk-down is done by calling {@link CpsTransformer#visit(ASTNode)} (to recursively visit ASTs), or by calling {@link CpsTransformer#literal(String)}
* methods, which generate string/class/etc literals, as sometimes {@link Builder} methods need them as well.
*
* @author Kohsuke Kawaguchi
*/
class CpsTransformer extends CompilationCustomizer implements GroovyCodeVisitor {
private int iota=0;
private SourceUnit sourceUnit;
private TransformerConfiguration config = new TransformerConfiguration();
CpsTransformer() {
super(CompilePhase.CANONICALIZATION)
}
public void setConfiguration(@Nonnull TransformerConfiguration config) {
this.config = config;
}
@Override
void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
this.sourceUnit = source;
// copy(source.ast.methods)?.each { visitMethod(it) }
// classNode?.declaredConstructors?.each { visitMethod(it) } // can't transform constructor
copy(classNode?.methods)?.each { visitMethod(it) }
// classNode?.objectInitializerStatements?.each { it.visit(visitor) }
// classNode?.fields?.each { visitor.visitField(it) }
// groovy puts timestamp of compilation into a class file, causing serialVersionUID to change.
// this tends to be undesirable for CPS involving persistence.
// set the timestamp to some bogus value will prevent Verifier from adding a field that encodes
// timestamp in the field name
// see http://stackoverflow.com/questions/15310136/neverhappen-variable-in-compiled-classes
if (classNode.getField(Verifier.__TIMESTAMP)==null)
classNode.addField(Verifier.__TIMESTAMP,Modifier.STATIC|Modifier.PRIVATE, ClassHelper.long_TYPE,
new ConstantExpression(0L));
}
private List copy(List t) {
if (t==null) return t;
else return new ArrayList(t);
}
/**
* Should this method be transformed?
*/
protected boolean shouldBeTransformed(MethodNode node) {
return !node.isSynthetic() && !hasAnnotation(node, NonCPS.class) && !hasAnnotation(node, WorkflowTransformed.class);
}
private boolean hasAnnotation(MethodNode node, Class extends Annotation> a) {
node.annotations.find { it.classNode.name == a.name } != null
}
private boolean extendsFromScript(ClassNode c) {
while (c!=null) {
if (c.name==Script.class.name)
return true;
c = c.superClass
}
return false;
}
/**
* Transforms asynchronous workflow method.
*
* From:
*
* ReturnT foo( T1 arg1, T2 arg2, ...) {
* ... body ...
* }
*
* To:
*
* private static CpsFunction ___cps___N = ___cps___N();
*
* private static final CpsFunction ___cps___N() {
* return new CpsFunction(['arg1','arg2','arg3',...], CPS-transformed-method-body)
* }
*
* ReturnT foo( T1 arg1, T2 arg2, ...) {
* throw new CpsCallableInvocation(___cps___N, this, new Object[] {arg1, arg2, ...})
* }
*/
public void visitMethod(MethodNode m) {
if (!shouldBeTransformed(m)) {
visitNontransformedMethod(m);
return;
}
Expression body;
// transform the body
parent = { e -> body=e }
m.code.visit(this)
def params = new ListExpression();
m.parameters.each { params.addExpression(new ConstantExpression(it.name))}
/*
CpsFunction ___cps___N() {
Builder b = new Builder(new MethodLocation(...));
b.withClosureType(...);
return new CpsFunction( << parameters >>, << body: AST tree building code >>);
}
*/
def cpsName = "___cps___${iota++}"
def builderMethod = m.declaringClass.addMethod(cpsName, PRIVATE_STATIC_FINAL, FUNCTION_TYPE, new Parameter[0], new ClassNode[0],
new BlockStatement([
new ExpressionStatement(new DeclarationExpression(BUILDER, new Token(ASSIGN, "=", -1, -1),
new ConstructorCallExpression(BUIDER_TYPE, new TupleExpression(
new ConstructorCallExpression(METHOD_LOCATION_TYPE, new TupleExpression(
new ConstantExpression(m.declaringClass.name),
new ConstantExpression(m.name),
new ConstantExpression(sourceUnit.name)
))
)))),
new ExpressionStatement(
new MethodCallExpression(BUILDER, "withClosureType",
new TupleExpression(new ClassExpression(config.closureType)))),
new ReturnStatement(new ConstructorCallExpression(FUNCTION_TYPE, new TupleExpression(params, body)))
], new VariableScope())
)
builderMethod.addAnnotation(new AnnotationNode(WORKFLOW_TRANSFORMED_TYPE))
def f = m.declaringClass.addField(cpsName, PRIVATE_STATIC_FINAL, FUNCTION_TYPE,
new StaticMethodCallExpression(m.declaringClass, cpsName, new TupleExpression()));
// new ConstructorCallExpression(FUNCTION_TYPE, new TupleExpression(params, body)));
def paramArray = new ArrayExpression(ClassHelper.OBJECT_TYPE, m.parameters.collect {new VariableExpression(it)});
def args = new TupleExpression(new VariableExpression(f), THIS, paramArray);
m.code = new ThrowStatement(new ConstructorCallExpression(CPSCALLINVK_TYPE,args));
m.addAnnotation(new AnnotationNode(WORKFLOW_TRANSFORMED_TYPE));
}
/**
* For methods that are not CPS-transformed.
*/
protected void visitNontransformedMethod(MethodNode m) {
}
/**
* As we visit expressions in the method body, we convert them to the {@link Builder} invocations
* and pass them back to this closure.
*/
protected Closure parent;
protected void visit(ASTNode e) {
if (e instanceof EmptyExpression) {
// working around a bug in EmptyExpression.visit() that doesn't call any method
visitEmptyExpression(e);
} else
if (e instanceof EmptyStatement) {
// working around a bug in EmptyStatement.visit() that doesn't call any method
visitEmptyStatement(e);
} else {
e.visit(this);
}
}
protected void visit(Collection extends ASTNode> col) {
for (def e : col) {
visit(e);
}
}
/**
* Makes an AST fragment that calls {@link Builder} with specific method.
*
* @param methodName
* Method on {@link Builder} to call.
* @param args
* Can be closure for building argument nodes, Expression, or List of Expressions.
*/
protected void makeNode(String methodName, Object args) {
parent(new MethodCallExpression(BUILDER, methodName, makeChildren(args)));
}
/**
* Makes an AST fragment that instantiates a new instance of the given type.
*
* @param args
* Can be closure for building argument nodes, Expression, or List of Expressions.
*/
protected void makeNode(ClassNode type, Object args) {
parent(new ConstructorCallExpression(type, makeChildren(args)));
}
/**
* Given closure, {@link Expression} or a list of them, package them up into
* {@link TupleExpression}
*/
protected TupleExpression makeChildren(args) {
if (args==null) return new TupleExpression();
if (args instanceof Closure) {
def argExps = []
def old = parent;
try {
parent = { a -> argExps.add(a) }
args(); // evaluate arguments
args = argExps;
} finally {
parent = old
}
}
return new TupleExpression(args);
}
protected void makeNode(String methodName) {
makeNode(methodName,null)
}
protected void loc(ASTNode e) {
literal(e.lineNumber);
}
/**
* Used in the closure block of {@link #makeNode(String, Object)} to create a literal string argument.
*/
protected void literal(String s) {
parent(new ConstantExpression(s))
}
protected void literal(ClassNode c) {
parent(new ClassExpression(c))
}
protected void literal(int n) {
parent(new ConstantExpression(n,true))
}
protected void literal(boolean b) {
parent(new ConstantExpression(b,true))
}
void visitEmptyExpression(EmptyExpression e) {
makeNode("noop")
}
void visitEmptyStatement(EmptyStatement e) {
makeNode("noop")
}
void visitMethodCallExpression(MethodCallExpression call) {
makeNode("functionCall") {
loc(call)
// isImplicitThis==true even when objectExpression is not 'this'.
// See InvocationWriter.makeCall,
if (call.isImplicitThis() && AsmClassGenerator.isThisExpression(call.objectExpression))
makeNode("javaThis_")
else
visit(call.objectExpression);
// TODO: spread
visit(call.method);
literal(call.safe);
visit(((TupleExpression)call.arguments).expressions)
}
}
void visitBlockStatement(BlockStatement b) {
makeNode("block") {
visit(b.statements)
}
}
void visitForLoop(ForStatement forLoop) {
if (forLoop.variable==ForStatement.FOR_LOOP_DUMMY) {
// for ( e1; e2; e3 ) { ... }
ClosureListExpression loop = forLoop.collectionExpression
assert loop.expressions.size()==3;
makeNode("forLoop") {
literal(forLoop.statementLabel)
visit(loop.expressions)
visit(forLoop.loopBlock)
}
} else {
// for (x in col) { ... }
makeNode("forInLoop") {
loc(forLoop);
literal(forLoop.statementLabel)
literal(forLoop.variableType)
literal(forLoop.variable.name)
visit(forLoop.collectionExpression)
visit(forLoop.loopBlock)
}
}
}
void visitWhileLoop(WhileStatement loop) {
makeNode("while_") {
literal(loop.statementLabel)
visit(loop.booleanExpression)
visit(loop.loopBlock)
}
}
void visitDoWhileLoop(DoWhileStatement loop) {
makeNode("doWhile") {
literal(loop.statementLabel)
visit(loop.booleanExpression)
visit(loop.loopBlock)
}
}
void visitIfElse(IfStatement stmt) {
makeNode("if_") {
visit(stmt.booleanExpression)
visit(stmt.ifBlock)
visit(stmt.elseBlock)
}
}
void visitExpressionStatement(ExpressionStatement statement) {
visit(statement.expression)
}
void visitReturnStatement(ReturnStatement statement) {
makeNode("return_") {
visit(statement.expression);
}
}
void visitAssertStatement(AssertStatement statement) {
def j = new Janitor()
def text = new SourceText(statement, sourceUnit, j).normalizedText
j.cleanup()
makeNode("assert_") {
visit(statement.booleanExpression)
visit(statement.messageExpression)
literal(text)
}
}
void visitTryCatchFinally(TryCatchStatement stmt) {
makeNode("tryCatch") {
visit(stmt.tryStatement)
visit(stmt.finallyStatement)
visit(stmt.catchStatements)
}
}
void visitSwitch(SwitchStatement stmt) {
makeNode("switch_") {
literal(stmt.statementLabel)
visit(stmt.expression)
visit(stmt.defaultStatement)
visit(stmt.caseStatements)
}
}
void visitCaseStatement(CaseStatement stmt) {
makeNode("case_") {
loc(stmt)
visit(stmt.expression)
visit(stmt.code)
}
}
void visitBreakStatement(BreakStatement statement) {
makeNode("break_", new ConstantExpression(statement.label));
}
void visitContinueStatement(ContinueStatement statement) {
makeNode("continue_", new ConstantExpression(statement.label));
}
void visitThrowStatement(ThrowStatement st) {
makeNode("throw_") {
loc(st)
visit(st.expression)
}
}
void visitSynchronizedStatement(SynchronizedStatement statement) {
throw new UnsupportedOperationException();
}
void visitCatchStatement(CatchStatement stmt) {
makeNode(CATCH_EXPRESSION_TYPE) {
literal(stmt.exceptionType)
literal(stmt.variable.name)
visit(stmt.code)
}
}
void visitStaticMethodCallExpression(StaticMethodCallExpression exp) {
makeNode("staticCall") {
loc(exp)
literal(exp.ownerType)
literal(exp.method)
visit(((TupleExpression)exp.arguments).expressions)
}
}
void visitConstructorCallExpression(ConstructorCallExpression call) {
makeNode("new_") {
loc(call)
literal(call.type)
visit(((TupleExpression)call.arguments).expressions)
}
}
void visitTernaryExpression(TernaryExpression exp) {
makeNode("ternaryOp") {
visit(exp.booleanExpression)
visit(exp.trueExpression)
visit(exp.falseExpression)
}
}
void visitShortTernaryExpression(ElvisOperatorExpression exp) {
makeNode("elvisOp") {
visit(exp.booleanExpression)
visit(exp.falseExpression)
}
}
// Constants from Token.type to a method on Builder
private static Map BINARY_OP_TO_BUILDER_METHOD = [
(COMPARE_EQUAL) :"compareEqual",
(COMPARE_NOT_EQUAL) :"compareNotEqual",
(COMPARE_TO) :"compareTo",
(COMPARE_GREATER_THAN) :"greaterThan",
(COMPARE_GREATER_THAN_EQUAL) :"greaterThanEqual",
(COMPARE_LESS_THAN) :"lessThan",
(COMPARE_LESS_THAN_EQUAL) :"lessThanEqual",
(LOGICAL_AND) :"logicalAnd",
(LOGICAL_OR) :"logicalOr",
(BITWISE_AND) :"bitwiseAnd",
(BITWISE_AND_EQUAL) :"bitwiseAndEqual",
(BITWISE_OR) :"bitwiseOr",
(BITWISE_OR_EQUAL) :"bitwiseOrEqual",
(BITWISE_XOR) :"bitwiseXor",
(BITWISE_XOR_EQUAL) :"bitwiseXorEqual",
(PLUS) :"plus",
(PLUS_EQUAL) :"plusEqual",
(MINUS) :"minus",
(MINUS_EQUAL) :"minusEqual",
(MULTIPLY) :"multiply",
(MULTIPLY_EQUAL) :"multiplyEqual",
(DIVIDE) :"div",
(DIVIDE_EQUAL) :"divEqual",
(INTDIV) :"intdiv",
(INTDIV_EQUAL) :"intdivEqual",
(MOD) :"mod",
(MOD_EQUAL) :"modEqual",
(POWER) :"power",
(POWER_EQUAL) :"powerEqual",
(EQUAL) :"assign",
(KEYWORD_INSTANCEOF) :"instanceOf",
(LEFT_SQUARE_BRACKET) :"array",
(LEFT_SHIFT) :"leftShift",
(LEFT_SHIFT_EQUAL) :"leftShiftEqual",
(RIGHT_SHIFT) :"rightShift",
(RIGHT_SHIFT_EQUAL) :"rightShiftEqual",
(RIGHT_SHIFT_UNSIGNED) :"rightShiftUnsigned",
(RIGHT_SHIFT_UNSIGNED_EQUAL) :"rightShiftUnsignedEqual",
(FIND_REGEX) :"findRegex",
(MATCH_REGEX) :"matchRegex",
(KEYWORD_IN) :"isCase",
]
/**
* @see org.codehaus.groovy.classgen.asm.BinaryExpressionHelper#eval(BinaryExpression)
*/
void visitBinaryExpression(BinaryExpression exp) {
def body = {// for building CPS tree for two expressions
loc(exp)
visit(exp.leftExpression)
visit(exp.rightExpression)
}
def name = BINARY_OP_TO_BUILDER_METHOD[exp.operation.type]
if (name!=null) {
makeNode(name,body)
return;
}
throw new UnsupportedOperationException("Operation: " + exp.operation + " not supported");
}
void visitPrefixExpression(PrefixExpression exp) {
makeNode("prefix"+ prepostfixOperatorSuffix(exp)) {
loc(exp)
visit(exp.expression)
}
}
void visitPostfixExpression(PostfixExpression exp) {
makeNode("postfix"+ prepostfixOperatorSuffix(exp)) {
loc(exp)
visit(exp.expression)
}
}
protected String prepostfixOperatorSuffix(Expression exp) {
switch (exp.operation.type) {
case PLUS_PLUS: return "Inc";
case MINUS_MINUS: return "Dec";
default:
throw new UnsupportedOperationException("Unknown operator:" + exp.operation.text)
}
}
void visitBooleanExpression(BooleanExpression exp) {
visit(exp.expression);
}
void visitClosureExpression(ClosureExpression exp) {
makeNode("closure") {
loc(exp)
def types = new ListExpression();
def params = new ListExpression();
// the interpretation of the 'parameters' is messed up. According to ClosureWriter,
// when the user explicitly defines no parameter "{ -> foo() }" then this is null,
// when the user doesn't define any parameter explicitly { foo() }, then this is empty,
if (exp.parameters==null) {
} else
if (exp.parameters.length==0) {
types.addExpression(new ClassExpression(OBJECT_TYPE));
params.addExpression(new ConstantExpression("it"));
} else {
exp.parameters.each {
types.addExpression(new ClassExpression(it.type));
params.addExpression(new ConstantExpression(it.name))
}
}
parent(types);
parent(params)
visit(exp.code)
}
}
void visitTupleExpression(TupleExpression expression) {
throw new UnsupportedOperationException();
}
void visitMapExpression(MapExpression exp) {
makeNode("map") {
exp.mapEntryExpressions.each { e ->
visit(e.keyExpression)
visit(e.valueExpression)
}
}
}
void visitMapEntryExpression(MapEntryExpression expression) {
throw new UnsupportedOperationException();
}
void visitListExpression(ListExpression exp) {
makeNode("list") {
visit(exp.expressions)
}
}
void visitRangeExpression(RangeExpression exp) {
makeNode("range") {
loc(exp)
visit(exp.from)
visit(exp.to)
literal(exp.inclusive)
}
}
void visitPropertyExpression(PropertyExpression exp) {
// TODO: spread
makeNode("property") {
loc(exp)
visit(exp.objectExpression)
visit(exp.property)
literal(exp.safe)
}
}
void visitAttributeExpression(AttributeExpression exp) {
// TODO: spread
makeNode("attribute") {
loc(exp)
visit(exp.objectExpression)
visit(exp.property)
literal(exp.safe)
}
}
void visitFieldExpression(FieldExpression exp) {
def f = exp.field
if (f.isStatic()) {
makeNode("staticField") {
loc(exp)
literal(f.type)
literal(exp.fieldName)
}
} else {
makeNode("property") {
loc(exp)
makeNode("this_")
literal(exp.fieldName)
}
}
}
void visitMethodPointerExpression(MethodPointerExpression exp) {
makeNode("methodPointer") {
loc(exp)
visit(exp.expression)
visit(exp.methodName)
}
}
void visitConstantExpression(ConstantExpression expression) {
makeNode("constant", expression)
}
void visitClassExpression(ClassExpression expression) {
makeNode("constant", expression)
}
void visitVariableExpression(VariableExpression exp) {
def ref = exp.accessedVariable
if (ref instanceof VariableExpression /* local variable */
|| ref instanceof Parameter) {
makeNode("localVariable") {
literal(exp.name)
}
} else
if (ref instanceof DynamicVariable
|| ref instanceof PropertyNode
|| ref instanceof FieldNode) {
makeNode("property") {
loc(exp)
makeNode("javaThis_")
literal(exp.name)
}
} else
if (exp.name=="this") {
/* Kohsuke: TODO: I don't really understand the 'true' block of the code, so I'm missing something
if (controller.isStaticMethod() || (!controller.getCompileStack().isImplicitThis() && controller.isStaticContext())) {
if (controller.isInClosure()) classNode = controller.getOutermostClass();
visitClassExpression(new ClassExpression(classNode));
} else {
loadThis();
}
*/
makeNode("this_")
} else
throw new UnsupportedOperationException("Unexpected variable type: ${ref}");
}
void visitDeclarationExpression(DeclarationExpression exp) {
if (exp.isMultipleAssignmentDeclaration()) {
// def (a,b)=list
makeNode("sequence") {
for (VariableExpression v in exp.tupleExpression.expressions) {
makeNode("declareVariable") {
loc(exp)
literal(v.type)
literal(v.name)
}
}
makeNode("assign") {
loc(exp)
visit(exp.leftExpression)
visit(exp.rightExpression)
}
}
} else {
// def x=v;
makeNode("declareVariable") {
def v = exp.variableExpression
loc(exp)
literal(v.type)
literal(v.name)
visit(exp.rightExpression) // this will not produce anything if this is EmptyExpression
}
}
}
void visitGStringExpression(GStringExpression exp) {
makeNode("gstring") {
loc(exp)
makeNode("list") {
visit(exp.values)
}
makeNode("list") {
visit(exp.strings)
}
}
}
void visitArrayExpression(ArrayExpression exp) {
if (exp.sizeExpression!=null) {
// array instanation like new String[1][2][3]
makeNode("newArray") {
loc(exp)
literal(exp.elementType)
visit(exp.sizeExpression)
}
} else {
throw new UnsupportedOperationException();
}
}
void visitSpreadExpression(SpreadExpression expression) {
throw new UnsupportedOperationException();
}
void visitSpreadMapExpression(SpreadMapExpression expression) {
throw new UnsupportedOperationException();
}
void visitNotExpression(NotExpression exp) {
makeNode("not") {
loc(exp)
visit(exp.expression)
}
}
void visitUnaryMinusExpression(UnaryMinusExpression exp) {
makeNode("unaryMinus") {
loc(exp)
visit(exp.expression)
}
}
void visitUnaryPlusExpression(UnaryPlusExpression exp) {
makeNode("unaryPlus") {
loc(exp)
visit(exp.expression)
}
}
void visitBitwiseNegationExpression(BitwiseNegationExpression exp) {
makeNode("bitwiseNegation") {
loc(exp)
visit(exp.expression)
}
}
void visitCastExpression(CastExpression exp) {
makeNode("cast") {
loc(exp)
visit(exp.expression)
literal(exp.type)
literal(exp.isCoerce())
}
}
void visitArgumentlistExpression(ArgumentListExpression expression) {
throw new UnsupportedOperationException();
}
void visitClosureListExpression(ClosureListExpression closureListExpression) {
throw new UnsupportedOperationException();
}
void visitBytecodeExpression(BytecodeExpression expression) {
throw new UnsupportedOperationException();
}
private static final ClassNode OBJECT_TYPE = ClassHelper.makeCached(Object.class);
private static final ClassNode FUNCTION_TYPE = ClassHelper.makeCached(CpsFunction.class);
private static final ClassNode CATCH_EXPRESSION_TYPE = ClassHelper.makeCached(CatchExpression.class);
private static final ClassNode BUILDER_TYPE = ClassHelper.makeCached(Builder.class);
private static final ClassNode CPSCALLINVK_TYPE = ClassHelper.makeCached(CpsCallableInvocation.class);
private static final ClassNode WORKFLOW_TRANSFORMED_TYPE = ClassHelper.makeCached(WorkflowTransformed.class);
private static final ClassNode BUIDER_TYPE = ClassHelper.makeCached(Builder.class);
private static final ClassNode METHOD_LOCATION_TYPE = ClassHelper.makeCached(MethodLocation.class);
private static final VariableExpression BUILDER = new VariableExpression("b",BUILDER_TYPE); // new PropertyExpression(new ClassExpression(BUILDER_TYPE), "INSTANCE")
private static final VariableExpression THIS = new VariableExpression("this");
/**
* Closure's default "it" parameter.
*/
private static final Parameter IT = new Parameter(ClassHelper.OBJECT_TYPE, "it", ConstantExpression.NULL);
private static final int PRIVATE_STATIC_FINAL = Modifier.STATIC | Modifier.PRIVATE | Modifier.FINAL
}