diff --git a/java/src/org/broadinstitute/sting/gatk/WalkerManager.java b/java/src/org/broadinstitute/sting/gatk/WalkerManager.java index 030f39e9e..e20600725 100755 --- a/java/src/org/broadinstitute/sting/gatk/WalkerManager.java +++ b/java/src/org/broadinstitute/sting/gatk/WalkerManager.java @@ -5,24 +5,18 @@ import net.sf.functionalj.reflect.JdkStdReflect; import net.sf.functionalj.FunctionN; import net.sf.functionalj.Functions; -import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.io.File; -import java.io.FilenameFilter; -import java.io.FileInputStream; import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import java.util.HashMap; import java.util.Map; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; import org.broadinstitute.sting.gatk.walkers.Walker; import org.broadinstitute.sting.gatk.walkers.WalkerName; -import org.broadinstitute.sting.utils.cmdLine.Argument; +import org.broadinstitute.sting.utils.JVMUtils; +import org.broadinstitute.sting.utils.PathUtils; import org.apache.log4j.Logger; /** @@ -46,7 +40,7 @@ public class WalkerManager { List walkerCandidates = new ArrayList(); // Load all classes that live in this jar. - final File location = getThisLocation(); + final File location = JVMUtils.getLocationFor( getClass() ); walkerCandidates.addAll(loadClassesFromLocation(location)); // Load all classes that live in the extension path. @@ -56,8 +50,13 @@ public class WalkerManager { File extensionPath = new File(pluginDirectory); if (extensionPath.exists()) { - List filesInPath = findFilesInPath(extensionPath, "", "class", false); - walkerCandidates.addAll(loadExternalClasses(extensionPath, filesInPath)); + List classFilesInPath = PathUtils.findFilesInPath(extensionPath, "", "class", false); + walkerCandidates.addAll(JVMUtils.loadExternalClasses(extensionPath, classFilesInPath)); + List jarsInPath = PathUtils.findFilesInPath(extensionPath, "", "jar", false); + for( String jarFileName: jarsInPath ) { + File jarFile = new File( extensionPath, jarFileName ); + walkerCandidates.addAll(JVMUtils.loadExternalClassesFromJar(jarFile) ); + } } walkerCandidates = filterWalkers(walkerCandidates); @@ -100,22 +99,6 @@ public class WalkerManager { return walkers.get(walkerName); } - /** - * Determines which jar file contains the WalkerManager class. - * - * @return Jar file containing the WalkerManager class. - */ - private File getThisLocation() throws IOException { - try { - java.net.URI locationURI = getClass().getProtectionDomain().getCodeSource().getLocation().toURI(); - return new File(locationURI); - } - catch (java.net.URISyntaxException ex) { - // a URISyntaxException here must be an IO error; wrap as such. - throw new IOException(ex); - } - } - /** * Load classes internal to the classpath from an arbitrary location. * @@ -126,175 +109,10 @@ public class WalkerManager { private List loadClassesFromLocation(File location) throws IOException { if (location.getAbsolutePath().endsWith(".jar")) - return loadClassesFromJar(location); + return JVMUtils.loadInternalClassesFromJar(location); else { - List classFileNames = findFilesInPath(location, "", "class", true); - return loadInternalClasses(classFileNames); - } - } - - /** - * Loads concrete classes from a jar which are both in the same package or 'sub-package' of baseClass, - * and which extend from baseClass. - * - * @param jarFile The jar file to search. - * @return A list of classes derived from baseClass. - */ - private List loadClassesFromJar(final File jarFile) - throws IOException { - List subclasses = new ArrayList(); - - JarInputStream jarInputStream = new JarInputStream(new FileInputStream(jarFile)); - - try { - JarEntry jarEntry = jarInputStream.getNextJarEntry(); - - while (jarEntry != null) { - String jarEntryName = jarEntry.getName(); - if (jarEntryName.endsWith(".class")) { - String className = fileNameToClassName(jarEntryName); - subclasses.add(Class.forName(className)); - } - jarEntry = jarInputStream.getNextJarEntry(); - } - } - catch (ClassNotFoundException ex) { - // A ClassNotFoundException here must be an IO error; wrap as such. - throw new IOException(ex); - } - finally { - jarInputStream.close(); - } - - return subclasses; - } - - /** - * Loads a list of classes currently on the classpath. - * - * @param classFileNames List of files representing classes. - * @return class objects. - * @throws IOException Unable to open any of the found classes. - */ - private List loadInternalClasses(List classFileNames) - throws IOException { - List internalClasses = new ArrayList(); - - for (String classFileName : classFileNames) { - String className = fileNameToClassName(classFileName); - try { - internalClasses.add(Class.forName(className)); - } - catch (ClassNotFoundException ex) { - // A ClassNotFoundException here must be an IO error; wrap as such. - throw new IOException(ex); - } - } - - return internalClasses; - } - - /** - * Load loose classes, external to the classloader, from the specified directory. - * - * @param path source path from which to load classes. - * @return A list of all loose classes contained in the path directory. - */ - private List loadExternalClasses(final File path, List classFileNames) - throws IOException { - List subclasses = new ArrayList(); - - URL pathURL = path.toURI().toURL(); - - ClassLoader cl = new URLClassLoader(new URL[]{pathURL}); - - List filesInPath = findFilesInPath(path, "", "class", false); - for (String file : filesInPath) { - String className = fileNameToClassName(file); - try { - subclasses.add(cl.loadClass(className)); - } - catch (ClassNotFoundException ex) { - // Class not found from a list of classes just looked up is an IO error. Wrap and throw. - throw new IOException(ex); - } - } - - return subclasses; - } - - /** - * Find the files in the given directory matching the given extension. - * - * @param basePath Path to search. - * @param relativePrefix What directory should the given files be presented relative to? - * @param extension Extension for which to search. - * @param recursive Search recursively. Beware of symlinks! - * @return A list of files matching the specified criteria. - * TODO: Move to a utils class. - * TODO: Test recursive traversal in the presence of a symlink. - */ - private List findFilesInPath(final File basePath, final String relativePrefix, final String extension, boolean recursive) { - List filesInPath = new ArrayList(); - - File[] contents = basePath.listFiles(new OrFilenameFilter(new DirectoryFilter(), new ExtensionFilter(extension))); - for (File content : contents) { - String relativeFileName = relativePrefix.trim().length() != 0 ? - relativePrefix + File.separator + content.getName() : - content.getName(); - if (relativeFileName.endsWith(extension)) - filesInPath.add(relativeFileName); - else if (content.isDirectory() && recursive) - filesInPath.addAll(findFilesInPath(content, relativeFileName, extension, recursive)); - } - - return filesInPath; - } - - /** - * Convert a filename of the form a/b/c.class to a.b.c. Makes no assurances about whether the - * class is valid on any classloader. - * - * @param fileName Filename to convert. - * @return classname represented by that file. - * TODO: Move to a utils class. - */ - private String fileNameToClassName(String fileName) { - return fileName.substring(0, fileName.lastIndexOf(".class")).replace('/', '.'); - } - - /** - * The following are general-purpose file selection filters. - * TODO: Move to a utils class. - */ - private class ExtensionFilter implements FilenameFilter { - private String extensionName = null; - - public ExtensionFilter(String extensionName) { - this.extensionName = extensionName; - } - - public boolean accept(File f, String s) { - return s.endsWith("." + extensionName); - } - } - - private class DirectoryFilter implements FilenameFilter { - public boolean accept(File f, String s) { - return new File(f, s).isDirectory(); - } - } - - private class OrFilenameFilter implements FilenameFilter { - private FilenameFilter lhs = null, rhs = null; - - public OrFilenameFilter(FilenameFilter lhs, FilenameFilter rhs) { - this.lhs = lhs; - this.rhs = rhs; - } - - public boolean accept(File f, String s) { - return lhs.accept(f, s) || rhs.accept(f, s); + List classFileNames = PathUtils.findFilesInPath(location, "", "class", true); + return JVMUtils.loadInternalClasses(classFileNames); } } @@ -306,27 +124,10 @@ public class WalkerManager { */ private List filterWalkers(List classes) { StdReflect reflect = new JdkStdReflect(); - FunctionN filterFunc = reflect.instanceFunction(new ClassFilter(Walker.class), "filter", Class.class); + FunctionN filterFunc = reflect.instanceFunction(new JVMUtils.ClassFilter(Walker.class), "filter", Class.class); return Functions.findAll(filterFunc.f1(), classes); } - /** - * A functor returning true for classes which extend from baseClass. - */ - private class ClassFilter { - private Class baseClass; - - public ClassFilter(Class baseClass) { - this.baseClass = baseClass; - } - - public Boolean filter(Class clazz) { - return baseClass.isAssignableFrom(clazz) && - !Modifier.isAbstract(clazz.getModifiers()) && - !Modifier.isInterface(clazz.getModifiers()); - } - } - /** * Instantiate the list of walker classes. Add them to the walker hashmap. * diff --git a/java/src/org/broadinstitute/sting/utils/JVMUtils.java b/java/src/org/broadinstitute/sting/utils/JVMUtils.java new file mode 100755 index 000000000..a9150b506 --- /dev/null +++ b/java/src/org/broadinstitute/sting/utils/JVMUtils.java @@ -0,0 +1,216 @@ +package org.broadinstitute.sting.utils; + +import java.io.File; +import java.io.IOException; +import java.io.FileInputStream; +import java.util.List; +import java.util.ArrayList; +import java.util.jar.JarInputStream; +import java.util.jar.JarEntry; +import java.net.URL; +import java.net.URLClassLoader; +import java.lang.reflect.Modifier; + +/** + * Created by IntelliJ IDEA. + * User: hanna + * Date: Mar 30, 2009 + * Time: 5:38:05 PM + * + * A set of static utility methods for determining information about this runtime environment. + * Introspects classes, loads jars, etc. + */ +public class JVMUtils { + /** + * Constructor access disallowed...static utility methods only! + */ + private JVMUtils() { } + + /** + * Determines which location contains the specified class. + * + * @return Location (either jar file or directory) of path containing class. + */ + public static File getLocationFor( Class clazz ) throws IOException { + try { + java.net.URI locationURI = clazz.getProtectionDomain().getCodeSource().getLocation().toURI(); + return new File(locationURI); + } + catch (java.net.URISyntaxException ex) { + // a URISyntaxException here must be an IO error; wrap as such. + throw new IOException(ex); + } + } + + /** + * Loads concrete classes from a jar which are both in the same package or 'sub-package' of baseClass, + * and which extend from baseClass. Loaded classes must already be on the classpath. + * + * @param jarFile The jar file to search. + * @return A list of classes derived from baseClass. + */ + public static List loadInternalClassesFromJar(final File jarFile) + throws IOException { + return loadClassesFromJar( jarFile, new InternalLoadingStrategy() ); + } + + /** + * Loads concrete classes from a jar which are both in the same package or 'sub-package' of baseClass, + * and which extend from baseClass. Loaded classes can be outside of the current classpath. + * + * @param jarFile The jar file to search. + * @return A list of classes derived from baseClass. + */ + public static List loadExternalClassesFromJar(final File jarFile) + throws IOException { + return loadClassesFromJar( jarFile, new ExternalLoadingStrategy(jarFile) ); + } + + /** + * Loads a list of classes currently on the classpath. + * + * @param classFileNames List of files representing classes. + * @return class objects. + * @throws IOException Unable to open any of the found classes. + */ + public static List loadInternalClasses(List classFileNames) + throws IOException { + return loadClasses( classFileNames, new InternalLoadingStrategy() ); + } + + /** + * Load loose classes, external to the classloader, from the specified directory. + * + * @param path source path from which to load classes. + * @return A list of all loose classes contained in the path directory. + */ + public static List loadExternalClasses(final File path, List classFileNames) + throws IOException { + return loadClasses( classFileNames, new ExternalLoadingStrategy( path ) ); + } + + /** + * Convert a filename of the form a/b/c.class to a.b.c. Makes no assurances about whether the + * class is valid on any classloader. + * + * @param fileName Filename to convert. + * @return classname represented by that file. + */ + public static String fileNameToClassName(String fileName) { + return fileName.substring(0, fileName.lastIndexOf(".class")).replace('/', '.'); + } + + /** + * A functor returning true for classes which extend from baseClass. + */ + public static class ClassFilter { + private Class baseClass; + + public ClassFilter(Class baseClass) { + this.baseClass = baseClass; + } + + public Boolean filter(Class clazz) { + return baseClass.isAssignableFrom(clazz) && + !Modifier.isAbstract(clazz.getModifiers()) && + !Modifier.isInterface(clazz.getModifiers()); + } + } + + /** + * Loads a list of classes from the given jar, using the provided loading strategy. + * @param jarFile Jar file from which to load. + * @param loader Dictates how these classes should be loaded. + * @return A list of loaded classes. + * @throws IOException In case there's an IO error trying to load the jar. + */ + private static List loadClassesFromJar( final File jarFile, final LoadingStrategy loader ) + throws IOException { + List classes = new ArrayList(); + + JarInputStream jarInputStream = new JarInputStream(new FileInputStream(jarFile)); + + try { + JarEntry jarEntry = jarInputStream.getNextJarEntry(); + + while (jarEntry != null) { + String jarEntryName = jarEntry.getName(); + if (jarEntryName.endsWith(".class")) { + String className = fileNameToClassName(jarEntryName); + classes.add( loader.load( className ) ); + } + jarEntry = jarInputStream.getNextJarEntry(); + } + } + catch (ClassNotFoundException ex) { + // A ClassNotFoundException here must be an IO error; wrap as such. + throw new IOException(ex); + } + finally { + jarInputStream.close(); + } + + return classes; + } + + /** + * Loads a list of classes, using the provided loading strategy. + * @param classFileNames Which class files to load. + * @param loader Dictates how these classes should be loaded. + * @return A list of loaded classes. + * @throws IOException In case there's an IO error trying to load the jar. + */ + private static List loadClasses( List classFileNames, LoadingStrategy loader ) + throws IOException { + List classes = new ArrayList(); + + for (String classFileName : classFileNames) { + String className = fileNameToClassName(classFileName); + try { + classes.add( loader.load( className ) ); + } + catch (ClassNotFoundException ex) { + // A ClassNotFoundException here must be an IO error; wrap as such. + throw new IOException(ex); + } + } + + return classes; + } + + + /** + * What mechanism should we use for loading a list of classes? + */ + private static interface LoadingStrategy { + Class load( String className ) throws ClassNotFoundException; + } + + /** + * An internal loading strategy, for loading classes already on the classpath. + */ + private static class InternalLoadingStrategy implements LoadingStrategy { + public Class load( String className ) + throws ClassNotFoundException { + return Class.forName( className ); + } + } + + /** + * An external loading strategy, for loading classes not necessarily already + * on the classpath. + */ + private static class ExternalLoadingStrategy implements LoadingStrategy { + private final ClassLoader classLoader; + + public ExternalLoadingStrategy( final File jarFile ) throws IOException { + URL pathURL = jarFile.toURI().toURL(); + classLoader = new URLClassLoader(new URL[]{pathURL}); + } + + public Class load( String className ) throws ClassNotFoundException { + return classLoader.loadClass(className); + } + } + +} diff --git a/java/src/org/broadinstitute/sting/utils/PathUtils.java b/java/src/org/broadinstitute/sting/utils/PathUtils.java new file mode 100755 index 000000000..e046b8bfe --- /dev/null +++ b/java/src/org/broadinstitute/sting/utils/PathUtils.java @@ -0,0 +1,92 @@ +package org.broadinstitute.sting.utils; + +import java.util.List; +import java.util.ArrayList; +import java.io.File; +import java.io.FilenameFilter; + +/** + * Created by IntelliJ IDEA. + * User: hanna + * Date: Mar 30, 2009 + * Time: 5:43:39 PM + * To change this template use File | Settings | File Templates. + * + * A set of static utility methods for common operations on paths. + */ +public class PathUtils { + /** + * Constructor access disallowed...static utility methods only! + */ + private PathUtils() { } + + /** + * Find the files in the given directory matching the given extension. + * + * @param basePath Path to search. + * @param relativePrefix What directory should the given files be presented relative to? + * @param extension Extension for which to search. + * @param recursive Search recursively. Beware of symlinks! + * @return A list of files matching the specified criteria. + * TODO: Test recursive traversal in the presence of a symlink. + */ + public static List findFilesInPath(final File basePath, final String relativePrefix, final String extension, boolean recursive) { + List filesInPath = new ArrayList(); + + FilenameFilter filter = new OrFilenameFilter(new DirectoryFilter(), + new ExtensionFilter(extension)); + File[] contents = basePath.listFiles( filter ); + for (File content : contents) { + String relativeFileName = relativePrefix.trim().length() != 0 ? + relativePrefix + File.separator + content.getName() : + content.getName(); + if (relativeFileName.endsWith(extension)) + filesInPath.add(relativeFileName); + else if (content.isDirectory() && recursive) + filesInPath.addAll(findFilesInPath(content, relativeFileName, extension, recursive)); + } + + return filesInPath; + } + + /** + * Filter files by extension. + */ + public static class ExtensionFilter implements FilenameFilter { + private String extensionName = null; + + public ExtensionFilter(String extensionName) { + this.extensionName = extensionName; + } + + public boolean accept(File f, String s) { + return s.endsWith("." + extensionName); + } + } + + /** + * Filter directories from list of files. + */ + public static class DirectoryFilter implements FilenameFilter { + public boolean accept(File f, String s) { + return new File(f, s).isDirectory(); + } + } + + /** + * Join two FilenameFilters together in a logical 'or' operation. + */ + public static class OrFilenameFilter implements FilenameFilter { + private FilenameFilter lhs = null, rhs = null; + + public OrFilenameFilter(FilenameFilter lhs, FilenameFilter rhs) { + this.lhs = lhs; + this.rhs = rhs; + } + + public boolean accept(File f, String s) { + return lhs.accept(f, s) || rhs.accept(f, s); + } + } + +}