Java tutorial
/* * Copyright (C) 2009-2011 Institute for Computational Biomedicine, * Weill Medical College of Cornell University * * This file is part of the Goby IO API. * * The Goby IO API 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 Goby IO API 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 Goby IO API. If not, see <http://www.gnu.org/licenses/>. */ package edu.cornell.med.icb.goby.modes; import com.martiansoftware.jsap.*; import edu.cornell.med.icb.goby.util.dynoptions.DynamicOptionRegistry; import org.apache.commons.lang.*; import java.io.IOException; import java.io.PrintStream; import java.util.Iterator; import java.util.Map; /** * Base abstract class for Goby modes. * * @author Kevin Dorff */ public abstract class AbstractCommandLineMode { private String jarFilename; protected AbstractCommandLineMode(final String jarFilename) { setJarFilename(jarFilename); } public String getJarFilename() { return jarFilename; } public void setJarFilename(final String jarFilename) { this.jarFilename = jarFilename; } /** * Returns the mode name defined by subclasses. * * @return The name of the mode */ public abstract String getModeName(); /** * The short version of the mode name. This default implementation is based on modeName, split by "-", * keeping only the first character of each split, such as "compact-file-stats" becomes "cfs". * This allows a short mode name for specifying goby modes on the command line, so the following * two command line options are synomymous: * goby -jar goby.jar --mode compact-file-stats * goby -jar goby.jar --mode cfs * The Goby command line parser will not accept duplicate short mode names, so if two modes return * the same shortModeName, the command line parser will not offer/accept a short mode name for these modes. * In this case, it is recommended that you create overriding getShortModeName() methods for all of these modes. * * @return The short name of the mode */ public String getShortModeName() { final String[] shortModeNameParts = getModeName().split("-"); final StringBuilder shortModeNameBuilder = new StringBuilder(shortModeNameParts.length); for (final String shortModeNamePart : shortModeNameParts) { shortModeNameBuilder.append(shortModeNamePart.charAt(0)); } return shortModeNameBuilder.toString(); } /** * Returns the mode description defined by subclasses. * * @return A description of the mode */ public abstract String getModeDescription(); /** * Returns true when the id represents a help option. * * @param id The id to check * @return true if the id is a known help option. */ private boolean isJSAPHelpId(final String id) { return "help".equals(id) || "wikihelp".equals(id) || "htmlhelp".equals(id) || "help-dynamic".equals(id); } /** * Print usage for the mode. * * @param jsap the configured JSAP object */ public void printUsage(final JSAP jsap) { final String modeName = getModeName(); final String modeDescription = getModeDescription(); final Parameter modeId = jsap.getByID("mode"); if (modeId != null) { jsap.unregisterParameter(modeId); } System.err.println("Usage: "); System.err.println("java -jar " + jarFilename + " (-m|--mode) " + modeName + " " + jsap.getUsage()); System.err.println(); System.err.println("Description: "); System.err.println(WordUtils.wrap(modeDescription, JSAP.DEFAULT_SCREENWIDTH - 1)); System.err.println(); System.err.println("Options: "); System.err.println(jsap.getHelp(JSAP.DEFAULT_SCREENWIDTH - 1)); } /** * Print the usage for a mode in the format appropriate for a * <a href="http://www.wikimedia.org/">Wikimedia</a> table. * * @param jsap The parameters for the mode */ public void printUsageAsWikiTable(final JSAP jsap) { final PrintStream stream = System.out; // Table definition/header stream.println( "{| class=\"wikitable\" style=\"margin: 1em auto 1em auto; background:#efefef;\" valign=\"top\" align=\"center\" border=\"1\" cellpadding=\"5\" width=\"80%\""); stream.println("|- valign=\"bottom\" align=\"left\" style=\"background:#ffdead;\""); stream.println("!width=\"15%\"| Flag"); stream.println("!width=\"10%\" | Arguments"); stream.println("!width=\"5%\"| Required"); stream.println("! Description"); stream.println(); // iterate through help entries for the mode final IDMap idMap = jsap.getIDMap(); final Iterator iterator = idMap.idIterator(); while (iterator.hasNext()) { final String id = (String) iterator.next(); if (!"mode".equals(id) && !isJSAPHelpId(id)) { // skip the mode and help ids final Parameter parameter = jsap.getByID(id); stream.println("|- valign=\"top\" align=\"left\""); stream.print("| <nowiki>"); if (parameter instanceof Flagged) { final Flagged flagged = (Flagged) parameter; final Character characterFlag = flagged.getShortFlagCharacter(); final String longFlag = flagged.getLongFlag(); if (characterFlag != null && StringUtils.isNotBlank(longFlag)) { stream.print("(-" + characterFlag + "|--" + longFlag + ")"); } else if (characterFlag != null) { stream.print("-" + characterFlag); } else if (StringUtils.isNotBlank(longFlag)) { stream.print("--" + longFlag); } } else { stream.print("n/a"); } stream.println("</nowiki>"); stream.print("| "); if (parameter instanceof Switch) { stream.println("n/a"); } else { stream.println(id); } final boolean required; if (parameter instanceof Option) { final Option option = (Option) parameter; required = option.required(); } else { required = !(parameter instanceof Switch); } stream.println("| " + BooleanUtils.toStringYesNo(required)); stream.println("| " + parameter.getHelp()); if (parameter.getDefault() != null) { stream.print("Default value: "); for (final String defaultValue : parameter.getDefault()) { stream.print(" "); stream.print(defaultValue); } } stream.println(); } } // table close stream.println("|}"); } /** * Print the usage for a mode in the format appropriate for a table in HTML. * * @param jsap The parameters for the mode */ public void printUsageAsHtmlTable(final JSAP jsap) { final PrintStream writer = System.out; // Table definition/header writer.println("<table style=\"background: #efefef;\" border=\"1\" cellpadding=\"5\">"); writer.println("<tbody>"); writer.println("<tr style=\"background: #ffdead;\" align=\"left\" valign=\"bottom\">"); writer.println("<th style=\"width: 15%\">Flag</th>"); writer.println("<th style=\"width: 10%\">Arguments</th>"); writer.println("<th style=\"width: 5%\">Required</th>"); writer.println("<th>Description</th>"); writer.println("</tr>"); // iterate through help entries for the mode final IDMap idMap = jsap.getIDMap(); final Iterator iterator = idMap.idIterator(); while (iterator.hasNext()) { final String id = (String) iterator.next(); if (!"mode".equals(id) && !isJSAPHelpId(id)) { // skip the mode and help ids final Parameter parameter = jsap.getByID(id); writer.println("<tr align=\"left\" valign=\"top\">"); writer.print("<td><code>"); if (parameter instanceof Flagged) { final Flagged flagged = (Flagged) parameter; final Character characterFlag = flagged.getShortFlagCharacter(); final String longFlag = flagged.getLongFlag(); if (characterFlag != null && StringUtils.isNotBlank(longFlag)) { writer.print("(-" + characterFlag + "|--" + longFlag + ")"); } else if (characterFlag != null) { writer.print("-" + characterFlag); } else if (StringUtils.isNotBlank(longFlag)) { writer.print("--" + longFlag); } } else { writer.print("n/a"); } writer.println("</code></td>"); writer.print("<td>"); if (parameter instanceof Switch) { writer.print("n/a"); } else { writer.print(id); } writer.println("</td>"); final boolean required; if (parameter instanceof Option) { final Option option = (Option) parameter; required = option.required(); } else { required = !(parameter instanceof Switch); } writer.println("<td>" + BooleanUtils.toStringYesNo(required) + "</td>"); writer.print("<td>"); // convert any strange characters to html codes final String htmlHelpString = StringEscapeUtils.escapeHtml(parameter.getHelp()); // and also convert "-" to the hyphen character code writer.print(StringUtils.replace(htmlHelpString, "-", "-")); if (parameter.getDefault() != null) { writer.print(" Default value: "); for (final String defaultValue : parameter.getDefault()) { writer.print(" "); writer.print(StringUtils.replace(defaultValue, "-", "-")); } } writer.println("</td>"); writer.println("</tr>"); } } // table close writer.println("</tbody>"); writer.println("</table>"); } /** * Load JSAP from resource with no modifications to help / defaults. * * @return the configured JSAP object * @throws IOException error reading or configuring * @throws JSAPException error reading or configuring */ public JSAP loadJsapFromResource() throws IOException, JSAPException { return loadJsapFromResource(null); } /** * Load JSAP XML configuration for the current class. With potential * modifications to help / defaults stored in the helpValues map. * * @param helpValues a map of values to replace in the jsap help with other values * @return the configured JSAP object * @throws IOException error reading or configuring * @throws JSAPException error reading or configuring */ @SuppressWarnings("unchecked") public JSAP loadJsapFromResource(final Map<String, String> helpValues) throws IOException, JSAPException { final Class thisClass = this.getClass(); final String className = ClassUtils.getShortCanonicalName(thisClass); JSAP jsap; try { jsap = new JSAP(thisClass.getResource(className + ".jsap")); reformatHelp(jsap); } catch (NullPointerException e) { // NOPMD // No JSAP for "this", no additional args for the specific driver jsap = new JSAP(); } // add options from driver to mode specific JSAP: final JSAP jsapSource = new JSAP(GenericToolsDriver.class .getResource(ClassUtils.getShortCanonicalName(GenericToolsDriver.class) + ".jsap")); final Iterator<String> idsIt = jsapSource.getIDMap().idIterator(); while (idsIt.hasNext()) { final String id = idsIt.next(); // remove parameters if they already exist in the destination. // This makes sure we use the source parameters, such as mode, help, // which contain variables to be replaced. if (jsap.getByID(id) != null) { jsap.unregisterParameter(jsap.getByID(id)); } jsap.registerParameter(jsapSource.getByID(id)); } if (helpValues == null) { return jsap; } final IDMap idMap = jsap.getIDMap(); final Iterator<String> idIterator = idMap.idIterator(); while (idIterator.hasNext()) { final String id = idIterator.next(); final Parameter param = jsap.getByID(id); final String help = param.getHelp(); final String[] defaults = param.getDefault(); for (final Map.Entry<String, String> entry : helpValues.entrySet()) { // replace values in help if (help.contains(entry.getKey())) { param.setHelp(StringUtils.replace(help, entry.getKey(), entry.getValue())); } // replace values in defaults if (defaults != null) { for (int i = 0; i < defaults.length; i++) { if (defaults[i].contains(entry.getKey())) { defaults[i] = StringUtils.replace(defaults[i], entry.getKey(), entry.getValue()); } } } } } return jsap; } private void reformatHelp(JSAP jsap) { final Iterator<String> idIterator = jsap.getIDMap().idIterator(); while (idIterator.hasNext()) { final String id = idIterator.next(); final Parameter param = jsap.getByID(id); final String help = param.getHelp(); // remove new lines so that justification works best: final String replaced = help.replaceAll("[ ]*\n[ ]+", " "); param.setHelp(replaced); } } /** * Parse the JSAP to JSAPResult. Handle if "help" was requested. * * @param jsap the parser to use when parsing arguments * @param args the arguments to parse * @return the JSAPResult */ public JSAPResult parseJsap(final JSAP jsap, final String[] args) { final JSAPResult jsapResult = jsap.parse(args); if (getModeName() != null) { if (jsap.getByID("help") != null && jsapResult.getBoolean("help")) { printUsage(jsap); System.exit(1); } if (jsap.getByID("help-dynamic") != null && jsapResult.getBoolean("help-dynamic")) { printDynamicHelpOptions(jsap); System.exit(1); } else if (jsap.getByID("htmlhelp") != null && jsapResult.getBoolean("htmlhelp")) { printUsageAsHtmlTable(jsap); System.exit(2); } else if (jsap.getByID("wikihelp") != null && jsapResult.getBoolean("wikihelp")) { printUsageAsWikiTable(jsap); System.exit(3); } } return jsapResult; } protected void printDynamicHelpOptions(JSAP jsap) { DynamicOptionRegistry.printHelp(); } /** * Configure the mode via command line arguments. * * @param args command line arguments * @return this object for chaining * @throws IOException error configuring * @throws JSAPException error configuring */ public abstract AbstractCommandLineMode configure(final String[] args) throws IOException, JSAPException; /** * Execute the mode. * * @throws IOException io error */ public abstract void execute() throws IOException; /** * Parse the JSAP arguments defined for the mode. Different arguments can be defined for * each mode. * * @param args command line arguments. * @param helpValues A map of option names to help strings * @return Parsed arguments. * @throws IOException if there was a problem configuring the parser * @throws JSAPException if there was a problem parsing the args * @see #loadJsapFromResource() * @see #loadJsapFromResource(java.util.Map) */ protected JSAPResult parseJsapArguments(final String[] args, final Map<String, String> helpValues) throws IOException, JSAPException { final JSAP jsap = loadJsapFromResource(helpValues); final JSAPResult jsapResult = parseJsap(jsap, args); abortOnError(jsap, jsapResult); return jsapResult; } /** * Parse the JSAP arguments defined for the mode. Different arguments can be defined for * each mode. * * @param args command line arguments. * @return Parsed arguments. * @throws IOException if there was a problem parsing the help text * @throws JSAPException if there was a problem parsing the args * @see #loadJsapFromResource() * @see #loadJsapFromResource(java.util.Map) */ protected JSAPResult parseJsapArguments(final String[] args) throws IOException, JSAPException { final JSAP jsap = loadJsapFromResource(); final JSAPResult jsapResult = parseJsap(jsap, args); abortOnError(jsap, jsapResult); return jsapResult; } /** * Print out specific error messages describing the problems * with the command line, THEN print usage, THEN print full * help. This is called "beating the user with a clue stick." * * @param jsap The argument parser * @param jsapResult The results of the parse */ private void abortOnError(final JSAP jsap, final JSAPResult jsapResult) { if (!jsapResult.success()) { System.err.println(); printUsage(jsap); System.err.println( "One or more errors were encountered parsing the command line. See usage above to correct these errors:"); for (final Iterator errs = jsapResult.getErrorMessageIterator(); errs.hasNext();) { System.err.println("Error: " + errs.next()); } System.err.println(); System.exit(1); } } }