Find where a Java class comes from, recursively 

There are 3 notes for this topic, click above title to see all notes.
Joined:
04/09/2007
Posts:
770

January 14, 2010 00:28:27    Last update: March 30, 2011 15:37:44
A task that a Java developer does so frequently is to find out where a certain class can be found - to resolve compilation errors, classpath issues, or version conflicts of the same class introduced by multiple class loaders. A long while back I wrote a simple Perl script to perform the task. Later I was informed that there are Swing based Jar Browser and Jars Browser.

Then, there are a couple of shell one-liners:
# one liner 1
find -name "*.jar" -print0 | xargs -0 grep -i SomeClassName

# one liner 2
for i in $(find . -iname \*.*ar) ; do echo $i ;jar tf $i | grep $1; done


But all of them share the same problem: if a class is in a jar nested in another jar, it cannot be found. Such is the case for a class inside a jar under the WEB-INF/lib directory of a war file packaged in an ear. This is a simple Java program that solves the problem. It also demonstrates how to read a jar nested inside another jar without using a temporary file.

Update 03/30/2011: add switch to report class version.
import java.io.*;
import java.util.zip.*;
import java.nio.ByteBuffer;

public class FindClass {
    private static final int BUFFER_SIZE = 4096;
    private static final String[] JAR_EXTENSIONS = {
                                                     ".zip", 
                                                     ".jar", 
                                                     ".war", 
                                                     ".ear", 
                                                     ".rar", 
                                                 };

    public static void findClass(String path,
                                 String classToLookFor,
                                 boolean recursive,
				 boolean reportVersion) 
                       throws Exception {
        if (path == null) {
            return;
        }

        // normalize class name
        if (!classToLookFor.contains("/")) {
            classToLookFor = classToLookFor.replace('.', '/');
	    if (classToLookFor.endsWith("/class")) {
		classToLookFor = classToLookFor.substring(
				    0, 
				    classToLookFor.length() - 6
				 ) 
			       + ".class";
	    }
        }

        // normalize path, change \ to /
        path = path.replace(File.separatorChar, '/'); 
        File f = new File(path);
        if (f.isDirectory()) {
            findClassInDirectory(path, classToLookFor, recursive, reportVersion);
        }
        else if (f.isFile() && hasJarExtension(path)) {
            ZipInputStream in = new ZipInputStream(new FileInputStream(path));
            findClassInJar(in, classToLookFor, path, recursive, reportVersion);
            in.close();
        }
        else if (f.isFile() && path.contains(classToLookFor)) {
	    if (reportVersion) {
		InputStream in = new FileInputStream(f);
		String classVersion = getClassVersion(in);
		in.close();
		path += " (" + classVersion + ")";
	    }
            showPath(path);
        }
    }

    private static String getClassVersion(InputStream in) throws IOException {
	byte[] b = new byte[8];
	in.read(b);

	ByteBuffer bb = (ByteBuffer) ByteBuffer.allocate(8).put(b).rewind();

	int magic = bb.getInt();
	if (magic != 0xCafeBabe) { // not a Java class file
	    return "not a Java class";
	}

	return String.format("%2$d.%1$d", bb.getShort(), bb.getShort());
    }

    private static void findClassInJar(ZipInputStream in, 
                                      String classToLookFor,
                                      String path, 
                                      boolean recursive,
				      boolean reportVersion) 
                       throws Exception {
        ZipEntry next = in.getNextEntry();
        while (next != null) {
            if (!next.isDirectory()) {
                if (next.getName().contains(classToLookFor)) {
		    if (reportVersion) {
			String classVersion = getClassVersion(in);
			showPath(path 
			       + ": " 
			       + next.getName() 
			       + " (" + classVersion 
			       + ")");
		    }
		    else {
			showPath(path + ": " + next.getName());
		    }
                }
                else if (recursive && hasJarExtension(next.getName())) {
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    byte[] buffer = new byte[BUFFER_SIZE];
                    int n = in.read(buffer, 0, BUFFER_SIZE);
                    while (n >= 0) {  // read returns -1 if end of current entry is reached.
                        out.write(buffer, 0, n);
                        n = in.read(buffer, 0, BUFFER_SIZE);
                    }

                    ZipInputStream in2 = new ZipInputStream(
                                                new ByteArrayInputStream(
                                                    out.toByteArray()
                                                )
                                         );
                    findClassInJar(in2, 
                                   classToLookFor, 
                                   path + "/" + next.getName(), 
                                   recursive,
				   reportVersion);
                    in2.close();
                }
            }
            next = in.getNextEntry();
        }
    }

    private static void findClassInDirectory(String dir,
                                             String classToLookFor,
                                             boolean recursive,
					     boolean reportVersion)
                        throws Exception {
        File d = new File(dir);
        String[] files = d.list();
        for (int i = 0; i < files.length; i++) {
            String filePath = dir + "/" + files[i];
            File file = new File(filePath);
            if (file.isFile() && filePath.contains(classToLookFor)) {
                int idx = filePath.indexOf(classToLookFor);
		if (reportVersion) {
		    InputStream in = new FileInputStream(file);
		    String classVersion = getClassVersion(in);
		    in.close();
		    showPath(filePath.substring(0, idx) 
			   + ": " 
			   + filePath.substring(idx)
			   + " (" + classVersion + ")");
		}
		else {
		    showPath(filePath.substring(0, idx) + ": " + filePath.substring(idx));
		}
            }
            else if (recursive) {
                if (file.isDirectory()) {
                    findClassInDirectory(filePath, classToLookFor, recursive, reportVersion);
                }
                else if (hasJarExtension(filePath)) {
                    ZipInputStream in = new ZipInputStream(new FileInputStream(file));
                    findClassInJar(in, classToLookFor, filePath, recursive, reportVersion);
                    in.close();
                }
            }
        }
    }

    private static boolean hasJarExtension(String fileName) {
        fileName = fileName.toLowerCase();
        for (int i = 0; i < JAR_EXTENSIONS.length; i++) {
            if (fileName.endsWith(JAR_EXTENSIONS[i])) {
                return true;
            }
        }
        return false;
    }

    private static void showPath(String p) {
        System.out.println(p);
    }

    public static void main(String[] args) throws Exception {
        if (args.length < 2) {
            System.out.println("Usage: java FindClass [-v] <jarFileName> <classToLookFor>");
            System.exit(1);
        }
        
	boolean reportVersion = args[0].equals("-v");
        String path = reportVersion ? args[1] : args[0];
        String classToLookFor = reportVersion ? args[2] : args[1];
        findClass(path, classToLookFor, true, reportVersion);
    }
}
Share |
| Comment  | Tags
3 comments