spring CommandRunner 源码

  • 2022-08-12
  • 浏览 (411)

springboot CommandRunner 代码

文件路径:/spring-boot-project/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandRunner.java

/*
 * Copyright 2012-2022 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.cli.command;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.springframework.boot.cli.command.status.ExitStatus;
import org.springframework.boot.cli.util.Log;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * Main class used to run {@link Command}s.
 *
 * @author Phillip Webb
 * @since 1.0.0
 * @see #addCommand(Command)
 * @see CommandRunner#runAndHandleErrors(String[])
 */
public class CommandRunner implements Iterable<Command> {

	private static final Set<CommandException.Option> NO_EXCEPTION_OPTIONS = EnumSet
			.noneOf(CommandException.Option.class);

	private final String name;

	private final List<Command> commands = new ArrayList<>();

	private Class<?>[] optionCommandClasses = {};

	private Class<?>[] hiddenCommandClasses = {};

	/**
	 * Create a new {@link CommandRunner} instance.
	 * @param name the name of the runner or {@code null}
	 */
	public CommandRunner(String name) {
		this.name = StringUtils.hasLength(name) ? name + " " : "";
	}

	/**
	 * Return the name of the runner or an empty string. Non-empty names will include a
	 * trailing space character so that they can be used as a prefix.
	 * @return the name of the runner
	 */
	public String getName() {
		return this.name;
	}

	/**
	 * Add the specified commands.
	 * @param commands the commands to add
	 */
	public void addCommands(Iterable<Command> commands) {
		Assert.notNull(commands, "Commands must not be null");
		for (Command command : commands) {
			addCommand(command);
		}
	}

	/**
	 * Add the specified command.
	 * @param command the command to add.
	 */
	public void addCommand(Command command) {
		Assert.notNull(command, "Command must not be null");
		this.commands.add(command);
	}

	/**
	 * Set the command classes which should be considered option commands. An option
	 * command is a special type of command that usually makes more sense to present as if
	 * it is an option. For example '--version'.
	 * @param commandClasses the classes of option commands.
	 * @see #isOptionCommand(Command)
	 */
	public void setOptionCommands(Class<?>... commandClasses) {
		Assert.notNull(commandClasses, "CommandClasses must not be null");
		this.optionCommandClasses = commandClasses;
	}

	/**
	 * Set the command classes which should be hidden (i.e. executed but not displayed in
	 * the available commands list).
	 * @param commandClasses the classes of hidden commands
	 */
	public void setHiddenCommands(Class<?>... commandClasses) {
		Assert.notNull(commandClasses, "CommandClasses must not be null");
		this.hiddenCommandClasses = commandClasses;
	}

	/**
	 * Returns if the specified command is an option command.
	 * @param command the command to test
	 * @return {@code true} if the command is an option command
	 * @see #setOptionCommands(Class...)
	 */
	public boolean isOptionCommand(Command command) {
		return isCommandInstanceOf(command, this.optionCommandClasses);
	}

	private boolean isHiddenCommand(Command command) {
		return isCommandInstanceOf(command, this.hiddenCommandClasses);
	}

