From 7b170368e5464bcedc7c673d7dc75604def523e4 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 16 May 2014 10:44:01 +0100 Subject: [PATCH] Require single main class Update run tasks to ensure that only a single main class is required when performing a class search. See gh-886 --- .../boot/gradle/task/ComputeMain.java | 4 +- .../boot/gradle/task/RunApp.java | 2 +- .../boot/loader/tools/MainClassFinder.java | 58 ++++++++++++++++++- .../boot/loader/tools/Repackager.java | 30 +--------- .../loader/tools/MainClassFinderTests.java | 24 ++++++++ .../springframework/boot/maven/RunMojo.java | 2 +- 6 files changed, 86 insertions(+), 34 deletions(-) diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/ComputeMain.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/ComputeMain.java index 019a0b63e2b..653c76edb7f 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/ComputeMain.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/ComputeMain.java @@ -29,7 +29,7 @@ import org.springframework.boot.loader.tools.MainClassFinder; /** * Add a main class if one is missing from the build - * + * * @author Dave Syer */ public class ComputeMain implements Action { @@ -68,7 +68,7 @@ public class ComputeMain implements Action { project.getLogger().debug( "Looking for main in: " + main.getOutput().getClassesDir()); try { - String mainClass = MainClassFinder.findMainClass(main.getOutput() + String mainClass = MainClassFinder.findSingleMainClass(main.getOutput() .getClassesDir()); project.getLogger().info("Computed main class: " + mainClass); return mainClass; diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/RunApp.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/RunApp.java index 5f873bf46ba..748704c0a69 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/RunApp.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/task/RunApp.java @@ -91,7 +91,7 @@ public class RunApp extends DefaultTask { } getLogger().info("Looking for main in: " + main.getOutput().getClassesDir()); try { - return MainClassFinder.findMainClass(main.getOutput().getClassesDir()); + return MainClassFinder.findSingleMainClass(main.getOutput().getClassesDir()); } catch (IOException ex) { throw new IllegalStateException("Cannot find main class", ex); diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/MainClassFinder.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/MainClassFinder.java index cdb0849f763..d41efb6f37e 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/MainClassFinder.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/MainClassFinder.java @@ -29,7 +29,9 @@ import java.util.Collections; import java.util.Comparator; import java.util.Deque; import java.util.Enumeration; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -85,6 +87,18 @@ public abstract class MainClassFinder { }); } + /** + * Find a single main class from a given folder. + * @param rootFolder the root folder to search + * @return the main class or {@code null} + * @throws IOException + */ + public static String findSingleMainClass(File rootFolder) throws IOException { + MainClassesCallback callback = new MainClassesCallback(); + MainClassFinder.doWithMainClasses(rootFolder, callback); + return callback.getMainClass(); + } + /** * Perform the given callback operation on all main classes from the given root * folder. @@ -93,7 +107,7 @@ public abstract class MainClassFinder { * @return the first callback result or {@code null} * @throws IOException */ - public static T doWithMainClasses(File rootFolder, ClassNameCallback callback) + static T doWithMainClasses(File rootFolder, ClassNameCallback callback) throws IOException { if (!rootFolder.exists()) { return null; // nothing to do @@ -160,6 +174,20 @@ public abstract class MainClassFinder { }); } + /** + * Find a single main class in a given jar file. + * @param jarFile the jar file to search + * @param classesLocation the location within the jar containing classes + * @return the main class or {@code null} + * @throws IOException + */ + public static String findSingleMainClass(JarFile jarFile, String classesLocation) + throws IOException { + MainClassesCallback callback = new MainClassesCallback(); + MainClassFinder.doWithMainClasses(jarFile, classesLocation, callback); + return callback.getMainClass(); + } + /** * Perform the given callback operation on all main classes from the given jar. * @param jarFile the jar file to search @@ -167,7 +195,7 @@ public abstract class MainClassFinder { * @return the first callback result or {@code null} * @throws IOException */ - public static T doWithMainClasses(JarFile jarFile, String classesLocation, + static T doWithMainClasses(JarFile jarFile, String classesLocation, ClassNameCallback callback) throws IOException { List classEntries = getClassEntries(jarFile, classesLocation); Collections.sort(classEntries, new ClassEntryComparator()); @@ -293,4 +321,30 @@ public abstract class MainClassFinder { T doWith(String className); } + + /** + * Find a single main class, throwing an {@link IllegalStateException} if multiple + * candidates exist. + */ + private static class MainClassesCallback implements ClassNameCallback { + + private final Set classNames = new LinkedHashSet(); + + @Override + public Object doWith(String className) { + this.classNames.add(className); + return null; + } + + public String getMainClass() { + if (this.classNames.size() > 1) { + throw new IllegalStateException( + "Unable to find a single main class from the following candidates " + + this.classNames); + } + return this.classNames.isEmpty() ? null : this.classNames.iterator().next(); + } + + } + } diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java index febf6c0ba9b..3f3a815e84f 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Repackager.java @@ -20,13 +20,9 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; import java.util.jar.JarFile; import java.util.jar.Manifest; -import org.springframework.boot.loader.tools.MainClassFinder.ClassNameCallback; - /** * Utility class that can be used to repackage an archive so that it can be executed using * '{@literal java -jar}'. @@ -228,10 +224,8 @@ public class Repackager { } protected String findMainMethod(JarFile source) throws IOException { - MainClassesCallback callback = new MainClassesCallback(); - MainClassFinder.doWithMainClasses(source, this.layout.getClassesLocation(), - callback); - return callback.getMainClass(); + return MainClassFinder.findSingleMainClass(source, + this.layout.getClassesLocation()); } private void renameFile(File file, File dest) { @@ -247,24 +241,4 @@ public class Repackager { } } - private static class MainClassesCallback implements ClassNameCallback { - - private final List classNames = new ArrayList(); - - @Override - public Object doWith(String className) { - this.classNames.add(className); - return null; - } - - public String getMainClass() { - if (this.classNames.size() > 1) { - throw new IllegalStateException( - "Unable to find a single main class from the following candidates " - + this.classNames); - } - return this.classNames.isEmpty() ? null : this.classNames.get(0); - } - - } } diff --git a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/MainClassFinderTests.java b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/MainClassFinderTests.java index 33d82eda36b..9e689126432 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/MainClassFinderTests.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/MainClassFinderTests.java @@ -23,6 +23,7 @@ import java.util.List; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.springframework.boot.loader.tools.MainClassFinder.ClassNameCallback; import org.springframework.boot.loader.tools.sample.ClassWithMainMethod; @@ -41,6 +42,9 @@ public class MainClassFinderTests { @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); + @Rule + public ExpectedException thrown = ExpectedException.none(); + private TestJarFile testJarFile; @Before @@ -73,6 +77,16 @@ public class MainClassFinderTests { assertThat(actual, equalTo("a.B")); } + @Test + public void findSingleJarSearch() throws Exception { + this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class); + this.testJarFile.addClass("a/b/c/E.class", ClassWithMainMethod.class); + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("Unable to find a single main class " + + "from the following candidates [a.B, a.b.c.E]"); + MainClassFinder.findSingleMainClass(this.testJarFile.getJarFile(), ""); + } + @Test public void findMainClassInJarSubLocation() throws Exception { this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class); @@ -108,6 +122,16 @@ public class MainClassFinderTests { assertThat(actual, equalTo("a.B")); } + @Test + public void findSingleFolderSearch() throws Exception { + this.testJarFile.addClass("a/B.class", ClassWithMainMethod.class); + this.testJarFile.addClass("a/b/c/E.class", ClassWithMainMethod.class); + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("Unable to find a single main class " + + "from the following candidates [a.B, a.b.c.E]"); + MainClassFinder.findSingleMainClass(this.testJarFile.getJarSource()); + } + @Test public void doWithFolderMainMethods() throws Exception { this.testJarFile.addClass("a/b/c/D.class", ClassWithMainMethod.class); diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java index 77ed9bf4955..0f6ce1323da 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java +++ b/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java @@ -217,7 +217,7 @@ public class RunMojo extends AbstractDependencyFilterMojo { String mainClass = this.mainClass; if (mainClass == null) { try { - mainClass = MainClassFinder.findMainClass(this.classesDirectory); + mainClass = MainClassFinder.findSingleMainClass(this.classesDirectory); } catch (IOException ex) { throw new MojoExecutionException(ex.getMessage(), ex);