4

I'm aware that this question has been asked before:

How to use URLClassLoader to load a *.class file?

However I don't really understand due to the lack of example. I'm currently working on a project and trying to load user-given .class objects which can be located in any directories on the machine.

//Create URL to hash function class file
URL url_hashFunctionPath = new URL("file:///" + _sHashFunctionFilePath);

//Packet URL to a URL array to be used by URLClassLoader
URL[] urlA_hashFunctionPath = {url_hashFunctionPath};

//Load URL for hash function via a URL class loader
URLClassLoader urlCl_hashFunctionClassLoader = new URLClassLoader(urlA_hashFunctionPath);

//Load user hash function into class to initialize later (TEST: HARD CODE FOR NOW)
m_classUserHashClass = urlCl_hashFunctionClassLoader.loadClass(_sUserHashClassName);

The last line gave me a ClassNotFoundException, from my experiment & understanding the user-given class function has to be in the classpath?

PS: 1st time posting questions feel free to correct me where I did not follow the appropriate manner.

//SOLUTION

The solution that I arrived at with the generous help of [WillShackleford][1], this solution can load the a .class file in a given filepath. For more information refer to code and their given comments.

//The absolute file path to the class that is to be loaded (_sHashFunctionFilePath = absolute file path)
String pathToClassFile = _sHashFunctionFilePath;
System.out.println("File to class: " + _sHashFunctionFilePath);

//Declare the process builder to execute class file at run time (Provided filepath to class)
ProcessBuilder pb = new ProcessBuilder("javap", pathToClassFile);
try
{
    //Start the process builder
    Process p = pb.start();

    //Declare string to hold class name
    String classname = null;
    //Declare buffer reader to read the class file & get class name
    try(BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())))
    {
        String line;
        while(null != (line = br.readLine()))
        {
            if(line.startsWith("public class"))
            {
                classname = line.split(" ")[2];
                break;
            }
        }
        System.out.println("classname = " + classname);
    }
    catch(IOException _error)
    {

    }

    //Declare file path to directory containing class to be loaded
    String pathToPackageBase = pathToClassFile.substring(0, pathToClassFile.length() - (classname + ".class").length());
    System.out.println("pathToPackageBase = " + pathToPackageBase);

    try
    {
        //Create class to hold the class to be loaded via a URL class loader
        Class clss = new URLClassLoader(
                new URL[]{new File(pathToPackageBase).toURI().toURL()}
        ).loadClass(classname);

        //Create ab object/instance of said class
        Object test = clss.newInstance();

        //Declare & create requested method from user hash function class (Method is work & takes no arguments)
        Method method = clss.getMethod("work", null);
        method.invoke(test, null);
    }
Community
  • 1
  • 1
