/** * Copyright (C) 2012-2013 Selventa, Inc. * * This file is part of the OpenBEL Framework. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The OpenBEL Framework is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>. * * Additional Terms under LGPL v3: * * This license does not authorize you and you are prohibited from using the * name, trademarks, service marks, logos or similar indicia of Selventa, Inc., * or, in the discretion of other licensors or authors of the program, the * name, trademarks, service marks, logos or similar indicia of such authors or * licensors, in any marketing or advertising materials relating to your * distribution of the program or any covered product. This restriction does * not waive or limit your obligation to keep intact all copyright notices set * forth in the program as delivered to you. * * If you distribute the program in whole or in part, or any modified version * of the program, and you assume contractual liability to the recipient with * respect to the program or modified version, then you will indemnify the * authors and licensors of the program for any liabilities that these * contractual assumptions directly impose on those licensors and authors. */ package org.openbel.framework.core; import static java.lang.System.err; import static java.lang.System.exit; import static java.lang.System.out; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.openbel.framework.common.BELUtilities.noItems; import static org.openbel.framework.common.enums.ExitCode.GENERAL_FAILURE; import static org.openbel.framework.common.enums.ExitCode.PARSE_ERROR; import static org.openbel.framework.common.enums.ExitCode.SUCCESS; import java.io.OutputStream; import java.io.PrintWriter; import java.util.List; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionGroup; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.openbel.framework.common.enums.ExitCode; /** * Base class for simple command-line applications. * <p> * This class is a clean design of {@link CommandLineApplication}. * CommandLineApplication has three characteristics that make it undesirable to * use: * <ul> * <li>Spring</li> * <li>Logging</li> * <li>Has hacks put in place to support "special" functionality</li> * </ul> * </p> */ public abstract class SimpleApplication { private final CommandLineParser cliParser; private final Options cliOptions; private final String[] cliArgs; private final CommandLine commandLine; /** * Constructs the simple application with the command-line arguments. * * @param args Command-line arguments */ public SimpleApplication(final String[] args) { cliParser = new GnuParser(); cliOptions = new Options(); this.cliArgs = args; List<Option> opts = getCommandLineOptions(); if (opts != null) { for (final Option opt : opts) { cliOptions.addOption(opt); } } addOption("h", "help", "prints this help"); CommandLine cl = null; try { cl = cliParser.parse(cliOptions, cliArgs); } catch (ParseException e) { err.println(e.getMessage()); printHelp(cliOptions, false, err); bail(PARSE_ERROR); } commandLine = cl; if (hasOption('h')) { printHelp(cliOptions, true, out); } } /** * Returns the non-null application name. * * @return Non-null string */ public abstract String getApplicationName(); /** * Returns the non-null application description. * * @return Non-null string */ public abstract String getApplicationDescription(); /** * Returns a detailed description of the application's command-line. * <p> * Optional arguments should be wrapped in {@code [] brackets}. For example: * {@code [-o somearg]}. Arguments that can occur more than once should be * followed with ellipses ({@code ...}), for example: {@code [-f FILE...]}. * </p> * * @return Non-null string */ public abstract String getUsage(); /** * Returns the command-line arguments. * * @return Command-line arguments */ protected final String[] getCommandLineArguments() { return cliArgs; } /** * Returns the number of command-line arguments, or {@code 0} if none are * available. * * @return int */ public final int getNumberOfCommandLineArgs() { if (commandLine == null) { return 0; } List<?> args = commandLine.getArgList(); if (args == null) { return 0; } return args.size(); } /** * Returns the extraneous command-line arguments. * * @return Non-null list of strings, may be empty */ protected final List<String> getExtraneousArguments() { if (commandLine == null) { return emptyList(); } String[] args = commandLine.getArgs(); if (noItems(args)) { return emptyList(); } return asList(args); } /** * Returns {@code true} if extraneous arguments are present, {@code false} * otherwise. * * @return boolean */ protected final boolean hasExtraneousArguments() { return !getExtraneousArguments().isEmpty(); } /** * Returns {@code true} if the option specified by {@code name} has been * set, {@code false} otherwise. * * @param name Single character option name * @return boolean */ protected final boolean hasOption(final char name) { return commandLine.hasOption(name); } /** * Returns {@code true} if the option specified by {@code name} has been * set, {@code false} otherwise. * * @param name string name * @return boolean */ protected final boolean hasOption(final String name) { return commandLine.hasOption(name); } /** * Returns the option value for the option specified by {@code name}. * * @param name Single character option name * @return String, which may be null */ protected final String getOptionValue(final char name) { return commandLine.getOptionValue(name); } /** * Returns the option values for the option specified by {@code name}. * * @param name Option name * @return String[] */ protected final String[] getOptionValues(final String name) { return commandLine.getOptionValues(name); } /** * Returns the option value for the option specified by {@code name}. * * @param name Option string name * @return String, which may be null */ protected final String getOptionValue(final String name) { return commandLine.getOptionValue(name); } /** * Returns the ordered, parsed command-line options. * * @return {@link Option}[] the ordered options array */ protected final Option[] getOptions() { return commandLine.getOptions(); } /** * Adds a command-line option. * * @param shortOpt Short, one character option (e.g., {@code -t}) * @param longOpt Long, one or two word option (e.g., {@code --long-option}) * @param desc Option description (e.g., {@code does something great}) */ public final void addOption(String shortOpt, String longOpt, String desc) { cliOptions.addOption(new Option(shortOpt, longOpt, false, desc)); } /** * Adds a command-line option. * * @param shortOpt Short, one character option (e.g., {@code -t}) * @param desc Option description (e.g., {@code does something great}) */ public final void addOption(String shortOpt, String desc) { cliOptions.addOption(new Option(shortOpt, desc)); } /** * Adds a command-line option. * * @param shortOpt Short, one character option (e.g., {@code -t}) * @param desc Option description (e.g., {@code does something great}) * @param hasArg boolean {@code true} if the option requires an argument, * {@code false} otherwise */ public final void addOption(String shortOpt, String desc, boolean hasArg) { cliOptions.addOption(new Option(shortOpt, hasArg, desc)); } /** * Adds a command-line option. * * @param shortOpt Short, one character option (e.g., {@code -t}) * @param longOpt Long, one or two word option (e.g., {@code --long-option}) * @param desc Option description (e.g., {@code does something great}) * @param hasArg boolean {@code true} if the option requires an argument, * {@code false} otherwise */ public final void addOption(String shortOpt, String longOpt, String desc, boolean hasArg) { cliOptions.addOption(new Option(shortOpt, longOpt, hasArg, desc)); } /** * Adds a command-line option. * * @param o Option */ public final void addOption(final Option o) { cliOptions.addOption(o); } /** * Adds a command-line option group. * * @param o Option group */ public final void addOptionGroup(final OptionGroup o) { cliOptions.addOptionGroup(o); } /** * Returns command-line options used by the application. * <p> * Options specifies by subclasses should adhere to the convention of a * single-character short option with an optional long option. * </p> * * @return List of options, may be null or empty * @see #addOption(String, String, String, boolean) */ public abstract List<Option> getCommandLineOptions(); /** * Prints usage information to {@code stdout}. */ public void printUsage() { printUsage(out); } /** * Prints usage information to the provided output stream. * * @param os Non-null output stream */ public void printUsage(final OutputStream os) { final StringBuilder bldr = new StringBuilder(); bldr.append("Usage: "); bldr.append(getUsage()); bldr.append("\n"); bldr.append("Try '--help' for more information.\n"); PrintWriter pw = new PrintWriter(os); pw.write(bldr.toString()); pw.close(); } /** * Write command-line help to standard out and invokes * {@link #printHelp(Options, boolean)}. * * @param exit Exit flag */ protected void printHelp(boolean exit) { printHelp(cliOptions, exit); } /** * Write command-line help to {@code stdout}. * * @param o Options * @param exit Exit flag */ private void printHelp(Options o, boolean exit) { printHelp(o, exit, out); } /** * Write command-line help to the provided stream. If {@code exit} is * {@code true}, exit with status code {@link #EXIT_FAILURE}. * * @param o Options * @param exit Exit flag */ private void printHelp(Options o, boolean exit, OutputStream os) { final PrintWriter pw = new PrintWriter(os); final String syntax = getUsage(); String header = getApplicationName(); header = header.concat("\nOptions:"); HelpFormatter hf = new HelpFormatter(); hf.printHelp(pw, 80, syntax, header, o, 2, 2, null, false); pw.flush(); if (exit) { bail(SUCCESS); } } /** * Exits, with {@link ExitCode#SUCCESS}. */ protected final void exitSuccess() { bail(SUCCESS); } /** * Prints the provided message to {@code stderr}, and invokes * {@link #bail(ExitCode)} with {@link ExitCode#GENERAL_FAILURE}. * * @param msg Message */ protected final void fatal(final String msg) { err.println(msg); bail(GENERAL_FAILURE); } /** * Stops the application's context and invokes {@link #exit(ExitCode) exit} * with the provided status code. * * @param e Exit code */ protected final void bail(final ExitCode e) { systemExit(e); } /** * The <b>ONLY</b> call to {@link System#exit(int)}! * * @param e Exit code */ protected final static void systemExit(final ExitCode e) { exit(e.getValue()); } }