From 08ece8df79c8f23af2ed7a04f0e987692d23abb1 Mon Sep 17 00:00:00 2001 From: hanna Date: Thu, 26 Mar 2009 03:11:56 +0000 Subject: [PATCH] Bug fixes and support for mutually exclusive options. Still a bit rough, but will be easier to clean up after a walker refactoring. git-svn-id: file:///humgen/gsa-scr1/gsa-engineering/svn_contents/trunk@198 348d0f76-0448-11de-a6fe-93d51630548a --- .../sting/utils/cmdLine/Argument.java | 1 + .../sting/utils/cmdLine/ArgumentParser.java | 184 +++++++++++++----- 2 files changed, 136 insertions(+), 49 deletions(-) diff --git a/java/src/org/broadinstitute/sting/utils/cmdLine/Argument.java b/java/src/org/broadinstitute/sting/utils/cmdLine/Argument.java index 8f117b535..3a863c820 100755 --- a/java/src/org/broadinstitute/sting/utils/cmdLine/Argument.java +++ b/java/src/org/broadinstitute/sting/utils/cmdLine/Argument.java @@ -23,4 +23,5 @@ public @interface Argument { String shortName() default ""; String doc() default ""; boolean required() default true; + String exclusive() default ""; } diff --git a/java/src/org/broadinstitute/sting/utils/cmdLine/ArgumentParser.java b/java/src/org/broadinstitute/sting/utils/cmdLine/ArgumentParser.java index da39e3b37..5118b55f0 100644 --- a/java/src/org/broadinstitute/sting/utils/cmdLine/ArgumentParser.java +++ b/java/src/org/broadinstitute/sting/utils/cmdLine/ArgumentParser.java @@ -13,6 +13,9 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; +import java.util.HashSet; +import java.util.Set; import org.broadinstitute.sting.utils.Pair; @@ -34,9 +37,6 @@ public class ArgumentParser { // what program are we parsing for private String programName; - // our m_options - private ArrayList m_option_names = new ArrayList(); - // where we eventually want the values to land private HashMap> m_storageLocations = new HashMap>(); @@ -91,7 +91,7 @@ public class ArgumentParser { .create(letterform); // add it to the option - AddToOptionStorage(name, letterform, fieldname, opt); + AddToOptionStorage(opt, fieldname); } @@ -99,37 +99,50 @@ public class ArgumentParser { /** * Used locally to add to the options storage we have, for latter processing * - * @param name the name of the option - * @param letterform it's short form + * @param opt the option * @param fieldname what field it should be stuck into on the calling class - * @param opt the option */ - private void AddToOptionStorage(String name, String letterform, String fieldname, Option opt) { - AddToOptionStorage(name, letterform, getField(prog, fieldname), opt); + private void AddToOptionStorage(Option opt, String fieldname) { + AddToOptionStorage( opt, getField(prog, fieldname) ); } /** * Used locally to add to the options storage we have, for latter processing * - * @param name the name of the option - * @param letterform it's short form - * @param field what field it should be stuck into on the calling class * @param opt the option + * @param field what field it should be stuck into on the calling class */ - private void AddToOptionStorage(String name, String letterform, Pair field, Option opt) { + private void AddToOptionStorage(Option opt, Pair field ) { + // first check to see if we've already added an option with the same name + if (m_options.hasOption( opt.getOpt() )) + throw new IllegalArgumentException(opt.getOpt() + " was already added as an option"); + // add to the option list m_options.addOption(opt); - // first check to see if we've already added an option with the same name - if (m_option_names.contains(letterform)) { - throw new IllegalArgumentException(letterform + " was already added as an option"); + // add the object with it's name to the storage location + m_storageLocations.put( opt.getLongOpt(), field ); + } + + /** + * Used locally to add a group of mutually exclusive options to options storage. + * @param options A list of pairs of param, field to add. + */ + private void AddToOptionStorage( List>> options ) { + OptionGroup optionGroup = new OptionGroup(); + boolean isRequired = true; + + for( Pair> option: options ) { + if (m_options.hasOption(option.first.getOpt()) ) + throw new IllegalArgumentException(option.first.getOpt() + " was already added as an option"); + + optionGroup.addOption(option.first); + m_storageLocations.put( option.first.getLongOpt(), option.second ); + isRequired &= option.first.isRequired(); } - // add the object with it's name to the storage location - m_storageLocations.put( name, field ); - - // add to the list of m_options - m_option_names.add(letterform); + optionGroup.setRequired(isRequired); + m_options.addOptionGroup(optionGroup); } private Pair getField( Object obj, String fieldName ) { @@ -161,7 +174,7 @@ public class ArgumentParser { .create(letterform); // add it to the option - AddToOptionStorage(name, letterform, fieldname, opt); + AddToOptionStorage( opt, fieldname ); } @@ -182,7 +195,7 @@ public class ArgumentParser { .withDescription(description) .create(letterform); // add it to the option - AddToOptionStorage(name, letterform, fieldname, opt); + AddToOptionStorage( opt, fieldname ); } @@ -207,7 +220,7 @@ public class ArgumentParser { .withDescription("(Required Option) " + description) .create(letterform); // add it to the option - AddToOptionStorage(name, letterform, fieldname, opt); + AddToOptionStorage(opt, fieldname); } @@ -239,7 +252,7 @@ public class ArgumentParser { // add it to the option - AddToOptionStorage(name, letterform, fieldname, opt); + AddToOptionStorage( opt, fieldname ); } @@ -272,7 +285,7 @@ public class ArgumentParser { .create(letterform); // add it to the option - AddToOptionStorage(name, letterform, fieldname, opt); + AddToOptionStorage( opt, fieldname ); } @@ -333,39 +346,112 @@ public class ArgumentParser { /** * Extract arguments stored in annotations from fields of a given class. - * @param source + * @param source Source of arguments, probably provided through Argument annotation. */ public void addArgumentSource( Object source ) { Field[] fields = source.getClass().getFields(); + + for( Set optionGroup: groupExclusiveOptions(fields) ) { + List>> options = new ArrayList>>(); + for( Field field: optionGroup ) { + Argument argument = field.getAnnotation(Argument.class); + Option option = createOptionFromField( source, field, argument ); + options.add( new Pair>( option, new Pair( source,field) ) ); + } + + if( options.size() == 1 ) + AddToOptionStorage( options.get(0).first, new Pair( source, options.get(0).second.second ) ); + else { + AddToOptionStorage( options ); + } + } + } + + /** + * Group mutually exclusive options together into sets. Non-exclusive options + * will be alone a set. Every option should appear in exactly one set. + * WARNING: Has no concept of nested dependencies. + * @param fields list of fields for which to check options. + * @return + */ + private List> groupExclusiveOptions( Field[] fields ) { + List> optionGroups = new ArrayList>(); for(Field field: fields) { - Argument arg = field.getAnnotation(Argument.class); - if(arg == null) + Argument argument = field.getAnnotation(Argument.class); + if(argument == null) continue; - String fullName = (arg.fullName().length() != 0) ? arg.fullName() : field.getName().trim().toLowerCase(); - String shortName = (arg.shortName().length() != 0) ? arg.shortName() : fullName.substring(0,1); - if(shortName.length() != 1) - throw new IllegalArgumentException("Invalid short name: " + shortName); - boolean isFlag = (field.getType() == Boolean.class) || (field.getType() == Boolean.TYPE); - boolean isCollection = field.getType().isArray() || Collection.class.isAssignableFrom(field.getType()); + String[] exclusives = argument.exclusive().split(","); + if( exclusives.length != 0 ) { + HashSet matchingFields = new HashSet(); - if( isFlag && isCollection ) - throw new IllegalArgumentException("Can't have an array of flags."); + // Find the set of all options exclusive to this one. + matchingFields.add( field ); + for( Field candidate: fields ) { + for( String exclusive: exclusives ) { + if( candidate.getName().equals( exclusive.trim() ) ) + matchingFields.add( candidate ); + } + } - String description = arg.doc(); - if( arg.required() ) - description = (isFlag ? "(Required Flag) " : "(Required Option) ") + description; + // Perform a set intersection to see whether this set of fields intersects + // with any existing set. + // + // If so, add any additional elements to the list. + boolean setExists = false; + for( Set optionGroup: optionGroups ) { + Set working = new HashSet(optionGroup); + working.retainAll( matchingFields ); + if( working.size() > 0 ) { + optionGroup.addAll( matchingFields ); + setExists = true; + } + } - OptionBuilder ob = OptionBuilder.withLongOpt(fullName); - if( !isFlag ) ob = ob.withArgName(fullName); - ob = isCollection ? ob.hasArgs() : ob.hasArg(); - if( arg.required() ) ob = ob.isRequired(); - if( description.length() != 0 ) ob = ob.withDescription( description ); - - Option option = ob.create( shortName ); - - AddToOptionStorage(fullName, shortName, new Pair( source, field ), option ); + // Otherwise, add a new option group. + if( !setExists ) + optionGroups.add( matchingFields ); + } } + + return optionGroups; + } + + /** + * Given a field with some annotations, create a command-line option. If not enough data is + * available to create a command-line option, return null. + * @param source Source class containing the field. + * @param field Field + * @return Option representing the field options. + */ + private Option createOptionFromField( Object source, Field field, Argument argument ) { + + String fullName = (argument.fullName().length() != 0) ? argument.fullName() : field.getName().trim().toLowerCase(); + String shortName = (argument.shortName().length() != 0) ? argument.shortName() : fullName.substring(0,1); + if(shortName.length() != 1) + throw new IllegalArgumentException("Invalid short name: " + shortName); + boolean isFlag = (field.getType() == Boolean.class) || (field.getType() == Boolean.TYPE); + boolean isCollection = field.getType().isArray() || Collection.class.isAssignableFrom(field.getType()); + + if( isFlag && isCollection ) + throw new IllegalArgumentException("Can't have an array of flags."); + + String description = String.format("[%s] %s", source, argument.doc()); + + OptionBuilder ob = OptionBuilder.withLongOpt(fullName); + if( !isFlag ) { + ob = ob.withArgName(fullName); + ob = isCollection ? ob.hasArgs() : ob.hasArg(); + if( argument.required() ) { + ob = ob.isRequired(); + description = String.format("[%s] (Required Option) %s", source, argument.doc()); + } + } + if( description.length() != 0 ) ob = ob.withDescription( description ); + + Option option = ob.create( shortName ); + + return option; } private Object constructFromString(Field f, String[] strs) {