Miscellaneous cleanup. Better display of help output. Better exception subtyping. More thought-out access routines.

git-svn-id: file:///humgen/gsa-scr1/gsa-engineering/svn_contents/trunk@608 348d0f76-0448-11de-a6fe-93d51630548a
This commit is contained in:
hanna 2009-05-06 21:16:01 +00:00
parent 5be75e0ae6
commit 62e7e46754
4 changed files with 188 additions and 45 deletions

View File

@ -9,6 +9,7 @@ import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Collections;
/**
* Created by IntelliJ IDEA.
@ -34,23 +35,28 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
private Set<ArgumentDefinition> argumentDefinitions = new HashSet<ArgumentDefinition>();
/**
* Adds an argument to the this argument definition list.
* @param argument The argument to add.
* @param sourceClass Class where the argument was defined.
* @param sourceField Field in which the argument was defined.
* The groupings of argument definitions. Used mainly for help output.
*/
public void add( Argument argument, Class sourceClass, Field sourceField ) {
ArgumentDefinition definition = new ArgumentDefinition( argument, sourceClass, sourceField );
if( definition.fullName.length() == 0 ) {
throw new IllegalArgumentException( "Argument cannot have 0-length fullname." );
private Set<ArgumentDefinitionGroup> argumentDefinitionGroups = new HashSet<ArgumentDefinitionGroup>();
/**
* Adds an argument to the this argument definition list.
* @param argumentDefinitionGroup The group of arguments to add.
*/
public void add( ArgumentDefinitionGroup argumentDefinitionGroup ) {
for( ArgumentDefinition definition: argumentDefinitionGroup ) {
// Do some basic validation before adding the definition.
if( definition.fullName.length() == 0 )
throw new IllegalArgumentException( "Argument cannot have 0-length fullname." );
if( hasArgumentDefinition( definition.fullName, FullNameDefinitionMatcher ) )
throw new StingException("Duplicate definition of argument with full name: " + definition.fullName);
if( hasArgumentDefinition( definition.shortName, ShortNameDefinitionMatcher ) )
throw new StingException("Duplicate definition of argument with short name: " + definition.shortName);
argumentDefinitions.add( definition );
}
if( hasArgumentDefinition( definition.fullName, FullNameDefinitionMatcher ) )
throw new StingException("Duplicate definition of argument with full name: " + definition.fullName);
if( hasArgumentDefinition( definition.shortName, ShortNameDefinitionMatcher ) )
throw new StingException("Duplicate definition of argument with short name: " + definition.shortName);
argumentDefinitions.add( definition );
argumentDefinitionGroups.add( argumentDefinitionGroup );
}
/**
@ -96,9 +102,17 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
return selectedArgumentDefinitions;
}
/**
* Return a list of the available argument groups.
* @return All the argument groups that have been added.
*/
Collection<ArgumentDefinitionGroup> getArgumentDefinitionGroups() {
return argumentDefinitionGroups;
}
/**
* Iterates through all command-line arguments.
* @return
* @return an iterator over command-line arguments.
*/
public Iterator<ArgumentDefinition> iterator() {
return argumentDefinitions.iterator();
@ -107,7 +121,7 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
/**
* Match the full name of a definition.
*/
public static DefinitionMatcher FullNameDefinitionMatcher = new DefinitionMatcher() {
static DefinitionMatcher FullNameDefinitionMatcher = new DefinitionMatcher() {
public boolean matches( ArgumentDefinition definition, Object key ) {
if( definition.fullName == null )
return key == null;
@ -119,7 +133,7 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
/**
* Match the short name of a definition.
*/
public static DefinitionMatcher ShortNameDefinitionMatcher = new DefinitionMatcher() {
static DefinitionMatcher ShortNameDefinitionMatcher = new DefinitionMatcher() {
public boolean matches( ArgumentDefinition definition, Object key ) {
if( definition.shortName == null )
return key == null;
@ -128,7 +142,7 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
}
};
public static AliasProvider ShortNameAliasProvider = new AliasProvider() {
static AliasProvider ShortNameAliasProvider = new AliasProvider() {
/**
* Short names can come in the form -Ofoo.txt, -O foo.txt, or -out (multi-character short name).
* Given the argument name and built-in provided, see if these can be formed into some other argument
@ -159,7 +173,7 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
/**
* Find all required definitions.
*/
public static DefinitionMatcher RequiredDefinitionMatcher = new DefinitionMatcher() {
static DefinitionMatcher RequiredDefinitionMatcher = new DefinitionMatcher() {
public boolean matches( ArgumentDefinition definition, Object key ) {
if( !(key instanceof Boolean) )
throw new IllegalArgumentException("RequiredDefinitionMatcher requires boolean key");
@ -168,6 +182,34 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
};
}
/**
* A group of argument definitions.
*/
class ArgumentDefinitionGroup implements Iterable<ArgumentDefinition> {
/**
* Name of this group.
*/
public final String groupName;
/**
* The argument definitions associated with this group.
*/
public final Collection<ArgumentDefinition> argumentDefinitions;
public ArgumentDefinitionGroup( String groupName, Collection<ArgumentDefinition> argumentDefinitions ) {
this.groupName = groupName;
this.argumentDefinitions = Collections.unmodifiableCollection( argumentDefinitions );
}
/**
* Iterate over the arguments in an argument definition group.
* @return
*/
public Iterator<ArgumentDefinition> iterator() {
return argumentDefinitions.iterator();
}
}
/**
* A specific argument definition. Maps one-to-one with a field in some class.
*/

View File

@ -73,7 +73,6 @@ public abstract class CommandLineProgram {
required=false)
protected Boolean debugMode = false;
/**
* our logging output patterns
*/
@ -145,7 +144,7 @@ public abstract class CommandLineProgram {
Class[] argumentSources = clp.getArgumentSources();
for( Class argumentSource: argumentSources )
parser.addArgumentSource( argumentSource );
parser.addArgumentSource( clp.getArgumentSourceName(argumentSource), argumentSource );
parser.parse(args);
parser.validate();
}

View File

@ -1,11 +1,20 @@
package org.broadinstitute.sting.utils.cmdLine;
import org.broadinstitute.sting.utils.StingException;
import java.util.Formatter;
import java.util.Locale;
import java.util.Formattable;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
import java.util.SortedSet;
import java.util.Collection;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
/**
@ -37,21 +46,67 @@ public class HelpFormatter {
* @param argumentDefinitions Argument definitions for which help should be printed.
*/
public void printHelp( ArgumentDefinitions argumentDefinitions ) {
System.out.printf("%s%n%n%s%n", getSynopsis(argumentDefinitions), getDetailed(argumentDefinitions) );
SortedSet<ArgumentDefinition> mainArguments = getMainArguments( argumentDefinitions );
Map<String,SortedSet<ArgumentDefinition>> pluginsByGroup = getPluginArguments( argumentDefinitions );
System.out.printf("%s%s%n",
getSynopsis(mainArguments,pluginsByGroup),
getDetailed(mainArguments,pluginsByGroup) );
}
/**
* Retrieve a sorted set of the command-line arguments for the main application from the ArgumentDefinitions object.
* @param argumentDefinitions Predefined argument definitions.
* @return A list of argument definitions.
*/
private SortedSet<ArgumentDefinition> getMainArguments( ArgumentDefinitions argumentDefinitions ) {
for( ArgumentDefinitionGroup argumentGroup: argumentDefinitions.getArgumentDefinitionGroups() ) {
// A null groupName is the signal that these are the application's main arguments.
if( argumentGroup.groupName == null ){
SortedSet<ArgumentDefinition> sortedDefinitions = new TreeSet<ArgumentDefinition>( new ArgumentDefinitionComparator() );
sortedDefinitions.addAll( argumentGroup.argumentDefinitions );
return sortedDefinitions;
}
}
throw new StingException( "Unable to retrieve main command-line arguments." );
}
/**
* Retrieve a sorted set of the command-line arguments for application plugin objects.
* @param argumentDefinitions Predefined argument definitions.
* @return A map of argument group name -> a list of argument definitions.
*/
private Map<String,SortedSet<ArgumentDefinition>> getPluginArguments( ArgumentDefinitions argumentDefinitions ) {
Map<String,SortedSet<ArgumentDefinition>> pluginsByGroup = new HashMap<String,SortedSet<ArgumentDefinition>>();
for( ArgumentDefinitionGroup argumentGroup: argumentDefinitions.getArgumentDefinitionGroups() ) {
// A null groupName is the signal that these are the application's main arguments.
if( argumentGroup.groupName == null )
continue;
SortedSet<ArgumentDefinition> sortedDefinitions = new TreeSet<ArgumentDefinition>( new ArgumentDefinitionComparator() );
sortedDefinitions.addAll( argumentGroup.argumentDefinitions );
pluginsByGroup.put( argumentGroup.groupName, sortedDefinitions );
}
return pluginsByGroup;
}
/**
* Gets the synopsis: the actual command to run.
* @param argumentDefinitions Argument definitions for which help should be printed.
* @param mainArguments Main program arguments.
* @praam pluginArgumentGroups Groups of plugin arguments
* @return A synopsis line.
*/
private String getSynopsis( ArgumentDefinitions argumentDefinitions ) {
private String getSynopsis( SortedSet<ArgumentDefinition> mainArguments, Map<String,SortedSet<ArgumentDefinition>> pluginArgumentGroups ) {
// Build out the synopsis all as one long line.
StringBuilder lineBuilder = new StringBuilder();
Formatter lineFormatter = new Formatter( lineBuilder );
lineFormatter.format("java -jar dist/GenomeAnalysisTK.jar");
List<ArgumentDefinition> argumentDefinitions = new ArrayList<ArgumentDefinition>();
argumentDefinitions.addAll( mainArguments );
for( SortedSet pluginArguments: pluginArgumentGroups.values() )
argumentDefinitions.addAll( pluginArguments );
for( ArgumentDefinition argumentDefinition: argumentDefinitions ) {
lineFormatter.format(" ");
if( !argumentDefinition.required ) lineFormatter.format("[");
@ -82,21 +137,34 @@ public class HelpFormatter {
/**
* Gets detailed output about each argument type.
* @param argumentDefinitions Argument definitions for which help should be printed.
* @param mainArguments Main program arguments.
* @param pluginArgumentGroups Groups of plugin arguments
* @return Detailed text about all arguments.
*/
private String getDetailed( ArgumentDefinitions argumentDefinitions ) {
private String getDetailed( SortedSet<ArgumentDefinition> mainArguments, Map<String,SortedSet<ArgumentDefinition>> pluginArgumentGroups ) {
StringBuilder builder = new StringBuilder();
builder.append( getDetailForGroup( mainArguments ) );
for( String groupName: pluginArgumentGroups.keySet() ) {
builder.append( String.format("Arguments for %s:%n", groupName ) );
builder.append( getDetailForGroup( pluginArgumentGroups.get(groupName) ) );
}
return builder.toString();
}
private String getDetailForGroup( SortedSet<ArgumentDefinition> argumentDefinitions ) {
StringBuilder builder = new StringBuilder();
Formatter formatter = new Formatter( builder );
// Try to fit the entire argument definition across the screen, but impose an arbitrary cap of 3/4 *
// LINE_WIDTH in case the length of the arguments gets out of control.
// LINE_WIDTH in case the length of the arguments gets out of control.
int argWidth = Math.min( findLongestArgumentCallingInfo(argumentDefinitions), (LINE_WIDTH*3)/4 - ARG_DOC_SEPARATION_WIDTH );
int docWidth = LINE_WIDTH - argWidth - ARG_DOC_SEPARATION_WIDTH;
for( ArgumentDefinition argumentDefinition: argumentDefinitions ) {
Iterator<String> wordWrappedArgs = wordWrap( getArgumentCallingInfo(argumentDefinition), argWidth ).iterator();
Iterator<String> wordWrappedDoc = wordWrap( argumentDefinition.doc, docWidth ).iterator();
Iterator<String> wordWrappedDoc = wordWrap( argumentDefinition.doc, docWidth ).iterator();
while( wordWrappedArgs.hasNext() || wordWrappedDoc.hasNext() ) {
String arg = wordWrappedArgs.hasNext() ? wordWrappedArgs.next() : "";
@ -135,7 +203,7 @@ public class HelpFormatter {
* @param argumentDefinitions argument definitions to inspect.
* @return longest argument length.
*/
private int findLongestArgumentCallingInfo( ArgumentDefinitions argumentDefinitions ) {
private int findLongestArgumentCallingInfo( Collection<ArgumentDefinition> argumentDefinitions ) {
int longest = 0;
for( ArgumentDefinition argumentDefinition: argumentDefinitions ) {
String argumentText = getArgumentCallingInfo( argumentDefinition );
@ -167,4 +235,23 @@ public class HelpFormatter {
}
return wrapped;
}
/**
* A comparator to reorder arguments in alphabetical order.
*/
private class ArgumentDefinitionComparator implements Comparator<ArgumentDefinition> {
public int compare( ArgumentDefinition lhs, ArgumentDefinition rhs ) {
if( lhs.shortName == null && rhs.shortName == null )
return lhs.fullName.compareTo( rhs.fullName );
// short names always come before long names.
else if ( lhs.shortName == null )
return -1;
else if ( rhs.shortName == null )
return 1;
else if ( lhs.shortName.equals(rhs.shortName) )
return lhs.fullName.compareTo( rhs.fullName );
else
return lhs.shortName.compareTo( rhs.shortName );
}
}
}

View File

@ -66,21 +66,33 @@ public class ParsingEngine {
}
/**
* Add an argument source. Argument sources are expected to have
* Add a main argument source. Argument sources are expected to have
* any number of fields with an @Argument annotation attached.
* @param source An argument source from which to extract
* command-line arguments.
* @param source An argument source from which to extract command-line arguments.
*/
public void addArgumentSource( Class source ) {
do {
addArgumentSource(null, source);
}
/**
* Add an argument source. Argument sources are expected to have
* any number of fields with an @Argument annotation attached.
* @param sourceName name for this argument source. 'Null' indicates that this source should be treated
* as the main module.
* @param source An argument source from which to extract command-line arguments.
*/
public void addArgumentSource( String sourceName, Class source ) {
Collection<ArgumentDefinition> argumentsFromSource = new ArrayList<ArgumentDefinition>();
while( source != null ) {
Field[] fields = source.getDeclaredFields();
for( Field field: fields ) {
Argument argument = field.getAnnotation(Argument.class);
if(argument != null)
argumentDefinitions.add( argument, source, field );
argumentsFromSource.add( new ArgumentDefinition(argument,source,field) );
}
source = source.getSuperclass();
} while( source != null );
}
argumentDefinitions.add( new ArgumentDefinitionGroup(sourceName, argumentsFromSource) );
}
/**
@ -456,10 +468,19 @@ public class ParsingEngine {
}
}
/**
* Generic class for handling misc parsing exceptions.
*/
class ParseException extends StingException {
public ParseException( String message ) {
super( message );
}
}
/**
* An exception indicating that some required arguments are missing.
*/
class MissingArgumentException extends StingException {
class MissingArgumentException extends ParseException {
public MissingArgumentException( Collection<ArgumentDefinition> missingArguments ) {
super( formatArguments(missingArguments) );
}
@ -467,17 +488,11 @@ class MissingArgumentException extends StingException {
private static String formatArguments( Collection<ArgumentDefinition> missingArguments ) {
StringBuilder sb = new StringBuilder();
for( ArgumentDefinition missingArgument: missingArguments )
sb.append( String.format("Argument with name '%s' is missing.", missingArgument.fullName) );
sb.append( String.format("Argument with name '%s' is missing.%n", missingArgument.fullName) );
return sb.toString();
}
}
class ParseException extends StingException {
public ParseException( String message ) {
super( message );
}
}
/**
* An exception for undefined arguments.
*/
@ -489,7 +504,7 @@ class InvalidArgumentException extends ParseException {
private static String formatArguments( Collection<ArgumentMatch> invalidArguments ) {
StringBuilder sb = new StringBuilder();
for( ArgumentMatch invalidArgument: invalidArguments )
sb.append( String.format("Argument with name '%s' isn't defined.", invalidArgument.label) );
sb.append( String.format("Argument with name '%s' isn't defined.%n", invalidArgument.label) );
return sb.toString();
}
}
@ -506,7 +521,7 @@ class InvalidArgumentValueException extends ParseException {
StringBuilder sb = new StringBuilder();
for( int index: invalidValues.indices.keySet() )
for( String value: invalidValues.indices.get(index) )
sb.append( String.format("Invalid argument value '%s' at position %d", value, index) );
sb.append( String.format("Invalid argument value '%s' at position %d.%n", value, index) );
return sb.toString();
}
}