> since you seemed to say in the beginning that you did not want to create
> objects (if I read this correctly), why creating AutoCloseable instances here
> would not pose a problem ?)
Best case scenario is that I get the same performance and memory footprint of
the original code (included below). So an AST transform is what I look to in
order to type less code but get the same runtime outcome. A macro method must
replace an expression with another expression, so writing out a try statement
is not possible AFAIK (unless it is embedded in a ClosureExpression or
something like that). However an AIC is a ConstructorCallExpression so that is
possible with a macro method. And I think the one extra plus going for the AIC
AutoCloseable is it could be reused anywhere an AutoCloseable is accepted.
If there is something simpler/groovier I am interested to hear about it. The
suggestion of converting Foo into an AutoCloseable significantly changes the
design/structure. I'm more looking to rewrite the contents of the bar() method
only.
class Foo {
private state
def bar() {
def temp = state
state = newState
try {
baz()
} finally {
state = temp
}
}
def baz() {
// make use of state; does not require previous values
}
}
In any case, I did manage to prototype it out and it works. Here it is in case
anyone wanted to have a look.
class Foo {
def bar() {
println field
try (def x = auto(field, 'new')) { // needed to provide "def x =" because
parser validates for this and macro transform runs after that
baz()
}
println field
}
def baz() {
println field
}
private field = 'old'
}
new Foo().bar() // should print "old", "new", "old"
import static org.codehaus.groovy.ast.ClassHelper.*
import static org.codehaus.groovy.ast.tools.GeneralUtils.*
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.ast.expr.*
import org.codehaus.groovy.macro.runtime.Macro
import org.codehaus.groovy.macro.runtime.MacroContext
import groovy.transform.AutoFinal
@AutoFinal
class MoreMacroMethods {
@Macro
static Expression auto(MacroContext context, VariableExpression oldValue,
Expression newValue = oldValue) {
if (isImplicitThis(context)) {
ClassNode type = newAutoCloseable(context)
FieldNode field = type.addField('temp', 0x12, OBJECT_TYPE, oldValue)
type.addObjectInitializerStatements(block(assignS(varX(oldValue.name),
newValue)))
type.addMethod('close', 0x11, VOID_TYPE, Parameter.EMPTY_ARRAY, new
ClassNode[] {make(Exception.class)}, null).tap {
addAnnotation(new AnnotationNode(make(Override.class)))
setCode(assignS(varX(oldValue.name), fieldX(field)))
setSourcePosition(context.call)
}
return new ConstructorCallExpression(type,
ArgumentListExpression.EMPTY_ARGUMENTS).tap {
usingAnonymousInnerClass = true
}
}
}
private static ClassNode newAutoCloseable(MacroContext context) {
def (ClassNode enclosingClass, MethodNode enclosingMethod) =
findEnclosing(context)
def name = enclosingClass.name + '$' + (anonymousClassCount(enclosingClass)
+ 1)
def type = new InnerClassNode(enclosingClass, name, 1, AUTOCLOSEABLE_TYPE)
type.anonymous = true; type.enclosingMethod = enclosingMethod
context.compilationUnit.addNewPhaseOperation({
org.codehaus.groovy.control.SourceUnit unit ->
if (unit.is(context.sourceUnit)) {
unit.AST.addClass(type)
}
} as org.codehaus.groovy.control.CompilationUnit.ISourceUnitOperation, 3)
return type
}
private static int anonymousClassCount(ClassNode node) {
int count = 0
for (Iterator<InnerClassNode> it = node.getInnerClasses(); it.hasNext();) {
InnerClassNode innerClass = it.next()
if (innerClass.isAnonymous()) {
count += 1
}
}
return count
}
private static Tuple2<ClassNode, MethodNode> findEnclosing(MacroContext
context) {
Tuple.tuple(context.sourceUnit.AST.classes[1],
context.sourceUnit.AST.classes[1].methods[0]) // TODO: search
context.sourceUnit.AST for enclosing type and method of context.call
}
private static boolean isImplicitThis(MacroContext context) {
context.call.with {
objectExpression instanceof VariableExpression &&
objectExpression.isThisExpression() &&
isImplicitThis()
}
}
}