/* * Copyright (c) 2003-2012 Fred Hutchinson Cancer Research Center * * 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 org.fhcrc.cpl.toolbox.commandline; import org.fhcrc.cpl.toolbox.ApplicationContext; import org.fhcrc.cpl.toolbox.commandline.CommandLineModuleExecutionException; import org.fhcrc.cpl.toolbox.proteomics.Protein; import org.fhcrc.cpl.toolbox.proteomics.MS2Modification; import org.fhcrc.cpl.toolbox.proteomics.feature.FeatureSet; import org.fhcrc.cpl.toolbox.commandline.CommandLineModule; import org.fhcrc.cpl.toolbox.commandline.arguments.*; import org.apache.log4j.Logger; import java.util.*; import java.io.PrintWriter; import java.io.File; import java.io.FileOutputStream; /** * Base class for command line modules. Command line modules do not have to * extend this class (they only need to implement CommandLineModule), but it's * highly recommended, because this class provides dozens of highly useful * convenience methods for argument management */ public abstract class BaseCommandLineModuleImpl implements CommandLineModule { static Logger _log = Logger.getLogger(BaseCommandLineModuleImpl.class); //store the command for a given module protected String mCommandName; protected String mUsageMessage = CommandLineModule.MODULE_USAGE_AUTOMATIC; protected String mHelpMessage=""; protected String mShortDescription=""; //A map of all argument definitions. Key = argument name, value = argument definition protected Map<String,CommandLineArgumentDefinition> mArgumentDefs; //A map of all populated argument names/values. Null until populated protected Map<String, Object> mArgumentValues; //A map of all arguments passed to the module (including any extraneous arguments) protected Map<String, String> mArgumentValueStrings; public String toString() { return "Commandline module for command " + mCommandName; } protected String makeHtmlSafe(String plainString) { if (plainString == null) return null; return plainString.replaceAll("<","<").replaceAll(">",">"); } /** * * @return a String telling the user how to invoke this command */ public String getUsage() { if (null == mUsageMessage || !(CommandLineModule.MODULE_USAGE_AUTOMATIC.equals(mUsageMessage))) return mUsageMessage; StringBuffer generatedUsage = new StringBuffer(); generatedUsage.append(getCommandName() + " "); String appendix = ""; for (CommandLineArgumentDefinition definition : getArgumentDefinitionsSortedForDisplay()) { if (CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_ARGUMENT.equals( definition.getArgumentName())) { appendix = definition.getValueDescriptor(); continue; } else if (CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT.equals( definition.getArgumentName())) { appendix = definition.getValueDescriptor() + " " + definition.getValueDescriptor() + " ..."; continue; } String thisArgUsage = "--" + definition.getArgumentName() + "="; String valueString = definition.getValueDescriptor(); thisArgUsage = thisArgUsage + valueString; if (!definition.isRequired()) thisArgUsage = "[" + thisArgUsage + "]"; generatedUsage.append(thisArgUsage); generatedUsage.append(" "); } generatedUsage.append(appendix); return generatedUsage.toString(); } /** * Returns the full help message to be displayed by --help <command> * @return */ public String getFullHelp() { StringBuffer result = new StringBuffer(); result.append(getHelpMessage()); StringBuffer argumentHelp = new StringBuffer(); for (CommandLineArgumentDefinition definition : getArgumentDefinitionsSortedForDisplay()) { String helpText = definition.getHelpText(); if (null == helpText) helpText = "(no details provided)"; String argumentName = definition.getArgumentDisplayName(); if (definition.hasDefaultValue()) helpText = helpText + " (default " + definition.getDefaultValueAsString() + ")"; String requiredString = ""; if (definition.isRequired()) requiredString = "*"; String thisArgHelp = "\t" + requiredString + argumentName + ":\t" + helpText + "\n"; argumentHelp.append(thisArgHelp); } result.append("\n\nArgument Details: ('*' indicates a required parameter)\n" + argumentHelp); return result.toString(); } /** * Does this module have any 'advanced' arguments? * @return */ public boolean hasAdvancedArguments() { try { for (CommandLineArgumentDefinition argDef : mArgumentDefs.values()) if (argDef.isAdvanced()) return true; } catch (Exception e) { } return false; } /** * Returns an HTML fragment containing full help information for this module * @return */ public String getHtmlHelpFragment() { StringBuffer result = new StringBuffer("<a name=\"" + this.getCommandName() + "\"></a>\n<p>\n"); result.append("<H2>" + getCommandName() + "</H2>\n"); result.append(getHelpMessage() + "\n<p>\n"); result.append("<h3>Usage:</h3>\n<p>\n" + makeHtmlSafe("--" + getUsage()) + "<p>"); String argumentsTitle = "Arguments:"; if (hasAdvancedArguments()) argumentsTitle = "Basic " + argumentsTitle; result.append("<h3>" + argumentsTitle + "</h3>\n"); //basic Arguments result.append(createArgsTableHTML(getBasicArgumentDefinitions())); if (hasAdvancedArguments()) { result.append("\n<p><h3>Advanced Arguments:</h3>\n"); result.append(createArgsTableHTML(getAdvancedArgumentDefinitions())); } result.append("\n"); return result.toString(); } protected String createArgsTableHTML(CommandLineArgumentDefinition[] argDefs) { StringBuffer result = new StringBuffer(); result.append("<table border=\"1\">\n\t<tr><th>Argument</th><th>Usage</th><th>Default</th><th>Description</th></tr>\n"); for (CommandLineArgumentDefinition definition : argDefs) { String helpText = definition.getHelpText(); if (null == helpText || helpText.length() < 1) helpText = "(no details provided)"; String argumentName = definition.getArgumentName(); if (CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_ARGUMENT.equals(argumentName)) { argumentName = "(unnamed)"; } if (CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT.equals(argumentName)) { argumentName = "(unnamed ...)"; } if (definition.isRequired()) argumentName = "<b>" + argumentName + "</b>"; String usage = definition.getValueDescriptor(); if (usage == null || usage.length()<1) usage = " "; String defaultValue = null; if (definition.hasDefaultValue()) defaultValue = definition.getDefaultValueAsString(); String thisArgHelp = "<tr><td>" + argumentName + "</td><td>" + makeHtmlSafe(usage) + "</td><td>" + makeHtmlSafe(defaultValue) + "</td><td>" + makeHtmlSafe(helpText) + "</td></tr>\n"; result.append(thisArgHelp); } result.append("</table>\n"); return result.toString(); } /** * * @return a String telling the user how to invoke this command; can be detailed */ public String getHelpMessage() { return mHelpMessage; } /** * * @return a String with a quick description of the feature */ public String getShortDescription() { return mShortDescription; } /** * * @return a String that tells Application to invoke this commandlinemodule * and not another one. Had better not collide with another one */ public String getCommandName() { return mCommandName; } /** * Add an argument definition * @param def */ protected void addArgumentDefinition(CommandLineArgumentDefinition def) { if (mArgumentDefs == null) mArgumentDefs = new HashMap<String,CommandLineArgumentDefinition>(); mArgumentDefs.put(def.getArgumentName().toLowerCase(),def); } /** * Add an argument definition * @param def */ protected void addArgumentDefinition(CommandLineArgumentDefinition def, boolean advanced) { addArgumentDefinition(def); def.setAdvanced(advanced); } /** * Add argument definitions * @param defArray */ protected void addArgumentDefinitions(CommandLineArgumentDefinition[] defArray) { if (mArgumentDefs == null) mArgumentDefs = new HashMap<String,CommandLineArgumentDefinition>(); for (CommandLineArgumentDefinition def : defArray) mArgumentDefs.put(def.getArgumentName().toLowerCase(),def); } /** * Add a argument definitions, setting the "advanced" state appropriately * @param defArray */ protected void addArgumentDefinitions(CommandLineArgumentDefinition[] defArray, boolean advanced) { for (CommandLineArgumentDefinition def : defArray) { def.setAdvanced(advanced); } addArgumentDefinitions(defArray); } /** * Implements a method in CommandLineModule * @return an array of argument definitions */ public CommandLineArgumentDefinition[] getBasicArgumentDefinitions() { CommandLineArgumentDefinition[] allArgDefs = getArgumentDefinitions(); Map<String, CommandLineArgumentDefinition> defMap = new HashMap<String, CommandLineArgumentDefinition>(); for (CommandLineArgumentDefinition argDef : allArgDefs) { if (!argDef.isAdvanced()) defMap.put(argDef.getArgumentName(), argDef); } return sortArgDefsForDisplay(defMap); } /** * Implements a method in CommandLineModule * @return an array of argument definitions */ public CommandLineArgumentDefinition[] getAdvancedArgumentDefinitions() { CommandLineArgumentDefinition[] allArgDefs = getArgumentDefinitions(); Map<String, CommandLineArgumentDefinition> defMap = new HashMap<String, CommandLineArgumentDefinition>(); for (CommandLineArgumentDefinition argDef : allArgDefs) { if (argDef.isAdvanced()) defMap.put(argDef.getArgumentName(), argDef); } return sortArgDefsForDisplay(defMap); } /** * Implements a method in CommandLineModule * @return an array of argument definitions */ public CommandLineArgumentDefinition[] getArgumentDefinitions() { if (mArgumentDefs == null) mArgumentDefs = new HashMap<String,CommandLineArgumentDefinition>(); return mArgumentDefs.values().toArray(new CommandLineArgumentDefinition[mArgumentDefs.size()]); } protected CommandLineArgumentDefinition[] sortArgDefsForDisplay(Map<String, CommandLineArgumentDefinition> defMap) { if (defMap == null) return new CommandLineArgumentDefinition[0]; List<String> mandatoryArgNames = new ArrayList<String>(); List<String> optionalArgNames = new ArrayList<String>(); CommandLineArgumentDefinition unnamedDef = null; for (CommandLineArgumentDefinition def : defMap.values()) { if (def.getArgumentName().equals(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_ARGUMENT) || def.getArgumentName().equals(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT)) { unnamedDef = def; } else if (def.isRequired()) mandatoryArgNames.add(def.getArgumentName()); else optionalArgNames.add(def.getArgumentName()); } CommandLineArgumentDefinition[] result = new CommandLineArgumentDefinition[defMap.size()]; int index = 0; String[] mandatoryArray = mandatoryArgNames.toArray(new String[mandatoryArgNames.size()]); String[] optionalArray = optionalArgNames.toArray(new String[optionalArgNames.size()]); Arrays.sort(mandatoryArray); Arrays.sort(optionalArray); if (unnamedDef != null) result[index++] = unnamedDef; for (String mandatoryArgName : mandatoryArray) result[index++] = getArgumentDefinition(mandatoryArgName); for (String optionalArgName : optionalArray) result[index++] = getArgumentDefinition(optionalArgName); return result; } /** * Return the array of argument definitions in the proper display order * @return */ public CommandLineArgumentDefinition[] getArgumentDefinitionsSortedForDisplay() { return sortArgDefsForDisplay(mArgumentDefs); } /** * Implements a method in CommandLineModule * @param argumentName * @return an argument matching the specified name, or null if not found */ public CommandLineArgumentDefinition getArgumentDefinition(String argumentName) { if (mArgumentDefs == null) mArgumentDefs = new HashMap<String,CommandLineArgumentDefinition>(); return mArgumentDefs.get(argumentName.toLowerCase()); } /** * process a bunch of arguments from the unnamed series definition, converting them * to a given argument type * @return */ protected Object[] getUnnamedSeriesArgumentValues() throws ArgumentValidationException { return (Object[]) getArgumentValue(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT); } /** * process an arguments from the unnamed definition, converting it * to a given argument type * @return */ protected Object getUnnamedArgumentValue() { return getArgumentValue(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_ARGUMENT); } /** * Convenience method for getting an array of files specified in an unnamed * series, since files are the things most commonly specified that way. * I do NOT recommend creating separate convenience methods for each type -- * that'd be bloat * @return * @throws ArgumentValidationException */ protected File[] getUnnamedSeriesFileArgumentValues() throws ArgumentValidationException { Object[] objectArray = getUnnamedSeriesArgumentValues(); if (objectArray == null) return null; File[] result = new File[objectArray.length]; for (int i=0; i<objectArray.length; i++) { result[i] = (File) objectArray[i]; FileToReadArgumentDefinition.checkFileForReading(result[i]); } return result; } /** * Digest raw string argument values. Make sure they're all there and convert properly * @param argumentValueMap * @throws ArgumentValidationException */ public void digestArguments(Map<String, String> argumentValueMap) throws ArgumentValidationException { mArgumentValueStrings = argumentValueMap; mArgumentValues = new HashMap<String, Object>(); //Capture only the args we care about for (CommandLineArgumentDefinition argDef : getArgumentDefinitions()) { String paramVal = argumentValueMap.get(argDef.getArgumentName().toLowerCase()); //If we're missing a required argument, warn and die if (paramVal == null) { //if not required, then whatever. If required, complain if (!argDef.isRequired()) continue; else { if (CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_ARGUMENT.equals(argDef.getArgumentName())) throw new ArgumentValidationException("Missing Required unnamed parameter for command " + getCommandName()); else if (CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT.equals(argDef.getArgumentName())) throw new ArgumentValidationException("Missing Required unnamed series of parameters for command " + getCommandName()); else throw new ArgumentValidationException("Missing Required parameter '" + argDef.getArgumentName() + "' for command " + getCommandName()); } } else { if (CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT.equals(argDef.getArgumentName())) { _log.debug("Unnamed series parameter value: \n" + paramVal); String[] unnamedParamStrings = paramVal.split(CommandLineModule.UNNAMED_ARG_SERIES_SEPARATOR); List<Object> unnamedParamList = new ArrayList<Object>(); _log.debug("Individual parameters:"); for (String unnamedParamString : unnamedParamStrings) { if (unnamedParamString != null && unnamedParamString.length() > 0) { unnamedParamList.add(argDef.convertArgumentValue(unnamedParamString)); _log.debug(unnamedParamList.get(unnamedParamList.size()-1)); } } mArgumentValues.put(argDef.getArgumentName(), unnamedParamList.toArray(new Object[unnamedParamList.size()])); } else { Object argValue = argDef.convertArgumentValue(paramVal); //System.err.println("adding arg: " + argDef.getArgumentName() + " = " + argValue); mArgumentValues.put(argDef.getArgumentName(), argValue); } } } assignArgumentValues(); } /** * Invoke the CommandLineModule, using the argument values specified in * the argumentValues array. Essentially this should just call digestArguments() * and then execute() * @param argumentValues */ public void invoke(Map<String, String> argumentValues) throws ArgumentValidationException, CommandLineModuleExecutionException { digestArguments(argumentValues); execute(); } /** * Asserts the presence of an argument. If absent, throws AVException. This is for * situations in which the presence of certain arguments makes other 'optional' arguments * mandatory * @param argumentName * @throws ArgumentValidationException */ protected void assertArgumentPresent(String argumentName) throws ArgumentValidationException { if (!hasArgumentValue(argumentName)) throw new ArgumentValidationException("The argument '" + argumentName + "' is required for the command usage you have attempted"); } /** * Asserts the presence of an argument. If absent, throws AVException. This is for * situations in which the presence of certain arguments makes other 'optional' arguments * mandatory. This version allows the user to specify which argument requires the other one * @throws ArgumentValidationException */ protected void assertArgumentPresent(String requiredArgumentName, String requiredByArgumentName) throws ArgumentValidationException { if (!hasArgumentValue(requiredArgumentName)) throw new ArgumentValidationException("The argument '" + requiredArgumentName + "' is required if the argument '" + requiredByArgumentName + "' is provided or has the provided value."); } /** * Asserts the absence of an argument. If presence, throws AVException. This is for * situations in which the presence of certain arguments makes other 'optional' arguments * meaningless * @param argumentName * @throws ArgumentValidationException */ protected void assertArgumentAbsent(String argumentName) throws ArgumentValidationException { if (hasArgumentValue(argumentName)) throw new ArgumentValidationException("The argument '" + argumentName + "' cannot be used with the other arguments you have provided"); } /** * Asserts the absence of an argument. If presence, throws AVException. This is for * situations in which the presence of certain arguments makes other 'optional' arguments * meaningless. This version allows the user to specify which argument made the arg * problematic * @param argumentName * @throws ArgumentValidationException */ protected void assertArgumentAbsent(String argumentName, String requiredAbsentByArgument) throws ArgumentValidationException { if (hasArgumentValue(argumentName)) throw new ArgumentValidationException("The argument '" + argumentName + "' cannot be used if the argument '" + requiredAbsentByArgument + "' is provided or has the provided value."); } /** * Convenience method to tell whether an argument map contains an argument * @param argumentName * @return */ protected boolean hasArgumentValue(String argumentName) { return mArgumentValues.containsKey(argumentName.toLowerCase()); } protected boolean hasUnnamedSeriesArgumentValue() { return hasArgumentValue(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT); } protected boolean hasUnnamedArgumentValue() { return hasArgumentValue(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_ARGUMENT); } //convenience methods for casting argument values protected String getStringArgumentValue(String argumentName) { return (String) getArgumentValue(argumentName.toLowerCase()); } protected List<String> getStringListArgumentValue(String argumentName) { return (List<String>) getArgumentValue(argumentName.toLowerCase()); } protected Double getDoubleArgumentValue(String argumentName) { return (Double) getArgumentValue(argumentName.toLowerCase()); } protected double[] getDoubleArrayArgumentValue(String argumentName) { return (double[]) getArgumentValue(argumentName.toLowerCase()); } protected Float getFloatArgumentValue(String argumentName) { Double doubleVal = getDoubleArgumentValue(argumentName); if (doubleVal == null) return null; return doubleVal.floatValue(); } protected Integer getIntegerArgumentValue(String argumentName) { return (Integer) getArgumentValue(argumentName.toLowerCase()); } protected boolean getBooleanArgumentValue(String argumentName) { return (Boolean) getArgumentValue(argumentName.toLowerCase()); } protected File getFileArgumentValue(String argumentName) { return (File) getArgumentValue(argumentName.toLowerCase()); } protected File[] getFileArrayArgumentValue(String argumentName) { return (File[]) getArgumentValue(argumentName.toLowerCase()); } protected File getUnnamedFileArgumentValue() { return getFileArgumentValue(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_ARGUMENT); } protected FeatureSet getUnnamedFeatureSetArgumentValue() { return (FeatureSet) getUnnamedArgumentValue(); } protected Protein[] getFastaFileArgumentValue(String argumentName) { return (Protein[]) getArgumentValue(argumentName.toLowerCase()); } protected DeltaMassArgumentDefinition.DeltaMassWithType getDeltaMassArgumentValue(String argumentName) { return (DeltaMassArgumentDefinition.DeltaMassWithType) getArgumentValue(argumentName.toLowerCase()); } protected MS2Modification[] getModificationListArgumentValue(String argumentName) { return (MS2Modification[]) getArgumentValue(argumentName.toLowerCase()); } protected Map<String, Object> getArgumentValues() { return mArgumentValues; } protected Object getArgumentValue(String argumentName) { Object result = mArgumentValues.get(argumentName); if (result == null) { if (mArgumentDefs.containsKey(argumentName)) result = getArgumentDefinition(argumentName).getDefaultValue(); else throw new RuntimeException("No such argument definition: " + argumentName); } return result; } protected FileToReadArgumentDefinition createUnnamedFileArgumentDefinition( boolean required, String helpText) { return new FileToReadArgumentDefinition(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_ARGUMENT, required, helpText); } /** * Cover method for ArgumentDefinitionFactory method * @param required * @param helpText * @return */ protected FileToReadArgumentDefinition createUnnamedSeriesFileArgumentDefinition( boolean required, String helpText) { return new FileToReadArgumentDefinition(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT, required, helpText); } /* * dhmay adding to centralize PrintWriter creation * Utility method: given an output filename, checks if null. * If not, returns a PrintWriter * on that file. If so, returns a PrintWriter on System.err * * This is purely a convenience method * * @param outFileName the output filename specified on the command line * @return an appropriate printwriter */ protected static PrintWriter getPrintWriter(File outFile) throws java.io.FileNotFoundException { PrintWriter pw = null; if (null != outFile) { try { pw = new PrintWriter(new FileOutputStream(outFile)); } catch (java.io.FileNotFoundException e) { ApplicationContext.infoMessage("Error creating PrintWriter for file " + outFile.getAbsolutePath()); throw e; } } else pw = new PrintWriter(System.out); return pw; } /** * Return the original argument name-value pairs that were passed into this module via digestArguments() * @return */ public Map<String, String> getArgumentValueStrings() { return mArgumentValueStrings; } }