diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/AbstractLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/AbstractLauncher.java new file mode 100644 index 0000000000..910e6a6db0 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/AbstractLauncher.java @@ -0,0 +1,104 @@ +/* + * Copyright 2013 the original author or authors. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +package org.springframework.boot.loader; + +import java.io.File; +import java.net.URI; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +/** + * Base class for launchers that can start an application with a fully configured + * classpath. + * + * @author Phillip Webb + * @author Dave Syer + */ +public abstract class AbstractLauncher implements ArchiveFilter { + + private Logger logger = Logger.getLogger(AbstractLauncher.class.getName()); + + private LaunchHelper helper = new LaunchHelper(); + + /** + * Launch the application. This method is the initial entry point that should be + * called by a subclass {@code public static void main(String[] args)} method. + * @param args the incoming arguments + */ + public void launch(String[] args) { + try { + launch(args, getClass().getProtectionDomain()); + } + catch (Exception ex) { + ex.printStackTrace(); + System.exit(1); + } + } + + /** + * Launch the application given the protection domain. + * @param args the incoming arguments + * @param protectionDomain the protection domain + * @throws Exception + */ + protected void launch(String[] args, ProtectionDomain protectionDomain) + throws Exception { + CodeSource codeSource = protectionDomain.getCodeSource(); + URI location = (codeSource == null ? null : codeSource.getLocation().toURI()); + String path = (location == null ? null : location.getPath()); + if (path == null) { + throw new IllegalStateException("Unable to determine code source archive"); + } + File root = new File(path); + if (!root.exists()) { + throw new IllegalStateException( + "Unable to determine code source archive from " + root); + } + Archive archive = (root.isDirectory() ? new ExplodedArchive(root) + : new JarFileArchive(root)); + launch(args, archive); + } + + /** + * Launch the application given the archive file + * @param args the incoming arguments + * @param archive the underlying (zip/war/jar) archive + * @throws Exception + */ + protected void launch(String[] args, Archive archive) throws Exception { + List lib = new ArrayList(); + lib.addAll(this.helper.findNestedArchives(archive, this)); + this.logger.fine("Added " + lib.size() + " entries"); + postProcessLib(archive, lib); + String mainClass = this.helper.getMainClass(archive); + this.helper.launch(args, mainClass, lib); + } + + /** + * Called to post-process lib entries before they are used. Implementations can add + * and remove entries. + * @param archive the archive + * @param lib the existing lib + * @throws Exception + */ + protected void postProcessLib(Archive archive, List lib) throws Exception { + } + +} diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Archive.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Archive.java index bda0cf5f5d..f7ca8a2e01 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Archive.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Archive.java @@ -22,7 +22,7 @@ import java.net.URL; import java.util.jar.Manifest; /** - * An archive that can be launched by the {@link Launcher}. + * An archive that can be launched by the {@link AbstractLauncher}. * * @author Phillip Webb * @see JarFileArchive diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ArchiveFilter.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ArchiveFilter.java new file mode 100644 index 0000000000..f3cfa0ca95 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ArchiveFilter.java @@ -0,0 +1,26 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +package org.springframework.boot.loader; + +/** + * @author Dave Syer + */ +public interface ArchiveFilter { + + public boolean isArchive(Archive.Entry entry); + +} diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java index 87720bdd9d..91b47bfcbf 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java @@ -19,15 +19,19 @@ package org.springframework.boot.loader; import java.util.List; /** - * {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are - * included inside a {@code /lib} directory. + * {@link AbstractLauncher} for JAR based archives. This launcher assumes that dependency + * jars are included inside a {@code /lib} directory. * * @author Phillip Webb */ -public class JarLauncher extends Launcher { +public class JarLauncher extends AbstractLauncher { + + public static void main(String[] args) { + new JarLauncher().launch(args); + } @Override - protected boolean isNestedArchive(Archive.Entry entry) { + public boolean isArchive(Archive.Entry entry) { return !entry.isDirectory() && entry.getName().startsWith("lib/"); } @@ -36,8 +40,4 @@ public class JarLauncher extends Launcher { lib.add(0, archive); } - public static void main(String[] args) { - new JarLauncher().launch(args); - } - } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchHelper.java similarity index 54% rename from spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java rename to spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchHelper.java index 8ab325434f..e5882ee4c0 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,110 +16,71 @@ package org.springframework.boot.loader; -import java.io.File; import java.lang.reflect.Constructor; -import java.net.URI; import java.net.URL; -import java.security.CodeSource; -import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.List; -import java.util.jar.JarEntry; import java.util.logging.Logger; /** - * Base class for launchers that can start an application with a fully configured - * classpath. + * Common convenience methods shared by launcher implementations. * - * @author Phillip Webb + * @author Dave Syer */ -public abstract class Launcher { +public class LaunchHelper { - private Logger logger = Logger.getLogger(Launcher.class.getName()); + private Logger logger = Logger.getLogger(LaunchHelper.class.getName()); /** * The main runner class. This must be loaded by the created ClassLoader so cannot be * directly referenced. */ - private static final String RUNNER_CLASS = Launcher.class.getPackage().getName() - + ".MainMethodRunner"; + private static final String RUNNER_CLASS = AbstractLauncher.class.getPackage() + .getName() + ".MainMethodRunner"; /** - * Launch the application. This method is the initial entry point that should be - * called by a subclass {@code public static void main(String[] args)} method. * @param args the incoming arguments - */ - public void launch(String[] args) { - try { - launch(args, getClass().getProtectionDomain()); - } - catch (Exception ex) { - ex.printStackTrace(); - System.exit(1); - } - } - - /** - * Launch the application given the protection domain. - * @param args the incoming arguments - * @param protectionDomain the protection domain + * @param mainClass the main class + * @param lib a collection of archives (zip/jar/war or directory) * @throws Exception */ - protected void launch(String[] args, ProtectionDomain protectionDomain) + public void launch(String[] args, String mainClass, List lib) throws Exception { - CodeSource codeSource = protectionDomain.getCodeSource(); - URI location = (codeSource == null ? null : codeSource.getLocation().toURI()); - String path = (location == null ? null : location.getPath()); - if (path == null) { - throw new IllegalStateException("Unable to determine code source archive"); - } - File root = new File(path); - if (!root.exists()) { - throw new IllegalStateException( - "Unable to determine code source archive from " + root); - } - Archive archive = (root.isDirectory() ? new ExplodedArchive(root) - : new JarFileArchive(root)); - launch(args, archive); + ClassLoader classLoader = createClassLoader(lib); + launch(args, mainClass, classLoader); } /** - * Launch the application given the archive file - * @param args the incoming arguments - * @param archive the underlying (zip/war/jar) archive + * @param archive the archive to search + * @return an accumulation of nested archives * @throws Exception */ - protected void launch(String[] args, Archive archive) throws Exception { + public List findNestedArchives(Archive archive, ArchiveFilter filter) + throws Exception { List lib = new ArrayList(); for (Archive.Entry entry : archive.getEntries()) { - if (isNestedArchive(entry)) { + if (filter.isArchive(entry)) { this.logger.fine("Adding: " + entry.getName()); lib.add(archive.getNestedArchive(entry)); } } - - this.logger.fine("Added " + lib.size() + " entries"); - postProcessLib(archive, lib); - ClassLoader classLoader = createClassLoader(lib); - launch(args, archive, classLoader); + return lib; } /** - * Determine if the specified {@link JarEntry} is a nested item that should be added - * to the classpath. The method is called once for each entry. - * @param jarEntry the jar entry - * @return {@code true} if the entry is a nested item (jar or folder) - */ - protected abstract boolean isNestedArchive(Archive.Entry jarEntry); - - /** - * Called to post-process lib entries before they are used. Implementations can add - * and remove entries. + * Obtain the main class that should be used to launch the application. By default + * this method uses a {@code Start-Class} manifest entry. * @param archive the archive - * @param lib the existing lib + * @return the main class * @throws Exception */ - protected void postProcessLib(Archive archive, List lib) throws Exception { + public String getMainClass(Archive archive) throws Exception { + String mainClass = archive.getManifest().getMainAttributes() + .getValue("Start-Class"); + if (mainClass == null) { + throw new IllegalStateException("No 'Start-Class' manifest entry specified"); + } + return mainClass; } /** @@ -136,26 +97,15 @@ public abstract class Launcher { return createClassLoader(urls); } - /** - * Create a classloader for the specified URLs - * @param urls the URLs - * @return the classloader - * @throws Exception - */ - protected ClassLoader createClassLoader(URL[] urls) throws Exception { - return new LaunchedURLClassLoader(urls, getClass().getClassLoader()); - } - /** * Launch the application given the archive file and a fully configured classloader. * @param args the incoming arguments - * @param archive the archive + * @param mainClass the main class to run * @param classLoader the classloader * @throws Exception */ - protected void launch(String[] args, Archive archive, ClassLoader classLoader) + protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { - String mainClass = getMainClass(archive); Runnable runner = createMainMethodRunner(mainClass, args, classLoader); Thread runnerThread = new Thread(runner); runnerThread.setContextClassLoader(classLoader); @@ -164,19 +114,13 @@ public abstract class Launcher { } /** - * Obtain the main class that should be used to launch the application. By default - * this method uses a {@code Start-Class} manifest entry. - * @param archive the archive - * @return the main class + * Create a classloader for the specified URLs + * @param urls the URLs + * @return the classloader * @throws Exception */ - protected String getMainClass(Archive archive) throws Exception { - String mainClass = archive.getManifest().getMainAttributes() - .getValue("Start-Class"); - if (mainClass == null) { - throw new IllegalStateException("No 'Start-Class' manifest entry specified"); - } - return mainClass; + protected ClassLoader createClassLoader(URL[] urls) throws Exception { + return new LaunchedURLClassLoader(urls, getClass().getClassLoader()); } /** diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java index ba12d25755..039ece5a58 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java @@ -25,7 +25,7 @@ import java.security.PrivilegedExceptionAction; import org.springframework.boot.loader.jar.RandomAccessJarFile; /** - * {@link ClassLoader} used by the {@link Launcher}. + * {@link ClassLoader} used by the {@link AbstractLauncher}. * * @author Phillip Webb */ diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/MainMethodRunner.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/MainMethodRunner.java index 2c2f6b4b40..ba76c90d5d 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/MainMethodRunner.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/MainMethodRunner.java @@ -19,7 +19,7 @@ package org.springframework.boot.loader; import java.lang.reflect.Method; /** - * Utility class that used by {@link Launcher}s to call a main method. This class allows + * Utility class that used by {@link AbstractLauncher}s to call a main method. This class allows * methods to be executed within a thread configured with a specific context classloader. * * @author Phillip Webb diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java index 4f74a2597d..6e18e175e5 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java @@ -23,7 +23,6 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; -import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -33,8 +32,8 @@ import java.util.logging.Logger; import org.springframework.boot.loader.util.SystemPropertyUtils; /** - * {@link Launcher} for archives with user-configured classpath and main class via a - * properties file. This model is often more flexible and more amenable to creating + * {@link AbstractLauncher} for archives with user-configured classpath and main class via + * a properties file. This model is often more flexible and more amenable to creating * well-behaved OS-level services than a model based on executable jars. * *

@@ -49,19 +48,20 @@ import org.springframework.boot.loader.util.SystemPropertyUtils; * System properties in case the file doesn't exist): * *

    - *
  • loader.path: a comma-separated list of classpath directories - * (containing file resources and/or archives in *.jar or *.zip). Defaults to - * lib (i.e. a directory in the current working directory)
  • + *
  • loader.path: a comma-separated list of directories to append to the + * classpath (containing file resources and/or nested archives in *.jar or *.zip). + * Defaults to lib (i.e. a directory in the current working directory)
  • *
  • loader.main: the main method to delegate execution to once the class - * loader is set up. No default, but will fall back to looking in a - * MANIFEST.MF if there is one.
  • + * loader is set up. No default, but will fall back to looking for a + * Start-Class in a MANIFEST.MF, if there is one in + * ${loader.home}/META-INF. *
* * @author Dave Syer */ -public class PropertiesLauncher extends Launcher { +public class PropertiesLauncher implements ArchiveFilter { - private Logger logger = Logger.getLogger(Launcher.class.getName()); + private Logger logger = Logger.getLogger(AbstractLauncher.class.getName()); /** * Properties key for main class @@ -85,10 +85,32 @@ public class PropertiesLauncher extends Launcher { private Properties properties = new Properties(); + private LaunchHelper helper = new LaunchHelper(); + + public static void main(String[] args) { + new PropertiesLauncher().launch(args); + } + + /** + * Launch the application. This method is the initial entry point that should be + * called by a subclass {@code public static void main(String[] args)} method. + * @param args the incoming arguments + */ + public void launch(String[] args) { + try { + File home = getHomeDirectory(); + initialize(home); + this.helper.launch(args, getMainClass(home), getLibrary(home, this.paths)); + } + catch (Exception ex) { + ex.printStackTrace(); + System.exit(1); + } + } + @Override - protected void launch(String[] args, ProtectionDomain protectionDomain) - throws Exception { - launch(args, new ExplodedArchive(getHomeDirectory())); + public boolean isArchive(Archive.Entry entry) { + return entry.isDirectory() || isArchive(entry.getName()); } protected File getHomeDirectory() { @@ -96,28 +118,62 @@ public class PropertiesLauncher extends Launcher { "${user.dir}"))); } - /** - * Look in various places for a properties file to extract loader settings. Default to - * application.properties either on the current classpath or in the - * current working directory. - * @see org.springframework.boot.loader.Launcher#launch(java.lang.String[], - * org.springframework.boot.loader.Archive) - */ - @Override - protected void launch(String[] args, Archive archive) throws Exception { - initialize(); - super.launch(args, archive); + protected String getMainClass(File home) throws Exception { + if (System.getProperty(MAIN) != null) { + return SystemPropertyUtils.resolvePlaceholders(System.getProperty(MAIN)); + } + if (this.properties.containsKey(MAIN)) { + return SystemPropertyUtils.resolvePlaceholders(this.properties + .getProperty(MAIN)); + } + return this.helper.getMainClass(new ExplodedArchive(home)); } - protected void initialize() throws Exception { - initializeProperties(); + protected void initialize(File home) throws Exception { + initializeProperties(home); initializePaths(); } - private void initializeProperties() throws Exception, IOException { + private boolean isArchive(String name) { + return name.endsWith(".jar") || name.endsWith(".zip"); + } + + /** + * Search the configured paths and look for nested archives. + * + * @param home the home directory for this launch + * @param paths the directory roots for classpath entries + * @return a library of archives that can be used as a classpath + * @throws Exception + */ + private List getLibrary(File home, List paths) throws Exception { + List lib = new ArrayList(); + for (String path : paths) { + String root = cleanupPath(stripFileUrlPrefix(path)); + File file = new File(root); + if (!root.startsWith("/")) { + file = new File(home, root); + } + if (file.isDirectory()) { + this.logger.info("Adding classpath entries from " + path); + Archive archive = new ExplodedArchive(file); + lib.addAll(this.helper.findNestedArchives(archive, this)); + lib.add(0, archive); + } + else { + this.logger.info("No directory found at " + path); + } + } + return lib; + } + + private void initializeProperties(File home) throws Exception, IOException { String config = SystemPropertyUtils.resolvePlaceholders(System.getProperty( CONFIG_NAME, "application")) + ".properties"; InputStream resource = getClasspathResource(config); + if (resource == null) { + resource = getResource(new File(home, config).getAbsolutePath()); + } if (resource == null) { config = SystemPropertyUtils.resolvePlaceholders(System.getProperty( CONFIG_LOCATION, config)); @@ -262,43 +318,4 @@ public class PropertiesLauncher extends Launcher { return path; } - @Override - protected boolean isNestedArchive(Archive.Entry entry) { - String name = entry.getName(); - for (String path : this.paths) { - if (path.length() > 0) { - if ((entry.isDirectory() && name.equals(path)) - || (!entry.isDirectory() && name.startsWith(path) && isArchive(name))) { - return true; - } - } - } - return false; - } - - private boolean isArchive(String name) { - return name.endsWith(".jar") || name.endsWith(".zip"); - } - - @Override - protected void postProcessLib(Archive archive, List lib) throws Exception { - lib.add(0, archive); - } - - @Override - protected String getMainClass(Archive archive) throws Exception { - if (System.getProperty(MAIN) != null) { - return SystemPropertyUtils.resolvePlaceholders(System.getProperty(MAIN)); - } - if (this.properties.containsKey(MAIN)) { - return SystemPropertyUtils.resolvePlaceholders(this.properties - .getProperty(MAIN)); - } - return super.getMainClass(archive); - } - - public static void main(String[] args) { - new PropertiesLauncher().launch(args); - } - } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java index 34a269613a..da57b306ea 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java @@ -20,16 +20,20 @@ import java.io.IOException; import java.util.List; /** - * {@link Launcher} for WAR based archives. This launcher for standard WAR archives. - * Supports dependencies in {@code WEB-INF/lib} as well as {@code WEB-INF/lib-provided}, - * classes are loaded from {@code WEB-INF/classes}. + * {@link AbstractLauncher} for WAR based archives. This launcher for standard WAR + * archives. Supports dependencies in {@code WEB-INF/lib} as well as + * {@code WEB-INF/lib-provided}, classes are loaded from {@code WEB-INF/classes}. * * @author Phillip Webb */ -public class WarLauncher extends Launcher { +public class WarLauncher extends AbstractLauncher { + + public static void main(String[] args) { + new WarLauncher().launch(args); + } @Override - protected boolean isNestedArchive(Archive.Entry entry) { + public boolean isArchive(Archive.Entry entry) { if (entry.isDirectory()) { return entry.getName().equals("WEB-INF/classes/"); } @@ -64,8 +68,4 @@ public class WarLauncher extends Launcher { }); } - public static void main(String[] args) { - new WarLauncher().launch(args); - } - } diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java index 035b87b80c..9ccd130faa 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java @@ -48,14 +48,14 @@ public class PropertiesLauncherTests { @Test public void testUserSpecifiedMain() throws Exception { - this.launcher.initialize(); + this.launcher.initialize(new File(".")); assertEquals("demo.Application", this.launcher.getMainClass(null)); } @Test public void testUserSpecifiedConfigName() throws Exception { System.setProperty("loader.config.name", "foo"); - this.launcher.initialize(); + this.launcher.initialize(new File(".")); assertEquals("my.Application", this.launcher.getMainClass(null)); assertEquals("[etc/]", ReflectionTestUtils.getField(this.launcher, "paths") .toString()); @@ -64,7 +64,7 @@ public class PropertiesLauncherTests { @Test public void testSystemPropertySpecifiedMain() throws Exception { System.setProperty("loader.main", "foo.Bar"); - this.launcher.initialize(); + this.launcher.initialize(new File(".")); assertEquals("foo.Bar", this.launcher.getMainClass(null)); }