1

I need to pass an instance of an anonymous class as a function parameter, in Spoon. In my case I have an anonymous Comparator that I need to pass to a function that gets Comparator<?> as a parameter.

Here's my code:

public boolean isToBeProcessed(CtType<?> candidate) {
        if(candidate instanceof CtClass<?> && ((CtClass<?>)candidate).isAnonymous()==true){
            CtClass<?> clas = (CtClass<?>)candidate;
            List<CtMethod<?>> list = clas.filterChildren(
                  new AbstractFilter<CtMethod<?>>(CtMethod.class) {
                    @Override
                    public boolean matches(CtMethod<?> method) {
                      return method.getSimpleName().equals("compare");
                    }
                  }
                ).list();
        return !(list==null || list.isEmpty());
        }
        return false;
    }
    
    @Override
    public void process(CtType<?> element) {
        // here I need to pass the anonymous Comparator class
        testComparator( ??? ); 
    }

    public void testComparator(Comparator<?> comparator) {
      ......
    }

I'm new to Spoon and I would appreciate your help with this.

Thanks.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
user1579191
  • 91
  • 2
  • 10
  • @Holger I guess `public void testComparator...` is from code to be processed with a processor (overriding `isToBeProcessed` and `process`). `CtType` is `spoon.reflect.declaration.CtClass` and not `javassist.CtClass`. – Dmytro Mitin Sep 12 '20 at 09:15
  • @ Dmytro Mitin is right. The above code is from a Processor class of Spoon. Exactly as shown in the code he wrote. – user1579191 Sep 13 '20 at 08:57
  • 1
    @DmytroMitin ok. So it's not Javassist. However, since your answer still doesn't show, how the OP can create a `Comparator` out of the `CtType`, to be passed to `testComparator`, you seem to agree that this isn't what the OP should do. Since the OP didn't ask how to get a `CtNewClass>`, perhaps, you should tell the OP, what to do with that `CtNewClass>`... – Holger Sep 13 '20 at 13:42
  • @user1579191 Could you write code before transformation by processor and desired code after transformation? – Dmytro Mitin Sep 13 '20 at 14:15
  • @user1579191 At the moment it seems that the code **before** transformation is like `testComparator(new Comparator() { @Override public int compare(Object o1, Object o2) { return 0; } });` – Dmytro Mitin Sep 13 '20 at 14:18
  • I didn't plan to do transformation. The method testComparator(Comparator> comparator) is a method I wrote (so I can change the signature if needed). The method gets a Comparator and only test if it's valid or not ( if it could throw a "Comparison method violates its general contract!" exception). So I tried creating an instance of the comparator from the anonymous class. Maybe I should first create a concrete class for the comparator and save it to a file, and than create an instance of this class? Do you know how to do that? thanks! – user1579191 Sep 15 '20 at 06:43
  • @user1579191 Why do you need Spoon? What problem are you solving with it? Why can't you write just `testComparator(new Comparator...`? I still don't understand how you're going to use your processor. Is `isToBeProcessed` correct? With current `isToBeProcessed` your processor seems to handle calls like `testComparator(...)` where some anonymous-class comparator is already inside. – Dmytro Mitin Sep 15 '20 at 12:43
  • 1
    @Dmytro Mitin - I need to detect all invalid comparators in our system. Just detect them, not to correct them yet. We have hundreds of comparators. So I wrote a function that handles one Comparator and tells if it's valid or not. Now I want to invoke this function for every Comparator in our system and write to a file all invalid comparators. So I need to pass somehow the Comparator class or instance to testComparator(), but I have hard time doing this. – user1579191 Sep 16 '20 at 07:03
  • @user1579191 Thank you for your answer, now I understand better what you're doing. So `testComparator` is not what should be processed by processor as I thought, it's what should be called every time when processor processes something according to `isToBeProcessed`. Could you provide the code of `testComparator`? Can't it be written with signature `void testComparator(CtElement comparator)` rather than `void testComparator(Comparator> comparator)` i.e. having AST of comparator rather than object of comparator itself? Transformation of AST of X to object of X is actually compilation. You ... – Dmytro Mitin Sep 16 '20 at 11:24
  • @user1579191 ... can run compiler programmatically inside processor but there can be an issue with resolving names. Imagine `foo(new Comparator...`. `A` can make sense in context of code to be processed (`dir` in my answer) but not in context of processor. – Dmytro Mitin Sep 16 '20 at 11:25
  • @user1579191 I guess I managed to do what you asked. See new version of my answer. – Dmytro Mitin Sep 18 '20 at 02:14
  • @user1579191 Did you have a chance to look at my answer? Does it work for you? – Dmytro Mitin Sep 20 '20 at 16:35

