I'm trying to do something rather simple. I would like to wrap the whole method code into an additional closure block that would measure the execution time. Right now I'm getting a really not helpful error message:
Error:Groovyc: NPE while processing Test.groovy
Annotation:
#Retention(RetentionPolicy.SOURCE)
#Target([ElementType.METHOD])
#GroovyASTTransformationClass(["WithTimingASTTransformation"])
public #interface WithTiming {
}
My wrapping closure:
class Benchmark {
static def measureTime(Closure cl) {
def start = System.currentTimeMillis()
def result = cl()
def time = System.currentTimeMillis() - start
println "it took $time"
result
}
}
My Transformation:
#GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
class WithTimingASTTransformation implements ASTTransformation {
#Override
void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
MethodNode method = astNodes[1]
method.code = wrap(method)
}
private Statement wrap(MethodNode method) {
def newBlock = new BlockStatement()
newBlock.addStatement(
new ExpressionStatement(
new StaticMethodCallExpression(
new ClassNode(Benchmark),
'measureTime',
new ArgumentListExpression(
new ClosureExpression(new Parameter[0], method.code)
))))
newBlock
}
}
I'm really stuck here and don't know how can I debug the problem.
There is an answer on a similar topic (wrapping whole method body into a try/catch block here). This works fine but my case is slightly different.
In my case similar NPE was coming from:
java.lang.NullPointerException
at org.codehaus.groovy.classgen.asm.ClosureWriter.createClosureClass(ClosureWriter.java:194)
at org.codehaus.groovy.classgen.asm.ClosureWriter.getOrAddClosureClass(ClosureWriter.java:159)
at org.codehaus.groovy.classgen.asm.ClosureWriter.writeClosure(ClosureWriter.java:90)
at org.codehaus.groovy.classgen.AsmClassGenerator.visitClosureExpression(AsmClassGenerator.java:673)
Whereas:
if (parameters == null || expression.getVariableScope() == null) {
parameters = Parameter.EMPTY_ARRAY;
} else if (parameters.length == 0) {
// let's create a default 'it' parameter
Parameter it = new Parameter(ClassHelper.OBJECT_TYPE, "it", ConstantExpression.NULL);
parameters = new Parameter[]{it};
Variable ref = expression.getVariableScope().getDeclaredVariable("it");
if (ref != null) it.setClosureSharedVariable(ref.isClosureSharedVariable());
}
and line 194 (as of https://github.com/groovy/groovy-core/commit/a52d0d3c5dd1cbb342992d36235171718a563c8b) is:
Variable ref = expression.getVariableScope().getDeclaredVariable("it");
Thus you need to define a VariableScope for your ClosureExpression. I had to add tracing into org.codehaus.groovy.ast.ClosureWriter to find this, because there is an issue with exception display on stage of Class Generation - both in IntelliJ Idea and in Groovy Console - it does not show proper lines of code.
Furthermore, I think that either ClosureWriter or ClosureExpression constructor can be fixed to work aligned by default - without this NPE. I will possibly submit an issue to Groovy Jira for this.
Now I am able to inject closure expression in my code. But struggling to call this closure.
Getting:
groovy.lang.MissingMethodException: No signature of method: com.a9ae0b01f0ffc.VSMSGEN.implementation.T_visa_recon_generator$_convert_vts_log_to_ctf_closure2.call() is applicable for argument types: () values: []
Long story short, after some iterations my method-wrapping AST looks like this:
BlockStatement bs = new BlockStatement()
ClosureExpression closureExp = new ClosureExpression( methodNode.parameters, methodNode.code )
closureExp.variableScope = new VariableScope() // <- this does the trick!
bs.addStatement new ExpressionStatement( new StaticMethodCallExpression( new ClassNode( TransactionUtil ), 'wrap', new ArgumentListExpression( closureExp ) ) )
methodNode.code = bs
The line closureExp.variableScope = new VariableScope() avoids the NPE in ClosureWriter.java:194 and the whole thing runs like a charm!
Hope it helps someone...
Related
Is there a way to skip MissingPropertyException while using GroovyShell.evaluate?
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
shell.evaluate("a=5; b=1") // works fine
// How to not get MissingPropertyException, or silently ignore property 'a'
shell.evaluate("a; b=1") // MissingPropertyException for 'a'
I know about the Expando solution, is there a way to do it without defining a class?
A very minimal approach would be to override Binding.getVariable.
Note that this is very straight forward: "all exceptions" are ignored - you might want to have better logging or more accurate error handling.
import groovy.lang.*
class NoOpBinding extends Binding {
#Override
Object getVariable(String name) {
try {
return super.getVariable(name)
}
catch (Throwable t) {
println "Ignoring variable=`$name`"
return null
}
}
}
def shell = new GroovyShell(new NoOpBinding())
shell.evaluate("a; b=1") // MissingPropertyException for 'a'
// → Ignoring variable=`a`
println shell.getVariable('b')
// → 1
You could do something like this:
def binding = [:].withDefault { }
def shell = new GroovyShell(binding as Binding)
shell.evaluate 'a; b=1'
The original question was deleted before I could follow up with a further answer it, so I'm reposting the question with the answer to follow:
I am unable to modify my method using AST Transformation as I cannot figure out how to execute previous method statements after my modification. I extract the statements from the method, save it in some temporary variable, but later, after my modification, when I try to execute it I get MissingPropertyException: No such property: code for class: Calculator as like I am trying to use a property from my class and not the previous code block from my method. Any ideas what am I doing wrong?
//annotation
#Retention(RetentionPolicy.SOURCE)
#Target([ElementType.TYPE])
#GroovyASTTransformationClass("CreatedAtTransformation")
public #interface CreatedAt {
String name() default "";
}
//AST Transformation
#GroovyASTTransformation(phase = SEMANTIC_ANALYSIS)
public class CreatedAtTransformation implements ASTTransformation {
public void visit(ASTNode[] astNodes, SourceUnit source) {
//private final long field creation
ClassNode myClass = (ClassNode) astNodes[1]
ClassNode longClass = new ClassNode(Long.class)
FieldNode field = new FieldNode("timeOfInstantiation", FieldNode.ACC_PRIVATE | FieldNode.ACC_FINAL, longClass, myClass, new ConstantExpression(System.currentTimeMillis()))
myClass.addField(field)
//statement
AstBuilder ab = new AstBuilder()
List<ASTNode> statement = ab.buildFromCode {
timeOfInstantiation
}
//value of the annotation expression(name of the method)
def annotationExpression = astNodes[0].members.name
String annotationValueString = annotationExpression.value
//public final method creation
myClass.addMethod(annotationValueString, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, ClassHelper.Long_TYPE,[] as Parameter[], [] as ClassNode[], statement[0])
//modification of method "add"
def addMethods = myClass.getMethods("add")
for(m in addMethods){
def code = m.getCode().statements
//statement
AstBuilder abc = new AstBuilder()
List<ASTNode> statement1 = abc.buildFromCode {
timeOfInstantiation = System.currentTimeMillis()
for(c in code){
c.expression
}
}
m.setCode(statement1[0])
}
//modification of method "subtract"
def subtractMethods = myClass.getMethods("subtract")
for(m in subtractMethods){
def code = m.getCode().statements
//statement
AstBuilder abc = new AstBuilder()
List<ASTNode> statement1 = abc.buildFromCode {
timeOfInstantiation = System.currentTimeMillis()
for(c in code){
c.expression
}
}
m.setCode(statement1[0])
}
}
}
//class
final calculator = new GroovyShell(this.class.getClassLoader()).evaluate('''
#CreatedAt(name = "timestamp")
class Calculator {
int sum = 0
def add(int value) {
int v = sum + value
sum = v
}
def subtract(int value) {
sum -= value
}
}
new Calculator()
''')
//test
assert System.currentTimeMillis() >= calculator.timestamp()
assert calculator.timestamp() == calculator.timestamp()
def oldTimeStamp = calculator.timestamp()
sleep(1000)
calculator.add(10)
assert oldTimeStamp < calculator.timestamp()
assert calculator.timestamp() == calculator.timestamp()
oldTimeStamp = calculator.timestamp()
sleep(1000)
calculator.subtract(1)
assert oldTimeStamp < calculator.timestamp()
assert calculator.timestamp() == calculator.timestamp()
println 'well done'
There is more code than is actually needed for the question. Important part are those modifications of methods. Thanks in advance.
From my perspective I don't know if the code from the AST is actually useful or just to get and example working, to learn AST Transforms...
So my original answer was:
It can be hard to get help when it comes to AST transforms. While it is a guess I'm thinking it might have to do with variable scope. There is a VariableScopeVisitor that is run early in the AST process, setting the scoping of variables, however from the description of your AST you are adding code that you want to access later. So you may have to run the VariableScopeVisitor again, to fix it so that the existing code has access to your injected code.
I did an intro AST talk at GR8Conf.US this year, which has a lot of resources:
https://docs.google.com/presentation/d/1D4B0YQd0_0HYxK2FOt3xILM9XIymh-G-jh1TbQldbVA/edit?usp=sharing
I would take a look at this article that talks about variable scope:
However the real answer is that
AST Transforms can be difficult, and using the AstBuilder while it can be convience can introduce issues, so I often use the API directly. Once I learn Macros and Macro Methods, new in Groovy 2.5 I might not have to use the API as much, but until then I rewrote part of the code using the API like this:
//modification of method "add"
def addMethods = myClass.getMethods("add")
for(m in addMethods){
def code = m.getCode().statements
//statement
//AstBuilder abc = new AstBuilder()
Statement s1 = new ExpressionStatement(
new BinaryExpression(
new VariableExpression('timeOfInstantiation'),
Token.newSymbol(org.codehaus.groovy.syntax.Types.EQUAL,0,0),
new MethodCallExpression(
new ClassExpression(new ClassNode(java.lang.System)),
'currentTimeMillis',
ArgumentListExpression.EMPTY_ARGUMENTS
)
)
)
// List<ASTNode> statement1 = abc.buildFromString('timeOfInstantiation = System.currentTimeMillis()')
// List<ASTNode> statement1 = abc.buildFromCode {
// timeOfInstantiation = System.currentTimeMillis()
// for(c in code){
// c.expression
// }
// }
code.add(0,s1)
//m.setCode(statement1[0])
}
This code can be cleaned up a bit, but it should work. I also had to change the timeOfInstantiation to be private, instead of final, so that the assignment code would work like this:
FieldNode field = new FieldNode("timeOfInstantiation", FieldNode.ACC_PRIVATE, longClass, myClass, new ConstantExpression(System.currentTimeMillis()))
I would also look into the test application reference in my presentation, as it will allow for debugging AST Transforms and using the Groovy Console to see what the transforms are doing.
I was able to get a passing test for the dumbed down version of my code (thanks to cgrim! Spock: method not recognized as an invocation), but with the real code it won't work unless the getAssetIdBatch returns something that is non-null. I can't figure out why my interactions aren't being implemented. Below, you can see three attempts to get getAssetIdBatch to return the map1 sample.
Here's a dumbed down version of the code:
class VmExportTaskSplitter implements TaskSplitter<Export> {
#Inject
AssetServiceClient assetServiceClient
#Override
int splitAndSend(Export export) {
Map batch = [:]
Map tags = [:]
if (true) {
println('test')
batch = assetServiceClient.getAssetIdBatch(export.containerUuid,
export.userUuid, (String) batch.scrollId, tags)
print('batch: ')
println(batch)
}
return 1
}
}
And now the test:
class VmExportTaskSplitterSpecification extends Specification{
def "tags should be parsed correctly"(){
setup:
Export export = new Export(containerUuid: "000", userUuid: "000", chunkSize: 10)
FilterSet filterSet = new FilterSet()
filterSet.tag = [:]
filterSet.tag['tag.Location'] = 'Boston'
filterSet.tag['tag.Color'] = 'red'
Map<String, String> expectedTags = ['tag.Location':'Boston', 'tag.Color':'red']
ObjectMapper mapper = new ObjectMapper()
export.filters = mapper.writeValueAsString(filterSet)
def assetServiceClient = Mock(AssetServiceClientImpl) {
Map map1 = [assetIds:["1","2","3","4","5"],scrollId:null]
getAssetIdBatch(_ as String,_ as String, null, _ as Map) >> map1
getAssetIdBatch('000', '000', null, ['tag.Location':'Boston', 'tag.Color':'red']) >> map1
getAssetIdBatch(_, _, _, _) >> map1
}
VmExportTaskSplitter splitter = new VmExportTaskSplitter()
splitter.assetServiceClient = assetServiceClient
when:
splitter.splitAndSend(export)
then:
1 * assetServiceClient.getAssetIdBatch(_ as String, _ as String, _, _ as Map)
}
}
When this is run it can be seen that batch is still being printed as null. What am I doing wrong with setting up the interactions?
Using logging directory: './logs'
Using log file prefix: ''
test
batch: null
You –like so many before– ran into the one giant gotcha of Spock: the combination of Mocking and Stubbing and the fact that it has to happen in one line. Form the docs:
Mocking and stubbing of the same method call has to happen in the same interaction.
You stubbed assetServiceClient.getAssetIdBatch to return map1 in your given block and then you verified the mocked call in your then block. The latter one implicitly instructs the mock to return null instead of map1. Think
1 * assetServiceClient.getAssetIdBatch(_ as String, _ as String, _, _ as Map) // >> null
Change that line to
1 * assetServiceClient.getAssetIdBatch(_ as String, _ as String, _, _ as Map) >> map1
and define map1 in the method's scope and it will work as expected.
You probably want to remove the duplicate from the given block as well.
Don't worry about this being in the then block. Spock executes all mocking and stubbing before you enter the when block. Step through the code if you'd like to see it.
I don't understand why xml."con:cred"."ser:user" = "modified_username" doesn't change the text. Can someone explain this?
input = """
<kuk:acc xmlns:kuk="kuk">
<con:cred xmlns:con="http://www.bea.com/wli/sb/resources/config">
<ser:user xmlns:ser="http://www.bea.com/wli/sb/services">username</ser:user>
</con:cred>
</kuk:acc>
"""
def xml = new XmlSlurper(keepWhitespace:true).parseText(input).declareNamespace(
ser:"http://www.bea.com/wli/sb/services",
con:"http://www.bea.com/wli/sb/resources/config")
println xml."con:cred"."ser:user"
xml."con:cred"."ser:user" = "modified_username" // That doesn't work
println xml."con:cred"."ser:user"
xml.cred.user = "modified_username" // That works
println xml."con:cred"."ser:user"
/*
def outputBuilder = new StreamingMarkupBuilder()
String result = outputBuilder.bind{ mkp.yield xml }
println result
*/
I've been digging in this problem some time and was about to ask just the same thing. Given that the method invoked when using the overloaded '=' operator is putAt(int, Object), a closer look into GPathResult code:
public void putAt(final int index, final Object newValue) {
final GPathResult result = (GPathResult)getAt(index);
if (newValue instanceof Closure) {
result.replaceNode((Closure)newValue);
} else {
result.replaceBody(newValue);
}
}
shows that replaceBody should be invoked. As *tim_yates* points out, replaceBody works well, so it seems that replaceNode is invoked instead (I cannot see why). Digging in NodeChildren's replaceNode, we can see that
protected void replaceNode(final Closure newValue) {
final Iterator iter = iterator();
while (iter.hasNext()) {
final NodeChild result = (NodeChild) iter.next();
result.replaceNode(newValue);
}
}
the closure never gets called, so nothing is done when replaceNode is invoked. So I think that there's a bug in replaceNode (it does nothing), and when doing xml."con:cred"."ser:user" = "modified_username" the right part of the expression is evaluated as a Closure (I need help in this point to understand why :-).
For simplicity let's say I have code similar to this:
def testMethod(String txt) {
return txt;
}
public String evaluate(String expression) {
//String result = "${testMethod('asdasdasd')}";
String result = "${expression}";
return result;
}
I need the expression value which is passed to method "evaluate" to be executed.
in case of calling
// everything works perfectly well,
String result = "${testMethod('samplestring')}";
in case of calling
// (when expression = testMethod) - everything works perfectly well,
String result = "${expression}"("samplestring");
in case of calling
// (when expression = testMethod('samplestring')) - it's not working.
// I see testMethod('samplestring') as the result, but I need it to be evaluated.
String result = "${expression}"
How can I do that?
Thanks.
Thus should work as well;
Eval.me( "${expression}" )
Edit
As pointed out, this won't work as it stands, you need to pass in the script that contains the method with Eval.x like so:
def testMethod(String txt) {
txt
}
public String evaluate(String expression) {
String result = Eval.x( this, "x.${expression}" )
result
}
println evaluate( "testMethod('samplestring')" )
That will print samplestring
You may use the GroovyShell class for this purpose, but you will need to define a Binding AFAIK. This works in the Groovy Console:
def testMethod(txt) {
"$txt!";
}
def evaluate(String expression) {
def binding = new Binding(['testMethod': testMethod])
new GroovyShell(binding).evaluate(expression)
}
evaluate('testMethod("Hello World")');