javaparser icon indicating copy to clipboard operation
javaparser copied to clipboard

Solve method declarations in anonymous inner classes

Open julian5392 opened this issue 1 year ago • 10 comments

When solving for method declarations in anonymous inner classes, the result is not what I want. The result is:

Main.main(java.lang.String[])
java.io.PrintStream.println(java.lang.String)
"Main.main(java.lang.String[])" -> "java.io.PrintStream.println(java.lang.String)"
java.lang.Runnable.run()
"Main.main(java.lang.String[])" -> "java.lang.Runnable.run()"
Main.Anonymous-9904eed3-5b31-434d-9b07-63e5b069a3a2.run()
java.io.PrintStream.println(java.lang.String)
"Main.Anonymous-9904eed3-5b31-434d-9b07-63e5b069a3a2.run()" -> "java.io.PrintStream.println(java.lang.String)"

Why have such results as Anonymous-9904eed3-5b31-434d-9b07-63e5b069a3a2 when solving the method declaration "run"?However, when solving the methodcallexpr "runnable.run()", the result is java.lang.Runnable.run().

This is a testcode:

package testcode;

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;

import java.util.List;

public class Main {
    public static void main(String[] args) {
        String sourcecode= "public class Main {\n" +
                "\n" +
                "        public static void main(String[] args) {\n" +
                "            Runnable runnable = new Runnable() {\n" +
                "                @Override\n" +
                "                public void run() {\n" +
                "                    System.out.println(\"This is an anonymous inner class.\");\n" +
                "                }\n" +
                "            };\n" +
                "\n" +
                "            runnable.run();\n" +
                "\n" +
                "    }\n" +
                "\n" +
                "}";
        CombinedTypeSolver myTypeSolver = new CombinedTypeSolver();
        myTypeSolver.add(new ReflectionTypeSolver());
        JavaSymbolSolver symbolSolver = new JavaSymbolSolver(myTypeSolver);

        StaticJavaParser
                .getConfiguration()
                .setSymbolResolver(symbolSolver);
        CompilationUnit cu = StaticJavaParser.parse(sourcecode);

        List<MethodDeclaration> methods = cu.findAll(MethodDeclaration.class);
        for (MethodDeclaration method : methods) {
            String fullCallerMethodName = method.resolve().getQualifiedSignature();

            System.out.println(fullCallerMethodName);
            List<MethodCallExpr> methodCallees = method.findAll(MethodCallExpr.class);
            for (MethodCallExpr callee : methodCallees) {
                String calleeMethodName = callee.resolve().getQualifiedSignature();
                System.out.println(calleeMethodName);
                System.out.println("\"" + fullCallerMethodName + "\"" + " -> " + "\"" + calleeMethodName + "\"");
            }

        }
    }
}

julian5392 avatar Jul 17 '23 09:07 julian5392

It is by convention part of the name of the anonymous class.

jlerbsc avatar Jul 17 '23 11:07 jlerbsc

OK, I got it. But themethod run() is overridden by the anonymous class, why the calling relationship is "Main.main(java.lang.String[])" -> "java.lang.Runnable.run()" and not "Main.main(java.lang.String[])" -> "Main.Anonymous-9904eed3-5b31-434d-9b07-63e5b069a3a2.run()"

julian5392 avatar Jul 17 '23 12:07 julian5392

And I don't know if it's a bug: for the following test case, I instantiated the Animal interface and enabled the anonymous class, but didn't call the overridden method which defined in the anonymous class. The analysis results of the javaparser include Main.main(java.lang.String[])" -> "java.io.PrintStream.println(java.lang.String)" and "Main.main(java.lang.String[])" -> "Person.speak()". I think this is a bit abnormal. This is the result:

Main.main(java.lang.String[])
java.io.PrintStream.println(java.lang.String)
"Main.main(java.lang.String[])" -> "java.io.PrintStream.println(java.lang.String)"
Person.speak()
"Main.main(java.lang.String[])" -> "Person.speak()"
Animal.eat()
"Main.main(java.lang.String[])" -> "Animal.eat()"
Main.Anonymous-36bd0a62-0aab-453c-b378-0b040f5bd08b.makeSound()
java.io.PrintStream.println(java.lang.String)
"Main.Anonymous-36bd0a62-0aab-453c-b378-0b040f5bd08b.makeSound()" -> "java.io.PrintStream.println(java.lang.String)"
Person.speak()
"Main.Anonymous-36bd0a62-0aab-453c-b378-0b040f5bd08b.makeSound()" -> "Person.speak()"
Animal.makeSound()
Animal.eat()
java.io.PrintStream.println(java.lang.String)
"Animal.eat()" -> "java.io.PrintStream.println(java.lang.String)"
Person.speak()
java.io.PrintStream.println(java.lang.String)
"Person.speak()" -> "java.io.PrintStream.println(java.lang.String)"
public class MsgLogger {
   public static void main(String[] args) {
       Animal animal = new Animal() {
           public void makeSound() {
               System.out.println("The animal is making a sound.");
               Person person = new Person();
               person.speak();
           }
       };
       animal.eat(); 
   }
}

