diff --git a/java/src/org/broadinstitute/sting/gatk/WalkerManager.java b/java/src/org/broadinstitute/sting/gatk/WalkerManager.java index 801e67a30..a26ff32b5 100755 --- a/java/src/org/broadinstitute/sting/gatk/WalkerManager.java +++ b/java/src/org/broadinstitute/sting/gatk/WalkerManager.java @@ -34,22 +34,22 @@ public class WalkerManager { public WalkerManager( String pluginDirectory ) { try { - final File jarFile = getThisJarFile(); - - if(pluginDirectory == null) - pluginDirectory = jarFile.getParent() + File.separator + "walkers"; - - System.out.println("plugin directory: " + pluginDirectory); - List walkerClasses = new ArrayList(); // Load all classes that live in this jar. - walkerClasses.addAll( loadClassesFromJar( jarFile ) ); + final File location = getThisLocation(); + walkerClasses.addAll( loadClassesFromLocation( location ) ); // Load all classes that live in the extension path. + if(pluginDirectory == null) + pluginDirectory = location.getParent() + File.separator + "walkers"; + System.out.println("plugin directory: " + pluginDirectory); + File extensionPath = new File( pluginDirectory ); - if(extensionPath.exists()) - walkerClasses.addAll( loadClassesFromPath( extensionPath ) ); + if(extensionPath.exists()) { + List filesInPath = findFilesInPath( extensionPath, "", "class", false ); + walkerClasses.addAll( loadExternalClasses( extensionPath, filesInPath ) ); + } walkerClasses = filterWalkers(walkerClasses); @@ -95,10 +95,10 @@ public class WalkerManager { * Determines which jar file contains the WalkerManager class. * @return Jar file containing the WalkerManager class. */ - private File getThisJarFile() throws IOException { + private File getThisLocation() throws IOException { try { - java.net.URI jarURI = getClass().getProtectionDomain().getCodeSource().getLocation().toURI(); - return new File( jarURI ); + 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. @@ -106,6 +106,22 @@ public class WalkerManager { } } + /** + * Load classes internal to the classpath from an arbitrary location. + * @param location Location from which to load classes. + * @return List of classes. + * @throws IOException Problem occurred reading classes. + */ + private List loadClassesFromLocation( File location ) + throws IOException { + if( location.getAbsolutePath().endsWith(".jar") ) + return loadClassesFromJar( 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. @@ -125,7 +141,7 @@ public class WalkerManager { String jarEntryName = jarEntry.getName(); if(jarEntryName.endsWith(".class")) { - String className = jarEntryName.substring(0,jarEntryName.lastIndexOf(".class")).replace('/','.'); + String className = fileNameToClassName(jarEntryName); subclasses.add( Class.forName(className) ); } jarEntry = jarInputStream.getNextJarEntry(); @@ -143,11 +159,35 @@ public class WalkerManager { } /** - * Load loose classes from the specified directory. + * 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 loadClassesFromPath(final File path) + private List loadExternalClasses(final File path, List classFileNames) throws IOException { List subclasses = new ArrayList(); @@ -155,14 +195,9 @@ public class WalkerManager { ClassLoader cl = new URLClassLoader(new URL[] { pathURL }); - File[] classFiles = path.listFiles( - new FilenameFilter() { public boolean accept( File f, String s ) { return s.endsWith(".class"); } }); - for(File classFile: classFiles) { - // Poor person's way of getting the classname: assume classes are unpackaged. - // Chop out the classname section of the URL, and assume the class is unpackaged. - String fileName = classFile.getName(); - String className = fileName.substring(fileName.lastIndexOf(File.separator)+1,fileName.lastIndexOf(".class")); - + List filesInPath = findFilesInPath( path, "", "class", false ); + for( String file: filesInPath ) { + String className = fileNameToClassName( file ); try { subclasses.add(cl.loadClass(className)); } @@ -170,12 +205,69 @@ public class WalkerManager { // 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 ); } + } + /** * Given a list of classes, return a list of those classes which extend from the Walker base interface. * @param classes Arbitrary list of classes.