From e513fe4666acb99c97fdbedd85afb85849bb753f Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 22 Jan 2020 01:07:55 -0800 Subject: [PATCH] Add layertools jarmode Add a new `spring-boot-layertools` module which provides jarmode support for working with layers. The module works with both classic fat jars, as well as layered jars. Closes gh-19849 --- eclipse/spring-boot-project.setup | 2 +- settings.gradle | 1 + .../spring-boot-dependencies/build.gradle | 1 + .../spring-boot-layertools/build.gradle | 21 ++ .../boot/layertools/Command.java | 335 ++++++++++++++++++ .../boot/layertools/Context.java | 93 +++++ .../boot/layertools/ExtractCommand.java | 107 ++++++ .../boot/layertools/HelpCommand.java | 105 ++++++ .../boot/layertools/ImplicitLayers.java | 83 +++++ .../boot/layertools/IndexedLayers.java | 102 ++++++ .../boot/layertools/LayerToolsJarMode.java | 89 +++++ .../boot/layertools/Layers.java | 55 +++ .../boot/layertools/ListCommand.java | 46 +++ .../boot/layertools/package-info.java | 20 ++ .../main/resources/META-INF/spring.factories | 2 + .../boot/layertools/CommandTests.java | 166 +++++++++ .../boot/layertools/ContextTests.java | 108 ++++++ .../boot/layertools/ExtractCommandTests.java | 138 ++++++++ .../boot/layertools/HelpCommandTests.java | 63 ++++ .../boot/layertools/ImplicitLayersTests.java | 83 +++++ .../boot/layertools/IndexedLayersTests.java | 85 +++++ .../layertools/LayerToolsJarModeTests.java | 71 ++++ .../boot/layertools/ListCommandTests.java | 48 +++ .../boot/layertools/TestPrintStream.java | 76 ++++ .../boot/layertools/help-extract-output.txt | 7 + .../boot/layertools/help-output.txt | 7 + .../boot/layertools/list-output.txt | 4 + 27 files changed, 1917 insertions(+), 1 deletion(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/build.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/java/org/springframework/boot/layertools/Command.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/java/org/springframework/boot/layertools/Context.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/java/org/springframework/boot/layertools/ExtractCommand.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/java/org/springframework/boot/layertools/HelpCommand.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/java/org/springframework/boot/layertools/ImplicitLayers.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/java/org/springframework/boot/layertools/IndexedLayers.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/java/org/springframework/boot/layertools/LayerToolsJarMode.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/java/org/springframework/boot/layertools/Layers.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/java/org/springframework/boot/layertools/ListCommand.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/java/org/springframework/boot/layertools/package-info.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/resources/META-INF/spring.factories create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/test/java/org/springframework/boot/layertools/CommandTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/test/java/org/springframework/boot/layertools/ContextTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/test/java/org/springframework/boot/layertools/ExtractCommandTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/test/java/org/springframework/boot/layertools/HelpCommandTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/test/java/org/springframework/boot/layertools/ImplicitLayersTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/test/java/org/springframework/boot/layertools/IndexedLayersTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/test/java/org/springframework/boot/layertools/LayerToolsJarModeTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/test/java/org/springframework/boot/layertools/ListCommandTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/test/java/org/springframework/boot/layertools/TestPrintStream.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/test/resources/org/springframework/boot/layertools/help-extract-output.txt create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/test/resources/org/springframework/boot/layertools/help-output.txt create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-layertools/src/test/resources/org/springframework/boot/layertools/list-output.txt diff --git a/eclipse/spring-boot-project.setup b/eclipse/spring-boot-project.setup index b940e2276f..608ecf8a84 100644 --- a/eclipse/spring-boot-project.setup +++ b/eclipse/spring-boot-project.setup @@ -127,7 +127,7 @@ name="spring-boot-tools"> + pattern="spring-boot-(tools|antlib|configuration-.*|loader|.*-tools|*.layertools|.*-plugin|autoconfigure-processor|cloudnativebuildpack)"/> diff --git a/settings.gradle b/settings.gradle index b04b4ca821..13c5353026 100644 --- a/settings.gradle +++ b/settings.gradle @@ -45,6 +45,7 @@ include 'spring-boot-project:spring-boot-tools:spring-boot-cloudnativebuildpack' include 'spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata' include 'spring-boot-project:spring-boot-tools:spring-boot-configuration-processor' include 'spring-boot-project:spring-boot-tools:spring-boot-gradle-plugin' +include 'spring-boot-project:spring-boot-tools:spring-boot-layertools' include 'spring-boot-project:spring-boot-tools:spring-boot-loader' include 'spring-boot-project:spring-boot-tools:spring-boot-loader-tools' include 'spring-boot-project:spring-boot-tools:spring-boot-maven-plugin' diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 0b37761d8d..0b60838002 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1425,6 +1425,7 @@ bom { 'spring-boot-configuration-metadata', 'spring-boot-configuration-processor', 'spring-boot-devtools', + 'spring-boot-layertools', 'spring-boot-loader', 'spring-boot-loader-tools', 'spring-boot-properties-migrator', diff --git a/spring-boot-project/spring-boot-tools/spring-boot-layertools/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-layertools/build.gradle new file mode 100644 index 0000000000..9ffb23e381 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-layertools/build.gradle @@ -0,0 +1,21 @@ +plugins { + id 'java-library' + id 'org.springframework.boot.conventions' + id 'org.springframework.boot.deployed' + id 'org.springframework.boot.internal-dependency-management' +} + +description = 'Spring Boot Layers Tools' + +dependencies { + api platform(project(':spring-boot-project:spring-boot-parent')) + + implementation project(':spring-boot-project:spring-boot-tools:spring-boot-loader') + implementation project(':spring-boot-project:spring-boot') + implementation 'org.springframework:spring-core' + + testImplementation "org.assertj:assertj-core" + testImplementation "org.junit.jupiter:junit-jupiter" + testImplementation "org.mockito:mockito-core" +} + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/java/org/springframework/boot/layertools/Command.java b/spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/java/org/springframework/boot/layertools/Command.java new file mode 100644 index 0000000000..49623002fe --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-layertools/src/main/java/org/springframework/boot/layertools/Command.java @@ -0,0 +1,335 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.layertools; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * A command that can be launched from the layertools jarmode. + * + * @author Phillip Webb + */ +abstract class Command { + + private final String name; + + private final String description; + + private final Options options; + + private final Parameters parameters; + + /** + * Create a new {@link Command} instance. + * @param name the name of the command + * @param description a description of the command + * @param options the command options + * @param parameters the command parameters + */ + Command(String name, String description, Options options, Parameters parameters) { + this.name = name; + this.description = description; + this.options = options; + this.parameters = parameters; + } + + /** + * Return the name of this command. + * @return the command name + */ + String getName() { + return this.name; + } + + /** + * Return the description of this command. + * @return the command description + */ + String getDescription() { + return this.description; + } + + /** + * Return options that this command accepts. + * @return the command options + */ + Options getOptions() { + return this.options; + } + + /** + * Return parameters that this command accepts. + * @return the command parameters + */ + Parameters getParameters() { + return this.parameters; + } + + /** + * Run the command by processing the remaining arguments. + * @param args a mutable deque of the remaining arguments + */ + final void run(Deque args) { + List parameters = new ArrayList<>(); + Map options = new HashMap<>(); + while (!args.isEmpty()) { + String arg = args.removeFirst(); + Option option = this.options.find(arg); + if (option != null) { + options.put(option, option.claimArg(args)); + } + else { + parameters.add(arg); + } + } + run(options, parameters); + } + + /** + * Run the actual command. + * @param options any options extracted from the arguments + * @param parameters any parameters extracted from the arguements + */ + protected abstract void run(Map options, List parameters); + + /** + * Static method that can be used to find a single command from a collection. + * @param commands the commands to search + * @param name the name of the command to find + * @return a {@link Command} instance or {@code null}. + */ + static Command find(Collection commands, String name) { + for (Command command : commands) { + if (command.getName().equals(name)) { + return command; + } + } + return null; + } + + /** + * Parameters that the command accepts. + */ + protected static final class Parameters { + + private final List descriptions; + + private Parameters(String[] descriptions) { + this.descriptions = Collections.unmodifiableList(Arrays.asList(descriptions)); + } + + /** + * Return the parameter descriptions. + * @return the descriptions + */ + List getDescriptions() { + return this.descriptions; + } + + @Override + public String toString() { + return this.descriptions.toString(); + } + + /** + * Factory method used if there are no expected parameters. + * @return a new {@link Parameters} instance + */ + protected static Parameters none() { + return of(); + } + + /** + * Factory method used to create a new {@link Parameters} instance with specific + * descriptions. + * @param descriptions the parameter descriptions + * @return a new {@link Parameters} instance with the given descriptions + */ + protected static Parameters of(String... descriptions) { + return new Parameters(descriptions); + } + + } + + /** + * Options that the command accepts. + */ + protected static final class Options { + + private final Option[] values; + + private Options(Option[] values) { + this.values = values; + } + + private Option find(String arg) { + if (arg.startsWith("--")) { + String name = arg.substring(2); + for (Option candidate : this.values) { + if (candidate.getName().equals(name)) { + return candidate; + } + } + } + return null; + } + + /** + * Return if this options collection is empty. + * @return if there are no options + */ + boolean isEmpty() { + return this.values.length == 0; + } + + /** + * Return a stream of each option. + * @return a stream of the options + */ + Stream