Last
  • 59
  • 1
  • 6
  • What's the point of `URL.toURI().toURL()`? – Andreas Sep 24 '15 at 21:22
  • Can't help you. We don't know what `_sUserHashClassName` is. Or `_sHashFunctionFilePath` for that matter, – Andreas Sep 24 '15 at 21:23
  • possible duplicate of [How to use URLClassLoader to load a \*.class file?](http://stackoverflow.com/questions/738393/how-to-use-urlclassloader-to-load-a-class-file) – blm Sep 24 '15 at 21:23
  • @blm That's the link OP already posted. – Andreas Sep 24 '15 at 21:24
  • Sorry it was meant for file.toURI().toURL(), i got mixed up thinking it was toURL() straight was a deprecated way. – Last Sep 24 '15 at 21:26
  • _sUserHashClassName is the name of the class file (Example: Test.Class, name is Test) _sHashFunctionFilePath is the absolute file path to the .class file – Last Sep 24 '15 at 21:27
  • The code mixes _sHashFunctionFilePath and the example path I had which makes the relationship between them unclear. Put in checks to determine which variable is null and what line the NPE happens. – WillShackleford Sep 25 '15 at 09:53

2 Answers2

5

In the directory /home/shackle/somedir/classes/pkg I have a file Test.class created from a java file with package pkg; eg :

package pkg;

public class Test {

    public String toString() {
        return "secret_string";
    }
}

Then I load it with :

System.out.println(new URLClassLoader(
        new URL[]{new File("/home/shackle/somedir/classes").toURI().toURL()}
).loadClass("pkg.Test").newInstance().toString());

Notice that I do not put the pkg/Test in the URL string but the load class argument has the pkg. prefix.

You can get the class name directly from the file like this:

Class clsReaderClss = ClassLoader.getSystemClassLoader().loadClass("jdk.internal.org.objectweb.asm.ClassReader");
System.out.println("clsReaderClss = " + clsReaderClss);
Constructor con = clsReaderClss.getConstructor(InputStream.class);
Object reader = con.newInstance(new FileInputStream(directFile));
Method m = clsReaderClss.getMethod("getClassName");
String name = m.invoke(reader).toString().replace('/', '.');
System.out.println("name = " + name);

An alternative that doesn't require access to internal classes.

String pathToClassFile = "/home/shackle/somedir/classes/pkg/Test.class";
ProcessBuilder pb = new ProcessBuilder("javap",pathToClassFile);
Process p = pb.start();
String classname = null;
try(BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
   String line;
   while(null != (line = br.readLine())) {
       if(line.startsWith("public class")) {
           classname = line.split(" ")[2];
           break;
       }
   }
}
System.out.println("classname = " + classname);

Class can then be loaded with:

String pathToPackageBase = pathToClassFile.substring(0, pathToClassFile.length() - (classname + ".class").length());
System.out.println("pathToPackagBase = " + pathToPackageBase);
Class clss = new URLClassLoader(
        new URL[]{new File(pathToPackageBase).toURI().toURL()}
).loadClass(classname);
System.out.println(clss.newInstance().toString());
WillShackleford
  • 6,918
  • 2
  • 17
  • 33
  • I understand but wouldn't that mean that I would need to have explicit knowledge of the package name which is used by class Test. (As I assume my user provides the class file hence I shouldn't have knowledge about the package name he/she is using, or is my understanding about package misguided somewhere?) – Last Sep 24 '15 at 22:02
  • Cheers will look into that thanks, I can see from the 2 current answers that there is a lot more to this then meets the eye. Will get back to you once I done my research. – Last Sep 24 '15 at 22:13
  • Thanks, but can i check what does this line do .getSystemClassLoader().loadClass("jdk....ClassReader"); – Last Sep 25 '15 at 02:07
  • So you this JDK required is an external library where only the "importing" side of things is required to install as an external library? Would I require to add the file in my project for future compilation if required to run on other machines? – Last Sep 25 '15 at 02:28
  • The alternative that does not require access to internal classes, hence once you have the classname how do you instantiate an instance of said class & use its methods? – Last Sep 25 '15 at 03:04
  • Hi edited question of additional issues when invoking methods as it will be too messy and tedious for the comments. PS: Sorry not enough reputation to chat – Last Sep 25 '15 at 03:40
  • Hi Will, posted the solution that works for me & added comments based on what I understood from it feel free to edit solution or let me know so I can do so for you. Thanks for the patient help & for making my virgin question on stack overflow a pleasant one, cheers! – Last Sep 26 '15 at 09:03
0

Your _sHashFunctionFilePath needs to have the package name of the target class removed from it, so the ClassLoader will look in _sHashFunctionFilePath + package.name + HashFunction.class as the path to the file. If you don't do that, the ClassLoader won't be able to find the file.

So if the target class is my.great.HashFunction in HashFunction.class, then it needs to be in a directory called my/great/ if you want to use URLClassLoader. Then, you'd use /path/to as the file:/// URL for your URLClassLoader if the .class file was actually found in /path/to/my/great/HashFunction.class.

Christopher Schultz
  • 20,221
  • 9
  • 60
  • 77
  • Sorry I saw a lot of example mentioning packets as well using similar notations such as your example "my.great.HashFunction", I'm afraid I don't understand the purpose of these packets. (As I assume my user provides the class file hence I shouldn't have knowledge about the package name he/she is using, or is my understanding about package misguided somewhere?) – Last Sep 24 '15 at 22:04
  • If you can't predict the package name (the term `package` has a definite meaning in Java), then you can't use `ClassLoader.loadClass` to fetch the class. Instead, you'll have to call `ClassLoader.defineClass` and `ClassLoader.resolveClass` which, as you may note, is tricky because those methods are protected. You also have to know the name of the class in advance, because you have to know a class's name to define it. You may be able to use something like BCEL to get metadata from the raw bytes of a class, and then feed that to `ClassLoader.defineClass`. – Christopher Schultz Sep 24 '15 at 22:07
  • Cheers will look into that thanks, I can see from the 2 current answers that there is a lot more to this then meets the eye. Will get back to you once I done my research – Last Sep 24 '15 at 22:14
  • Your best bet would be to load `.jar` files from contributors and not try to load `.class` files directly. That way, the JAR file will contain all the structure necessary, and you can even scan the JAR files for the classes that are inside of them, and ... I dunno, maybe decide which class to load. – Christopher Schultz Sep 24 '15 at 22:16