/* * 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.viewer; import org.fhcrc.cpl.toolbox.ApplicationContext; import org.fhcrc.cpl.toolbox.TextProvider; import org.fhcrc.cpl.toolbox.datastructure.Pair; import org.fhcrc.cpl.toolbox.commandline.CommandLineModuleExecutionException; import org.fhcrc.cpl.toolbox.commandline.arguments.ArgumentValidationException; import org.fhcrc.cpl.toolbox.commandline.arguments.CommandLineArgumentDefinition; import org.fhcrc.cpl.viewer.commandline.ViewerCommandLineModuleDiscoverer; import org.fhcrc.cpl.toolbox.commandline.CommandLineModule; import org.fhcrc.cpl.viewer.gui.WorkbenchFileChooser; import org.apache.log4j.Logger; import javax.swing.*; import java.awt.event.ActionEvent; import java.io.File; import java.io.BufferedReader; import java.io.FileReader; import java.util.Map; import java.util.HashMap; /** * Provides macro-running functionality in MSInspect. Knows how to read commands and * arguments form a macro file in the following format: * -Comment lines begin with # and are ignored * -Commands begin immediately on a given line. Anything other than a comment that begins * immediately at the start of a line is considered a command * -Arguments start with a tab character. Anything other than a comment that begins with * a tab is considered an argument. Arguments are of the format NAME=VALUE. For this reason, * '=' is not currently allowed in arguments. For unnamed arguments, simply us a tab * followed by the argument value * -Arguments for a given command should follow directly after that command * * All handling of individual macro commands is spelled out here. In general it's desirable * to have these macros follow the same code path as the UI, to the greatest extent possible */ public class CommandFileRunner { private static Logger _log = Logger.getLogger(CommandFileRunner.class); /** * Parse a macro file. For each command, with arguments, invoke one of the known * command handlers if there is one for this command * @param macroFile */ public static void runMacroFile(File macroFile) { if (macroFile == null || !macroFile.exists()) { ApplicationContext.infoMessage(TextProvider.getText( "FILE_FILE_DOES_NOT_EXIST", macroFile.getAbsolutePath())); return; } BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(macroFile)); } catch (Exception e) { e.printStackTrace(System.err); } String command = null; Map<String, CommandLineModule> moduleMap = ViewerCommandLineModuleDiscoverer.getSingletonInstance().findAllCommandLineModules(); //all macro command methods return true if success, false if failure. //If failure, stop executing further commands boolean keepGoing = true; while (keepGoing && (command = getNextCommand(reader)) != null) { if (command.toLowerCase().startsWith("echo") && (command.length() == "echo".length() || Character.isWhitespace(command.charAt("echo".length())))) { System.err.println(command.substring(Math.min("echo".length() + 1, command.length()))); continue; } command = command.toLowerCase(); CommandLineModule module = moduleMap.get(command); if (module == null) throw new RuntimeException("Unknown command " + command); Map<String,String> argNameValueMap = getArguments(reader, module); try { module.digestArguments(argNameValueMap); } catch (ArgumentValidationException e) { ApplicationContext.infoMessage(TextProvider.getText("FAILED_ARGUMENT_VALIDATION") + "\n" + TextProvider.getText("ERROR") + ": " + e.getMessage()); return; } try { module.execute(); ApplicationContext.infoMessage(TextProvider.getText("COMMAND_COMPLETE", module.getCommandName())); } catch (CommandLineModuleExecutionException e) { ApplicationContext.errorMessage(TextProvider.getText("ERROR_RUNNING_COMMAND_COMMAND", module.getCommandName()),e); } //short sleep just to space out commands, to allow for cleanup try { Thread.sleep(500); } catch (Exception e) {} } } /** * skips past any argument lines to the next command, in my funky file format * TODO: this should probably be reimplemented as an Iterator * @param reader * @return */ public static String getNextCommand(BufferedReader reader) { String result = null; try{ String line = null; while ((line = reader.readLine()) != null) { if (line.length() > 0 && !line.startsWith("\t") && !line.startsWith("#")) { result = line.trim(); break; } } } catch (Exception e) { e.printStackTrace(System.err); } return result; } /** * Gobbles a list of arguments, stopping when it hits eof or * a protein definition. If it hits a protein definition, it backs up * @param reader * @return */ public static Map<String,String> getArguments(BufferedReader reader, CommandLineModule module) { Map<String,String> result = new HashMap<String,String>(); try { String line = null; reader.mark(2000); while ((line = reader.readLine()) != null) { if (line.startsWith("\t")) { Pair<String,String> argument = processArgument(line, module); if (argument != null) result.put(argument.first, argument.second); reader.mark(2000); } else if (line.startsWith("#")) { continue; } else { //follow the mark back to after the last command block reader.reset(); break; } } } catch (Exception e) { e.printStackTrace(System.err); } return result; } /** * split an argument around an = sign * TODO: add error handling * @param argument * @return the argument name and value, or null if splitting around = gives something * other than two strings */ protected static Pair<String,String> processArgument(String argument, CommandLineModule module) { argument = argument.trim(); String[] param = argument.split("="); Pair<String,String> result = new Pair<String,String>("",""); if (param.length == 1) { if (module.getArgumentDefinition(CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT) != null) result.first = CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT; else result.first = CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_ARGUMENT; result.second = param[0]; } else if (param.length == 2) { result.first = param[0]; result.second = param[1]; } else return null; if (result.first.equals( CommandLineArgumentDefinition.UNNAMED_PARAMETER_VALUE_SERIES_ARGUMENT)) { _log.debug("Unnamed series. Before:\n" + result.second); result.second = result.second.trim(); result.second = result.second.replaceAll(" ", CommandLineModule.UNNAMED_ARG_SERIES_SEPARATOR); _log.debug("After:\n" + result.second); } return result; } /** * print out a generic error message */ protected static void macroError() { ApplicationContext.infoMessage("FAILED_TO_RUN_MACRO"); } /** * print out a missing-arguments error message */ protected static void macroMissingArguments() { ApplicationContext.infoMessage(TextProvider.getText("MACRO_COMMAND_MISSING_ARGUMENTS")); } /** * Utility method to create a command, for use in a command file, * based on a command name and argument map * @param commandName * @param argumentMap */ public static String createCommandFileEntry(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(); } /** * action for the menu item that kicks macro-running off */ public static class CommandFileRunnerAction extends AbstractAction { public void actionPerformed(ActionEvent event) { WorkbenchFileChooser chooser = new WorkbenchFileChooser(); int chooserStatus = chooser.showOpenDialog(ApplicationContext.getFrame()); //if user didn't hit OK, ignore if (chooserStatus != JFileChooser.APPROVE_OPTION) return; File file = chooser.getSelectedFile(); runMacroFile(file); } } }