Here is my usecase. I am running on version 2.1.6
I am trying to convert all array declarations to a more custom datatype which is not exposed to the end user.
For example:
def strr = new String[4] will be modified to def strr=new mycustomType(datatypeForString,4)
def str = [1,2,3] as String[] will be modified to def str=new myCustomType(dataTypeForString,list)
So far at the AST level, I registered a custom visitor and visitDeclarationExpression().
I am currently compiling the user script using custom GroovyClassLoader and executing it on the fly using myclassLoader.parseClass(usertext).newInstance().run()
When I am executing the above the sequence(compiling, loading, executing) the AST transformations are executed(verified through sys printls) but the final code execution does not reflect it.
Here is the snippet of the code that modifies the expression and sets it to the parent.
#Override
public void visitDeclarationExpression(DeclarationExpression expression) {
// TODO Auto-generated method stub
if (expression.getLeftExpression() instanceof VariableExpression
&& expression.getRightExpression() instanceof ArrayExpression) {
// Inspect the arrayExpressio
expression.setRightExpression(transformArrayExpression((ArrayExpression) expression.getRightExpression()));
}
else if (expression.getLeftExpression() instanceof VariableExpression
&& expression.getRightExpression() instanceof CastExpression) {
// Inspect the cast expression
expression.setRightExpression(transformCastExpression((CastExpression) expression.getRightExpression()));
}
super.visitDeclarationExpression(expression);
}
Any pointers?
Related
I have this junit (using JUnit 4.12) test in groovy that should only be executed if getenv != null:
import org.junit.Assume
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
...
#Test
public void skipWhenNull() throws Exception {
def getenv = System.getenv("bogos")
if( getenv == null) {
println "Its null!"
}
Assume.assumeNotNull(getenv);
println "Test executing!"
}
But when I run the test it prints: Its null! and then throws a NullPointer exception (in the line: Assume.assumeNotNull(getenv); ). Is the point of: Assume.assumeNotNull(expr) not that it will skip the test when expr evaluates to null instead of throwing a Nullpointer?
I thought this might be a bug, but I think this is rather a consequence of Groovy's dynamic type system in its default behavior. Assume.assumeNotNull(Object... objects) method uses varargs parameters. It means that in case of passing a non-array element compiler wraps it within an array of expected type.
String getenv = System.getenv("bogos");
Assume.assumeNotNull(getenv); // --> Assume.assumeNotNull(new Object[] { getenv });
This is what Java's static compiler does. So in case of getenv == null we end up with:
Assume.assumeNotNull(new Object[] { null });
A non-empty array that contains a single null element. On the other hand, if we specify a variable with an array type and we assign a null value to it, calling the same method will cause NullPointerException, just like in the following example:
String[] array = null;
Assume.assumeNotNull(array); // --> throws NPE
This is at least what happens in Java. Now, why it fails in Groovy unit test? Groovy is a dynamically typed language by default, so it behaves quite differently in this area. It seems like Groovy's type system applies null value to the method type without wrapping it within an array, so in case of passing a null to a method that expects e.g. Object... objects we always get objects == null instead of objects == new Object[] { null }.
I started questioning myself if this is a bug or not. From one perspective I would expect dynamic Groovy to behave in the same way that statically compiled code behaves. But on the other hand in a dynamically typed system, this distinction is acceptable (and maybe even desirable), because dynamic type system infers the type at the runtime. It sees null so it thinks that we intend to assign null value to a variable of type Object[].
Solution
There are two ways you can solve this problem.
1. Enable static compilation
If you don't use Groovy's dynamic and metaprogramming features in your test case you can easily annotate it with #groovy.transform.CompileStatic annotation to generate a bytecode that is more similar to Java's bytecode. For instance, this is what the bytecode of your method in the dynamic Groovy looks like:
#Test
public void skipWhenNull() throws Exception {
CallSite[] var1 = $getCallSiteArray();
Object getenv = var1[4].call(System.class, "bogos");
if (ScriptBytecodeAdapter.compareEqual(getenv, (Object)null)) {
var1[5].callCurrent(this, "Its null!");
}
var1[6].call(Assume.class, getenv);
var1[7].callCurrent(this, "Test executing!");
}
And here is the same method but annotated with #CompileStatic from the bytecode perspective:
#Test
public void skipWhenNull() throws Exception {
String getenv = System.getenv("bogos");
Object var10000;
if (getenv == null) {
DefaultGroovyMethods.println(this, "Its null!");
var10000 = null;
}
Assume.assumeNotNull(new Object[]{getenv});
var10000 = null;
DefaultGroovyMethods.println(this, "Test executing!");
var10000 = null;
}
2. Wrap getenv with an array
Alternatively, you can make more explicit call to Assume.assumeNotNull method. If you replace:
Assume.assumeNotNull(getenv);
with:
Assume.assumeNotNull([getenv] as Object[]);
then you will wrap parameter explicitly with Object[] array and you will prevent from passing an array object that is represented by null, but a single element array holding null value instead.
The code snippet is from the book < Groovy in action 2nd >, with minor modifications.
1 this code works as expected
package test
class InspectMe {
int outer(){
return inner()
}
int inner(){
return 1
}
}
def tracer = new TracingInterceptor(writer: new StringWriter())
def proxyMetaClass = ProxyMetaClass.getInstance(InspectMe)
proxyMetaClass.interceptor = tracer
InspectMe inspectMe = new InspectMe()
inspectMe.metaClass = proxyMetaClass
inspectMe.outer()
println(tracer.writer.toString())
output:
before test.InspectMe.outer()
before test.InspectMe.inner()
after test.InspectMe.inner()
after test.InspectMe.outer()
2 but this code's output is different
package test
class InspectMe {
int outer(){
return inner()
}
int inner(){
return 1
}
}
def tracer = new TracingInterceptor(writer: new StringWriter())
def proxyMetaClass = ProxyMetaClass.getInstance(InspectMe)
proxyMetaClass.interceptor = tracer
InspectMe inspectMe = new InspectMe()
proxyMetaClass.use(inspectMe){
inspectMe.outer()
}
println(tracer.writer.toString())
output:
before test.InspectMe.outer()
after test.InspectMe.outer()
It seems TracingInterceptor dosen't intercept inner methods in the second code.
Maybe it's normal behavior, But it seems to me like a bug.
Can somebody please explain this?
I don't know if this is a bug or not, but I can explain why this different behavior happens. Let's start with analyzing what InspectMe.outer() method implementation looks like at the bytecode level (we decompile .class file):
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.BytecodeInterface8;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
public class InspectMe implements GroovyObject {
public InspectMe() {
CallSite[] var1 = $getCallSiteArray();
MetaClass var2 = this.$getStaticMetaClass();
this.metaClass = var2;
}
public int outer() {
CallSite[] var1 = $getCallSiteArray();
return !__$stMC && !BytecodeInterface8.disabledStandardMetaClass() ? this.inner() : DefaultTypeTransformation.intUnbox(var1[0].callCurrent(this));
}
public int inner() {
CallSite[] var1 = $getCallSiteArray();
return 1;
}
}
As you can see, the outer() method tests the following predicate
!__$stMC && !BytecodeInterface8.disabledStandardMetaClass()
and if it evaluates to true, it invokes directly this.inner() method avoiding Groovy's MOP (meta-object protocol) layer (no metaclass involved in this case). Otherwise, it invokes var1[0].callCurrent(this) which means that inner() method gets invoked through Groovy's MOP with metaclass and interceptor involved in its execution.
The two examples you have shown in the question present a different way of setting metaclass field. In the first case:
def tracer = new TracingInterceptor(writer: new StringWriter())
def proxyMetaClass = ProxyMetaClass.getInstance(InspectMe)
proxyMetaClass.interceptor = tracer
InspectMe inspectMe = new InspectMe()
inspectMe.metaClass = proxyMetaClass // <-- setting metaClass with DefaultGroovyMethods
inspectMe.outer()
println(tracer.writer.toString())
we are invoking inspectMe.setMetaClass(proxyMetaClass) method using Groovy's MOP layer. This method gets added to InspectMe class by DefaultGroovyMethods.setMetaClass(GroovyObject self, MetaClass metaClass).
Now, if we take a quick look at how this setMetaClass method is implemented we will find something interesting:
/**
* Set the metaclass for a GroovyObject.
* #param self the object whose metaclass we want to set
* #param metaClass the new metaclass value
* #since 2.0.0
*/
public static void setMetaClass(GroovyObject self, MetaClass metaClass) {
// this method was introduced as to prevent from a stack overflow, described in GROOVY-5285
if (metaClass instanceof HandleMetaClass)
metaClass = ((HandleMetaClass)metaClass).getAdaptee();
self.setMetaClass(metaClass);
disablePrimitiveOptimization(self);
}
private static void disablePrimitiveOptimization(Object self) {
Field sdyn;
Class c = self.getClass();
try {
sdyn = c.getDeclaredField(Verifier.STATIC_METACLASS_BOOL);
sdyn.setBoolean(null, true);
} catch (Throwable e) {
//DO NOTHING
}
}
It invokes at the end private method disablePrimitiveOptimization(self). This method is responsible for assigning true to __$stMC class field (the constant Verifier.STATIC_METACLASS_BOOL stores __$stMC value). What does it mean in our case? It means that the predicate in outer() method:
return !__$stMC && !BytecodeInterface8.disabledStandardMetaClass() ? this.inner() : DefaultTypeTransformation.intUnbox(var1[0].callCurrent(this));
evaluates to false, because __$stMC is set to true. And in this case inner() method gets executed via MOP with metaClass and interceptor.
OK, but it explains the first case that works as expected. What happens in the second case?
def tracer = new TracingInterceptor(writer: new StringWriter())
def proxyMetaClass = ProxyMetaClass.getInstance(InspectMe)
proxyMetaClass.interceptor = tracer
InspectMe inspectMe = new InspectMe()
proxyMetaClass.use(inspectMe){
inspectMe.outer()
}
println(tracer.writer.toString())
Firstly, we need to check what does proxyMetaClass.use() look like:
/**
* Use the ProxyMetaClass for the given Closure.
* Cares for balanced setting/unsetting ProxyMetaClass.
*
* #param closure piece of code to be executed with ProxyMetaClass
*/
public Object use(GroovyObject object, Closure closure) {
// grab existing meta (usually adaptee but we may have nested use calls)
MetaClass origMetaClass = object.getMetaClass();
object.setMetaClass(this);
try {
return closure.call();
} finally {
object.setMetaClass(origMetaClass);
}
}
It's pretty simple - it replaces metaClass for the time of closure execution and it sets the old metaClass back when closure's execution completes. Sounds like something similar to the first case, right? Not necessarily. This is Java code and it invokes object.setMetaClass(this) method directly (the object variable is of type GroovyObject which contains setMetaClass method). It means that the field __$stMC is not set to true (the default value is false), so the predicate in outer() method has to evaluate:
BytecodeInterface8.disabledStandardMetaClass()
If we run the second example we will see that this method call returns false:
And that is why the whole expression
!__$stMC && !BytecodeInterface8.disabledStandardMetaClass()
evaluates to true and the branch that invokes this.inner() directly gets executed.
Conclusion
I don't know if it was intended or not, but as you can see dynamic setMetaClass method disables primitive optimizations and continues using MOP, while ProxyMetaClass.use() sets the metaClass keeping primitive optimizations enabled and caused a direct method call. I guess this example shows a corner case no one thought about when implementing ProxyMetaClass class.
UPDATE
It seems like the difference between these two methods exists because ProxyMetaClass.use() was implemented in 2005 for Groovy 1.x and it got updated for the last time in 2009. This __$stMC field was added in 2011 and the DefaultGroovyMethods.setMetaClass(GroovyObject object, Closure cl) was introduced in 2012 according to its javadoc that says this method is available since Groovy 2.0.
I'm running into a problem with GroovyScriptEngine - it seems not to be able to work with inner classes. Anyone know whether there's some limitation in GroovyScriptEngine or a workaround?
I have a directory with these two files:
// MyClass.groovy
public class MyClass {
MyOuter m1;
MyOuter.MyInner m2;
}
and
// MyOuter.groovy
public class MyOuter {
public static class MyInner {}
}
I have a following test class:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import groovy.util.GroovyScriptEngine;
public class TestGroovyScriptEngine {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
final File myGroovySourceDir = new File("C:/MyGroovySourceDir");
final URL[] urls = { myGroovySourceDir.toURL() };
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(urls,
Thread.currentThread().getContextClassLoader());
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
}
}
When I run it I get the following compilation error:
Exception in thread "main" org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
C:\MyGroovySourceDir\MyClass.groovy: 3: unable to resolve class MyOuter.MyInner
# line 3, column 2.
MyOuter.MyInner m2;
^
1 error
at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:311)
at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:983)
at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:633)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:582)
at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:354)
at groovy.lang.GroovyClassLoader.access$300(GroovyClassLoader.java:87)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:323)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:320)
at org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache.getAndPut(ConcurrentCommonCache.java:147)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:318)
at groovy.util.GroovyScriptEngine$ScriptClassLoader.doParseClass(GroovyScriptEngine.java:248)
at groovy.util.GroovyScriptEngine$ScriptClassLoader.parseClass(GroovyScriptEngine.java:235)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:307)
at groovy.lang.GroovyClassLoader.recompile(GroovyClassLoader.java:811)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:767)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:836)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:824)
I would have expected a "clean compile", but the inner class seems to be causing problems.
My groovy classes compile fine at the command line using groovyc, or in Eclipse.
You have faced an edge case here. To clarify what happens let's define the initial conditions:
you have a Java (or Groovy) class that gets executed inside JVM
you have two Groovy classes that get loaded outside of the JVM
The problem you have described does not exist if you put these two Groovy classes inside the same path you execute your Java class from - in this case IDE takes care to compile these Groovy classes and put them to the classpath of a JVM that gets started to run your Java test class.
But this is not your case and you are trying to load these two Groovy classes outside the running JVM using GroovyClassLoader (which extends URLClassLoader btw). I will try to explain in the simplest possible words what happened that adding field of type MyOuter does not throw any compilation error, but MyOuter.MyInner does.
When you execute:
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
Groovy class loader goes to script file lookup part, because it was not able to find MyClass in the current classpath. This is the part responsible for it:
// at this point the loading from a parent loader failed
// and we want to recompile if needed.
if (lookupScriptFiles) {
// try groovy file
try {
// check if recompilation already happened.
final Class classCacheEntry = getClassCacheEntry(name);
if (classCacheEntry != cls) return classCacheEntry;
URL source = resourceLoader.loadGroovySource(name);
// if recompilation fails, we want cls==null
Class oldClass = cls;
cls = null;
cls = recompile(source, name, oldClass);
} catch (IOException ioe) {
last = new ClassNotFoundException("IOException while opening groovy source: " + name, ioe);
} finally {
if (cls == null) {
removeClassCacheEntry(name);
} else {
setClassCacheEntry(cls);
}
}
}
Source: src/main/groovy/lang/GroovyClassLoader.java#L733-L753
Here URL source = resourceLoader.loadGroovySource(name); it loads the full file URL to the source file and here cls = recompile(source, name, oldClass); it executes class compilation.
There are several phases involved in Groovy class compilation. One of them is Phase.SEMANTIC_ANALYSIS which analyses class fields and their types for instance. At this point ClassCodeVisitorSupport executes visitClass(ClassNode node) for MyClass class and following line
node.visitContents(this);
starts class contents processing. If we take a look at the source code of this method:
public void visitContents(GroovyClassVisitor visitor) {
// now let's visit the contents of the class
for (PropertyNode pn : getProperties()) {
visitor.visitProperty(pn);
}
for (FieldNode fn : getFields()) {
visitor.visitField(fn);
}
for (ConstructorNode cn : getDeclaredConstructors()) {
visitor.visitConstructor(cn);
}
for (MethodNode mn : getMethods()) {
visitor.visitMethod(mn);
}
}
Source: src/main/org/codehaus/groovy/ast/ClassNode.java#L1066-L108
we will see that it analyses and process class properties, fields, constructors and methods. At this phase it resolves all types defined for these elements. It sees that there are two properties m1 and m2 with types MyOuter and MyOuter.MyInner accordingly, and it executes visitor.visitProperty(pn); for them. This method executes the one we are looking for - resolve()
private boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses) {
resolveGenericsTypes(type.getGenericsTypes());
if (type.isResolved() || type.isPrimaryClassNode()) return true;
if (type.isArray()) {
ClassNode element = type.getComponentType();
boolean resolved = resolve(element, testModuleImports, testDefaultImports, testStaticInnerClasses);
if (resolved) {
ClassNode cn = element.makeArray();
type.setRedirect(cn);
}
return resolved;
}
// test if vanilla name is current class name
if (currentClass == type) return true;
String typeName = type.getName();
if (genericParameterNames.get(typeName) != null) {
GenericsType gt = genericParameterNames.get(typeName);
type.setRedirect(gt.getType());
type.setGenericsTypes(new GenericsType[]{ gt });
type.setGenericsPlaceHolder(true);
return true;
}
if (currentClass.getNameWithoutPackage().equals(typeName)) {
type.setRedirect(currentClass);
return true;
}
return resolveNestedClass(type) ||
resolveFromModule(type, testModuleImports) ||
resolveFromCompileUnit(type) ||
resolveFromDefaultImports(type, testDefaultImports) ||
resolveFromStaticInnerClasses(type, testStaticInnerClasses) ||
resolveToOuter(type);
}
Source: src/main/org/codehaus/groovy/control/ResolveVisitor.java#L343-L378
This method gets executed for both MyOuter and MyOuter.MyInner classes. It is worth mentioning that class resolving mechanism only checks if given class is available in the classpath and it does not load or parse any classes. That is why MyOuter gets recognized when this method reaches resolveToOuter(type). If we take a quick look at its source code we will understand why it works for this class:
private boolean resolveToOuter(ClassNode type) {
String name = type.getName();
// We do not need to check instances of LowerCaseClass
// to be a Class, because unless there was an import for
// for this we do not lookup these cases. This was a decision
// made on the mailing list. To ensure we will not visit this
// method again we set a NO_CLASS for this name
if (type instanceof LowerCaseClass) {
classNodeResolver.cacheClass(name, ClassNodeResolver.NO_CLASS);
return false;
}
if (currentClass.getModule().hasPackageName() && name.indexOf('.') == -1) return false;
LookupResult lr = null;
lr = classNodeResolver.resolveName(name, compilationUnit);
if (lr!=null) {
if (lr.isSourceUnit()) {
SourceUnit su = lr.getSourceUnit();
currentClass.getCompileUnit().addClassNodeToCompile(type, su);
} else {
type.setRedirect(lr.getClassNode());
}
return true;
}
return false;
}
Source: src/main/org/codehaus/groovy/control/ResolveVisitor.java#L725-L751
When Groovy class loader tries to resolve MyOuter type name it reaches
lr = classNodeResolver.resolveName(name, compilationUnit);
which locates script with a name MyOuter.groovy and it creates a SourceUnit object associated with this script file name. It is simply something like saying "OK, this class is not in my classpath at the moment, but there is a source file I can see that once compiled it will provide a valid type of name MyOuter". This is why it finally reaches:
currentClass.getCompileUnit().addClassNodeToCompile(type, su);
where currentClass is an object associated with MyClass type - it adds this source unit to MyClass compilation unit, so it gets compiled with the MyClass class. And this is the point where resolving
MyOuter m1
class property ends.
In the next step it picks MyOuter.MyInner m2 property and it tries to resolve its type. Keep in mind - MyOuter got resolved correctly, but it didn't get loaded to the classpath, so it's static inner class does not exist in any scope, yet. It goes through the same resolving strategies as MyOuter, but any of them works for MyOuter.MyInner class. And this is why ResolveVisitor.resolveOrFail() eventually throws this compilation exception.
Workaround
OK, so we know what happens, but is there anything we can do about it? Luckily, there is a workaround for this problem. You can run your program and load MyClass successfully only if you load MyOuter class to Groovy script engine first:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import groovy.util.GroovyScriptEngine;
public class TestGroovyScriptEngine {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
final File myGroovySourceDir = new File("C:/MyGroovySourceDir");
final URL[] urls = { myGroovySourceDir.toURL() };
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(urls,
Thread.currentThread().getContextClassLoader());
groovyScriptEngine.getGroovyClassLoader().loadClass("MyOuter");
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
}
}
Why does it work? Well, semantic analysis of MyOuter class does not cause any problems, because all types are known at this stage. This is why loading MyOuter class succeeds and it results in Groovy script engine instance knows what MyOuter and MyOuter.MyInner types are. So when you next load MyClass from the same Groovy script engine it will apply different resolving strategy - it will find both classes available to the current compilation unit and it wont have to resolve MyOuter class based on its Groovy script file.
Debugging
If you want to examine this use case better it is worth to run a debugger and see analyze what happens at the runtime. You can create a breakpoint at line 357 of ResolveVisitor.java file for instance, to see described scenario in action. Keep in mind one thing though - resolveFromDefaultImports(type, testDefaultImports) will try to lookup MyClass and MyOuter classes by applying default packages like java.util, java.io, groovy.lang etc. This resolve strategy kicks in before resolveToOuter(type) so you have to patiently jump through them. But it is worth it to see and get a better understanding about how things work. Hope it helps!
Generated accessors of parse tree context nodes do not conform getProperty()/isProperty()/hasProperty() standard. As a result, ST can’t be applied to the parse tree directly. There seems to be 3 alternatives to apply ST to the generated parse trees:
Create ST model adapter classes for each generated context node. Then ST can be applied directly to the generated parse tree. Duplicate work here is creating model adapters.
For every parse tree node create a wrapper node that conforms getProperty()/isProperty()/hasProperty() standard. Then ST can be applied to wrapper nodes. Duplicate work here is creating wrapper nodes. (In this case parse tree is not even required; auto-parse-tree construction could be turned off and wrapper (AST) nodes could be created in grammar actions).
Create a Visitor. Each visit*() instantiates an ST specific to a context node being visited, sets parameters (which could be STs returned by visiting child nodes or simple strings) and returns the ST. This is the option I’m currently using. Duplicate work here is creating visitor and assigning template parameters in code.
Is there an Antlr4 option that generates accessors of parse tree context nodes that conform getProperty()/isProperty()/hasProperty() standard? Or is there an ST4 option that allows it accessing property() instead of looking for getProperty()?
It would be nice to simply instantiate an ST template with a root context node as a parameter and let ST traverse the tree.
Just wanted to share a solution that almost avoids duplicate work while using approach #1 from my question.
Step 1: create a model adaptor that uses reflection to call a method that does not conform getProperty()/isProperty()/hasProperty() standard.
private static class MyModelAdaptor extends ObjectModelAdaptor {
#Override
public synchronized Object getProperty(Interpreter interp, ST self, Object o, Object property, String propertyName) throws STNoSuchPropertyException {
try {
return super.getProperty(interp, self, o, property, propertyName);
} catch (STNoSuchPropertyException noProperty) {
final Class<?> cls = o.getClass();
try {
return cls.getMethod(propertyName).invoke(o);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw noProperty;
}
}
}
}
Step 2: Register model adaptors
public static STGroup registerAdaptors(STGroup stg) {
final MyModelAdaptor adaptor = new MyModelAdaptor();
for (final Class<?> cls : MyParser.class.getDeclaredClasses()) {
if (isSubclassOf(cls, ParserRuleContext.class)) {
stg.registerModelAdaptor(cls, adaptor);
}
}
return stg;
}
Step 3: implement isSubclassOf() method so that registerAdaptors() compiles:
private static boolean isSubclassOf(Class<?> cls, Class<?> superCls) {
while (cls != null) {
if (cls == superCls) {
return true;
}
cls = cls.getSuperclass();
}
return false;
}
I have a java file which uses java.sql.Statement.execute as below.
public class Dummy
{
public void execute(String q) throws SQLException
{
...
Statement stmt = conn.createStatement();
...
stmt.execute(q);
...
}
}
My use case is I want to identify what are all the classes and their method names which use "Statement.execute(String)" using JDT ASTVisitor. Is this possible?
I found below entry using eclipse ASTView plugin.
method binding: Statement.execute(String)
How can I get this method binding value in my ASTVisitor.
I tried this.
#Override
public boolean visit(MethodInvocation node)
{
IMethodBinding iMethod = (IMethodBinding) node.resolveMethodBinding();
if(iMethod != null)
System.out.println("Binding "+iMethod.getName());
return super.visit(node);
}
but node.resolveMethodBinding() always returns null.
... i want to identify what are all the classes and its method names which using "Statement.execute(String)"
This sounds like a job for the org.eclipse.jdt.core.search.SearchEngine, which will produce the results much faster than traversing all your source files using a visitor.
... node.resolveMethodBinding() always returns null
This depends on how you obtained the AST. See, e.g., org.eclipse.jdt.core.dom.ASTParser.setResolveBindings(boolean)