/*
* This file is part of Alida, a Java library for
* Advanced Library for Integrated Development of Data Analysis Applications.
*
* Copyright (C) 2010 - @YEAR@
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Fore more information on Alida, visit
*
* http://www.informatik.uni-halle.de/alida/
*
*/
package de.unihalle.informatik.Alida.tools;
import de.unihalle.informatik.Alida.annotations.ALDAOperator;
import de.unihalle.informatik.Alida.annotations.Parameter;
import de.unihalle.informatik.Alida.annotations.indexing.SezPozAdapter;
import de.unihalle.informatik.Alida.dataio.ALDDataIOManagerCmdline;
import de.unihalle.informatik.Alida.dataio.provider.ALDDataIOCmdline;
import de.unihalle.informatik.Alida.dataio.provider.cmdline.ALDParametrizedClassDataIOCmdline;
import de.unihalle.informatik.Alida.exceptions.ALDDataIOManagerException;
import de.unihalle.informatik.Alida.exceptions.ALDDataIOProviderException;
import de.unihalle.informatik.Alida.exceptions.ALDException;
import de.unihalle.informatik.Alida.exceptions.ALDOperatorException;
import de.unihalle.informatik.Alida.helpers.ALDParser;
import de.unihalle.informatik.Alida.operator.ALDOpParameterDescriptor;
import de.unihalle.informatik.Alida.operator.ALDOperator;
import de.unihalle.informatik.Alida.operator.events.ALDOperatorExecutionProgressEvent;
import de.unihalle.informatik.Alida.operator.events.ALDOperatorExecutionProgressEventListener;
import net.java.sezpoz.Index;
import net.java.sezpoz.IndexItem;
import java.util.*;
import java.util.regex.*;
/**
* Generic commandline interface to run an Alida operator.
* <p>
* An operator needs to be annotated to allow execution mode CMDLINE.
* <p>
* Reading of IN and INOUT parameters and writing of OUT and INOUT parameters
* is accomplished using the interface
* {@link de.unihalle.informatik.Alida.dataio.provider.ALDDataIOCmdline}
* in conjunction with {@link ALDDataIOManagerCmdline}.
* For each parameter which should be read or written according to its
* annotated <code>direction</code>, a name=value pair has to be given as
* argument.
* <p>
* If the flag <code>--donotrun</code> or <code>-n</code> is given, only the
* parameters of the operator and there details will be printed but the
* operator is not invoked.
* <p>
* The flag <code>noDefaultHistory</code> specifies that not for each input
* parameter a history file should be read and associated with the parameter
* for all data types (irrespective whether the corresponding provider does so).
* Likewise this flags specifies if a history is to be written for all output
* parameters.
* <p>
* If the flag <code>--verbose</code> or <code>-v</code> the interface of the
* operator will be printed to stdout (and potentially further information).
* In addition the flag <code>--showProgress</code> or <code>-s</code> is
* available to switch on display of progress messages.
*
* @author Stefan Posch
*
*/
public class ALDOpRunner implements ALDOperatorExecutionProgressEventListener {
/** If true, the operator will not be invoked but only its
* interface printed to stdout.
*/
private boolean donotrun;
/**
* If true, history files are not read and written by default.
*/
private boolean noDefaultHistory = false;
/** If true, matching of operator name from commandline to the
* annoteated ALDOperators uses regular expression matching,
* otherwise exact substring matching
*/
protected boolean useRegEx;
/** verbose flag to outout additional information to stdout.
*/
private boolean verbose;
/**
* Flag to enable/disable display of progress events.
* <p>
* The flag is set to false by default and can be set to true using the
* command line parameters <code>--showProgress</code> or <code>-s</code>.
*/
private boolean showProgressEvents;
/** debug flag for additional debuging information.
*/
private boolean debug;
/** command line arguments.
*/
private String [] args;
/** The operator to be invoked.
*/
private ALDOperator op;
/** List of fully qualified class names of operators available
*/
protected LinkedList<String> matchingClassNames= null;
/** Hashmap to collect parameter name as given on command line and
* correspnding value string
*/
private HashMap<String,String> nameValueMap;
/** Hashmap to collect parameter name as given on command line and matching
* parameter name.
*/
private HashMap<String,String> nameParameterMap;
/** Print short usage to stderr.
*/
private static void printUsage() {
System.err.println( "Usage: ALDOpRunner " +
"[-u|--usage] " +
"[-v|--verbose] [-s|--showProgress] [-d|--debug]\n" +
"\t[-n|--donotrun] " +
"\t[-m|--modifyParameters] " +
"\t[--noDefaultHistory" +
"\t[-r|--useRegEx]\n" +
"\tAlidaOperatorClassname {parametername=valuestring}*"
);
}
/** Print verbose usage to stderr.
*/
private void printUsageVerbose() {
printUsage();
System.err.print( "\n" +
"-u or --usage prints this explanation\n" +
"-d or --debug debug information of internal processing is printed to stdout\n" +
"\n" +
"Generic commandline tools to execute an Alida operator.\n" +
"An Alida operator can only be executed if it supplies a public standard constructor.\n" +
"\n" +
"The operator is looked up with the following procedure:\n" +
"First annotated operators matching <AlidaOperatorClassname> are searched.\n" +
"If the option -r or --useRegEx is given <AlidaOperatorClassname> is interpreted as a Java regular expression.\n" +
"Otherwise an annotated operators with <AlidaOperatorClassname> as an exact exact is looked for.\n" +
"If this does not exist all annotated operators with <AlidaOperatorClassname> as an exact substring are looked up.\n" +
"If the latter results in more than one matching operator, operators with <AlidaOperatorClassname>\n" +
"as a suffix are found. If not exactly one match is found substring matches are retained.\n" +
"\n" +
"If the above procedure returns not exactly one matching annotated operator, a class\n" +
"with name <AlidaOperatorClassname> is instantiated as the\n" +
"operator of interest may not have been annotated\n" +
"or is still ambigous.\n" +
"If this fails the previous (maybe empty list) of operators is retained.\n" +
"\n" +
"If this procedure returns not exactly one operator, the (maybe empty list)\n" +
"is printed to stdout and ALDOpRunner stops execution\n" +
"\n" +
"Otherwise the operator is intantiated.\n +"
+ "If -n or --donotrun is given the interface is printed to stdout " +
"and the oprunner exits\n" +
"\n" +
"Subsequently the parameters given on the command line are validated agains the operators interface.\n" +
"For each parameter which should be read or written accoring to its\n" +
"annotated direction, a argument as name=value pair has to be specified.\n" +
"The parametername specified on the command line is interpreted as a prefix of\n" +
"a parameter of the interface of the operator.\n" +
"\n" +
"Subsequently the operator is execute unless the option -n and --donotrun are given.\n" +
"\n" +
"Examples\n" +
"\tPrint all annotated Alida operators:\n" +
"\t\tjava de/unihalle/informatik/Alida/tools/ALDOpRunner -r \'.*\'\n" +
"\tPrint all annotated Alida operators with suffix PDE:\n" +
"\t\tjava de/unihalle/informatik/Alida/tools/ALDOpRunner -r -n '.*PDE$'\n" +
"\tPrint interface of an operator:\n" +
"\t\tjava de/unihalle/informatik/Alida/tools/ALDOpRunner -v -n Dilate\n" +
"\tExecute ImgDilate:\n" +
"\t\tjava de/unihalle/informatik/Alida/tools/ALDOpRunner Dilate in=in.tif res=out.tif mask=3\n" +
""
);
}
/** Construct a <code>ALDOpRunner</code> using <code>args</code>
*
* @param arguments command line arguments
*/
public ALDOpRunner( String [] arguments) {
this.verbose = false;
this.showProgressEvents = false;
this.debug = false;
this.donotrun = false;
this.noDefaultHistory = false;
this.useRegEx = false;
this.args = arguments;
}
/** Main routine of <code>ALDOpRunner</code> , see usage.
*
* @param args command line arguments
*/
public static void main(String [] args) {
// init the SezPoz adapter properly
SezPozAdapter.initAdapter();
// start the application
ALDOpRunner runner = new ALDOpRunner( args);
runner.runIt( );
}
/** This method does the complete work to scan arguments, read and write parameters
* and <code>runOp</code> the operator.
*/
public void runIt() {
// retrieve command line options
int i = 0;
while ( i < this.args.length && this.args[i].charAt(0) == '-') {
if ( this.args[i].equals( "-u") || this.args[i].equals( "--usage") ||
this.args[i].equals( "-h") || this.args[i].equals( "--help") ) {
printUsageVerbose();
System.exit(0);
} else if ( this.args[i].equals( "-v") || this.args[i].equals( "--verbose") ) {
this.verbose = true;
} else if ( this.args[i].equals( "-s")
|| this.args[i].equals( "--showProgress") ) {
this.showProgressEvents = true;
} else if ( this.args[i].equals( "-d") || this.args[i].equals( "--debug") ) {
this.debug = true;
} else if ( this.args[i].equals( "-n") || this.args[i].equals( "--donotrun") ) {
this.donotrun = true;
} else if ( this.args[i].equals( "--noDefaultHistory") ) {
this.noDefaultHistory = true;
} else if ( this.args[i].equals( "-r") || this.args[i].equals( "--useRegEx") ) {
this.useRegEx = true;
} else {
System.err.println( "ERROR: unknown option " + this.args[i]);
System.err.println( "");
printUsage();
System.exit(2);
}
i++;
}
// at least one remaining arguments?
if ( i >= this.args.length ) {
printUsage();
System.exit(2);
}
// find the operator
findOperators( this.args[i]);
if ( this.matchingClassNames.size() == 0 ) {
System.err.println( "found no matching ALDOperator for <" + this.args[i] + ">" +
" using " + (this.useRegEx ? "" : "NOT") + " regular expressions");
System.exit(2);
} else if ( this.matchingClassNames.size() > 1 ) {
System.out.println( "found more then one matching ALDOperator for <" + this.args[i] + ">" +
" using " + (this.useRegEx ? "" : "NOT") + " regular expressions");
for ( String opName : this.matchingClassNames )
System.out.println( " " + opName);
System.exit(2);
}
// instantiate the operator
this.op = null;
String opName = this.matchingClassNames.peek();
if ( this.verbose )
System.out.println( "About to instantiate the ALDOperator <" + opName + ">");
this.op = getOperator( opName);
if ( this.verbose ) {
this.op.print();
}
// do the work
if ( ! this.donotrun ) {
int firstIndex = i+1;
// validate and get parameters which may modify parameter descriptors
// and set their values from the value string
validateParameternames( firstIndex, true);
readParameterValues( firstIndex, true);
if ( verbose )
this.op.printInterface();
boolean oldHistoryState = ALDDataIOManagerCmdline.getInstance().isDoHistory();
if ( ! this.noDefaultHistory ) {
ALDDataIOManagerCmdline.getInstance().setDoHistory(true);
}
// validate and get parameters all
// and set their values from the value string if not already done
// (in case the do modify parameter descriptors)
validateParameternames( firstIndex, false);
readParameterValues( firstIndex, false);
try {
if (this.showProgressEvents)
this.op.addOperatorExecutionProgressEventListener(this);
this.op.runOp();
if (this.showProgressEvents)
this.op.removeOperatorExecutionProgressEventListener(this);
} catch (Exception e) {
System.err.println( "ALDOpRunner failed to runOp the operator <" + this.op.getName() +
"> with exception " + e);
e.printStackTrace();
System.exit(2);
}
writeParameterValues( firstIndex);
ALDDataIOManagerCmdline.getInstance().setDoHistory(oldHistoryState);
} else {
this.op.printInterface();
}
}
/** Validate the <code>parametername=valuestring</code> pairs with respect to syntax
* and optionally validate the existence of the parameter in the operator interface.
* <p>
* If <code>modifyingParameters</code> is true, only parameters which indicate
* they might modifyParameterDescriptors are considered and not existing
* parameters are ignored.
* <p>
* The parameter names found and the corresponding value strings are collected
* in the hash maps <code>nameParameterMap</code> and <code>nameValueMap</code> respectively
* as a side effect.
*
* @param firstIndex index of first parametername in args
* @param modifyingParameters if true only parameters which indicate
* they might modifyParameterDescriptors are considered
*/
private void validateParameternames( int firstIndex, boolean modifyingParameters) {
this.nameParameterMap = new HashMap<String,String>();
this.nameValueMap = new HashMap<String,String>();
// first scan: validate parameter names
for ( int i = firstIndex ; i < this.args.length ; i++ ) {
ArrayList<String> parts = new ArrayList<String>(ALDParser.split( this.args[i].trim(), '='));
if ( parts.size() != 2 ) {
System.err.println("ERROR: found = sign " + (parts.size() -1)
+ " times, instead of once");
System.err.println( "");
printUsage();
System.exit(2);
}
String name = parts.get(0);
LinkedList<String> pNames = ALDParametrizedClassDataIOCmdline.lookupParameternames( this.op, name);
if ( pNames.size() > 1 ) {
System.err.print( "ERROR:found more than one matching parameter names for " + name + ": ");
for ( String pName : pNames ) {
System.err.print( pName + " ");
}
System.err.println();
this.op.printInterface();
System.exit(2);
} else if ( pNames.size() == 0 && ! modifyingParameters) {
System.err.println( "ERROR:found no matching parameter names for <" + name + ">\n");
this.op.printInterface() ;
System.exit(2);
}
if ( this.debug ) {
System.out.print( "Matching parameter names for " + name + ": ");
for ( String pName : pNames ) {
System.out.print( pName + " ");
}
System.out.println();
}
ALDOpParameterDescriptor descr = null;
if ( modifyingParameters) {
try {
descr = this.op.getParameterDescriptor(name);
} catch (ALDOperatorException e) {
// this parameter might be created setting a modifying parameter
}
}
if ( ! modifyingParameters ||
( descr != null
&& descr.parameterModificationMode()
!= Parameter.ParameterModificationMode.MODIFIES_NOTHING) )
this.nameParameterMap.put( parts.get(0), pNames.peek());
this.nameValueMap.put( parts.get(0), parts.get(1));
}
}
/** Read all IN and INOUT parameters specified on the command line.
*
* @param firstIndex index of first parametername in args
*/
private void readParameterValues( int firstIndex, boolean modifyingParameters) {
StringBuffer buf = new StringBuffer("{ ");
for ( int i = firstIndex ; i < this.args.length ; i++) {
LinkedList<String> parts = ALDParser.split( this.args[i], '=');
if ( parts.peek() != null ) {
try {
String expandedParameterName = this.nameParameterMap.get( parts.peek());
if ( expandedParameterName == null) {
continue;
}
ALDOpParameterDescriptor descr = this.op.getParameterDescriptor( expandedParameterName);
// modifying parameters have already been set thus skip them
if ( ! modifyingParameters &&
descr.parameterModificationMode()
!= Parameter.ParameterModificationMode.MODIFIES_NOTHING ) {
continue;
}
if ( descr.getDirection() == Parameter.Direction.IN ||
descr.getDirection() == Parameter.Direction.INOUT ) {
buf.append( this.args[i] + " , ");
}
} catch ( ALDOperatorException e) {
printException(e);
System.exit(1);
}
}
}
buf.delete( buf.length()-3, buf.length()).append("}");
try {
ALDParametrizedClassDataIOCmdline provider = (ALDParametrizedClassDataIOCmdline)ALDDataIOManagerCmdline.getInstance().getProvider(ALDOperator.class, ALDDataIOCmdline.class);
this.op = (ALDOperator) provider.parse(null, this.op.getClass(), new String( buf), this.op);
} catch (ALDDataIOManagerException e) {
printException(e);
System.exit(2);
} catch (ALDDataIOProviderException e) {
printException(e);
System.exit(2);
}
if ( this.op == null ) {
System.err.println( "ERROR: cannot configure operator, i.e. read parameters failed");
System.exit(2);
}
}
/** Write all OUT and INOUT parameters specified on the command line.
*
* @param firstIndex index of first parametername in args
*/
private void writeParameterValues( int firstIndex) {
for ( String pName : this.nameParameterMap.keySet() ) {
ALDOpParameterDescriptor descr = null;
Object value = null;
try {
descr = this.op.getParameterDescriptor( this.nameParameterMap.get( pName));
// this should never occur as we checked before
} catch (ALDOperatorException e) {
e.printStackTrace();
System.err.println( "ERROR: parametername <" + pName + "> not known by operator");
System.err.println( "");
printUsage();
System.exit(2);
}
String mappedPname = this.nameParameterMap.get( pName);
if ( mappedPname == null ) {
// this should never occur
System.err.println( "ERROR: parametername <" + pName + "> cannot be mapped");
System.exit(2);
}
try {
if ( descr.getDirection() == Parameter.Direction.OUT ||
descr.getDirection() == Parameter.Direction.INOUT ) {
value = this.op.getParameter( mappedPname);
String str = null;
if ( this.debug ) {
System.out.println( "Write parameter " + mappedPname + ", value = " + value);
}
if ( value != null ) {
try {
str = ALDDataIOManagerCmdline.getInstance().writeData( value, this.nameValueMap.get( pName));
if ( str != null ) {
System.out.println( this.nameParameterMap.get( pName) + " = " + str);
} else {
if ( verbose)
System.out.println( this.nameParameterMap.get( pName) + " written using " + this.nameValueMap.get( pName));
}
} catch (ALDDataIOManagerException e) {
printException(e);
} catch (ALDDataIOProviderException e) {
printException(e);
}
}
}
} catch (ALDOperatorException e) {
System.err.println( "ERROR: cannot get value for parametername <" + pName + "> mapped to <" + mappedPname + ">");
System.err.println( "");
printUsage();
System.exit(2);
}
}
}
/** This method is called to instantiate the ALDOperator.
* May be overrridden may extending classes.
*
*
*/
protected ALDOperator getOperator( String opName) {
try {
return (ALDOperator)(Class.forName( opName).newInstance());
} catch (Exception e) {
System.err.println( "ALDOpRunner failed to instantiate the operator <" + opName + ">");
System.exit(0);
}
return null;
}
/** This method is call once to populate the member <code>matchingClassNames</code>.
* May be overrridden may extending classes.
*
* @param opNamePattern string with pattern for operator name
*/
protected void findOperators(String opNamePattern) {
this.matchingClassNames = findALDOperators( opNamePattern);
}
/** Find operator with given pattern among all annotated ALDOperators
* and return the full qualified names as a list.
* Operators found depend on member variable useRegEx
* return only ALDOperators annotated with appropriate execution mode.
*
* @param opNamePattern string with pattern for operator name
*/
public LinkedList<String> findALDOperators(String opNamePattern) {
LinkedList<String> classNames = new LinkedList<String> ();
if ( this.debug )
System.out.println( "Looking up all annotated @Operators for <" + opNamePattern + ">");
Index<ALDAOperator, ALDOperator> indexItems =
SezPozAdapter.load(ALDAOperator.class, ALDOperator.class);
for ( final IndexItem<ALDAOperator,ALDOperator> item : indexItems ) {
if ( item.annotation().genericExecutionMode() == ALDAOperator.ExecutionMode.ALL ||
item.annotation().genericExecutionMode() == ALDAOperator.ExecutionMode.CMDLINE ) {
String className = item.className();
if ( !this.useRegEx && className.equals( opNamePattern) ) {
// found an exact match, keep it and we are done
classNames.clear();
classNames.add( className);
if ( this.debug )
System.out.println( "exact match: " + className);
break;
} else if ( ( this.useRegEx && Pattern.matches( opNamePattern, className) ) ||
(!this.useRegEx && isExactSubstring( opNamePattern, className) ) ) {
classNames.add( className);
if ( this.debug )
System.out.println( "match: " + className);
} else {
if ( this.debug )
System.out.println( "no match: " + className);
}
}
}
if ( this.debug ) {
System.out.println( "findOperators: annotated operators found");
for ( String className : classNames ) {
System.out.println( "\t" + className);
}
}
// if we do not use regex and have more than one match, try to disambiguate: require the
// given name to match a suffix of an annotated operator
if ( (! this.useRegEx ) && (classNames.size() > 1) ) {
LinkedList<String> classNamesSuffix = new LinkedList<String> ();
for ( String className : classNames ) {
if ( className.endsWith( opNamePattern) )
classNamesSuffix.add( className);
}
if ( classNamesSuffix.size() == 1 )
classNames = classNamesSuffix;
if ( this.debug ) {
System.out.println( "findOperators: suffix matches found");
for ( String className : classNames ) {
System.out.println( "\t" + className);
}
}
}
if ( this.debug ) {
System.out.println( "findOperators: returning");
for ( String className : classNames ) {
System.out.println( "\t" + className);
}
}
return classNames;
}
/** return true, if <code>substr</code> is a exact substring of <code>str</code>
*/
protected boolean isExactSubstring( String substr, String str) {
for ( int i=0 ; i < str.length() - substr.length() + 1 ; i++ ) {
if ( str.substring(i).startsWith( substr)) {
return true;
}
}
return false;
}
private void printException( ALDException e) {
System.err.println( "Exception of type " + e.getIdentString());
System.err.println( e.getCommentString());
}
/* (non-Javadoc)
* @see de.unihalle.informatik.Alida.operator.events.ALDOperatorExecutionProgressEventListener#handleOperatorExecutionProgressEvent(de.unihalle.informatik.Alida.operator.events.ALDOperatorExecutionProgressEvent)
*/
@Override
public void handleOperatorExecutionProgressEvent(
ALDOperatorExecutionProgressEvent e) {
// simply print the progress status to standard out
System.out.println(e.getExecutionProgressDescr());
}
}