4

I'm attempting to use reflection to load a custom object (Rod) from a jar file. I have managed to get it to find the jar file and scan the class for the needed annotation, but whenever I call classLoader.loadClass() I get a ClassNotFoundException for the class that the class I'm attempting to load extends.

This is the code for the ClassLoader:

public static Set<Rod> getRods(File rodDirectory) throws Exception {
    rodDirectory.mkdir();
    URLClassLoader classLoader;
    Set<Rod> rods = new HashSet<Rod>();

    for (File f : rodDirectory.listFiles()) {
        if (f.isDirectory() || !f.getName().endsWith(".jar"))
            continue;

        JarFile jar = new JarFile(f);
        Enumeration<JarEntry> e = jar.entries();
        classLoader = URLClassLoader.newInstance(new URL[]{new URL("jar:file:" + f.getAbsolutePath() + "!/")});

        while (e.hasMoreElements()) {
            JarEntry j = (JarEntry) e.nextElement();
            if(j.isDirectory() || !j.getName().endsWith(".class")){
                continue;
            }
            Class<?> c = classLoader.loadClass(j.getName().substring(0, j.getName().length() - 6));
            CustomRod a = c.getAnnotation(CustomRod.class);
            if (a == null)
                continue;
            if (a.minimumVersion() < RodsTwo.getVersion())
                continue;
            rods.add((Rod) c.getConstructor().newInstance());
        }

        jar.close();

    }
    return rods;
}

This is the code inside the jar I'm attempting to load:

import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;

import ca.kanoa.RodsTwo.Objects.ConfigOptions;
import ca.kanoa.RodsTwo.Objects.CustomRod;
import ca.kanoa.RodsTwo.Objects.Rod;

@CustomRod(minimumVersion=1.001)
public class Test extends Rod {

    public Test() throws Exception {
        super("Test", 1, 46, new ConfigOptions(), 200);
    }

    @Override
    public boolean run(Player arg0, ConfigurationSection arg1) {
        arg0.sendMessage("HI!");
        return true;
    }



}

And all my other code can by found here.

I've just started playing around with reflection and any help with be awesome!

PM 77-1
  • 12,933
  • 21
  • 68
  • 111

2 Answers2

0

There can be other classes in the JAR, which a custom subclass needs, but you are only picking out the one class file.

For example, say someone creates AbstractCustomRod and then MyCustomRod which extends it and puts them both in the JAR file? Your code will skip over AbstractCustomRod, and MyCustomRod cannot be loaded.

If you want to specify that there can be no inheritance other than from Rod, and no helper classes in the JAR file, then the only value JARs add is that they could be signed - if you don't care about that, you might as well receive plain .class files, which guarantees it's exactly one class.

Depending on what you're actually doing, you may want to rethink this overall design - this sort of thing is very fragile to changes in parent classes, and depending on where these custom subclasses come from, it would be very easy to end up running malicious code.

Tim Boudreau
  • 1,741
  • 11
  • 13
0

The OP code gets the name from the JarEntry, strips the ".class" and calls ClassLoader.loadClass.

The "name" of a ZIP entry is the relative path separated by '/' characters (all platforms). However, loadClass requires a binary name separated by '.' characters. Therefore, replace all '/' characters in the name with '.'.

.replace('/', '.')
Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305