Java ClassFinder
Dan Switzer yesterday asked me for some help figuring out a way to tell exactly what classes where available in a Java package if you have no documentation for the package and don’t want to manually surch through everything. Well I remembered something David Medinets had shared with me at one time and by the time I got my hands on it Dan had already manually gone through his Java packages and found his class but he asked me to blog the code David shared with me anyways. Here it is.
[Note this code was written by Scott Dunbar ]
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Enumeration;
import java.util.Vector;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
/**
* A simple utility to find a particular class name in a directory
containing jar, war, and ear files. The application recursively
* decends into the directory looking for files that end in
".jar", ".war", and ".ear" (case
* insensitive which is not really the standard).
* <p>
*
* A sample command line might look like: <br>
* java -classpath lib/classfinder.jar -d some/directory/name -c
SomeClassName
* <p>
*
* The command line arguments are:
* <ul>
* <li><b>-d </b> specifies the directory name to use. This must be a
directory. This is a required argument.</li>
* <li><b>-c </b> specifies the class name to look for in the jar files.
The search is case insensitive. This is a required
* argument. Note that this argument can be a class name like
MyClassName in which case ClassFinder will look for the provided name
* regardless of the package. Alternately, you can specify a package
declaration like com.xigole.MyClassName. Either way ClassFinder
* is simply looking for a pattern that looks like the given string.
Regular expressions are not supported.</li>
* <li><b>-p </b> pay attention to the class name case. This makes it
easier to find something like "Log" when many
* classes may be in the "logging" package</li>
* <li><b>-v </b> an optional argument that verbosely prints out each
file that is being searched.</li>
* </ul>
* <p>
*
* Why did I write this? Because I find that when I'm looking for a
particular class to compile with I never seem to know which jar
* file it is in. This utility simplifies that task. It is basically
like a Unix "find" command piped to the
* "jar" command that would "grep" for the class
name. Cross-platform issues made it easier to put into a small
* java application rather than have to install Cygwin on every Windows
box I use.
*
* Copyright (C) 2004-2006 Scott Dunbar (scott@xigole.com)
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you
may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
*/
public class ClassFinder {
File directory = new File("C:\\data\\support");
String classNameToCompare = "javax.portlet.GenericPortlet";
private Vector<File> files = new Vector<File>();
private String dirName = null;
private String className = null;
private boolean verbose = false;
private boolean ignoreCase = false;
/**
* The driver for the ClassFinder.
*/
public static void main(String argv[]) {
try {
new ClassFinder(argv);
} catch (IllegalArgumentException iae) {
System.err.println(iae.getMessage());
System.exit(1);
}
System.exit(0);
}
/**
* Public constructor that expects to be given the argument
array from the command line.
*
* @param argv -
* an array of Strings from the command line.
*
* @throws an
* IllegalArgumentException if the command line is
invalid or if the directory name specified is not really a
* directory.
*
*/
public ClassFinder(String argv[]) throws
IllegalArgumentException {
// parseArgs(argv);
// File directory = new File(dirName); // String classNameToCompare = className;
// File directory = new File("C:\\DATA\\support\\apache-tomcat-5.5.17\\webapps\\muse\\WEB-INF\\l
ib");
// File directory = new File("C:\\data\\support\\muse-2.0.0-bin\\trunk");
// File directory = new File("C:\\data\\support\\muse-1.0");
if (this.ignoreCase)
this.classNameToCompare =
this.classNameToCompare.toLowerCase();
//
// class files are stored with a forward slash in the jar files
// so convert any package-like paths into file system paths
//
this.classNameToCompare =
this.classNameToCompare.replaceAll("\\.", "/");
if (!this.directory.exists()) {
usage();
throw (new IllegalArgumentException("The
directory \"" + this.dirName + "\" does not exist"));
}
if (!this.directory.isDirectory()) {
usage();
throw (new IllegalArgumentException("The file
\"" + this.dirName + "\" is not a directory"));
}
buildFileList(this.directory);
for (int i = 0; i < this.files.size(); i++) {
File nextFile = (File)
(this.files.elementAt(i));
JarFile nextJarFile = null;
try {
nextJarFile = new JarFile(nextFile);
if (this.verbose) {
System.out.println("looking in "
+ nextFile.getAbsolutePath());
}
for (Enumeration jarEntries =
nextJarFile.entries(); jarEntries.hasMoreElements();) {
boolean found = false;
ZipEntry nextEntry = (ZipEntry)
jarEntries.nextElement();
if (this.ignoreCase) {
if
(nextEntry.getName().toLowerCase().indexOf(this.classNameToCompare) !=
-1)
found = true;
} else {
if
(nextEntry.getName().indexOf(this.classNameToCompare) != -1)
found = true;
}
if (found) {
System.out.print("\"" +
this.className + "\" found in ");
System.out.print(nextFile.getAbsolutePath() + " as ");
System.out.println(nextEntry.getName());
}
}
} catch (FileNotFoundException e) {
System.out.println(" File not found: "
+ nextFile.getAbsolutePath());
} catch (ZipException e) {
System.out.println(" Problem opening
zip file: " + nextFile.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
private void buildFileList(File nextFile) {
File[] fileList = nextFile.listFiles();
if (fileList != null) {
for (int i = 0; i < fileList.length; i++) {
if (fileList[i].isDirectory()) {
buildFileList(fileList[i]);
}
if
(fileList[i].getName().toLowerCase().endsWith(".jar") ||
fileList[i].getName().toLowerCase().endsWith(".war") ||
fileList[i].getName().toLowerCase().endsWith(".rar")
||
fileList[i].getName().toLowerCase().endsWith(".ear")) {
this.files.add(fileList[i]);
}
}
}
}
/*
* private void parseArgs(String argv[]) throws
IllegalArgumentException { for (int i = 0; i < argv.length; i++) { if
* (argv[i].equals("-v")) { verbose = true; continue; }
*
* if (argv[i].equals("-p")) { ignoreCase = false; continue; }
*
* if (argv[i].equals("-d")) { if (i + 1 >= argv.length) {
usage(); throw (new IllegalArgumentException("Directory name must be
* specified")); }
*
* dirName = argv[i + 1]; i++; continue; }
*
* if (argv[i].equals("-c")) { if (i + 1 >= argv.length) {
usage(); throw (new IllegalArgumentException("Class name must be
* specified")); }
*
* className = argv[i + 1]; i++; continue; }
*
* usage(); throw (new IllegalArgumentException("Unknown
argument \"" + argv[i] + "\"")); }
*
* if (dirName == null) { usage(); throw (new
IllegalArgumentException("Directory name must be specified")); }
*
* if (className == null) { usage(); throw (new
IllegalArgumentException("Class name must be specified")); } }
*/
private void usage() {
System.err.println("usage: java " + getClass().getName()
+ " -d <dir_name> -c <class_name> [-p] [-v]");
}
}
String location = Class.forName(yourclass).getProtectionDomain().getCodeSource()
.getLocation().toString();
It would be simple to write a CFM or JSP to do this one a app server. This approach has the added advantage of ensuring that you're searching the same code paths as your web application (assuming you need to do it for your web app); it can happen (it does to me!) that mistakes are made in command line parameters.
This is especially important when checking on applications deployed in different locations, since there may be dead JAR/WAR/EAR files floating about that have old versions of a class.
Thanks again for digging this up for me the other day. I'm sure it's not the last time I'll need to lookup in CFMX to see if a class is already available.
Thanks.