Gradle plugin with reflections

Hello.
I want to do a Gradle task on Java that will look for classes marked an annotation in my Java application and process those classes. This my task method:

@TaskAction
public void greet() throws {

Reflections reflections = new Reflections(“ru.mycomp”);

for (Class<?> clazz : reflections.getTypesAnnotatedWith(MisEntity2.class)) {
System.out.println(clazz.toString());
}
}
But the code find the clasess in Gradle project, not in my application.
Any ideas…
Thanks.

The classpath for tasks is defined by the buildscript { classpath { ... } }.

I’m guessing you’ll want to create a UrlClassLoader based on configurations.runtime.files and pass that to Reflections

Thank you very much Lance for your answer. Could you answer more in detail with example, because I am beginner.

@TaskAction
public void greet() {
    Set<File> runtimeFiles = project.configurations.runtime.files
    URL[] urls = runtimeFiles*.toUri().toUrl()
    ClassLoader classLoader = new java.net.URLClassLoader(urls)
    Configuration config = new ConfigurationBuilder("ru.mycomp", classLoader)
    Reflections reflections = new Reflections(config)
    for (Class<?> clazz : reflections.getTypesAnnotatedWith(MisEntity2.class)) {
        ...
    }
}

Thanks a lot. Tomorrow I will try. When I have results, I will post them.

This is my function which I made from answer above:

private void scanAnnotatedFields2() throws MalformedURLException {
        URL[] urls;
        List<URL> listOfURL = new ArrayList<>();
        for (Configuration configuration:  getProject().getConfigurations() ){
            System.out.println(configuration.getName());
        }
        System.out.println("---");
        Set<File> runtimeFiles = getProject().getConfigurations().findByName("runtime").getFiles();
        for(File file : runtimeFiles){
            listOfURL.add(file.toURI().toURL());
            System.out.println(file.getPath());
        }
        urls = listOfURL.toArray(new URL[0]);

        ClassLoader classLoader = new java.net.URLClassLoader(urls);

        Reflections reflections = new Reflections("ru.spsr", classLoader);

        for (Class<?> clazz : reflections.getTypesAnnotatedWith(MisEntity3.class)) {
            System.out.println(clazz.toString());
        }
    }

It don’t work. You advised “The classpath for tasks was defined by the buildscript { classpath { … } }.”. I have in this section reference on gradle plugin. Perhaps this is problem?

You’ll need to wire your task into Gradle’s DAG so that it runs after JavaCompile. Eg:

task myTask(type: MyReflectionsTask) {
   dependsOn compileJava
   ...
}

You’ll obviously need reflections on the buildscript.depencies.classpath (I’m not sure if this task is from buildSrc or a plugin). You won’t need the scanned classes on the buildscript classpath because we are using UrlClassloader to avoid a chicken-or-egg scenario.

This is wiring:

public void apply(Project project) {
                Task task = project.getTasks().create("mis", Task.class);
                org.gradle.api.Task javaCompile = project.getTasks().getByName("compileJava");
                org.gradle.api.Task build = project.getTasks().getByName("build");
                //build.dependsOn(task);
                task.dependsOn(javaCompile);
            }    

This is classpath:

buildscript {
	repositories {
		mavenLocal()
		mavenCentral()
	}
	dependencies {
		classpath 'ru.spsr:mis-template-plugin:1.0.0-SNAPSHOT'
		classpath 'org.reflections:reflections:0.9.11'
	}
}

It doesn’t work :frowning:
Application shows the classes of plugin project… :frowning:

You might want to use the following so reflections doesn’t scan the buildscript classloader

new UrlClassloader(urls, null) 

You can check that the classloader is configured properly by attempting:

classLoader.loadClass("foo.bar.MyClass")

If that’s able to load one of the classes you are scanning for, I think you’re best to head over to the reflections forum as this is no longer a gradle question

java.lang.ClassNotFoundException: ru.spsr.Request - The class is that I am scanning for in my application. :frowning:

Perhaps I’ve misunderstood configurations.runtime. Please add this before creating the URL array

listOfURL.add(project.sourceSets.main.output.classesDir.toUri().toUrl())

In java code this is a little different than groovy. I think you’ll need to do

SourceSetContainer ssc = getProject().getConvention().getPlugin(JavaPluginConvention.class).getSourceSets();
File classesDir = ssc.getByName("main").getOutput().getClassesDir();
listOfURL.add(classesDir.toUri().toUrl());

Yes!!!
It works perfectly.
How can I thank you?

Hi I am new to gradle.
I intend to use reflection as part of the gradle task to read all the classes in one of the packages in my multi-module repo. Need some help as I end up getting the following error:

Execution failed for task ‘:hello’.

No signature of method: java.io.File.toUri() is applicable for argument types: () values:
Possible solutions: toURI(), toURL(), mkdir(), toString(), toPath(), toString()

######### Task Snippet

