1

I have a package in which there are many classes, and these classes all have a static method called onLoad. I want to call the onLoad method when the program starts, however I do not want to hard-wire each and every one, i.e. classA.onLoad(); classB.onLoad(); etc.

How can I list all classes in package com.foo.bar.asd and run onLoad() on all of them?

Thanks in advance

Jachdich
  • 782
  • 8
  • 23
  • Sorry, I messed up the title. It said that I wanted to create instances of these classes which I do not. I want to call static methods in them, and I have edited the title. – Jachdich Oct 26 '19 at 16:30
  • Core reflection doesn't offer a way to list all classes in a package. However, there are libraries which can help with this, many of which are showcased [here](https://stackoverflow.com/questions/520328/). From there you'll likely either have the `Class` object or the class name. If you end up with a class name then you can [get the `Class` object using reflection](https://stackoverflow.com/questions/1438420/). After you have the `Class` object you just [have to find and invoke the static method](https://stackoverflow.com/questions/2467544/). – Slaw Oct 26 '19 at 16:46

2 Answers2

0

I found this question quite interesting so I came up with a solution. The tricky part here is to actually find all classes that are in a given package.

In this example I assume that all classes are in the same package where the class C is and that all classes except C have an onLoad method which may be inaccessible (i.e. private). Then you can use the following example code.

public final class C {

    public static void main(String[] args) throws Exception {
        for (Class<?> cls : getClasses(C.class)) {
            if (cls != C.class) {
                Method onLoad = cls.getDeclaredMethod("onLoad");
                onLoad.setAccessible(true);
                onLoad.invoke(null);
            }
        }
    }

    private static List<Class<?>> getClasses(Class<?> caller)
            throws IOException, URISyntaxException {
        return Files.walk(getPackagePath(caller))
                .filter(Files::isRegularFile)
                .filter(file -> file.toString().endsWith(".class"))
                .map(path -> mapPathToClass(path, caller.getPackage().getName()))
                .collect(Collectors.toList());
    }

    private static Class<?> mapPathToClass(Path clsPath, String packageName) {
        String className = clsPath.toFile().getName();
        className = className.substring(0, className.length() - 6);
        return loadClass(packageName + "." + className);
    }

    private static Path getPackagePath(Class<?> caller)
            throws IOException, URISyntaxException {
        String packageName = createPackageName(caller);
        Enumeration<URL> resources = caller.getClassLoader()
                .getResources(packageName);
        return Paths.get(resources.nextElement().toURI());
    }

    private static String createPackageName(Class<?> caller) {
        return caller.getPackage().getName().replace(".", "/");
    }

    private static Class<?> loadClass(String name) {
        try {
            return Class.forName(name);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }
}

I've omitted all exception checking and statements like resources.nextElement() may even throw an exception. So if you want to cover those cases, you need to add a few checks here and there.

Alex R
  • 3,139
  • 1
  • 18
  • 28
  • I shall try this when I have time. Is there a way to have the class be in a different package? Not really needed but it neatens up the source tree a bit – Jachdich Oct 26 '19 at 18:30
  • @Jachdich Sure, in the main method, pass one of the classes that are inside the package and have an `onLoad` method to `getClasses` (instead of `C.class`). Then you can safely remove the `if` statement, too. – Alex R Oct 26 '19 at 18:34
  • Cool, I'll try it tomorrow. Thanks! – Jachdich Oct 26 '19 at 21:27
  • Sorry didn't have time yesterday. I'll try tonight – Jachdich Oct 28 '19 at 08:53
  • Yes it does work thank you! however when I try to export as a JAR, it fails with "FileSystemNotFoundException". A full log can be found [here](https://pastebin.com/yY0Ee854) – Jachdich Oct 28 '19 at 22:04
  • do you know why it doesn't work inside a jar? That's kinda important for a program that needs to be distributed – Jachdich Nov 13 '19 at 08:47
0

You can use the reflections library.

...
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

public static void main(String[] args)
   throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException 
{
  Reflections reflections = new Reflections(new ConfigurationBuilder()
    .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
    .setUrls(ClasspathHelper.forClassLoader(ClasspathHelper.contextClassLoader()))
    .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("com.foo.bar.asd"))));

  Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);


  for (Class<?> clazz : classes)
  {
     Method method = clazz.getMethod("onLoad");
     method.invoke(null);
  }
}

Please note that this library also has dependencies (guava.jar and javassist.jar).

SternK
  • 11,649
  • 22
  • 32
  • 46
  • I'd really like to keep away from guava, as it's google, but other people will no doubt find this useful – Jachdich Oct 26 '19 at 18:29