1 Answers1

0

I guess I managed to do what you asked.

dir/App.java

import java.util.Comparator;

public class App {
    public static void main(String[] args) {
        foo1(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return 0;
            }
        });

        foo2(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return 0;
            }
        });
    }
}

src/main/java/MyProcessor.java

import spoon.Launcher;
import spoon.processing.AbstractProcessor;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtType;
import spoon.reflect.visitor.filter.AbstractFilter;
import spoon.support.compiler.FileSystemFile;

import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;

public class MyProcessor extends AbstractProcessor<CtType<?>> {
    @Override
    public boolean isToBeProcessed(CtType<?> candidate) {
        if (candidate instanceof CtClass<?> && ((CtClass<?>) candidate).isAnonymous() == true) {
            CtClass<?> clas = (CtClass<?>) candidate;
            List<CtMethod<?>> list = clas.filterChildren(
                    new AbstractFilter<CtMethod<?>>(CtMethod.class) {
                        @Override
                        public boolean matches(CtMethod<?> method) {
                            return method.getSimpleName().equals("compare");
                        }
                    }
            ).list();
            return !(list == null || list.isEmpty());
        }
        return false;
    }

    @Override
    public void process(CtType<?> element) {
        String implementsString = element.getParent().toString()
                .replace("()", "")
                .replace("new", "implements");

        String codeA =
                "public class A {\n" +
                "  public void m() {\n" +
                "    Main.testComparator(new B());\n" +
                "  }\n" +
                "}";

        String codeB = "public class B " + implementsString;

        String fileNameA = "dir2/A.java";
        String fileNameB = "dir2/B.java";
        try (FileWriter fileWriterA = new FileWriter(fileNameA);
             FileWriter fileWriterB = new FileWriter(fileNameB)) {
            fileWriterA.write(codeA);
            fileWriterB.write(codeB);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        Launcher launcher = new Launcher();
        launcher.addInputResource("dir2");
        Collection<CtType<?>> allTypes = launcher.buildModel().getAllTypes();
        CtClass<?> ctClassA = (CtClass<?>) allTypes.stream().findFirst().get();

        Object instance = ctClassA.newInstance();
        Class<?> classA = instance.getClass();
        try {
            Method method = classA.getMethod("m");
            method.invoke(instance);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
}

src/main/java/Main.java

import spoon.Launcher;

import java.util.Comparator;

public class Main {
    public static void testComparator(Comparator<?> comparator) {
        System.out.println("test Comparator");
    }

    public static void main(String[] args) {
        Launcher launcher = new Launcher();
        launcher.addInputResource("dir");
        launcher.addProcessor(new MyProcessor());
        launcher.run();
    }
}

dir2/A.java

dir2/B.java

pom.xml

...
<dependency>
    <groupId>fr.inria.gforge.spoon</groupId>
    <artifactId>spoon-core</artifactId>
    <version>8.2.0</version>
</dependency>

Also directory spooned-classes has to be added to classpath. I'm using sbt. This can be specified there as unmanagedClasspath in Runtime ++= Seq(file("spooned-classes")).classpath. In Maven this should be specified similarly.

Output:

[main] INFO spoon.Launcher - Running in NOCLASSPATH mode (doc: http://spoon.gforge.inria.fr/launcher.html).
[main] INFO spoon.Launcher - Running in NOCLASSPATH mode (doc: http://spoon.gforge.inria.fr/launcher.html).
test Comparator
[main] INFO spoon.Launcher - Running in NOCLASSPATH mode (doc: http://spoon.gforge.inria.fr/launcher.html).
test Comparator

So testComparator in Main was executed twice corresponding to foo1 and foo2 in App.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66