From bd73705393644e7fca2227795fae6b82e23101cd Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 10 Jan 2014 10:41:08 +0000 Subject: [PATCH] FileOptions -> SourceOptions to work with String paths instead of Files We check for existence of the sources and (as before) resolve multiple resources on the classpath if a path is not a File. In addition supports Spring pseudo-URL prefixes as well as normal URLs as source locations. In addition sources can now be specified as a directory (searched recursively by default), or a resource pattern (e.g. app/**/*.groovy). Fixes gh-207 --- .../boot/cli/command/FileOptions.java | 140 --------------- .../boot/cli/command/GrabCommand.java | 4 +- .../boot/cli/command/InitCommand.java | 5 +- .../boot/cli/command/RunCommand.java | 10 +- .../boot/cli/command/SourceOptions.java | 113 ++++++++++++ .../boot/cli/command/TestCommand.java | 4 +- .../boot/cli/compiler/GroovyCompiler.java | 43 +++-- .../cli/runner/SpringApplicationRunner.java | 28 +-- .../boot/cli/testrunner/TestRunner.java | 5 +- .../boot/cli/util/ResourceUtils.java | 166 ++++++++++++++++++ .../springframework/boot/cli/CliTester.java | 10 +- .../cli/DirectorySourcesIntegrationTests.java | 52 ++++++ .../ScriptCompilationCustomizerTests.java | 22 +-- .../boot/cli/util/ResourceUtilsTests.java | 135 ++++++++++++++ .../test/resources/dir-sample/code/app.groovy | 23 +++ 15 files changed, 560 insertions(+), 200 deletions(-) delete mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/command/FileOptions.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/command/SourceOptions.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/util/ResourceUtils.java create mode 100644 spring-boot-cli/src/test/java/org/springframework/boot/cli/DirectorySourcesIntegrationTests.java create mode 100644 spring-boot-cli/src/test/java/org/springframework/boot/cli/util/ResourceUtilsTests.java create mode 100644 spring-boot-cli/src/test/resources/dir-sample/code/app.groovy diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/FileOptions.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/FileOptions.java deleted file mode 100644 index 2955b68f7db..00000000000 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/FileOptions.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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.cli.command; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; - -import joptsimple.OptionSet; - -/** - * Extract file options (anything following '--' in an {@link OptionSet}). - * - * @author Phillip Webb - * @author Dave Syer - * @author Greg Turnquist - */ -public class FileOptions { - - private List files; - - private List args; - - /** - * Create a new {@link FileOptions} instance. - * @param options the source option set - */ - public FileOptions(OptionSet options) { - this(options, null); - } - - /** - * Create a new {@link FileOptions} instance. If it is an error to pass options that - * specify non-existent files, but the default paths are allowed not to exist (the - * paths are tested before use). If default paths are provided and the option set - * contains no file arguments it is not an error even if none of the default paths - * exist). - * - * @param optionSet the source option set - * @param classLoader an optional classloader used to try and load files that are not - * found in the local filesystem - * @param defaultPaths the default paths to use if no files are provided in the option - * set - */ - public FileOptions(OptionSet optionSet, ClassLoader classLoader, - String... defaultPaths) { - List nonOptionArguments = optionSet.nonOptionArguments(); - List files = new ArrayList(); - for (Object option : nonOptionArguments) { - if (option instanceof String) { - String filename = (String) option; - if ("--".equals(filename)) { - break; - } - if (filename.endsWith(".groovy") || filename.endsWith(".java")) { - List file = getFiles(filename, classLoader); - if (file.isEmpty()) { - throw new IllegalArgumentException("Can't find " + filename); - } - files.addAll(file); - } - } - } - this.args = Collections.unmodifiableList(nonOptionArguments.subList(files.size(), - nonOptionArguments.size())); - if (files.size() == 0) { - if (defaultPaths.length == 0) { - throw new RuntimeException("Please specify at least one file to run"); - } - for (String path : defaultPaths) { - for (File file : getFiles(path, classLoader)) { - if (file != null && file.exists()) { - files.add(file); - } - } - } - } - this.files = Collections.unmodifiableList(files); - } - - private List getFiles(String filename, ClassLoader classLoader) { - File file = new File(filename); - if (file.isFile() && file.canRead()) { - return Arrays.asList(file); - } - List result = new ArrayList(); - if (classLoader != null) { - Enumeration urls; - try { - urls = classLoader.getResources(filename); - while (urls.hasMoreElements()) { - URL url = urls.nextElement(); - if (url != null && url.toString().startsWith("file:")) { - result.add(new File(url.toString().substring("file:".length()))); - } - } - } - catch (IOException e) { - // Ignore - } - } - return result; - } - - public List getArgs() { - return this.args; - } - - public String[] getArgsArray() { - return this.args.toArray(new String[this.args.size()]); - } - - public List getFiles() { - return this.files; - } - - public File[] getFilesArray() { - return this.files.toArray(new File[this.files.size()]); - } - -} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/GrabCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/GrabCommand.java index 6475d0e631b..4f4cf8f508a 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/GrabCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/GrabCommand.java @@ -43,7 +43,7 @@ public class GrabCommand extends OptionParsingCommand { @Override protected void run(OptionSet options) throws Exception { - FileOptions fileOptions = new FileOptions(options); + SourceOptions fileOptions = new SourceOptions(options); List repositoryConfiguration = RepositoryConfigurationFactory .createDefaultRepositoryConfiguration(); @@ -56,7 +56,7 @@ public class GrabCommand extends OptionParsingCommand { } GroovyCompiler groovyCompiler = new GroovyCompiler(configuration); - groovyCompiler.compile(fileOptions.getFilesArray()); + groovyCompiler.compile(fileOptions.getSourcesArray()); } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/InitCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/InitCommand.java index 7eadae13b3f..0b29193700a 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/InitCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/InitCommand.java @@ -20,7 +20,6 @@ import groovy.lang.Closure; import groovy.lang.GroovyClassLoader; import groovy.lang.Script; -import java.io.File; import java.util.List; import java.util.Map; import java.util.ServiceLoader; @@ -76,9 +75,9 @@ public class InitCommand extends OptionParsingCommand { ClassLoader loader = Thread.currentThread().getContextClassLoader(); boolean enhanced = false; - FileOptions fileOptions = new FileOptions(options, loader, "init.groovy", + SourceOptions fileOptions = new SourceOptions(options, loader, "init.groovy", "spring.groovy"); - File[] files = fileOptions.getFilesArray(); + String[] files = fileOptions.getSourcesArray(); if (!(loader instanceof GroovyClassLoader)) { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java index fb11f1fba1b..ab4574fdb5e 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/RunCommand.java @@ -36,7 +36,7 @@ import static java.util.Arrays.asList; /** * {@link Command} to 'run' a groovy script or scripts. - * + * * @author Phillip Webb * @author Dave Syer * @author Andy Wilkinson @@ -95,22 +95,22 @@ public class RunCommand extends OptionParsingCommand { "Already running. Please stop the current application before running another."); } - FileOptions fileOptions = new FileOptions(options); + SourceOptions fileOptions = new SourceOptions(options); if (options.has(this.editOption)) { - Desktop.getDesktop().edit(fileOptions.getFiles().get(0)); + Desktop.getDesktop().edit(new File(fileOptions.getSources().get(0))); } List repositoryConfiguration = RepositoryConfigurationFactory .createDefaultRepositoryConfiguration(); repositoryConfiguration.add(0, new RepositoryConfiguration("local", new File( - "repository").toURI(), true)); + "repository").toURI(), true)); SpringApplicationRunnerConfiguration configuration = new SpringApplicationRunnerConfigurationAdapter( options, this, repositoryConfiguration); this.runner = new SpringApplicationRunner(configuration, - fileOptions.getFilesArray(), fileOptions.getArgsArray()); + fileOptions.getSourcesArray(), fileOptions.getArgsArray()); this.runner.compileAndRun(); } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/SourceOptions.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/SourceOptions.java new file mode 100644 index 00000000000..2c93a449027 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/SourceOptions.java @@ -0,0 +1,113 @@ +/* + * 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.cli.command; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import joptsimple.OptionSet; + +import org.springframework.boot.cli.util.ResourceUtils; + +/** + * Extract source file options (anything following '--' in an {@link OptionSet}). + * + * @author Phillip Webb + * @author Dave Syer + * @author Greg Turnquist + */ +public class SourceOptions { + + private List sources; + + private List args; + + /** + * Create a new {@link SourceOptions} instance. + * @param options the source option set + */ + public SourceOptions(OptionSet options) { + this(options, null); + } + + /** + * Create a new {@link SourceOptions} instance. If it is an error to pass options that + * specify non-existent sources, but the default paths are allowed not to exist (the + * paths are tested before use). If default paths are provided and the option set + * contains no source file arguments it is not an error even if none of the default + * paths exist). + * + * @param optionSet the source option set + * @param classLoader an optional classloader used to try and load files that are not + * found in the local filesystem + * @param defaultPaths the default paths to use if no files are provided in the option + * set + */ + public SourceOptions(OptionSet optionSet, ClassLoader classLoader, + String... defaultPaths) { + List nonOptionArguments = optionSet.nonOptionArguments(); + List sources = new ArrayList(); + for (Object option : nonOptionArguments) { + if (option instanceof String) { + String filename = (String) option; + if ("--".equals(filename)) { + break; + } + List urls = ResourceUtils.getUrls(filename, classLoader); + for (String url : urls) { + if (url.endsWith(".groovy") || url.endsWith(".java")) { + sources.add(url); + } + } + if ((filename.endsWith(".groovy") || filename.endsWith(".java")) + && urls.isEmpty()) { + throw new IllegalArgumentException("Can't find " + filename); + } + } + } + this.args = Collections.unmodifiableList(nonOptionArguments.subList( + sources.size(), nonOptionArguments.size())); + if (sources.size() == 0) { + if (defaultPaths.length == 0) { + throw new IllegalArgumentException( + "Please specify at least one file to run"); + } + for (String path : defaultPaths) { + sources.addAll(ResourceUtils.getUrls(path, classLoader)); + } + } + this.sources = Collections.unmodifiableList(sources); + } + + public List getArgs() { + return this.args; + } + + public String[] getArgsArray() { + return this.args.toArray(new String[this.args.size()]); + } + + public List getSources() { + return this.sources; + } + + public String[] getSourcesArray() { + return this.sources.toArray(new String[this.sources.size()]); + } + +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java index 6b7079f09bd..6f638235c56 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/TestCommand.java @@ -46,10 +46,10 @@ public class TestCommand extends OptionParsingCommand { @Override protected void run(OptionSet options) throws Exception { - FileOptions fileOptions = new FileOptions(options); + SourceOptions fileOptions = new SourceOptions(options); TestRunnerConfiguration configuration = new TestRunnerConfigurationAdapter( options, this); - this.runner = new TestRunner(configuration, fileOptions.getFilesArray(), + this.runner = new TestRunner(configuration, fileOptions.getSourcesArray(), fileOptions.getArgsArray()); this.runner.compileAndRunTests(); } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java index 64770803690..e6517a49ea9 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java @@ -18,8 +18,8 @@ package org.springframework.boot.cli.compiler; import groovy.lang.GroovyClassLoader; import groovy.lang.GroovyClassLoader.ClassCollector; +import groovy.lang.GroovyCodeSource; -import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.net.URL; @@ -49,10 +49,12 @@ import org.springframework.boot.cli.compiler.grape.GrapeEngineInstaller; import org.springframework.boot.cli.compiler.transformation.DependencyAutoConfigurationTransformation; import org.springframework.boot.cli.compiler.transformation.GroovyBeansTransformation; import org.springframework.boot.cli.compiler.transformation.ResolveDependencyCoordinatesTransformation; +import org.springframework.boot.cli.util.ResourceUtils; /** - * Compiler for Groovy source files. Primarily a simple Facade for - * {@link GroovyClassLoader#parseClass(File)} with the following additional features: + * Compiler for Groovy sources. Primarily a simple Facade for + * {@link GroovyClassLoader#parseClass(GroovyCodeSource)} with the following additional + * features: *
    *
  • {@link CompilerAutoConfiguration} strategies will be read from * META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration @@ -152,32 +154,33 @@ public class GroovyCompiler { this.loader.getConfiguration().addCompilationCustomizers(customizers); } - public Object[] sources(File... files) throws CompilationFailedException, IOException { - List compilables = new ArrayList(); + public Object[] sources(String... sources) throws CompilationFailedException, + IOException { + List compilables = new ArrayList(); List others = new ArrayList(); - for (File file : files) { - if (file.getName().endsWith(".groovy") || file.getName().endsWith(".java")) { - compilables.add(file); + for (String source : sources) { + if (source.endsWith(".groovy") || source.endsWith(".java")) { + compilables.add(source); } else { - others.add(file); + others.add(source); } } - Class[] compiled = compile(compilables.toArray(new File[compilables.size()])); + Class[] compiled = compile(compilables.toArray(new String[compilables.size()])); others.addAll(0, Arrays.asList(compiled)); return others.toArray(new Object[others.size()]); } /** - * Compile the specified Groovy source files, applying any - * {@link CompilerAutoConfiguration}s. All classes defined in the files will be + * Compile the specified Groovy sources, applying any + * {@link CompilerAutoConfiguration}s. All classes defined in the sources will be * returned from this method. - * @param file the file to compile + * @param sources the sources to compile * @return compiled classes * @throws CompilationFailedException * @throws IOException */ - public Class[] compile(File... file) throws CompilationFailedException, + public Class[] compile(String... sources) throws CompilationFailedException, IOException { this.loader.clearCache(); @@ -187,13 +190,15 @@ public class GroovyCompiler { CompilationUnit compilationUnit = new CompilationUnit(configuration, null, this.loader); - SourceUnit sourceUnit = new SourceUnit(file[0], configuration, this.loader, - compilationUnit.getErrorCollector()); - ClassCollector collector = this.loader.createCollector(compilationUnit, - sourceUnit); + ClassCollector collector = this.loader.createCollector(compilationUnit, null); compilationUnit.setClassgenCallback(collector); - compilationUnit.addSources(file); + for (String source : sources) { + List paths = ResourceUtils.getUrls(source, this.loader); + for (String path : paths) { + compilationUnit.addSource(new URL(path)); + } + } addAstTransformations(compilationUnit); diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunner.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunner.java index 12fa1befa9b..63e219f9ce1 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunner.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/runner/SpringApplicationRunner.java @@ -39,7 +39,7 @@ public class SpringApplicationRunner { private final SpringApplicationRunnerConfiguration configuration; - private final File[] files; + private final String[] files; private final String[] args; @@ -56,7 +56,7 @@ public class SpringApplicationRunner { * @param args input arguments */ public SpringApplicationRunner( - final SpringApplicationRunnerConfiguration configuration, File[] files, + final SpringApplicationRunnerConfiguration configuration, String[] files, String... args) { this.configuration = configuration; this.files = files.clone(); @@ -176,10 +176,13 @@ public class SpringApplicationRunner { public FileWatchThread() { super("filewatcher-" + (watcherCounter++)); this.previous = 0; - for (File file : SpringApplicationRunner.this.files) { - long current = file.lastModified(); - if (current > this.previous) { - this.previous = current; + for (String path : SpringApplicationRunner.this.files) { + File file = new File(path); + if (file.exists()) { + long current = file.lastModified(); + if (current > this.previous) { + this.previous = current; + } } } setDaemon(false); @@ -190,11 +193,14 @@ public class SpringApplicationRunner { while (true) { try { Thread.sleep(TimeUnit.SECONDS.toMillis(1)); - for (File file : SpringApplicationRunner.this.files) { - long current = file.lastModified(); - if (this.previous < current) { - this.previous = current; - compileAndRun(); + for (String path : SpringApplicationRunner.this.files) { + File file = new File(path); + if (file.exists()) { + long current = file.lastModified(); + if (this.previous < current) { + this.previous = current; + compileAndRun(); + } } } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunner.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunner.java index 61c3463c607..7763487f5d5 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunner.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/testrunner/TestRunner.java @@ -16,7 +16,6 @@ package org.springframework.boot.cli.testrunner; -import java.io.File; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; @@ -34,7 +33,7 @@ public class TestRunner { private static final String JUNIT_TEST_ANNOTATION = "org.junit.Test"; - private final File[] files; + private final String[] files; private final GroovyCompiler compiler; @@ -44,7 +43,7 @@ public class TestRunner { * @param files * @param args */ - public TestRunner(TestRunnerConfiguration configuration, File[] files, String[] args) { + public TestRunner(TestRunnerConfiguration configuration, String[] files, String[] args) { this.files = files.clone(); this.compiler = new GroovyCompiler(configuration); } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/ResourceUtils.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/ResourceUtils.java new file mode 100644 index 00000000000..ee930b289f7 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/ResourceUtils.java @@ -0,0 +1,166 @@ +/* + * 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.cli.util; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Utilities for manipulating resource paths and URLs. + * + * @author Dave Syer + */ +public abstract class ResourceUtils { + + /** Pseudo URL prefix for loading from the class path: "classpath:" */ + public static final String CLASSPATH_URL_PREFIX = "classpath:"; + + /** Pseudo URL prefix for loading all resources from the class path: "classpath*:" */ + public static final String ALL_CLASSPATH_URL_PREFIX = "classpath*:"; + + /** URL prefix for loading from the file system: "file:" */ + public static final String FILE_URL_PREFIX = "file:"; + + /** Wildcard character in source path */ + public static final CharSequence WILDCARD = "*"; + + public static List getUrls(String path, ClassLoader classLoader) { + + if (classLoader == null) { + classLoader = ClassUtils.getDefaultClassLoader(); + } + + path = StringUtils.cleanPath(path); + if (path.contains(WILDCARD)) { + if (path.contains(":")) { + try { + Resource[] resources = new PathMatchingResourcePatternResolver( + classLoader).getResources(path); + List result = new ArrayList(); + for (Resource resource : resources) { + result.add(resource.getURL().toExternalForm()); + } + return result; + } + catch (IOException e) { + throw new IllegalArgumentException("Cannot resolve paths at [" + path + + "]", e); + } + } + else { + try { + return getUrls(FILE_URL_PREFIX + path, classLoader); + } + catch (IllegalArgumentException e) { + // ignore + } + return getUrls(ALL_CLASSPATH_URL_PREFIX + path, classLoader); + } + } + + if (path.contains(":")) { + + if (path.startsWith(CLASSPATH_URL_PREFIX)) { + path = path.substring(CLASSPATH_URL_PREFIX.length()); + } + else { + return getFilePath(path); + } + + } + else { + try { + return getFilePath(path); + } + catch (IllegalArgumentException e) { + // ignore + } + } + + while (path.startsWith("/")) { + path = path.substring(1); + } + List result = new ArrayList(); + if (classLoader != null) { + Enumeration urls; + try { + urls = classLoader.getResources(path); + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + result.add(url.toExternalForm()); + } + } + catch (IOException e) { + // Ignore + } + } + return result; + } + + private static List getFilePath(String path) { + FileSystemResource resource = new FileSystemResource(path); + if (resource.exists()) { + try { + if (resource.getFile().isDirectory()) { + Resource[] resources = new PathMatchingResourcePatternResolver() + .getResources(resource.getURL() + "/**"); + List result = new ArrayList(); + for (Resource sub : resources) { + if (!sub.getFile().isDirectory()) { + result.add(sub.getURL().toExternalForm()); + } + } + return result; + } + return Collections.singletonList(resource.getURL().toExternalForm()); + } + catch (IOException e) { + throw new IllegalArgumentException("Cannot create URL from path [" + path + + "]", e); + } + } + try { + UrlResource url = new UrlResource(path); + if (url.exists()) { + try { + return Collections.singletonList(url.getURL().toExternalForm()); + } + catch (IOException e) { + throw new IllegalArgumentException("Cannot create URL from path [" + + path + "]", e); + } + } + } + catch (MalformedURLException ex) { + throw new IllegalArgumentException("Cannot create URL from path [" + path + + "]", ex); + } + return Collections.emptyList(); + } +} diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java index b57bd73a19f..f11e2fe37ff 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java @@ -17,6 +17,7 @@ package org.springframework.boot.cli; import java.io.BufferedReader; +import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; @@ -104,8 +105,13 @@ public class CliTester implements TestRule { final String[] sources = new String[args.length]; for (int i = 0; i < args.length; i++) { String arg = args[i]; - if (arg.startsWith("-")) { - sources[i] = arg; + if (!arg.endsWith(".groovy") && !arg.endsWith(".xml")) { + if (new File(this.prefix + arg).isDirectory()) { + sources[i] = this.prefix + arg; + } + else { + sources[i] = arg; + } } else { sources[i] = this.prefix + arg; diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/DirectorySourcesIntegrationTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/DirectorySourcesIntegrationTests.java new file mode 100644 index 00000000000..8a1e523cce0 --- /dev/null +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/DirectorySourcesIntegrationTests.java @@ -0,0 +1,52 @@ +/* + * 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.cli; + +import org.junit.Rule; +import org.junit.Test; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThat; + +/** + * Integration tests for code in directories. + * + * @author Dave Syer + */ +public class DirectorySourcesIntegrationTests { + + @Rule + public CliTester cli = new CliTester("src/test/resources/dir-sample/"); + + @Test + public void runDirectory() throws Exception { + this.cli.run("code"); + assertThat(this.cli.getOutput(), containsString("Hello World")); + } + + @Test + public void runDirectoryRecursive() throws Exception { + this.cli.run(""); + assertThat(this.cli.getOutput(), containsString("Hello World")); + } + + @Test + public void runPathPattern() throws Exception { + this.cli.run("**/*.groovy"); + assertThat(this.cli.getOutput(), containsString("Hello World")); + } +} diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/ScriptCompilationCustomizerTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/ScriptCompilationCustomizerTests.java index 42b043a62d8..1960a6a4356 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/ScriptCompilationCustomizerTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/ScriptCompilationCustomizerTests.java @@ -18,7 +18,6 @@ package org.springframework.boot.cli.command; import groovy.lang.Closure; -import java.io.File; import java.util.List; import java.util.Map; @@ -55,8 +54,8 @@ public class ScriptCompilationCustomizerTests { @Test public void simpleCompile() throws Exception { - Class[] types = this.compiler.compile(new File( - "src/test/resources/scripts/command.groovy")); + Class[] types = this.compiler + .compile("src/test/resources/scripts/command.groovy"); Class main = types[0]; assertEquals("org.test.command.TestCommand", main.getName()); assertTrue(Command.class.isAssignableFrom(main)); @@ -64,24 +63,21 @@ public class ScriptCompilationCustomizerTests { @Test public void addsOptionHandler() throws Exception { - Class[] types = this.compiler.compile(new File( - "src/test/resources/scripts/handler.groovy")); + Class[] types = this.compiler.compile("classpath:/scripts/handler.groovy"); Class main = types[0]; assertTrue(OptionHandler.class.isAssignableFrom(main)); } @Test public void addsCommands() throws Exception { - Class[] types = this.compiler.compile(new File( - "src/test/resources/scripts/commands.groovy")); + Class[] types = this.compiler.compile("classpath:scripts/commands.groovy"); Class main = types[0]; assertTrue(Commands.class.isAssignableFrom(main)); } @Test public void closureWithStringArgs() throws Exception { - Class[] types = this.compiler.compile(new File( - "src/test/resources/scripts/commands.groovy")); + Class[] types = this.compiler.compile("classpath:scripts/commands.groovy"); Class main = types[0]; Map> commands = ((Commands) main.newInstance()).getCommands(); assertEquals(1, commands.size()); @@ -93,8 +89,8 @@ public class ScriptCompilationCustomizerTests { @Test public void closureWithEmptyArgs() throws Exception { - Class[] types = this.compiler.compile(new File( - "src/test/resources/scripts/commands.groovy")); + Class[] types = this.compiler + .compile("src/test/resources/scripts/commands.groovy"); Class main = types[0]; Map> commands = ((Commands) main.newInstance()).getCommands(); assertEquals(1, commands.size()); @@ -106,8 +102,8 @@ public class ScriptCompilationCustomizerTests { @Test public void closureAndOptionsDefined() throws Exception { - Class[] types = this.compiler.compile(new File( - "src/test/resources/scripts/options.groovy")); + Class[] types = this.compiler + .compile("src/test/resources/scripts/options.groovy"); Class main = types[0]; Commands commands = (Commands) main.newInstance(); Map> closures = commands.getCommands(); diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/util/ResourceUtilsTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/util/ResourceUtilsTests.java new file mode 100644 index 00000000000..3658b4b6c24 --- /dev/null +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/util/ResourceUtilsTests.java @@ -0,0 +1,135 @@ +/* + * 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.cli.util; + +import java.util.List; + +import org.junit.Test; +import org.springframework.util.ClassUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Dave Syer + */ +public class ResourceUtilsTests { + + @Test + public void explicitClasspathResource() { + List urls = ResourceUtils.getUrls("classpath:init.groovy", + ClassUtils.getDefaultClassLoader()); + assertEquals(1, urls.size()); + assertTrue(urls.get(0).startsWith("file:")); + } + + @Test + public void explicitClasspathResourceWithSlash() { + List urls = ResourceUtils.getUrls("classpath:/init.groovy", + ClassUtils.getDefaultClassLoader()); + assertEquals(1, urls.size()); + assertTrue(urls.get(0).startsWith("file:")); + } + + @Test + public void implicitClasspathResource() { + List urls = ResourceUtils.getUrls("init.groovy", + ClassUtils.getDefaultClassLoader()); + assertEquals(1, urls.size()); + assertTrue(urls.get(0).startsWith("file:")); + } + + @Test + public void implicitClasspathResourceWithSlash() { + List urls = ResourceUtils.getUrls("/init.groovy", + ClassUtils.getDefaultClassLoader()); + assertEquals(1, urls.size()); + assertTrue(urls.get(0).startsWith("file:")); + } + + @Test + public void nonexistentClasspathResource() { + List urls = ResourceUtils.getUrls("classpath:nonexistent.groovy", null); + assertEquals(0, urls.size()); + } + + @Test + public void explicitFile() { + List urls = ResourceUtils.getUrls("file:src/test/resources/init.groovy", + ClassUtils.getDefaultClassLoader()); + assertEquals(1, urls.size()); + assertTrue(urls.get(0).startsWith("file:")); + } + + @Test + public void implicitFile() { + List urls = ResourceUtils.getUrls("src/test/resources/init.groovy", + ClassUtils.getDefaultClassLoader()); + assertEquals(1, urls.size()); + assertTrue(urls.get(0).startsWith("file:")); + } + + @Test + public void nonexistentFile() { + List urls = ResourceUtils.getUrls("file:nonexistent.groovy", null); + assertEquals(0, urls.size()); + } + + @Test + public void recursiveFiles() { + List urls = ResourceUtils.getUrls("src/test/resources/dir-sample", + ClassUtils.getDefaultClassLoader()); + assertEquals(1, urls.size()); + assertTrue(urls.get(0).startsWith("file:")); + } + + @Test + public void recursiveFilesByPatternWithPrefix() { + List urls = ResourceUtils.getUrls( + "file:src/test/resources/dir-sample/**/*.groovy", + ClassUtils.getDefaultClassLoader()); + assertEquals(1, urls.size()); + assertTrue(urls.get(0).startsWith("file:")); + } + + @Test + public void recursiveFilesByPattern() { + List urls = ResourceUtils.getUrls( + "src/test/resources/dir-sample/**/*.groovy", + ClassUtils.getDefaultClassLoader()); + assertEquals(1, urls.size()); + assertTrue(urls.get(0).startsWith("file:")); + } + + @Test + public void directoryOfFilesWithPrefix() { + List urls = ResourceUtils.getUrls( + "file:src/test/resources/dir-sample/code/*", + ClassUtils.getDefaultClassLoader()); + assertEquals(1, urls.size()); + assertTrue(urls.get(0).startsWith("file:")); + } + + @Test + public void directoryOfFiles() { + List urls = ResourceUtils.getUrls("src/test/resources/dir-sample/code/*", + ClassUtils.getDefaultClassLoader()); + assertEquals(1, urls.size()); + assertTrue(urls.get(0).startsWith("file:")); + } + +} diff --git a/spring-boot-cli/src/test/resources/dir-sample/code/app.groovy b/spring-boot-cli/src/test/resources/dir-sample/code/app.groovy new file mode 100644 index 00000000000..f273c49434a --- /dev/null +++ b/spring-boot-cli/src/test/resources/dir-sample/code/app.groovy @@ -0,0 +1,23 @@ +package org.test + +@Component +class Example implements CommandLineRunner { + + @Autowired + private MyService myService + + void run(String... args) { + println "Hello ${this.myService.sayWorld()} From ${getClass().getClassLoader().getResource('samples/app.groovy')}" + } +} + + +@Service +class MyService { + + String sayWorld() { + return "World!" + } +} + +