/*
* 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.filehandler.TempFileManager;
import org.fhcrc.cpl.toolbox.commandline.arguments.CommandLineArgumentDefinition;
import org.fhcrc.cpl.toolbox.commandline.arguments.BooleanArgumentDefinition;
import org.fhcrc.cpl.toolbox.ApplicationContext;
import org.fhcrc.cpl.toolbox.TextProvider;
import org.apache.log4j.Logger;
import java.io.*;
import java.util.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
/**
* Utility class for commandline modules
*/
public class CommandLineModuleUtilities
{
public static Logger _log = Logger.getLogger(CommandLineModuleUtilities.class);
public static String createOutputFilename(String inputFileName, String newSuffix)
{
String outputPrefix = null;
if (inputFileName.contains("."))
outputPrefix = inputFileName.substring(0, inputFileName.indexOf("."));
else
outputPrefix = inputFileName;
return outputPrefix + "." + newSuffix;
}
public static File createOutputFile(File inputFile, String newSuffix, File outputDir)
{
String inputFileName = inputFile.getName();
return new File(outputDir, createOutputFilename(inputFileName, newSuffix));
}
public static File findFileLikeFile(File inputFile, File directory, String suffix)
throws FileNotFoundException
{
String inputFileName = inputFile.getName();
String inputFilePrefix = null;
try
{
inputFilePrefix = inputFileName.substring(0, inputFileName.indexOf("."));
_log.debug("Searching directory '" + directory.getName() + "' for file with prefix '" + inputFilePrefix +
"' and suffix '" + suffix + "'");
}
catch (StringIndexOutOfBoundsException e)
{
throw new FileNotFoundException("File with no extension cannot be matched with another file: " + inputFileName);
}
File result = findFileWithPrefix(inputFilePrefix, directory, suffix);
if (result == null)
throw new FileNotFoundException("Failed to find matching file for input file " +
inputFile.getAbsolutePath() + " in directory " + directory.getAbsolutePath());
return result;
}
public static File findFileWithPrefix(String prefix, File directory, String suffix)
throws FileNotFoundException
{
File result = null;
for (File potentialFile : directory.listFiles())
{
String potentialFileName = potentialFile.getName();
if (suffix != null && !potentialFileName.endsWith(suffix))
continue;
if (potentialFileName.startsWith(prefix))
{
result = potentialFile;
break;
}
}
if (result == null)
throw new FileNotFoundException("Failed to find matching file for prefix " +
prefix + " in directory " + directory.getAbsolutePath());
return result;
}
/**
*
* @param module
* @param exception
* @param isLogEnabled
* @param logFile
* @return
* @throws IOException
*/
public static File createFailureReport(CommandLineModule module, Exception exception,
boolean isLogEnabled, File logFile, String headerText)
throws IOException
{
File reportFile = TempFileManager.createTempFile("failure_" + module.getCommandName() + ".txt", module);
PrintWriter pw = new PrintWriter(reportFile);
pw.println(headerText);
pw.println("revision=" + ApplicationContext.getProperty("REVISION"));
DateFormat dateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
pw.println("date=" + dateFormat.format(new Date()));
pw.println("java_version=" + String.valueOf(System.getProperty("java.version")));
pw.println("free_heap=" + (Runtime.getRuntime().freeMemory() / 1024 / 1024) + "MB");
pw.println("total_current_heap=" + (Runtime.getRuntime().totalMemory() / 1024 / 1024) + "MB");
pw.println("max_heap=" + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "MB");
pw.println("OS=" + String.valueOf(System.getProperty("os.name")));
pw.println("command=" + module.getCommandName());
pw.println("arguments:");
pw.println("**********");
pw.println(createFailureReportEntry(module.getCommandName(), module.getArgumentValueStrings()));
pw.println("**********");
pw.flush();
pw.println("exception_message=" + exception.getMessage());
if (exception instanceof CommandLineModuleExecutionException)
{
Exception nestedException = ((CommandLineModuleExecutionException) exception).getNestedException();
pw.println("nested_exception_type=" + (nestedException == null? "null" : nestedException.getClass().getName()));
}
pw.println("stack_trace:");
exception.printStackTrace(pw);
pw.flush();
if (isLogEnabled)
{
pw.println("**********\nsession_log\n**********");
FileReader logReader = new FileReader(logFile);
BufferedReader br = new BufferedReader(logReader);
String line = null;
while ((line = br.readLine()) != null)
{
pw.println(line);
pw.flush();
}
}
pw.close();
return reportFile;
}
/**
* Create an entry in the failure report for an invocation of a command
* @param commandName
* @param argumentMap
* @return
*/
public static String createFailureReportEntry(String commandName,
Map<String,String> argumentMap)
{
StringBuffer resultBuf = new StringBuffer();
resultBuf.append(commandName + "\n");
for (String argumentName : argumentMap.keySet())
{
resultBuf.append("\t");
if (argumentName.equalsIgnoreCase(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT)
|| argumentName.equalsIgnoreCase(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_ARGUMENT))
{
//do not append anything for the argument name or equals sign
}
else
{
resultBuf.append(argumentName + "=");
}
resultBuf.append(argumentMap.get(argumentName) + "\n");
}
return resultBuf.toString();
}
public static String createFailureReportAndPrompt(CommandLineModule module,
Exception exception, boolean logEnabled, File logFile,
String additionalErrorText, String failureReportHeaderText)
{
String result = null;
try
{
File failureReportFile = CommandLineModuleUtilities.createFailureReport(module, exception,
logEnabled, logFile, failureReportHeaderText);
result = "\nA failure report has been generated in the file:\n " +
failureReportFile.getAbsolutePath() + "\n" + additionalErrorText + "\n";
if (!logEnabled)
result += "For more detailed logging of this failure, re-run your command from the command line \n" +
"with the '--log' option.";
}
catch (IOException e)
{
result = "Failed to create failure report!";
}
return result;
}
public static boolean isUnnamedSeriesArgument(CommandLineArgumentDefinition argDef)
{
return argDef.getArgumentName().equals(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT);
}
public static boolean isUnnamedArgument(CommandLineArgumentDefinition argDef)
{
return argDef.getArgumentName().equals(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_ARGUMENT);
}
/**
* Does this set of argument definitions allow the unnamed (single) argument?
* @return
*/
public static CommandLineArgumentDefinition getUnnamedArgumentDefinition(CommandLineModule module)
{
return module.getArgumentDefinition(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_ARGUMENT);
}
public static CommandLineArgumentDefinition getUnnamedArgumentSeriesDefinition(CommandLineModule module)
{
return module.getArgumentDefinition(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT);
}
/**
* Parse the raw arguments passed in by the user, in the context of the supplied module. Return a
* map from argument name to argument value. If undefined arguments are supplied, throw an
* IllegalArgumentException
* @param module
* @param args
* @return
* @throws IllegalArgumentException if undefined arguments are supplied
*/
public static Map<String,String> parseRawArguments(CommandLineModule module, String[] args)
throws IllegalArgumentException
{
HashMap<String, String> argNameValueMap = new HashMap<String, String>();
boolean alreadyProcessedNakedArgument = false;
List<String> unnamedArgumentSeriesList = new ArrayList<String>();
for (int i = 0; i < args.length; i++)
{
_log.debug("Processing argument: " + args[i]);
boolean ignoreThisArg = false;
String[] param = args[i].split("=");
//if there's something really funky with the argument, punt
if (param.length < 1 || param.length > 2)
{
throw new IllegalArgumentException("Unable to parse argument " + args[i]);
}
if (param.length == 1)
{
//an argument with no = sign. First check for any BooleanArgumentDefinitions
//that match the name
if (param[0].startsWith("--"))
{
CommandLineArgumentDefinition argDef =
module.getArgumentDefinition(param[0].substring(2).toLowerCase());
if (argDef != null)
{
//if it's a boolean argument definition, set value to true. Otherwise, bad
if (argDef instanceof BooleanArgumentDefinition)
{
param = new String[2];
param[0] = argDef.getArgumentName();
param[1] = "true";
}
else
{
//make translatable?
ApplicationContext.infoMessage("No argument value specified for argument " + argDef.getArgumentName());
}
}
}
// Check for short args (e.g. "-m0.75")
else if (param[0].startsWith("-"))
{
String paramName = param[0].substring(1,2);
String paramVal = param[0].substring(2);
// Command-line implementation explicitly ignores case. This can be deadly for
// short args, so currently disallow all upper-case args.
if (!paramName.equalsIgnoreCase(paramName))
{
ApplicationContext.infoMessage("Upper case short-form arguments are disallowed; found '" +
paramName + "'");
}
if ("".equals(paramVal))
paramVal = checkBooleanDefault(module, paramName);
if (null != paramVal)
{
param = new String[2];
param[0] = paramName;
param[1] = paramVal;
}
}
//Not an unnamed BooleanArgumentDefinition... try the naked argument
else
{
//if we've got an argument with no = sign, then check to see if this
//command allows that. If so, handle it that way.
if (CommandLineModuleUtilities.getUnnamedArgumentDefinition(module) != null)
{
//two naked arguments, die
if (alreadyProcessedNakedArgument)
{
throw new IllegalArgumentException(TextProvider.getText("UNKNOWN_PARAMETER_COLON_PARAM",
args[i]));
}
param = new String[2];
param[0] = CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_ARGUMENT;
param[1] = args[i];
alreadyProcessedNakedArgument = true;
}
//ok, maybe this module allows a series of unnamed arguments....
else if (CommandLineModuleUtilities.getUnnamedArgumentSeriesDefinition(module) != null)
{
unnamedArgumentSeriesList.add(args[i]);
ignoreThisArg = true;
}
else
{
throw new IllegalArgumentException(TextProvider.getText("UNKNOWN_PARAMETER_COLON_PARAM",
args[i]));
}
}
}
if (ignoreThisArg)
continue;
String paramName = param[0];
if (paramName.startsWith("--"))
paramName = paramName.substring(2);
String paramVal = null;
if (param.length > 1)
paramVal = param[1];
_log.debug("Got arg " + paramName + " = '" + paramVal + "'");
if (module.getArgumentDefinition(paramName) != null)
{
if (argNameValueMap.containsKey(paramName.toLowerCase()))
{
throw new IllegalArgumentException("Argument " + paramName +
" is specified more than once. Quitting.\n");
}
argNameValueMap.put(paramName.toLowerCase(), paramVal);
}
else
{
throw new IllegalArgumentException(TextProvider.getText("UNKNOWN_PARAMETER_COLON_PARAM",
paramName));
}
} // End of arg loop
if (CommandLineModuleUtilities.getUnnamedArgumentSeriesDefinition(module) != null &&
!unnamedArgumentSeriesList.isEmpty())
{
StringBuffer combinedUnnamedArguments = new StringBuffer();
boolean firstArg=true;
for (String unnamedArgumentValue : unnamedArgumentSeriesList)
{
//use '*ARGSEP*' as arg separator
if (!firstArg)
combinedUnnamedArguments.append(CommandLineModule.UNNAMED_ARG_SERIES_SEPARATOR);
combinedUnnamedArguments.append(unnamedArgumentValue);
firstArg = false;
}
argNameValueMap.put(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT,
combinedUnnamedArguments.toString());
}
return argNameValueMap;
}
/**
* Return the default value of a Boolean argument as a String
* @param module
* @param paramName
* @return
*/
private static String checkBooleanDefault(CommandLineModule module, String paramName)
{
CommandLineArgumentDefinition argDef =
module.getArgumentDefinition(paramName);
// Argument not defined
if (argDef == null)
return null;
// Not a boolean argument
if (!(argDef instanceof BooleanArgumentDefinition))
{
ApplicationContext.infoMessage("No argument value specified for argument " + argDef.getArgumentName());
return null;
}
return "true";
}
}