views:

54

answers:

4

I'm looking a utility method so that given a class will return the full classpath required to run this class externally. This means the jar the class is in as well as all jars (or folders) of classes that it uses.

UPDATE: there are tools that analyze .class files to find dependencies. This is not what I'm looking for. I'm looking for something that uses Java's reflection API on an already loaded class. I'll settle for something that analyzes byte code, if it goes recursively into classes it finds through the class loader

A: 

There are cases when you cannot determine this prior to using your class.

thelost
A: 

This cannot always be known. For instance, a class can be dynamically created at run time and then loaded with a custom ClassLoader.

I do not believe Java stores this information.

Borealid
Since I want to run it externally, lets assume I know all dependencies are retrieved from actual files/jars.
IttayD
+1  A: 

I don't think this is possible. Certainly, the reflection APIs don't support it.

You can find out a classes classloader, but you cannot find out:

  • which of the classloader's possible JAR files and directories contained the class,
  • what the static dependencies of the class are, or
  • what the dynamic dependencies of the class are; e.g. what it or its dependants loaded using Class.forName().

Actually, this overstates things somewhat:

  • You can in theory figure out which classes came from which JARs if you can find out what the classpath is. There are possibly ways to dig this out of a classloader.
  • You can in theory figure out what a classes dependants are, but you been to dig around in the class'es bytecode file using (for instance) BCEL to find this out.
  • You can in theory figure out what was dynamically loaded if you are prepared to write your own classloader. It may be possible to link this back to the class that initiated the loading using some hairy analysis of the stack frames.

But this is all extremely complicated, and I'd expect it to be unreliable under certain circumstances.

Stephen C
clazz.getResource("/" + clazz.getName().replace(".","/") + ".class") will return the file from which the file was loaded. removing everything after ! will give the jar.
IttayD
@IttayD - I don't believe that works for all classloaders.
Stephen C
+1  A: 

Reflection will not help you a lot for this one. You will need to analyse the byte code to find dependencies.

UPDATE:

Alright then. I am using a library that I made years ago, that you can download here.

The following code:

package classdep;

import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.jedo.classfile.ClassFile;
import org.jedo.classfile.ConstantPool;

public class Main {

    public static void main(String[] args) {
        try {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            List<String> classes = new ArrayList<String>();
            classes.add(args[0].replace('.', '/'));
            for (int i = 0; i < classes.size(); ++i) {
                String className = classes.get(i);
                URL url = cl.getResource(className + ".class");
                if (url == null) {
                    System.out.println("--- class not found " + className);
                } else {
                    System.out.println(url);
                    ClassFile classFile = new ClassFile();
                    InputStream in = url.openStream();
                    try {
                        classFile.load(in);
                    } finally {
                        in.close();
                    }
                    ConstantPool cp = classFile.getConstantPool();
                    for (String name: cp.getClassNames()) {
                        if (!classes.contains(name)) {
                            classes.add(name);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Will give you all the dependencies of a class. When applied to org.jedo.classfile.ClassFile, it produces the following output:

file:/D:/projects/casagrande/jedo/build/classes/org/jedo/classfile/ClassFile.class
file:/D:/projects/casagrande/jedo/build/classes/org/jedo/classfile/ConstantPool.class
file:/D:/projects/casagrande/jedo/build/classes/org/jedo/classfile/FieldInfo.class
file:/D:/projects/casagrande/jedo/build/classes/org/jedo/classfile/MethodInfo.class
file:/D:/projects/casagrande/jedo/build/classes/org/jedo/classfile/AttributeInfo.class
jar:file:/C:/Program%20Files/Java/jdk1.6.0_18/jre/lib/rt.jar!/java/io/File.class
jar:file:/C:/Program%20Files/Java/jdk1.6.0_18/jre/lib/rt.jar!/java/io/FileInputStream.class
jar:file:/C:/Program%20Files/Java/jdk1.6.0_18/jre/lib/rt.jar!/java/io/DataInputStream.class
jar:file:/C:/Program%20Files/Java/jdk1.6.0_18/jre/lib/rt.jar!/java/io/StreamCorruptedException.class
jar:file:/C:/Program%20Files/Java/jdk1.6.0_18/jre/lib/rt.jar!/java/io/FileOutputStream.class
jar:file:/C:/Program%20Files/Java/jdk1.6.0_18/jre/lib/rt.jar!/java/io/DataOutputStream.class
jar:file:/C:/Program%20Files/Java/jdk1.6.0_18/jre/lib/rt.jar!/java/lang/StringBuilder.class
jar:file:/C:/Program%20Files/Java/jdk1.6.0_18/jre/lib/rt.jar!/java/lang/StringBuffer.class
jar:file:/C:/Program%20Files/Java/jdk1.6.0_18/jre/lib/rt.jar!/java/lang/Object.class
jar:file:/C:/Program%20Files/Java/jdk1.6.0_18/jre/lib/rt.jar!/java/io/IOException.class
...

Followed by a lot of system classes. You need to filter out system classes, and parse the other urls to extract either the .jar file if it is a jar: url, or the directory if it is a file: url.

Maurice Perry
I'll settle for something that analyzes byte code, if it goes recursively into classes it finds through the class loader
IttayD