/* * Copyright 2016 MiLaboratory.com * * 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 com.milaboratory.cli; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterException; import java.io.PrintStream; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; public class JCommanderBasedMain implements ActionHelper { // LinkedHashMap to preserve order of actions protected final Map<String, Action> actions = new LinkedHashMap<>(); protected final String command; protected boolean printHelpOnError = false; protected boolean printStackTrace = false; protected Runnable versionInfoCallback = null; protected PrintStream outputStream = System.err; protected String[] arguments; public JCommanderBasedMain(String command, Action... actions) { this.command = command; for (Action action : actions) reg(action); } public void setOutputStream(PrintStream outputStream) { this.outputStream = outputStream; } @Override public PrintStream getDefaultPrintStream() { return outputStream; } @Override public String getCommandLineArguments() { StringBuilder builder = new StringBuilder(); for (String arg : arguments) { if (builder.length() != 0) builder.append(" "); builder.append(arg); } return builder.toString(); } public boolean isPrintStackTrace() { return printStackTrace; } public void setPrintStackTrace(boolean printStackTrace) { this.printStackTrace = printStackTrace; } protected void reg(Action a) { actions.put(a.command(), a); } public ProcessResult main(String... args) throws Exception { // Saving current arguments this.arguments = args; if (args.length == 0) { printGlobalHelp(); return ProcessResult.Help; } // Setting up JCommander MainParameters mainParameters = getMainParameters(); JCommander commander = new JCommander(mainParameters); commander.setProgramName(command); for (Action a : actions.values()) commander.addCommand(a.command(), a.params()); // Getting command name String commandName = args[0]; // Getting corresponding action Action action = actions.get(commandName); try { if (action != null && (action instanceof ActionParametersParser)) { ((ActionParametersParser) action).parseParameters(Arrays.copyOfRange(args, 1, args.length)); } else { commander.parse(args); // Print Version information if requested and exit. if (mainParameters instanceof MainParametersWithVersion && ((MainParametersWithVersion) mainParameters).version()) { versionInfoCallback.run(); return ProcessResult.Version; } // Print complete help if requested if (mainParameters.help()) { // Creating new instance of jCommander to add only non-hidden actions printGlobalHelp(); return ProcessResult.Help; } if (args.length == 1 && !args[0].startsWith("-")) { action = actions.get(commandName); if (!action.getClass().isAnnotationPresent(AllowNoArguments.class)) { System.out.println("Error: missing required arguments.\n"); printActionHelp(commander, action); return ProcessResult.Error; } } // Getting parsed command // assert parsedCommand.equals(commandName) final String parsedCommand = commander.getParsedCommand(); // Processing potential errors if (parsedCommand == null || !actions.containsKey(parsedCommand)) { if (parsedCommand == null) outputStream.println("No command specified."); else outputStream.println("Command " + parsedCommand + " not supported."); outputStream.println("Use -h option to get a list of supported commands."); return ProcessResult.Error; } action = actions.get(parsedCommand); } if (action.params().help()) { printActionHelp(commander, action); } else { action.params().validate(); action.go(this); } } catch (ParameterException | ProcessException pe) { printException(pe, commander, action); return ProcessResult.Error; } return ProcessResult.Ok; } private MainParameters getMainParameters() { return versionInfoCallback != null ? new MainParametersWithVersion() : new MainParameters(); } protected void printGlobalHelp() { // Creating new instance of jCommander to add only non-hidden actions JCommander tmpCommander = new JCommander(getMainParameters()); tmpCommander.setProgramName(command); for (Action a : actions.values()) if (!a.getClass().isAnnotationPresent(HiddenAction.class)) tmpCommander.addCommand(a.command(), a.params()); StringBuilder builder = new StringBuilder(); tmpCommander.usage(builder); outputStream.print(builder); } protected void printActionHelp(JCommander commander, Action action) { StringBuilder builder = new StringBuilder(); if (action instanceof ActionHelpProvider) { if (((ActionHelpProvider) action).printDefaultHelp()) { commander.usage(action.command(), builder); builder.append("\n"); } ((ActionHelpProvider) action).printHelp(builder); } else commander.usage(action.command(), builder); outputStream.print(builder); } protected void printException(RuntimeException e, JCommander commander, Action action) { outputStream.println("Error: " + e.getMessage()); if (printStackTrace) e.printStackTrace(new PrintStream(outputStream)); if (printHelpOnError) printActionHelp(commander, action); } public enum ProcessResult { Ok, Version, Help, Error } /** * Enables -v / --version parameter. * * Sets callback that will be invoked if this option is specified by user. * * {@literal null} disables -v parameter. * * @param versionInfoCallback callback to be will be invoked if user specified -v option. {@literal null} disables * -v parameter. */ public void setVersionInfoCallback(Runnable versionInfoCallback) { this.versionInfoCallback = versionInfoCallback; } public static class MainParameters { @Parameter(names = {"-h", "--help"}, help = true, description = "Displays this help message.") public Boolean help; public boolean help() { return help != null && help; } } public static class MainParametersWithVersion extends MainParameters { @Parameter(names = {"-v", "--version"}, help = true, description = "Output version information.") public Boolean version; public boolean version() { return version != null && version; } } }