code: package morfologik.tools;
  
  import java.io.PrintStream;
  import java.util.List;
  import java.util.Locale;
  import java.util.concurrent.Callable;
  
  import com.beust.jcommander.JCommander;
  import com.beust.jcommander.MissingCommandException;
  import com.beust.jcommander.Parameter;
  import com.beust.jcommander.ParameterException;
  import com.beust.jcommander.Parameters;
  
  
  /**
   * Base class for command-line applications.
   */
  public abstract class CliTool implements Callable<ExitStatus> {
    protected final static String ARG_OVERWRITE = "--overwrite";
    protected final static String ARG_VALIDATE = "--validate";
  
    @Parameter(
        names = {"--exit"},
        hidden = true,
        arity = 1,
        description = "Call System.exit() at the end of command processing.")
    private boolean callSystemExit = true;
  
    @Parameter(
        names = {"-h", "--help"},
        help = true,
        hidden = true,
        description = "Help about options and switches.")
    private boolean help;
  
    public CliTool() {
      if (!getClass().isAnnotationPresent(Parameters.class)) {
        throw new RuntimeException();
      }
    }
  
    /**
     * Call {@link System#exit(int)} at the end of command processing.
     * 
     * @param flag Call {@link System#exit(int)} if <code>true</code>.
     */
    public void setCallSystemExit(boolean flag) {
      this.callSystemExit = flag;
    }
  
    /**
     * Parse and execute one of the commands.
     * 
     * @param args Command line arguments (command and options).
     * @param commands A list of commands.
     */
    protected static void main(String[] args, CliTool... commands) {
      if (commands.length == 1) {
        main(args, commands[0]);
      } else {
        JCommander jc = new JCommander();
        for (CliTool command : commands) {
          jc.addCommand(command);
        }
        jc.addConverterFactory(new CustomParameterConverters());
        jc.setProgramName("");
    
        ExitStatus exitStatus = ExitStatus.SUCCESS;
        try {
          jc.parse(args);
  
          final String commandName = jc.getParsedCommand();
          if (commandName == null) {
            helpDisplayCommandOptions(System.err, jc);
          } else {
            List<Object> objects = jc.getCommands().get(commandName).getObjects();
            if (objects.size() != 1) {
              throw new RuntimeException();
            }
  
            CliTool command = CliTool.class.cast(objects.get(0));
            exitStatus = command.call();
            if (command.callSystemExit) {
              System.exit(exitStatus.code);
            }
          }
        } catch (ExitStatusException e) {
          System.err.println(e.getMessage());
          if (e.getCause() != null) {
            e.getCause().printStackTrace(System.err);
          }
          exitStatus = e.exitStatus;
        } catch (MissingCommandException e) {
          System.err.println("Invalid argument: " + e);
          System.err.println();
          helpDisplayCommandOptions(System.err, jc);
          exitStatus = ExitStatus.ERROR_INVALID_ARGUMENTS;
        } catch (ParameterException e) {
          System.err.println("Invalid argument: " + e.getMessage());
          System.err.println();
    
          if (jc.getParsedCommand() == null) {
            helpDisplayCommandOptions(System.err, jc);
          } else {
            helpDisplayCommandOptions(System.err, jc.getParsedCommand(), jc);
          }
          exitStatus = ExitStatus.ERROR_INVALID_ARGUMENTS;
        } catch (Throwable t) {
          System.err.println("An unhandled exception occurred. Stack trace below.");
          t.printStackTrace(System.err);
          exitStatus = ExitStatus.ERROR_OTHER;
        }
      }
    }
  
    /**
     * Parse and execute a single command.
     *  
     * @param args Command line arguments (command and options).
     * @param command The command to execute.
     */
    protected static void main(String[] args, CliTool command) {
      JCommander jc = new JCommander(command);
      jc.addConverterFactory(new CustomParameterConverters());
      jc.setProgramName(command.getClass().getAnnotation(Parameters.class).commandNames()[0]);
  
      ExitStatus exitStatus = ExitStatus.SUCCESS;
      try {
        jc.parse(args);
        if (command.help) {
          helpDisplayCommandOptions(System.err, jc);
        } else {
          exitStatus = command.call();
        }
      } catch (ExitStatusException e) {
        System.err.println(e.getMessage());
        if (e.getCause() != null) {
          e.getCause().printStackTrace(System.err);
        }
        exitStatus = e.exitStatus;
      } catch (MissingCommandException e) {
        System.err.println("Invalid argument: " + e);
        System.err.println();
        helpDisplayCommandOptions(System.err, jc);
        exitStatus = ExitStatus.ERROR_INVALID_ARGUMENTS;
      } catch (ParameterException e) {
        System.err.println("Invalid argument: " + e.getMessage());
        System.err.println();
  
        if (jc.getParsedCommand() == null) {
          helpDisplayCommandOptions(System.err, jc);
        } else {
          helpDisplayCommandOptions(System.err, jc.getParsedCommand(), jc);
        }
        exitStatus = ExitStatus.ERROR_INVALID_ARGUMENTS;
      } catch (Throwable t) {
        System.err.println("An unhandled exception occurred. Stack trace below.");
        t.printStackTrace(System.err);
        exitStatus = ExitStatus.ERROR_OTHER;
      }
  
      if (command.callSystemExit) {
        System.exit(exitStatus.code);
      }
    }
  
    protected static void printf(String msg, Object... args) {
      System.out.println(String.format(Locale.ROOT, msg, args));
    }
  
    protected static <T> T checkNotNull(T arg) {
      if (arg == null) {
        throw new IllegalArgumentException("Argument must not be null.");
      }
      return arg;
    }
  
    private static void helpDisplayCommandOptions(PrintStream pw, String command, JCommander jc) {
      StringBuilder sb = new StringBuilder();
      jc = jc.getCommands().get(command);
      jc.getUsageFormatter().usage(sb, "");
      pw.print(sb);
    }
  
    private static void helpDisplayCommandOptions(PrintStream pw, JCommander jc) {
      StringBuilder sb = new StringBuilder();
      jc.getUsageFormatter().usage(sb, "");
      pw.print(sb);
    }  
  }
  