Rename spring-boot-layertools

Rename `spring-boot-layertools` to `spring-boot-jarmode-layertools`.

Closes gh-19853
This commit is contained in:
Phillip Webb
2020-01-22 20:23:04 -08:00
parent 13f7db2d8c
commit 71ce212f7f
27 changed files with 27 additions and 27 deletions

View File

@@ -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.jarmode.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<String> args) {
List<String> parameters = new ArrayList<>();
Map<Option, String> 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<Option, String> options, List<String> 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<? extends Command> 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<String> descriptions;
private Parameters(String[] descriptions) {
this.descriptions = Collections.unmodifiableList(Arrays.asList(descriptions));
}
/**
* Return the parameter descriptions.
* @return the descriptions
*/
List<String> 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<Option> stream() {
return Arrays.stream(this.values);
}
/**
* Factory method used if there are no expected options.
* @return a new {@link Options} instance
*/
protected static Options none() {
return of();
}
/**
* Factory method used to create a new {@link Options} instance with specific
* values.
* @param values the option values
* @return a new {@link Options} instance with the given values
*/
protected static Options of(Option... values) {
return new Options(values);
}
}
/**
* An individual option that the command can accepts. Can either be an option with a
* value (e.g. {@literal --log debug}) or a flag (e.g. {@literal
* --verbose}).
*/
protected static final class Option {
private final String name;
private final String valueDescription;
private final String description;
private Option(String name, String valueDescription, String description) {
this.name = name;
this.description = description;
this.valueDescription = valueDescription;
}
/**
* Return the name of the option.
* @return the options name
*/
String getName() {
return this.name;
}
/**
* Return the description of the expected argument value or {@code null} if this
* option is a flag/switch.
* @return the option value description
*/
String getValueDescription() {
return this.valueDescription;
}
/**
* Return the name and the value description combined.
* @return the name and value description
*/
String getNameAndValueDescription() {
return this.name + ((this.valueDescription != null) ? " " + this.valueDescription : "");
}
/**
* Return a description of the option.
* @return the option description
*/
String getDescription() {
return this.description;
}
private String claimArg(Deque<String> args) {
return (this.valueDescription != null) ? args.removeFirst() : null;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return this.name.equals(((Option) obj).name);
}
@Override
public int hashCode() {
return this.name.hashCode();
}
@Override
public String toString() {
return this.name;
}
/**
* Factory method to create a flag/switch option.
* @param name the name of the option
* @param description a description of the option
* @return a new {@link Option} instance
*/
protected static Option flag(String name, String description) {
return new Option(name, null, description);
}
/**
* Factory method to create value option.
* @param name the name of the option
* @param valueDescription a description of the expected value
* @param description a description of the option
* @return a new {@link Option} instance
*/
protected static Option of(String name, String valueDescription, String description) {
return new Option(name, valueDescription, description);
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.jarmode.layertools;
import java.io.File;
import java.nio.file.Paths;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.util.Assert;
/**
* Context for use by commands.
*
* @author Phillip Webb
*/
class Context {
private final File jarFile;
private final File workingDir;
private String relativeDir;
/**
* Create a new {@link Context} instance.
*/
Context() {
this(new ApplicationHome().getSource(), Paths.get(".").toAbsolutePath().normalize().toFile());
}
/**
* Create a new {@link Context} instance with the specified value.
* @param jarFile the source jar file
* @param workingDir the working directory
*/
Context(File jarFile, File workingDir) {
Assert.state(jarFile != null && jarFile.isFile() && jarFile.exists()
&& jarFile.getName().toLowerCase().endsWith(".jar"), "Unable to find source JAR");
this.jarFile = jarFile;
this.workingDir = workingDir;
this.relativeDir = deduceRelativeDir(jarFile.getParentFile(), this.workingDir);
}
private String deduceRelativeDir(File sourceFolder, File workingDir) {
String sourcePath = sourceFolder.getAbsolutePath();
String workingPath = workingDir.getAbsolutePath();
if (sourcePath.equals(workingPath) || !sourcePath.startsWith(workingPath)) {
return null;
}
String relativePath = sourcePath.substring(workingPath.length() + 1);
return (relativePath.length() > 0) ? relativePath : null;
}
/**
* Return the source jar file that is running in tools mode.
* @return the jar file
*/
File getJarFile() {
return this.jarFile;
}
/**
* Return the current working directory.
* @return the working dir
*/
File getWorkingDir() {
return this.workingDir;
}
/**
* Return the directory relative to {@link #getWorkingDir()} that contains the jar or
* {@code null} if none relative directory can be deduced.
* @return the relative dir ending in {@code /} or {@code null}
*/
String getRelativeJarDir() {
return this.relativeDir;
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.jarmode.layertools;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
/**
* The {@code 'extract'} tools command.
*
* @author Phillip Webb
*/
class ExtractCommand extends Command {
static final Option DESTINATION_OPTION = Option.of("destination", "string", "The destination to extract files to");
private final Context context;
private final Layers layers;
ExtractCommand(Context context) {
this(context, Layers.get(context));
}
ExtractCommand(Context context, Layers layers) {
super("extract", "Extracts layers from the jar for image creation", Options.of(DESTINATION_OPTION),
Parameters.of("[<layer>...]"));
this.context = context;
this.layers = layers;
}
@Override
protected void run(Map<Option, String> options, List<String> parameters) {
try {
File destination = options.containsKey(DESTINATION_OPTION) ? new File(options.get(DESTINATION_OPTION))
: this.context.getWorkingDir();
for (String layer : this.layers) {
if (parameters.isEmpty() || parameters.contains(layer)) {
mkDirs(new File(destination, layer));
}
}
try (ZipInputStream zip = new ZipInputStream(new FileInputStream(this.context.getJarFile()))) {
ZipEntry entry = zip.getNextEntry();
while (entry != null) {
if (!entry.isDirectory()) {
String layer = this.layers.getLayer(entry);
if (parameters.isEmpty() || parameters.contains(layer)) {
write(zip, entry, new File(destination, layer));
}
}
entry = zip.getNextEntry();
}
}
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
private void write(ZipInputStream zip, ZipEntry entry, File destination) throws IOException {
String path = StringUtils.cleanPath(entry.getName());
File file = new File(destination, path);
if (file.getAbsolutePath().startsWith(destination.getAbsolutePath())) {
mkParentDirs(file);
try (OutputStream out = new FileOutputStream(file)) {
StreamUtils.copy(zip, out);
}
Files.setAttribute(file.toPath(), "creationTime", entry.getCreationTime());
}
}
private void mkParentDirs(File file) throws IOException {
mkDirs(file.getParentFile());
}
private void mkDirs(File file) throws IOException {
if (!file.exists() && !file.mkdirs()) {
throw new IOException("Unable to create folder " + file);
}
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.jarmode.layertools;
import java.io.PrintStream;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
/**
* Implicit {@code 'help'} command.
*
* @author Phillip Webb
*/
class HelpCommand extends Command {
private final Context context;
private final List<Command> commands;
HelpCommand(Context context, List<Command> commands) {
super("help", "Help about any command", Options.none(), Parameters.of("[<command]"));
this.context = context;
this.commands = commands;
}
@Override
protected void run(Map<Option, String> options, List<String> parameters) {
run(System.out, options, parameters);
}
void run(PrintStream out, Map<Option, String> options, List<String> parameters) {
Command command = (!parameters.isEmpty()) ? Command.find(this.commands, parameters.get(0)) : null;
if (command != null) {
printCommandHelp(out, command);
return;
}
printUsageAndCommands(out);
}
private void printCommandHelp(PrintStream out, Command command) {
out.println(command.getDescription());
out.println();
out.println("Usage:");
out.println(" " + getJavaCommand() + " " + getUsage(command));
if (!command.getOptions().isEmpty()) {
out.println();
out.println("Options:");
int maxNameLength = getMaxLength(0, command.getOptions().stream().map(Option::getNameAndValueDescription));
command.getOptions().stream().forEach((option) -> printOptionSummary(out, option, maxNameLength));
}
}
private void printOptionSummary(PrintStream out, Option option, int padding) {
out.println(String.format(" --%-" + padding + "s %s", option.getNameAndValueDescription(),
option.getDescription()));
}
private String getUsage(Command command) {
StringBuilder usage = new StringBuilder();
usage.append(command.getName());
if (!command.getOptions().isEmpty()) {
usage.append(" [options]");
}
command.getParameters().getDescriptions().forEach((param) -> usage.append(" " + param));
return usage.toString();
}
private void printUsageAndCommands(PrintStream out) {
out.println("Usage:");
out.println(" " + getJavaCommand());
out.println();
out.println("Available commands:");
int maxNameLength = getMaxLength(getName().length(), this.commands.stream().map(Command::getName));
this.commands.forEach((command) -> printCommandSummary(out, command, maxNameLength));
printCommandSummary(out, this, maxNameLength);
}
private int getMaxLength(int minimum, Stream<String> strings) {
return Math.max(minimum, strings.mapToInt(String::length).max().orElse(0));
}
private void printCommandSummary(PrintStream out, Command command, int padding) {
out.println(String.format(" %-" + padding + "s %s", command.getName(), command.getDescription()));
}
private String getJavaCommand() {
return "java -Djarmode=layertools -jar " + this.context.getJarFile().getName();
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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.jarmode.layertools;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
/**
* {@link Layers} implementation that uses implicit rules to slice the application.
*
* @author Phillip Webb
*/
class ImplicitLayers implements Layers {
private static final String DEPENDENCIES_LAYER = "dependencies";
private static final String SNAPSHOT_DEPENDENCIES_LAYER = "snapshot-dependencies";
private static final String RESOURCES_LAYER = "resources";
private static final String APPLICATION_LAYER = "application";
private static final List<String> LAYERS;
static {
List<String> layers = new ArrayList<>();
layers.add(DEPENDENCIES_LAYER);
layers.add(SNAPSHOT_DEPENDENCIES_LAYER);
layers.add(RESOURCES_LAYER);
layers.add(APPLICATION_LAYER);
LAYERS = Collections.unmodifiableList(layers);
}
private static final String[] CLASS_LOCATIONS = { "", "BOOT-INF/classes/" };
private static final String[] RESOURCE_LOCATIONS = { "META-INF/resources/", "resources/", "static/", "public/" };
@Override
public Iterator<String> iterator() {
return LAYERS.iterator();
}
@Override
public String getLayer(ZipEntry entry) {
return getLayer(entry.getName());
}
String getLayer(String name) {
if (name.endsWith("SNAPSHOT.jar")) {
return SNAPSHOT_DEPENDENCIES_LAYER;
}
if (name.endsWith(".jar")) {
return DEPENDENCIES_LAYER;
}
if (!name.endsWith(".class")) {
for (String classLocation : CLASS_LOCATIONS) {
for (String resourceLocation : RESOURCE_LOCATIONS) {
if (name.startsWith(classLocation + resourceLocation)) {
return RESOURCES_LAYER;
}
}
}
}
return APPLICATION_LAYER;
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.jarmode.layertools;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.NoSuchFileException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
/**
* {@link Layers} implementation backed by a {@code BOOT-INF/layers.idx} file.
*
* @author Phillip Webb
*/
class IndexedLayers implements Layers {
private static final String APPLICATION_LAYER = "application";
private static final String SPRING_BOOT_APPLICATION_LAYER = "springbootapplication";
private static final Pattern LAYER_PATTERN = Pattern.compile("^BOOT-INF\\/layers\\/([a-zA-Z0-9-]+)\\/.*$");
private List<String> layers;
IndexedLayers(String indexFile) {
String[] lines = indexFile.split("\n");
this.layers = Arrays.stream(lines).map(String::trim).filter((line) -> !line.isEmpty())
.collect(Collectors.toCollection(ArrayList::new));
Assert.state(!this.layers.isEmpty(), "Empty layer index file loaded");
if (!this.layers.contains(APPLICATION_LAYER)) {
this.layers.add(0, SPRING_BOOT_APPLICATION_LAYER);
}
}
@Override
public Iterator<String> iterator() {
return this.layers.iterator();
}
@Override
public String getLayer(ZipEntry entry) {
String name = entry.getName();
Matcher matcher = LAYER_PATTERN.matcher(name);
if (matcher.matches()) {
String layer = matcher.group(1);
Assert.state(this.layers.contains(layer), "Unexpected layer '" + layer + "'");
return layer;
}
return this.layers.contains(APPLICATION_LAYER) ? APPLICATION_LAYER : SPRING_BOOT_APPLICATION_LAYER;
}
/**
* Get an {@link IndexedLayers} instance of possible.
* @param context the context
* @return an {@link IndexedLayers} instance or {@code null} if this not a layered
* jar.
*/
static IndexedLayers get(Context context) {
try {
try (JarFile jarFile = new JarFile(context.getJarFile())) {
ZipEntry entry = jarFile.getEntry("BOOT-INF/layers.idx");
if (entry != null) {
String indexFile = StreamUtils.copyToString(jarFile.getInputStream(entry), StandardCharsets.UTF_8);
return new IndexedLayers(indexFile);
}
}
return null;
}
catch (FileNotFoundException | NoSuchFileException ex) {
return null;
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.jarmode.layertools;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import org.springframework.boot.loader.jarmode.JarMode;
/**
* {@link JarMode} providing {@code "layertools"} support.
*
* @author Phillip Webb
* @since 2.3.0
*/
public class LayerToolsJarMode implements JarMode {
@Override
public boolean accepts(String mode) {
return "layertools".equalsIgnoreCase(mode);
}
@Override
public void run(String mode, String[] args) {
try {
new Runner().run(args);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
static class Runner {
static Context contextOverride;
private final List<Command> commands;
private final HelpCommand help;
Runner() {
Context context = (contextOverride != null) ? contextOverride : new Context();
this.commands = getCommands(context);
this.help = new HelpCommand(context, this.commands);
}
private void run(String[] args) {
run(new ArrayDeque<>(Arrays.asList(args)));
}
private void run(Deque<String> args) {
if (!args.isEmpty()) {
Command command = Command.find(this.commands, args.removeFirst());
if (command != null) {
command.run(args);
return;
}
}
this.help.run(args);
}
static List<Command> getCommands(Context context) {
List<Command> commands = new ArrayList<Command>();
commands.add(new ListCommand(context));
commands.add(new ExtractCommand(context));
return Collections.unmodifiableList(commands);
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.jarmode.layertools;
import java.util.Iterator;
import java.util.zip.ZipEntry;
/**
* Provides information about the jar layers.
*
* @author Phillip Webb
* @see ExtractCommand
* @see ListCommand
*/
interface Layers extends Iterable<String> {
/**
* Return the jar layers in the order that they should be added (starting with the
* least frequently changed layer).
*/
@Override
Iterator<String> iterator();
/**
* Return the layer that a given entry is in.
* @param entry the entry to check
* @return the layer that the entry is in
*/
String getLayer(ZipEntry entry);
/**
* Return a {@link Layers} instance for the currently running application.
* @param context the command context
* @return a new layers instance
*/
static Layers get(Context context) {
IndexedLayers indexedLayers = IndexedLayers.get(context);
return (indexedLayers != null) ? indexedLayers : new ImplicitLayers();
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.jarmode.layertools;
import java.io.PrintStream;
import java.util.List;
import java.util.Map;
/**
* The {@code 'list-layers'} tools command.
*
* @author Phillip Webb
*/
class ListCommand extends Command {
private Context context;
ListCommand(Context context) {
super("list", "List layers from the jar that can be extracted", Options.none(), Parameters.none());
this.context = context;
}
@Override
protected void run(Map<Option, String> options, List<String> parameters) {
printLayers(Layers.get(this.context), System.out);
}
void printLayers(Layers layers, PrintStream out) {
layers.forEach(out::println);
}
}

View File

@@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* JarMode support for layertools.
*/
package org.springframework.boot.jarmode.layertools;

View File

@@ -0,0 +1,2 @@
org.springframework.boot.loader.jarmode.JarMode=\
org.springframework.boot.jarmode.layertools.LayerToolsJarMode