	private boolean isCommandInstanceOf(Command command, Class<?>[] commandClasses) {
		for (Class<?> commandClass : commandClasses) {
			if (commandClass.isInstance(command)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public Iterator<Command> iterator() {
		return getCommands().iterator();
	}

	protected final List<Command> getCommands() {
		return Collections.unmodifiableList(this.commands);
	}

	/**
	 * Find a command by name.
	 * @param name the name of the command
	 * @return the command or {@code null} if not found
	 */
	public Command findCommand(String name) {
		for (Command candidate : this.commands) {
			String candidateName = candidate.getName();
			if (candidateName.equals(name) || (isOptionCommand(candidate) && ("--" + candidateName).equals(name))) {
				return candidate;
			}
		}
		return null;
	}

	/**
	 * Run the appropriate and handle and errors.
	 * @param args the input arguments
	 * @return a return status code (non boot is used to indicate an error)
	 */
	public int runAndHandleErrors(String... args) {
		String[] argsWithoutDebugFlags = removeDebugFlags(args);
		boolean debug = argsWithoutDebugFlags.length != args.length;
		if (debug) {
			System.setProperty("debug", "true");
		}
		try {
			ExitStatus result = run(argsWithoutDebugFlags);
			// The caller will hang up if it gets a non-zero status
			if (result != null && result.isHangup()) {
				return (result.getCode() > 0) ? result.getCode() : 0;
			}
			return 0;
		}
		catch (NoArgumentsException ex) {
			showUsage();
			return 1;
		}
		catch (Exception ex) {
			return handleError(debug, ex);
		}
	}

	private String[] removeDebugFlags(String[] args) {
		List<String> rtn = new ArrayList<>(args.length);
		boolean appArgsDetected = false;
		for (String arg : args) {
			// Allow apps to have a --debug argument
			appArgsDetected |= "--".equals(arg);
			if ("--debug".equals(arg) && !appArgsDetected) {
				continue;
			}
			rtn.add(arg);
		}
		return StringUtils.toStringArray(rtn);
	}

	/**
	 * Parse the arguments and run a suitable command.
	 * @param args the arguments
	 * @return the outcome of the command
	 * @throws Exception if the command fails
	 */
	protected ExitStatus run(String... args) throws Exception {
		if (args.length == 0) {
			throw new NoArgumentsException();
		}
		String commandName = args[0];
		String[] commandArguments = Arrays.copyOfRange(args, 1, args.length);
		Command command = findCommand(commandName);
		if (command == null) {
			throw new NoSuchCommandException(commandName);
		}
		beforeRun(command);
		try {
			return command.run(commandArguments);
		}
		finally {
			afterRun(command);
		}
	}

	/**
	 * Subclass hook called before a command is run.
	 * @param command the command about to run
	 */
	protected void beforeRun(Command command) {
	}

	/**
	 * Subclass hook called after a command has run.
	 * @param command the command that has run
	 */
	protected void afterRun(Command command) {
	}

	private int handleError(boolean debug, Exception ex) {
		Set<CommandException.Option> options = NO_EXCEPTION_OPTIONS;
		if (ex instanceof CommandException commandException) {
			options = commandException.getOptions();
			if (options.contains(CommandException.Option.RETHROW)) {
				throw (CommandException) ex;
			}
		}
		boolean couldNotShowMessage = false;
		if (!options.contains(CommandException.Option.HIDE_MESSAGE)) {
			couldNotShowMessage = !errorMessage(ex.getMessage());
		}
		if (options.contains(CommandException.Option.SHOW_USAGE)) {
			showUsage();
		}
		if (debug || couldNotShowMessage || options.contains(CommandException.Option.STACK_TRACE)) {
			printStackTrace(ex);
		}
		return 1;
	}

	protected boolean errorMessage(String message) {
		Log.error((message != null) ? message : "Unexpected error");
		return message != null;
	}

	protected void showUsage() {
		Log.infoPrint("usage: " + this.name);
		for (Command command : this.commands) {
			if (isOptionCommand(command)) {
				Log.infoPrint("[--" + command.getName() + "] ");
			}
		}
		Log.info("");
		Log.info("       <command> [<args>]");
		Log.info("");
		Log.info("Available commands are:");
		for (Command command : this.commands) {
			if (!isOptionCommand(command) && !isHiddenCommand(command)) {
				String usageHelp = command.getUsageHelp();
				String description = command.getDescription();
				Log.info(String.format("%n  %1$s %2$-15s%n    %3$s", command.getName(),
						(usageHelp != null) ? usageHelp : "", (description != null) ? description : ""));
			}
		}
		Log.info("");
		Log.info("Common options:");
		Log.info(String.format("%n  %1$s %2$-15s%n    %3$s", "--debug", "Verbose mode",
				"Print additional status information for the command you are running"));
		Log.info("");
		Log.info("");
		Log.info("See '" + this.name + "help <command>' for more information on a specific command.");
	}

	protected void printStackTrace(Exception ex) {
		Log.error("");
		Log.error(ex);
		Log.error("");
	}

}

相关信息

spring 源码目录

相关文章

spring AbstractCommand 源码

spring Command 源码

spring CommandException 源码

spring CommandFactory 源码

spring HelpExample 源码

spring NoArgumentsException 源码

spring NoHelpCommandArgumentsException 源码

spring NoSuchCommandException 源码

spring OptionParsingCommand 源码

spring package-info 源码

0  赞