Search code examples
javareflectioncode-generationanonymous-classinria-spoon

Create instance of anonymous class with SPOON


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.


Solution

  • 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.