4

I've got a really strange problem.

I'm using a URLClassLoader to dynamically import files from a directory. The code works fine if I use a literal string, and works fine if I use a variable to a literal string, but this isn't what I need.

package test;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class Test {
    public static void main(String[] args) {

        try {
            File subfolder = new File("C:\\temp\\");
            URL classUrl = subfolder.toURI().toURL();
            URL[] classUrls = { classUrl };
            URLClassLoader ucl = new URLClassLoader(classUrls);

            for (File f : subfolder.listFiles()) {

                String name = f.getName()
                        .substring(0, f.getName().lastIndexOf(".")).trim();
                if (name.equals("TestClass"))
                        System.out.println(name);
                try {
                    MyInterface de = (MyInterface) Class.forName("TestClass", true, ucl)
                            .newInstance();
                    de.printSomething();
                } catch (ClassNotFoundException e) {
                }

                ucl.close();

            }
        } catch (MalformedURLException e) {
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

What I need is to be able to do this:

MyInterface de = (MyInterface) Class.forName(name, true, ucl).newInstance();

But it's not working even though "name" is a valid String and does equal "TestClass".

EDIT: I get the error:

java.lang.ClassNotFoundException: TestClass
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Unknown Source)
    at test.Test.main(Test.java:25)

What's wrong?

Apache
  • 619
  • 6
  • 22
  • When you say it worked for you with literals in what way did it work ? Did you make sure you can use the class properly (i.e. init and execute some methods) ? – giorashc Mar 03 '13 at 08:46
  • I'm using the example show here: http://stackoverflow.com/questions/2786416/how-to-access-java-classes-in-a-subfolder When I say it works, I mean that the code written above prints out "Hello World, from TestClass", just as it should, but using a variable instead of the literal string "TestClass" in ucl.loadClass(STRING) or Class.forName(STRING, true, ucl) throws the exception. Even though in the code above name.equals("TestClass") is true. They're both exactly the same. – Apache Mar 03 '13 at 08:55
  • Are you sure that `TestClass.class` is existing in folder `c:\temp`?? Because it looks like you have `TestClass.java` in that directory instead of `TestClass.class` as you have not tested if the name of file `endswith` `.class` String.. – Vishal K Mar 03 '13 at 09:08
  • The above code does infact work perfectly and as it should do. It's definitely TestClass.class which is in C:\temp – Apache Mar 03 '13 at 09:12
  • So if the above code works perfectly what's the question? – user207421 Aug 26 '15 at 01:20
  • It works with the literal string `"TestClass"` but not with a string variable. As stated in the question. – Apache Aug 26 '15 at 11:48
  • You said "works fine if I use a variable to a literal string," I'm guessing this is a typo? – Zarwan Aug 27 '15 at 03:51
  • @Apache Show a code sample that reproduces the problem - as detailed in one of the answers, the code you have shown can't result in that error. I suggest something like: `String name = "TestClass"; Class.forName("TestClass", true, ucl).newInstance(); Class.forName(name, true, ucl).newInstance();` without catching the exception - I very much doubt that the last line will throw an exception if the previous one doesn't... – assylias Aug 27 '15 at 06:33

4 Answers4

4

It appears your issue has to do with the fact that your class being loaded has a package. When Java loads these classes, it expects to find the directory structure related to that package. So, the following code works:

try {
        File subfolder = new File("/home/glen/TestClass");
        URL classUrl = subfolder.toURI().toURL();
        URL[] classUrls = { classUrl };
        URLClassLoader ucl = new URLClassLoader(classUrls);

        for (File f : subfolder.listFiles()[0].listFiles()) {

            System.out.println(f.getName());

            String name = f.getName()
                    .substring(0, f.getName().lastIndexOf(".")).trim();// "TestClass";
            if (name.equals("TestClass"))
                    System.out.println(name);
            try {
                MyInterface de = (MyInterface) Class.forName("test." + name, true, ucl)
                        .newInstance();
                de.printSomething();
            } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
            }

            ucl.close();

        }
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

Note how I am:

  1. Specifying the fully qualified class name, test.TestClass, in the Class.forName method
  2. Enumerating through the first subdirectory (in my case, the "test" directory) of the loading folder. The TestClass.class file is located in there. If I attempt to load directly through there, with or without specifying the package name, it fails.

In essence, the URLClassLoader requires a JAR-like directory structure, like test/TestClass.class, while having a URL root which contains the directory structure.

My working theory is that you are not just changing the name variable to a string literal, as when I do that it still works fine. Double check you're not changing anything else. Either way, I hope this points you in the right direction.

glen3b
  • 693
  • 1
  • 8
  • 22
4

I guess this is because of the ucl.close() inside the for loop. I added some test if there are directories: the following class works and instanciate itself if declared in the root package, and if eclipse is configure to generate .class file in "bin" directory:

public class Toto {

    public Toto(){
        // this writes "Toto" in the console if the class is well instanciated
        System.out.println("Toto");
    }

    public static void main(String[] args) {
        try {
            File subfolder = new File("bin");
            URL classUrl = subfolder.toURI().toURL();
            URL[] classUrls = { classUrl };
            URLClassLoader ucl = new URLClassLoader(classUrls);

            for (File f : subfolder.listFiles()) {
                String fileName= f.getName();
                int suffix = fileName.lastIndexOf('.');
                if(f.isDirectory() || suffix==-1){
                    continue;
                }
                String name = fileName.substring(0, suffix);
                try {
                    Class.forName(name, true, ucl).newInstance();

                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
            ucl.close();
        } catch (MalformedURLException e) {
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
sanastasiadis
  • 1,182
  • 1
  • 15
  • 23
pdem
  • 3,880
  • 1
  • 24
  • 38
3

What's wrong?

The first thing that's wrong is that this isn't the real code. This code catches and ignores ClassNotFoundException:

} catch (ClassNotFoundException e) {
}

So it cannot possibly produce the output shown.

The second thing that's wrong is that when this is corrected, the code works as expected.

Cannot reproduce.

Clearly you weren't running the code you thought you were running. Java simply does not behave as claimed.

user207421
  • 305,947
  • 44
  • 307
  • 483
2

Instead of

MyInterface de = (MyInterface) Class.forName(name, true, ucl).newInstance();

use

MyInterface de = (MyInterface) Class.forName(name + "Class", true, ucl).newInstance();

works for me.

Pankaj Shinde
  • 3,361
  • 2
  • 35
  • 44
  • 1
    Only if `name = "Test"`, and the OP clearly states it was `"TestClass"`. So it's difficult to understand what you were testing. But then it's impossible to understand what the OP was testing either, as neither his code nor Java behave as described. – user207421 Aug 27 '15 at 06:06