Search code examples
classjava-8byte-buddy

Load a package and inside classes


I'm creating a package with some classes that I generated with wsimport on the fly and now I'm trying to load it to use, how can I do this? natively? or with some lib like byte-buddy, I tried the bellow code to load each class in a package:

File [] files = new File("<Path to package in filesystem with classes (*.class)>").listFiles();
    List<URL> classUrls = new ArrayList<>();

    for(File file : files) {
        classUrls.add(new URL("file://" + file.getAbsolutePath()));
    }

    URL[] classz = new URL[classUrls.size()];
    classz = classUrls.toArray(classz);

    URLClassLoader child = new URLClassLoader(classz);
    Class.forName("com.abc.external.resources.genwn239aqyhmfz.SomeClass", true, child);

But I still getting (Package: com.abc.external.resources.genwn239aqyhmfz.SomeClass)

java.lang.ClassNotFoundException: com.abc.external.resources.genwn239aqyhmfz.SomeClass

Solution

  • The rules for the class path are not different to the rules you have to obey when launching your application. The class path entries are not class files nor directories containing them, but the roots of your package structure.

    So if the class you want to load is com.abc.external.resources.genwn239aqyhmfz.SomeClass, the class path entry has to be the directory containing the com directory, which contains the abc directory, and so on. If you know the expected full qualified name of one of the classes, it’s easy to find the right directory. Just traverse to the file hierarchy up as many times as the qualified name has package components. However, when you don’t know the name beforehand, finding it can be tricky. Here is a sketch:

    // pass the expected name of one class contained in f or null if not known
    static void loadClasses(File f, String predictedName)
            throws IOException, ClassNotFoundException {
        File[] classes = f.listFiles((d,n)->n.endsWith(".class"));
        if(classes == null || classes.length == 0) {
            System.err.println("no classes or not a directory");
            return;
        }
        if(predictedName == null) predictedName = predictName(classes[0]);
        for(int p = predictedName.indexOf('.'); p >= 0; p = predictedName.indexOf('.', p+1))
            f = f.getParentFile();
        URLClassLoader classLoader = new URLClassLoader(new URL[] { f.toURI().toURL() });
        String packageName = predictedName.substring(0, predictedName.lastIndexOf('.')+1);
        for(File cf: classes) {
            String name = cf.getName();
            name = name.substring(0, name.length()-6); // strip off ".class"
            Class<?> cl = classLoader.loadClass(packageName+name);
            // what do you wanna do with the classes?
            System.out.println(cl);
        }
    }
    
    private static String predictName(File classFile) throws IOException {
        byte[] data = Files.readAllBytes(classFile.toPath());
        return new ClassLoader() {
            String getName() {
                return defineClass(null, data, 0, data.length).getName();
            }
        }.getName();
    }
    

    The predictName implementation is a very simple one. If the class has dependencies to classes within the same file hierarchy which the JVM immediately tries to resolve, it will fail as we don’t have the necessary information yet. In that case, only a bytecode parsing library allowing to extract the name without loading the class would help. But that exceeds the scope of this question…