interface Animal {
   void makeSound();

   default void eat() {
       System.out.println("The animal is eating.");
   }
}
class Person {
   public void speak() {
       System.out.println("Hello, I'm a person.");
   }
}

julian5392 avatar Jul 17 '23 13:07 julian5392

OK, I got it. But themethod run() is overridden by the anonymous class, why the calling relationship is "Main.main(java.lang.String[])" -> "java.lang.Runnable.run()" and not "Main.main(java.lang.String[])" -> "Main.Anonymous-9904eed3-5b31-434d-9b07-63e5b069a3a2.run()"

Because the "runnable" variable is typed Runnable. So when JP tries to resolve the "run" method, he looks at the type of the scope which is Runnable.

jlerbsc avatar Jul 17 '23 15:07 jlerbsc

The analysis results of the javaparser include Main.main(java.lang.String[])" -> "java.io.PrintStream.println(java.lang.String)" and "Main.main(java.lang.String[])" -> "Person.speak()". I think this is a bit abnormal.

This result corresponds to what you have programmed. You are recursively searching for all method calls executed inside the "main" method.

jlerbsc avatar Jul 17 '23 15:07 jlerbsc

The analysis results of the javaparser include Main.main(java.lang.String[])" -> "java.io.PrintStream.println(java.lang.String)" and "Main.main(java.lang.String[])" -> "Person.speak()". I think this is a bit abnormal.

This result corresponds to what you have programmed. You are recursively searching for all method calls executed inside the "main" method.

But I only find the “findAll” method in javaparser to solve my problem and I dont think its the fault of recursively searching. The anonymous class is defined in the method, and "findAll" will find all the method call expressions in the method declaration (including the method call expression in the anonymous class). Sorry, I'm not too familiar with javaparser. Is there any way javaparser can solve my problem? Thanks for your help.

julian5392 avatar Jul 19 '23 01:07 julian5392

OK, I got it. But themethod run() is overridden by the anonymous class, why the calling relationship is "Main.main(java.lang.String[])" -> "java.lang.Runnable.run()" and not "Main.main(java.lang.String[])" -> "Main.Anonymous-9904eed3-5b31-434d-9b07-63e5b069a3a2.run()"

Because the "runnable" variable is typed Runnable. So when JP tries to resolve the "run" method, he looks at the type of the scope which is Runnable.

But in another example, I find this type-based approach problematic. When solving the method call expression obj.print(), the result is Parent.print(). Although the type of obj is Parent, the method that is actually called here is Child.print(). I think such results are problematic.

class Parent {
    void print() {
        System.out.println("Parent");
    }
}

class Child extends Parent {
    @Override
    void print() {
        System.out.println("Child");
    }
}

public class Main {
    public static void main(String[] args) {
        Parent obj = new Child();
        obj.print();
    }
}

julian5392 avatar Jul 19 '23 03:07 julian5392

Is there any way javaparser can solve my problem?

Yes there are certainly several ways to do this. The first that comes to mind would be to create your own visitor (inheriting from VoidVisitorAdapter) that would filter out the nodes you don't want to browse. You also have a method findAll which accepts a Predicate (which will allow you to apply a filter).

jlerbsc avatar Jul 19 '23 06:07 jlerbsc

But in another example, I find this type-based approach problematic. When solving the method call expression obj.print(), the result is Parent.print(). Although the type of obj is Parent, the method that is actually called here is Child.print(). I think such results are problematic.

You get the same behavior in an IDE. In your example the type of the "obj" variable is Parent.

jlerbsc avatar Jul 19 '23 06:07 jlerbsc

You get the same behavior in an IDE. In your example the type of the "obj" variable is Parent.

Yes, you're right. But I think this is problematic because the actual call is Child.print() instead of Parent.print(). This type-based approach can be problematic.

julian5392 avatar Jul 19 '23 07:07 julian5392