CLI updates

Signed-off-by: Paulo Lopes <pmlopes@gmail.com>
This commit is contained in:
Paulo Lopes
2019-05-28 11:52:37 +02:00
parent d9bfe3ed98
commit 7c9589fda7
22 changed files with 203 additions and 48 deletions

View File

@@ -267,4 +267,18 @@ public interface CLI {
*/
@GenIgnore
CLI usage(StringBuilder builder, String prefix);
/**
* @return the CLI priority.
*/
int getPriority();
/**
* Sets the priority of the CLI.
*
* @param priority the priority
* @return the current {@link CLI} instance
*/
@Fluent
CLI setPriority(int priority);
}

View File

@@ -48,10 +48,11 @@ public class CLIConfigurator {
if (name == null) {
throw new IllegalArgumentException("The command cannot be defined, the @Name annotation is missing.");
}
if (name.value() == null || name.value().isEmpty()) {
if (name.value().isEmpty()) {
throw new IllegalArgumentException("The command cannot be defined, the @Name value is empty or null.");
}
cli.setName(name.value());
cli.setPriority(name.priority());
if (summary != null) {
cli.setSummary(summary.value());

View File

@@ -32,4 +32,9 @@ public @interface Name {
*/
String value();
/**
* The command priority. If more than 1 with same name are available on the classpath the one with highest priority
* replaces the existing.
*/
int priority() default 0;
}

View File

@@ -24,6 +24,7 @@ import java.util.stream.Collectors;
public class DefaultCLI implements CLI {
protected String name;
protected int priority;
protected String description;
protected String summary;
protected boolean hidden;
@@ -238,4 +239,15 @@ public class DefaultCLI implements CLI {
new UsageMessageFormatter().usage(builder, prefix, this);
return this;
}
@Override
public int getPriority() {
return priority;
}
@Override
public CLI setPriority(int priority) {
this.priority = priority;
return this;
}
}

View File

@@ -29,7 +29,7 @@ public class ReflectionUtils {
public static <T> T newInstance(Class<T> clazz) {
try {
return clazz.newInstance();
return clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new IllegalArgumentException("Cannot instantiate " + clazz.getName(), e);
}

View File

@@ -21,6 +21,7 @@ import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.util.*;
import java.util.function.Supplier;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
@@ -115,7 +116,18 @@ public class VertxCommandLauncher extends UsageMessageFormatter {
protected void load() {
for (CommandFactoryLookup lookup : lookups) {
Collection<CommandFactory<?>> commands = lookup.lookup();
commands.forEach(this::register);
commands.forEach(factory -> {
CLI cli = factory.define();
CommandRegistration previous = commandByName.get(cli.getName());
if (previous == null) {
commandByName.put(cli.getName(), new CommandRegistration(factory, cli));
} else {
// command already registered, in this case we will replace IFF the priority is higher
if (cli.getPriority() > previous.cli.getPriority()) {
commandByName.put(cli.getName(), new CommandRegistration(factory, cli));
}
}
});
}
}
@@ -125,6 +137,7 @@ public class VertxCommandLauncher extends UsageMessageFormatter {
return this;
}
@Deprecated
@SuppressWarnings("unchecked")
public VertxCommandLauncher register(Class<? extends Command> clazz) {
DefaultCommandFactory factory = new DefaultCommandFactory(clazz);
@@ -133,6 +146,14 @@ public class VertxCommandLauncher extends UsageMessageFormatter {
return this;
}
@SuppressWarnings("unchecked")
public VertxCommandLauncher register(Class<? extends Command> clazz, Supplier<? extends Command> supplier) {
DefaultCommandFactory factory = new DefaultCommandFactory(clazz, supplier);
CLI cli = factory.define();
commandByName.put(cli.getName(), new CommandRegistration(factory, cli));
return this;
}
public VertxCommandLauncher unregister(String name) {
commandByName.remove(name);
return this;
@@ -215,6 +236,7 @@ public class VertxCommandLauncher extends UsageMessageFormatter {
if (main != null) {
context.put("Main", main);
context.put("Main-Class", main.getClass().getName());
context.put("Default-Verticle-Factory", getFromManifest("Default-Verticle-Factory"));
}
CLIConfigurator.inject(evaluated, cmd);
@@ -409,21 +431,24 @@ public class VertxCommandLauncher extends UsageMessageFormatter {
}
protected String getCommandFromManifest() {
return getFromManifest("Main-Command");
}
private String getFromManifest(String key) {
try {
Enumeration<URL> resources = RunCommand.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
InputStream stream = resources.nextElement().openStream();
Manifest manifest = new Manifest(stream);
Attributes attributes = manifest.getMainAttributes();
String mainClass = attributes.getValue("Main-Class");
if (main.getClass().getName().equals(mainClass)) {
String command = attributes.getValue("Main-Command");
if (command != null) {
stream.close();
return command;
try (InputStream stream = resources.nextElement().openStream()) {
Manifest manifest = new Manifest(stream);
Attributes attributes = manifest.getMainAttributes();
String mainClass = attributes.getValue("Main-Class");
if (main.getClass().getName().equals(mainClass)) {
String value = attributes.getValue(key);
if (value != null) {
return value;
}
}
}
stream.close();
}
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
@@ -442,26 +467,7 @@ public class VertxCommandLauncher extends UsageMessageFormatter {
* @return the main verticle, {@code null} if not found.
*/
protected String getMainVerticle() {
try {
Enumeration<URL> resources = RunCommand.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
InputStream stream = resources.nextElement().openStream();
Manifest manifest = new Manifest(stream);
Attributes attributes = manifest.getMainAttributes();
String mainClass = attributes.getValue("Main-Class");
if (main != null && main.getClass().getName().equals(mainClass)) {
String theMainVerticle = attributes.getValue("Main-Verticle");
if (theMainVerticle != null) {
stream.close();
return theMainVerticle;
}
}
stream.close();
}
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
return null;
return getFromManifest("Main-Verticle");
}
/**

View File

@@ -24,6 +24,6 @@ public class BareCommandFactory extends DefaultCommandFactory<BareCommand> {
* Creates a new instance of {@link BareCommandFactory}.
*/
public BareCommandFactory() {
super(BareCommand.class);
super(BareCommand.class, BareCommand::new);
}
}

View File

@@ -93,7 +93,7 @@ public abstract class ClasspathHandler extends DefaultCommand {
classloader = (classpath == null || classpath.isEmpty()) ?
ClasspathHandler.class.getClassLoader() : createClassloader();
Class<?> clazz = classloader.loadClass("io.vertx.core.impl.launcher.commands.VertxIsolatedDeployer");
return clazz.newInstance();
return clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
log.error("Failed to load or instantiate the isolated deployer", e);
throw new IllegalStateException(e);
@@ -156,6 +156,15 @@ public abstract class ClasspathHandler extends DefaultCommand {
Thread.currentThread().setContextClassLoader(classloader);
Method method = manager.getClass().getMethod("deploy", String.class, Vertx.class, DeploymentOptions.class,
Handler.class);
if (executionContext.get("Default-Verticle-Factory") != null) {
// there is a configured default
if (verticle.indexOf(':') == -1) {
// and the verticle is not using a explicit factory
verticle = executionContext.get("Default-Verticle-Factory") + ":" + verticle;
}
}
method.invoke(manager, verticle, vertx, options, completionHandler);
} catch (InvocationTargetException e) {
log.error("Failed to deploy verticle " + verticle, e.getCause());

View File

@@ -23,6 +23,6 @@ public class ListCommandFactory extends DefaultCommandFactory<ListCommand> {
* Creates a new {@link ListCommandFactory}.
*/
public ListCommandFactory() {
super(ListCommand.class);
super(ListCommand.class, ListCommand::new);
}
}

View File

@@ -24,6 +24,6 @@ public class RunCommandFactory extends DefaultCommandFactory<RunCommand> {
* Creates a new instance of {@link RunCommandFactory}.
*/
public RunCommandFactory() {
super(RunCommand.class);
super(RunCommand.class, RunCommand::new);
}
}

View File

@@ -24,6 +24,6 @@ public class StartCommandFactory extends DefaultCommandFactory<StartCommand> {
* Creates a new instance of {@link StartCommandFactory}.
*/
public StartCommandFactory() {
super(StartCommand.class);
super(StartCommand.class, StartCommand::new);
}
}

View File

@@ -24,6 +24,6 @@ public class StopCommandFactory extends DefaultCommandFactory<StopCommand> {
* Creates a new instance of {@link StopCommandFactory}.
*/
public StopCommandFactory() {
super(StopCommand.class);
super(StopCommand.class, StopCommand::new);
}
}

View File

@@ -24,6 +24,6 @@ public class VersionCommandFactory extends DefaultCommandFactory<VersionCommand>
* Creates a new instance of {@link VersionCommandFactory}.
*/
public VersionCommandFactory() {
super(VersionCommand.class);
super(VersionCommand.class, VersionCommand::new);
}
}

View File

@@ -16,6 +16,8 @@ import io.vertx.core.cli.CommandLine;
import io.vertx.core.cli.annotations.CLIConfigurator;
import io.vertx.core.cli.impl.ReflectionUtils;
import java.util.function.Supplier;
/**
* Default implementation of {@link CommandFactory}. This implementation defines the {@link CLI} from the
* given {@link Command} implementation (by reading the annotation). Then, {@link Command} instance are
@@ -26,14 +28,28 @@ import io.vertx.core.cli.impl.ReflectionUtils;
public class DefaultCommandFactory<C extends Command> implements CommandFactory<C> {
private final Class<C> clazz;
private final Supplier<C> supplier;
/**
* Creates a new {@link CommandFactory}.
*
* @param clazz the {@link Command} implementation
* @deprecated Please use {@link #DefaultCommandFactory(Class, Supplier)}
*/
@Deprecated
public DefaultCommandFactory(Class<C> clazz) {
this(clazz, () -> ReflectionUtils.newInstance(clazz));
}
/**
* Creates a new {@link CommandFactory}.
*
* @param clazz the {@link Command} implementation
* @param supplier the {@link Command} implementation
*/
public DefaultCommandFactory(Class<C> clazz, Supplier<C> supplier) {
this.clazz = clazz;
this.supplier = supplier;
}
/**
@@ -41,9 +57,7 @@ public class DefaultCommandFactory<C extends Command> implements CommandFactory<
*/
@Override
public C create(CommandLine cl) {
C c = ReflectionUtils.newInstance(clazz);
CLIConfigurator.inject(cl, c);
return c;
return supplier.get();
}
/**

View File

@@ -15,6 +15,6 @@ import io.vertx.core.spi.launcher.DefaultCommandFactory;
public class ComplexCommandFactory extends DefaultCommandFactory<ComplexCommand> {
public ComplexCommandFactory() {
super(ComplexCommand.class);
super(ComplexCommand.class, ComplexCommand::new);
}
}

View File

@@ -16,7 +16,7 @@ import io.vertx.core.spi.launcher.DefaultCommandFactory;
public class GoodByeCommandFactory extends DefaultCommandFactory<GoodByeCommand> {
public GoodByeCommandFactory() {
super(GoodByeCommand.class);
super(GoodByeCommand.class, GoodByeCommand::new);
}
@Override

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.core.impl.launcher.commands;
import io.vertx.core.cli.CLIException;
import io.vertx.core.cli.annotations.Description;
import io.vertx.core.cli.annotations.Name;
import io.vertx.core.cli.annotations.Option;
import io.vertx.core.cli.annotations.Summary;
import io.vertx.core.spi.launcher.DefaultCommand;
@Summary("A command saying hello.")
@Description("A simple command to wish you a good day. Pass your name with `--name`")
@Name(value = "hello", priority = 100)
public class Hello2Command extends HelloCommand {
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.core.impl.launcher.commands;
import io.vertx.core.spi.launcher.DefaultCommandFactory;
public class Hello2CommandFactory extends DefaultCommandFactory<Hello2Command> {
public Hello2CommandFactory() {
super(Hello2Command.class, Hello2Command::new);
}
}

View File

@@ -17,7 +17,7 @@ import io.vertx.core.spi.launcher.DefaultCommandFactory;
public class HelloCommandFactory extends DefaultCommandFactory<HelloCommand> {
public HelloCommandFactory() {
super(HelloCommand.class);
super(HelloCommand.class, HelloCommand::new);
}
}

View File

@@ -16,7 +16,7 @@ import io.vertx.core.spi.launcher.DefaultCommandFactory;
public class HiddenCommandFactory extends DefaultCommandFactory<HiddenCommand> {
public HiddenCommandFactory() {
super(HiddenCommand.class);
super(HiddenCommand.class, HiddenCommand::new);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.core.impl.launcher.commands;
import io.vertx.core.spi.launcher.Command;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test the priority check.
*/
public class PriorityCommandTest extends CommandTestBase {
private Command hello;
@Before
public void setUp() throws IOException {
super.setUp();
// run the command
cli.execute("hello", "-name", "priority 100");
// it should be stored as an instance so we can inspect it...
hello = cli.getExistingCommandInstance("hello");
}
@Test
public void testHelloCommandIsOfTypeHello2() {
// ensure that we get the right command, Hello2 is the command "hello" with higher priority
assertThat(hello.getClass())
.isEqualTo(Hello2Command.class);
}
}

View File

@@ -2,4 +2,6 @@
io.vertx.core.impl.launcher.commands.HelloCommandFactory
io.vertx.core.impl.launcher.commands.GoodByeCommandFactory
io.vertx.core.impl.launcher.commands.HiddenCommandFactory
io.vertx.core.impl.launcher.commands.ComplexCommandFactory
io.vertx.core.impl.launcher.commands.ComplexCommandFactory
# This command will have higher priority and override the previous one
io.vertx.core.impl.launcher.commands.Hello2CommandFactory