From 62e7e467544eeb3db76c2f7fdd836c73adf17849 Mon Sep 17 00:00:00 2001 From: hanna Date: Wed, 6 May 2009 21:16:01 +0000 Subject: [PATCH] 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 --- .../utils/cmdLine/ArgumentDefinitions.java | 80 ++++++++++---- .../utils/cmdLine/CommandLineProgram.java | 3 +- .../sting/utils/cmdLine/HelpFormatter.java | 103 ++++++++++++++++-- .../sting/utils/cmdLine/ParsingEngine.java | 47 +++++--- 4 files changed, 188 insertions(+), 45 deletions(-) diff --git a/java/src/org/broadinstitute/sting/utils/cmdLine/ArgumentDefinitions.java b/java/src/org/broadinstitute/sting/utils/cmdLine/ArgumentDefinitions.java index 0176ea74f..542410868 100755 --- a/java/src/org/broadinstitute/sting/utils/cmdLine/ArgumentDefinitions.java +++ b/java/src/org/broadinstitute/sting/utils/cmdLine/ArgumentDefinitions.java @@ -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 { private Set argumentDefinitions = new HashSet(); /** - * 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 argumentDefinitionGroups = new HashSet(); + + /** + * 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 { return selectedArgumentDefinitions; } + /** + * Return a list of the available argument groups. + * @return All the argument groups that have been added. + */ + Collection getArgumentDefinitionGroups() { + return argumentDefinitionGroups; + } + /** * Iterates through all command-line arguments. - * @return + * @return an iterator over command-line arguments. */ public Iterator iterator() { return argumentDefinitions.iterator(); @@ -107,7 +121,7 @@ class ArgumentDefinitions implements Iterable { /** * 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 { /** * 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 { } }; - 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 { /** * 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 { }; } +/** + * A group of argument definitions. + */ +class ArgumentDefinitionGroup implements Iterable { + /** + * Name of this group. + */ + public final String groupName; + + /** + * The argument definitions associated with this group. + */ + public final Collection argumentDefinitions; + + public ArgumentDefinitionGroup( String groupName, Collection argumentDefinitions ) { + this.groupName = groupName; + this.argumentDefinitions = Collections.unmodifiableCollection( argumentDefinitions ); + } + + /** + * Iterate over the arguments in an argument definition group. + * @return + */ + public Iterator iterator() { + return argumentDefinitions.iterator(); + } +} + /** * A specific argument definition. Maps one-to-one with a field in some class. */ diff --git a/java/src/org/broadinstitute/sting/utils/cmdLine/CommandLineProgram.java b/java/src/org/broadinstitute/sting/utils/cmdLine/CommandLineProgram.java index d2b53cec6..5a216e72b 100644 --- a/java/src/org/broadinstitute/sting/utils/cmdLine/CommandLineProgram.java +++ b/java/src/org/broadinstitute/sting/utils/cmdLine/CommandLineProgram.java @@ -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(); } diff --git a/java/src/org/broadinstitute/sting/utils/cmdLine/HelpFormatter.java b/java/src/org/broadinstitute/sting/utils/cmdLine/HelpFormatter.java index 6da588684..8a6607543 100755 --- a/java/src/org/broadinstitute/sting/utils/cmdLine/HelpFormatter.java +++ b/java/src/org/broadinstitute/sting/utils/cmdLine/HelpFormatter.java @@ -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 mainArguments = getMainArguments( argumentDefinitions ); + Map> 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 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 sortedDefinitions = new TreeSet( 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> getPluginArguments( ArgumentDefinitions argumentDefinitions ) { + Map> pluginsByGroup = new HashMap>(); + 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 sortedDefinitions = new TreeSet( 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 mainArguments, Map> 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 argumentDefinitions = new ArrayList(); + 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 mainArguments, Map> 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 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 wordWrappedArgs = wordWrap( getArgumentCallingInfo(argumentDefinition), argWidth ).iterator(); - Iterator wordWrappedDoc = wordWrap( argumentDefinition.doc, docWidth ).iterator(); + Iterator 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 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 { + 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 ); + } + } } diff --git a/java/src/org/broadinstitute/sting/utils/cmdLine/ParsingEngine.java b/java/src/org/broadinstitute/sting/utils/cmdLine/ParsingEngine.java index 7ddfab1c0..8991e516c 100755 --- a/java/src/org/broadinstitute/sting/utils/cmdLine/ParsingEngine.java +++ b/java/src/org/broadinstitute/sting/utils/cmdLine/ParsingEngine.java @@ -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 argumentsFromSource = new ArrayList(); + 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 missingArguments ) { super( formatArguments(missingArguments) ); } @@ -467,17 +488,11 @@ class MissingArgumentException extends StingException { private static String formatArguments( Collection 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 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(); } }