class AutogenTask extends DefaultTask {
@TaskAction
def greet() {

URL[] urls;
List<URL> listOfURL = new ArrayList<>();
SourceSetContainer ssc = getProject().getConvention().getPlugin(JavaPluginConvention.class).getSourceSets();
File classesDir = ssc.getByName("dir_name").getOutput().getClassesDir();
listOfURL.add(classesDir.toUri().toUrl());
urls = listOfURL.toArray(new URL[0]);

ClassLoader classLoader = new java.net.URLClassLoader(urls,null);

Reflections reflections = new Reflections("com.autogen.sample.common", classLoader);
Set<Class<? extends Object>> allClasses =
    reflections.getSubTypesOf(Object.class)
println "I am here2"
for(Class c:allClasses) {
  println "I am here3"
  println "Derived Class : " + c.getName()
}

}
}

task hello(type: AutogenTask)

Have you read the error message? It’s being pretty specific

Change:
classesDir.toUri()
To
classesDir.toURI()

This is not throwing errors but it is also not getting the class names as it is intended here…
All the .java classes are under : com.autogenerate.data.common

Code Snippet

class AutogenTask extends DefaultTask {
@TaskAction
def greet() {
List listOfURL = new ArrayList<>();
File classesDir = project.file(“auto/src/generatedData/java/com/autogenerate/data/common”)
listOfURL.add(classesDir.toURI().toURL());
URL[] urls = listOfURL.toArray(new URL[0]);
ClassLoader classLoader = new URLClassLoader(urls);

Reflections reflections = new Reflections("com.autogenerate.data.common", classLoader);
Set<Class<? extends Object>> allClasses =
    reflections.getSubTypesOf(Object.class)
println "I am here2"
for(Class c:allClasses) {
  println "I am here3"
  println "Derived Class : " + c.getName()
}

}
}


Even if I go the route of SourceSetGenerator, I am still not able to grab the classes, where the package is in : auto ->java->com.autogenerate.data.common

class AutogenTask extends DefaultTask {
@TaskAction
def greet() {
SourceSetContainer ssc = getProject().findProject(“urns”).getConvention().getPlugin(JavaPluginConvention.class).getSourceSets();
File dir = ssc.getByName(“auto”).getOutput().getClassesDir();
List listOfURL = new ArrayList<>();
listOfURL.add(dir.toURI().toURL());
URL[] urls = listOfURL.toArray(new URL[0]);

ClassLoader classLoader = new URLClassLoader(urls);

Reflections reflections = new Reflections("com.autogenerate.data.common", classLoader);

Set<Class<? extends Object>> allClasses =
    reflections.getSubTypesOf(Object.class)
for(Class c:allClasses) {
  println "Class Name : " + c.getName()
}

}
}

task auto(type: AutogenTask)

Hi every body,

here is the way I get URLs:

        final URL[] urls = project.sourceSets.main.output.classesDirs.files.collect { File dir ->
            dir.listFiles()
        }.flatten().collect {
            getLogger().lifecycle(
                    "File?: " + it
            )
            if (it != null) {
                it.toURI().toURL()
            }
        }.findAll {
            it != null
        } as URL[]

Notive that project.sourceSets.main.output.classesDir does not exist anymore.

Here is the Kotlin way I configure Reflections:

    /**
     * Secondary constructor with reflections configuration.
     *
     * @param packageName The name of the package to scan.
     * @param configuration The [Configuration] to use.
     */
    constructor(
        packageName: String,
        configuration: Configuration
    ) : this(
        packageName = packageName,
        reflections = Reflections(configuration)
    )

    /**
     * Secondary constructor.
     *
     * @param packageName The name of the package to scan.
     * @param classLoader The [ClassLoader] to use.
     */
    constructor(
        packageName: String,
        classLoader: ClassLoader
    ) : this(
        packageName = packageName,
        configuration = ConfigurationBuilder(
        ).setUrls(
            ClasspathHelper.forClassLoader(classLoader)
        ).setScanners(
            SubTypesScanner(false),
            TypeAnnotationsScanner()
        ).addClassLoader(
            classLoader
        )
    )

    /**
     * Secondary constructor.
     *
     * @param packageName The name of the package to scan.
     * @param urls The [URL] array to use.
     */
    constructor(
        packageName: String,
        urls: Array<URL>
    ) : this(
        packageName = packageName,
        classLoader = URLClassLoader(urls)
    )

The, when performing:

        val list = reflections.getSubTypesOf(
            Any::class.java
        ).asIterable().toList()

I get the following error:

Caused by: java.lang.ClassNotFoundException: com.github.roroche.eoplantumlbuilder.classes.ClsFiltered

The plugin code is here: https://github.com/RoRoche/plantuml-gradle-plugin
The project including it is here: https://github.com/RoRoche/eo-plantuml-builder

Any idea?

How to unit test it?

I don’t find unit testing Gradle plugins very helpful since it’s easy to get a false positive. So I usually only provide integration tests with TestKit. Since gradle scripts are groovy, you can put assert statements in your test gradle scripts which I find convenient.

Eg