Cleaning up, preparing to incorporate a better fix for Eric's problems with validation stringency in BAM files opened directly from the walkers.
git-svn-id: file:///humgen/gsa-scr1/gsa-engineering/svn_contents/trunk@1222 348d0f76-0448-11de-a6fe-93d51630548a
This commit is contained in:
parent
4c02607297
commit
b61f9af4d7
|
|
@ -2,7 +2,6 @@ package org.broadinstitute.sting.utils.cmdLine;
|
|||
|
||||
import org.broadinstitute.sting.utils.StingException;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collection;
|
||||
|
|
@ -262,42 +261,24 @@ class ArgumentDefinition {
|
|||
*/
|
||||
public final String validation;
|
||||
|
||||
public final Class sourceClass;
|
||||
public final Field sourceField;
|
||||
/**
|
||||
* The target into which to inject arguments meeting this definition.
|
||||
*/
|
||||
public final ArgumentSource source;
|
||||
|
||||
/**
|
||||
* Creates a new argument definition.
|
||||
* @param argument Attributes of the argument, read from the source field.
|
||||
* @param sourceClass Source class for the argument, provided to the ParsingEngine.
|
||||
* @param sourceField Source field for the argument, extracted from the sourceClass.
|
||||
* @param source Source information for defining the argument.
|
||||
*/
|
||||
public ArgumentDefinition( Argument argument, Class sourceClass, Field sourceField ) {
|
||||
this.sourceClass = sourceClass;
|
||||
this.sourceField = sourceField;
|
||||
public ArgumentDefinition( ArgumentSource source ) {
|
||||
this.source = source;
|
||||
|
||||
fullName = argument.fullName().trim().length() > 0 ? argument.fullName().trim() : sourceField.getName().toLowerCase();
|
||||
shortName = argument.shortName().trim().length() > 0 ? argument.shortName().trim() : null;
|
||||
doc = argument.doc();
|
||||
required = argument.required() && !isFlag();
|
||||
exclusiveOf = argument.exclusiveOf().trim().length() > 0 ? argument.exclusiveOf().trim() : null;
|
||||
validation = argument.validation().trim().length() > 0 ? argument.validation().trim() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can this argument support multiple values, or just one?
|
||||
* @return True if the argument supports multiple values.
|
||||
*/
|
||||
public boolean isMultiValued() {
|
||||
Class argumentType = sourceField.getType();
|
||||
return Collection.class.isAssignableFrom(argumentType) || sourceField.getType().isArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this argument a flag (meaning a boolean value whose presence indicates 'true').
|
||||
* @return True if this argument is a flag.
|
||||
*/
|
||||
public boolean isFlag() {
|
||||
return (sourceField.getType() == Boolean.class) || (sourceField.getType() == Boolean.TYPE);
|
||||
fullName = source.getFullName();
|
||||
shortName = source.getShortName();
|
||||
doc = source.getDoc();
|
||||
required = source.isRequired();
|
||||
exclusiveOf = source.getExclusiveOf();
|
||||
validation = source.getValidationRegex();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,25 +41,6 @@ public class ArgumentMatches implements Iterable<ArgumentMatch> {
|
|||
*/
|
||||
public ArgumentMatch MissingArgument = new ArgumentMatch();
|
||||
|
||||
void mergeInto( ArgumentMatch match ) {
|
||||
boolean definitionExists = false;
|
||||
|
||||
// Clone the list of argument matches to avoid ConcurrentModificationExceptions.
|
||||
for( ArgumentMatch argumentMatch: getUniqueMatches() ) {
|
||||
if( argumentMatch.definition == match.definition ) {
|
||||
argumentMatch.mergeInto( match );
|
||||
for( int index: match.indices.keySet() )
|
||||
argumentMatches.put( index, argumentMatch );
|
||||
definitionExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
if( !definitionExists ) {
|
||||
for( int index: match.indices.keySet() )
|
||||
argumentMatches.put( index, match );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an iterator cycling through *unique* command-line argument <-> definition matches.
|
||||
* @return Iterator over all argument matches.
|
||||
|
|
@ -89,14 +70,6 @@ public class ArgumentMatches implements Iterable<ArgumentMatch> {
|
|||
return argumentMatches.get(site);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines, of the argument matches by position, which are unique and returns that list.
|
||||
* @return A unique set of matches.
|
||||
*/
|
||||
private Set<ArgumentMatch> getUniqueMatches() {
|
||||
return new HashSet<ArgumentMatch>( argumentMatches.values() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the match collection have a match for this argument definition.
|
||||
* @param definition Definition to match.
|
||||
|
|
@ -106,6 +79,21 @@ public class ArgumentMatches implements Iterable<ArgumentMatch> {
|
|||
return findMatches( definition ).size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all argument matches of this source.
|
||||
* @param argumentSource Argument source to match.
|
||||
* @return List of all matches.
|
||||
*/
|
||||
|
||||
public Collection<ArgumentMatch> findMatches( ArgumentSource argumentSource ) {
|
||||
Collection<ArgumentMatch> matches = new HashSet<ArgumentMatch>();
|
||||
for( ArgumentMatch argumentMatch: getUniqueMatches() ) {
|
||||
if( argumentMatch.definition != null && argumentMatch.definition.source.equals( argumentSource ) )
|
||||
matches.add( argumentMatch );
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all argument matches of this definition.
|
||||
* @param definition Argument definition to match.
|
||||
|
|
@ -131,6 +119,52 @@ public class ArgumentMatches implements Iterable<ArgumentMatch> {
|
|||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find arguments that are unmatched to any definition.
|
||||
* @return Set of matches that have no associated definition.
|
||||
*/
|
||||
public Collection<ArgumentMatch> findUnmatched() {
|
||||
Collection<ArgumentMatch> matches = new HashSet<ArgumentMatch>();
|
||||
for( ArgumentMatch argumentMatch: getUniqueMatches() ) {
|
||||
if( argumentMatch.definition == null )
|
||||
matches.add( argumentMatch );
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the given argument match into the set of existing argument matches.
|
||||
* If multiple arguments are present, those arguments will end up grouped.
|
||||
* @param match The match to merge into.
|
||||
* @return The merged match.
|
||||
*/
|
||||
void mergeInto( ArgumentMatch match ) {
|
||||
boolean definitionExists = false;
|
||||
|
||||
// Clone the list of argument matches to avoid ConcurrentModificationExceptions.
|
||||
for( ArgumentMatch argumentMatch: getUniqueMatches() ) {
|
||||
if( argumentMatch.definition == match.definition ) {
|
||||
argumentMatch.mergeInto( match );
|
||||
for( int index: match.indices.keySet() )
|
||||
argumentMatches.put( index, argumentMatch );
|
||||
definitionExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
if( !definitionExists ) {
|
||||
for( int index: match.indices.keySet() )
|
||||
argumentMatches.put( index, match );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines, of the argument matches by position, which are unique and returns that list.
|
||||
* @return A unique set of matches.
|
||||
*/
|
||||
private Set<ArgumentMatch> getUniqueMatches() {
|
||||
return new HashSet<ArgumentMatch>( argumentMatches.values() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -215,6 +249,6 @@ class ArgumentMatch {
|
|||
* @return True if definition is known to be a flag; false if not known to be a flag.
|
||||
*/
|
||||
private boolean isArgumentFlag() {
|
||||
return definition != null && definition.isFlag();
|
||||
return definition != null && definition.source.isFlag();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright (c) 2009 The Broad Institute
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.broadinstitute.sting.utils.cmdLine;
|
||||
|
||||
import org.broadinstitute.sting.utils.StingException;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Describes the source field which defines a command-line argument.
|
||||
* A parsed-object version of the command-line argument will be
|
||||
* injected into an object containing this field.
|
||||
*
|
||||
* @author mhanna
|
||||
* @version 0.1
|
||||
*/
|
||||
public class ArgumentSource {
|
||||
/**
|
||||
* Class to which the field belongs.
|
||||
*/
|
||||
public final Class clazz;
|
||||
|
||||
/**
|
||||
* Field into which to inject command-line arguments.
|
||||
*/
|
||||
public final Field field;
|
||||
|
||||
/**
|
||||
* Descriptor for the argument. Contains name, validation info, etc.
|
||||
*/
|
||||
public final Argument descriptor;
|
||||
|
||||
/**
|
||||
* Create a new command-line argument target.
|
||||
* @param clazz Class containing the argument.
|
||||
* @param field Field containing the argument. Field must be annotated with 'Argument'.
|
||||
*/
|
||||
public ArgumentSource( Class clazz, Field field ) {
|
||||
this.clazz = clazz;
|
||||
this.field = field;
|
||||
this.descriptor = field.getAnnotation(Argument.class);
|
||||
if( descriptor == null )
|
||||
throw new StingException("Cannot build out a command-line argument without a descriptor.");
|
||||
}
|
||||
|
||||
/**
|
||||
* True if this argument source equals other.
|
||||
* @param other Another object, possibly an argument source, to test for equality. Any object can
|
||||
* be tested, but only instances of ArgumentSource will result in equals returning true.
|
||||
* @return True if this argument source matches other. False otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals( Object other ) {
|
||||
if( other == null )
|
||||
return false;
|
||||
if( !(other instanceof ArgumentSource) )
|
||||
return false;
|
||||
|
||||
ArgumentSource otherArgumentSource = (ArgumentSource)other;
|
||||
return this.clazz.equals(otherArgumentSource.clazz) && this.field.equals(otherArgumentSource.field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an appropriate hash code for this argument source.
|
||||
* @return A uniformly distributed hashcode representing this argument source.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return clazz.hashCode() ^ field.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the full name of the argument, specifiable with the '--' prefix. The full name can be
|
||||
* either specified explicitly with the fullName annotation parameter or implied by the field name.
|
||||
* @return full name of the argument. Never null.
|
||||
*/
|
||||
public String getFullName() {
|
||||
return descriptor.fullName().trim().length() > 0 ? descriptor.fullName().trim() : field.getName().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the short name of the argument, specifiable with the '-' prefix. The short name can
|
||||
* be specified or not; if left unspecified, no short name will be present.
|
||||
* @return short name of the argument. Null if no short name exists.
|
||||
*/
|
||||
public String getShortName() {
|
||||
return descriptor.shortName().trim().length() > 0 ? descriptor.shortName().trim() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Documentation for this argument. Mandatory field.
|
||||
* @return Documentation for this argument.
|
||||
*/
|
||||
public String getDoc() {
|
||||
return descriptor.doc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this field is required. Note that flag fields are always forced to 'not required'.
|
||||
* @return True if the field is mandatory and not a boolean flag. False otherwise.
|
||||
*/
|
||||
public boolean isRequired() {
|
||||
return descriptor.required() && !isFlag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies other arguments which cannot be used in conjunction with tihs argument. Comma-separated list.
|
||||
* @return A comma-separated list of exclusive arguments, or null if none are present.
|
||||
*/
|
||||
public String getExclusiveOf() {
|
||||
return descriptor.exclusiveOf().trim().length() > 0 ? descriptor.exclusiveOf().trim() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A regular expression which can be used for validation.
|
||||
* @return a JVM regex-compatible regular expression, or null to permit any possible value.
|
||||
*/
|
||||
public String getValidationRegex() {
|
||||
return descriptor.validation().trim().length() > 0 ? descriptor.validation().trim() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the field in the passed object to <code>value</code>
|
||||
* @param targetInstance Instance in which to find the field.
|
||||
* @param value Value to which to set the field.
|
||||
*/
|
||||
public void setValue( Object targetInstance, Object value ) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
field.set(targetInstance, value);
|
||||
}
|
||||
catch( IllegalAccessException ex ) {
|
||||
//logger.fatal("processArgs: cannot convert field " + field.toString());
|
||||
throw new StingException("processArgs: Failed conversion " + ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the argument is a flag (a 0-valued argument).
|
||||
* @return True if argument is a flag; false otherwise.
|
||||
*/
|
||||
public boolean isFlag() {
|
||||
return (field.getType() == Boolean.class) || (field.getType() == Boolean.TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can this argument support multiple values, or just one?
|
||||
* @return True if the argument supports multiple values.
|
||||
*/
|
||||
public boolean isMultiValued() {
|
||||
Class argumentType = field.getType();
|
||||
return Collection.class.isAssignableFrom(argumentType) || field.getType().isArray();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* Copyright (c) 2009 The Broad Institute
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.broadinstitute.sting.utils.cmdLine;
|
||||
|
||||
import org.broadinstitute.sting.utils.StingException;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* An factory capable of providing parsers that can parse any type
|
||||
* of supported command-line argument.
|
||||
*
|
||||
* @author mhanna
|
||||
* @version 0.1
|
||||
*/
|
||||
public abstract class FieldParser {
|
||||
|
||||
/**
|
||||
* our log, which we want to capture anything from org.broadinstitute.sting
|
||||
*/
|
||||
protected static Logger logger = Logger.getLogger(FieldParser.class);
|
||||
|
||||
/**
|
||||
* Name of the field which should be parsed by this argument.
|
||||
*/
|
||||
protected final String fieldName;
|
||||
|
||||
public static FieldParser create( Field field ) {
|
||||
Class type = field.getType();
|
||||
|
||||
if( Collection.class.isAssignableFrom(type) || type.isArray() )
|
||||
return new JRECompoundFieldParser( field );
|
||||
else
|
||||
return new JRESimpleFieldParser( field );
|
||||
}
|
||||
|
||||
static FieldParser create( String fieldName, Class type ) {
|
||||
if( Collection.class.isAssignableFrom(type) || type.isArray() )
|
||||
return new JRECompoundFieldParser( fieldName, type );
|
||||
else
|
||||
return new JRESimpleFieldParser( fieldName, type );
|
||||
}
|
||||
|
||||
protected FieldParser( Field field ) {
|
||||
fieldName = field.toString();
|
||||
}
|
||||
|
||||
protected FieldParser( String fieldName ) {
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
public abstract Object parse( String... values );
|
||||
}
|
||||
|
||||
class JRESimpleFieldParser extends FieldParser {
|
||||
private final Class type;
|
||||
|
||||
public JRESimpleFieldParser( Field field ) {
|
||||
super( field );
|
||||
this.type = field.getType();
|
||||
}
|
||||
|
||||
public JRESimpleFieldParser( String fieldName, Class type ) {
|
||||
super( fieldName );
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Object parse( String... values ) {
|
||||
if( values.length > 1 )
|
||||
throw new StingException("Simple argument parser is unable to parse multiple arguments.");
|
||||
|
||||
String value = values[0];
|
||||
|
||||
// lets go through the types we support
|
||||
try {
|
||||
if (type.isPrimitive()) {
|
||||
Method valueOf = primitiveToWrapperMap.get(type).getMethod("valueOf",String.class);
|
||||
return valueOf.invoke(null,value.trim());
|
||||
} else if (type.isEnum()) {
|
||||
return Enum.valueOf(type,value.toUpperCase().trim());
|
||||
} else {
|
||||
Constructor ctor = type.getConstructor(String.class);
|
||||
return ctor.newInstance(value);
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
logger.fatal("ArgumentParser: NoSuchMethodException: cannot convert field " + fieldName);
|
||||
throw new StingException("constructFromString:NoSuchMethodException: Failed conversion " + e.getMessage());
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.fatal("ArgumentParser: IllegalAccessException: cannot convert field " + fieldName);
|
||||
throw new StingException("constructFromString:IllegalAccessException: Failed conversion " + e.getMessage());
|
||||
} catch (InvocationTargetException e) {
|
||||
logger.fatal("ArgumentParser: InvocationTargetException: cannot convert field " + fieldName);
|
||||
throw new StingException("constructFromString:InvocationTargetException: Failed conversion " + e.getMessage());
|
||||
} catch (InstantiationException e) {
|
||||
logger.fatal("ArgumentParser: InstantiationException: cannot convert field " + fieldName);
|
||||
throw new StingException("constructFromString:InstantiationException: Failed conversion " + e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapping of the primitive types to their associated wrapper classes. Is there really no way to infer
|
||||
* this association available in the JRE?
|
||||
*/
|
||||
private static Map<Class,Class> primitiveToWrapperMap = new HashMap<Class,Class>() {
|
||||
{
|
||||
put( Boolean.TYPE, Boolean.class );
|
||||
put( Character.TYPE, Character.class );
|
||||
put( Byte.TYPE, Byte.class );
|
||||
put( Short.TYPE, Short.class );
|
||||
put( Integer.TYPE, Integer.class );
|
||||
put( Long.TYPE, Long.class );
|
||||
put( Float.TYPE, Float.class );
|
||||
put( Double.TYPE, Double.class );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class JRECompoundFieldParser extends FieldParser {
|
||||
private final Class type;
|
||||
private final Class componentType;
|
||||
private final FieldParser componentArgumentParser;
|
||||
|
||||
public JRECompoundFieldParser( Field field ) {
|
||||
super( field );
|
||||
Class candidateType = field.getType();
|
||||
|
||||
if( Collection.class.isAssignableFrom(candidateType) ) {
|
||||
|
||||
// If this is a generic interface, pick a concrete implementation to create and pass back.
|
||||
// Because of type erasure, don't worry about creating one of exactly the correct type.
|
||||
if( Modifier.isInterface(candidateType.getModifiers()) || Modifier.isAbstract(candidateType.getModifiers()) )
|
||||
{
|
||||
if( java.util.List.class.isAssignableFrom(candidateType) ) candidateType = ArrayList.class;
|
||||
else if( java.util.Queue.class.isAssignableFrom(candidateType) ) candidateType = java.util.ArrayDeque.class;
|
||||
else if( java.util.Set.class.isAssignableFrom(candidateType) ) candidateType = java.util.TreeSet.class;
|
||||
}
|
||||
|
||||
this.type = candidateType;
|
||||
|
||||
// If this is a parameterized collection, find the contained type. If blow up if only one type exists.
|
||||
if( field.getGenericType() instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType)field.getGenericType();
|
||||
if( parameterizedType.getActualTypeArguments().length > 1 )
|
||||
throw new IllegalArgumentException("Unable to determine collection type of field: " + field.toString());
|
||||
componentType = (Class)parameterizedType.getActualTypeArguments()[0];
|
||||
}
|
||||
else
|
||||
componentType = String.class;
|
||||
}
|
||||
else if( candidateType.isArray() ) {
|
||||
this.type = candidateType;
|
||||
this.componentType = candidateType.getComponentType();
|
||||
}
|
||||
else
|
||||
throw new StingException("Unsupported compound argument type: " + candidateType);
|
||||
|
||||
componentArgumentParser = FieldParser.create( fieldName, componentType );
|
||||
}
|
||||
|
||||
public JRECompoundFieldParser( String fieldName, Class type ) {
|
||||
super(fieldName);
|
||||
|
||||
this.type = type;
|
||||
|
||||
if( Collection.class.isAssignableFrom(type) ) {
|
||||
this.componentType = String.class;
|
||||
}
|
||||
else if( type.isArray() ) {
|
||||
this.componentType = type.getComponentType();
|
||||
}
|
||||
else
|
||||
throw new StingException("Unsupported compound argument type: " + type);
|
||||
|
||||
componentArgumentParser = FieldParser.create( fieldName, componentType );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parse( String... values )
|
||||
{
|
||||
if( Collection.class.isAssignableFrom(type) ) {
|
||||
Collection collection = null;
|
||||
try {
|
||||
collection = (Collection)type.newInstance();
|
||||
}
|
||||
catch (InstantiationException e) {
|
||||
logger.fatal("ArgumentParser: InstantiationException: cannot convert field " + fieldName);
|
||||
throw new StingException("constructFromString:InstantiationException: Failed conversion " + e.getMessage());
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
logger.fatal("ArgumentParser: IllegalAccessException: cannot convert field " + fieldName);
|
||||
throw new StingException("constructFromString:IllegalAccessException: Failed conversion " + e.getMessage());
|
||||
}
|
||||
|
||||
for( String value: values )
|
||||
collection.add( componentArgumentParser.parse(value) );
|
||||
|
||||
return collection;
|
||||
}
|
||||
else if( type.isArray() ) {
|
||||
Object arr = Array.newInstance(componentType,values.length);
|
||||
|
||||
for( int i = 0; i < values.length; i++ )
|
||||
Array.set( arr,i,componentArgumentParser.parse(values[i]));
|
||||
|
||||
return arr;
|
||||
}
|
||||
else
|
||||
throw new StingException("Unsupported compound argument type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ public class HelpFormatter {
|
|||
lineFormatter.format("-%s", argumentDefinition.shortName);
|
||||
else
|
||||
lineFormatter.format("--%s", argumentDefinition.fullName);
|
||||
if( !argumentDefinition.isFlag() )
|
||||
if( !argumentDefinition.source.isFlag() )
|
||||
lineFormatter.format(" <%s>", argumentDefinition.fullName);
|
||||
if( !argumentDefinition.required ) lineFormatter.format("]");
|
||||
}
|
||||
|
|
@ -146,7 +146,7 @@ public class HelpFormatter {
|
|||
if( argumentDefinition.shortName != null )
|
||||
formatter.format("-%s,", argumentDefinition.shortName);
|
||||
formatter.format("--%s", argumentDefinition.fullName);
|
||||
if( !argumentDefinition.isFlag() )
|
||||
if( !argumentDefinition.source.isFlag() )
|
||||
formatter.format(" <%s>", argumentDefinition.fullName);
|
||||
|
||||
return builder.toString();
|
||||
|
|
|
|||
|
|
@ -4,23 +4,8 @@ import org.broadinstitute.sting.utils.StingException;
|
|||
import org.broadinstitute.sting.utils.Pair;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.io.File;
|
||||
|
||||
import net.sf.samtools.SAMFileReader;
|
||||
import net.sf.samtools.SAMFileWriter;
|
||||
import net.sf.samtools.SAMFileWriterFactory;
|
||||
import net.sf.samtools.SAMFileHeader;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Created by IntelliJ IDEA.
|
||||
|
|
@ -40,6 +25,11 @@ import net.sf.samtools.SAMFileHeader;
|
|||
* A parser for Sting command-line arguments.
|
||||
*/
|
||||
public class ParsingEngine {
|
||||
/**
|
||||
* A collection of all the source fields which define command-line arguments.
|
||||
*/
|
||||
List<ArgumentSource> argumentSources = new ArrayList<ArgumentSource>();
|
||||
|
||||
/**
|
||||
* A list of defined arguments against which command lines are matched.
|
||||
* Package protected for testing access.
|
||||
|
|
@ -90,22 +80,12 @@ public class ParsingEngine {
|
|||
* 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.
|
||||
* @param sourceClass A class containing argument sources from which to extract command-line arguments.
|
||||
*/
|
||||
public void addArgumentSource( String sourceName, Class source ) {
|
||||
public void addArgumentSource( String sourceName, Class sourceClass ) {
|
||||
List<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)
|
||||
argumentsFromSource.add( new ArgumentDefinition(argument,source,field) );
|
||||
ArgumentCollection argumentCollection = field.getAnnotation(ArgumentCollection.class);
|
||||
if(argumentCollection != null)
|
||||
addArgumentSource(sourceName, field.getType());
|
||||
}
|
||||
source = source.getSuperclass();
|
||||
}
|
||||
for( ArgumentSource argumentSource: extractArgumentSources(sourceClass,true) )
|
||||
argumentsFromSource.add( new ArgumentDefinition(argumentSource) );
|
||||
argumentDefinitions.add( new ArgumentDefinitionGroup(sourceName, argumentsFromSource) );
|
||||
}
|
||||
|
||||
|
|
@ -131,8 +111,30 @@ public class ParsingEngine {
|
|||
* an empty object, but will never return null.
|
||||
*/
|
||||
public void parse( String[] tokens ) {
|
||||
argumentMatches = parseArguments( tokens );
|
||||
fitValuesToArguments( argumentMatches, tokens );
|
||||
argumentMatches = new ArgumentMatches();
|
||||
|
||||
int lastArgumentMatchSite = -1;
|
||||
|
||||
for( int i = 0; i < tokens.length; i++ ) {
|
||||
String token = tokens[i];
|
||||
// If the token is of argument form, parse it into its own argument match.
|
||||
// Otherwise, pair it with the most recently used argument discovered.
|
||||
if( isArgumentForm(token) ) {
|
||||
ArgumentMatch argumentMatch = parseArgument( token, i );
|
||||
if( argumentMatch != null ) {
|
||||
argumentMatches.mergeInto( argumentMatch );
|
||||
lastArgumentMatchSite = i;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if( argumentMatches.hasMatch(lastArgumentMatchSite) &&
|
||||
!argumentMatches.getMatch(lastArgumentMatchSite).hasValueAtSite(lastArgumentMatchSite))
|
||||
argumentMatches.getMatch(lastArgumentMatchSite).addValue( lastArgumentMatchSite, token );
|
||||
else
|
||||
argumentMatches.MissingArgument.addValue( i, token );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ValidationType { MissingRequiredArgument,
|
||||
|
|
@ -140,7 +142,7 @@ public class ParsingEngine {
|
|||
InvalidArgumentValue,
|
||||
ValueMissingArgument,
|
||||
TooManyValuesForArgument,
|
||||
MutuallyExclusive };
|
||||
MutuallyExclusive }
|
||||
|
||||
/**
|
||||
* Validates the list of command-line argument matches.
|
||||
|
|
@ -171,7 +173,7 @@ public class ParsingEngine {
|
|||
|
||||
// Find invalid arguments. Invalid arguments will have a null argument definition.
|
||||
if( !skipValidationOf.contains(ValidationType.InvalidArgument) ) {
|
||||
Collection<ArgumentMatch> invalidArguments = argumentMatches.findMatches(null);
|
||||
Collection<ArgumentMatch> invalidArguments = argumentMatches.findUnmatched();
|
||||
if( invalidArguments.size() > 0 )
|
||||
throw new InvalidArgumentException( invalidArguments );
|
||||
}
|
||||
|
|
@ -206,7 +208,7 @@ public class ParsingEngine {
|
|||
Collection<ArgumentMatch> overvaluedArguments = new ArrayList<ArgumentMatch>();
|
||||
for( ArgumentMatch argumentMatch: argumentMatches.findSuccessfulMatches() ) {
|
||||
// Warning: assumes that definition is not null (asserted by checks above).
|
||||
if( !argumentMatch.definition.isMultiValued() && argumentMatch.values().size() > 1 )
|
||||
if( !argumentMatch.definition.source.isMultiValued() && argumentMatch.values().size() > 1 )
|
||||
overvaluedArguments.add(argumentMatch);
|
||||
}
|
||||
|
||||
|
|
@ -240,35 +242,41 @@ public class ParsingEngine {
|
|||
* @param object Object into which to add arguments.
|
||||
*/
|
||||
public void loadArgumentsIntoObject( Object object ) {
|
||||
for( ArgumentMatch match: argumentMatches )
|
||||
loadArgumentIntoObject( match, object );
|
||||
// Get a list of argument sources, not including the children of this argument. For now, skip loading
|
||||
// arguments into the object recursively.
|
||||
List<ArgumentSource> argumentSources = extractArgumentSources( object.getClass(), false );
|
||||
for( ArgumentSource argumentSource: argumentSources ) {
|
||||
Collection<ArgumentMatch> argumentsMatchingSource = argumentMatches.findMatches( argumentSource );
|
||||
if( argumentsMatchingSource.size() != 0 )
|
||||
loadMatchesIntoObject( argumentsMatchingSource, object );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a single argument into the object.
|
||||
* @param argument Argument to load into the object.
|
||||
* @param argumentMatches Argument matches to load into the object.
|
||||
* @param object Target for the argument.
|
||||
*/
|
||||
public void loadArgumentIntoObject( ArgumentMatch argument, Object object ) {
|
||||
ArgumentDefinition definition = argument.definition;
|
||||
private void loadMatchesIntoObject( Collection<ArgumentMatch> argumentMatches, Object object ) {
|
||||
if( argumentMatches.size() > 1 )
|
||||
throw new StingException("Too many matches");
|
||||
|
||||
ArgumentMatch match = argumentMatches.iterator().next();
|
||||
ArgumentDefinition definition = match.definition;
|
||||
|
||||
// A null definition might be in the list if some invalid arguments were passed in but we
|
||||
// want to load in a subset of data for better error reporting. Ignore null definitions.
|
||||
if( definition == null )
|
||||
return;
|
||||
|
||||
if( definition.sourceClass.isAssignableFrom(object.getClass()) ) {
|
||||
try {
|
||||
definition.sourceField.setAccessible(true);
|
||||
if( !isArgumentFlag(definition) )
|
||||
definition.sourceField.set( object, constructFromString( definition.sourceField, argument.values() ) );
|
||||
else
|
||||
definition.sourceField.set( object, true );
|
||||
}
|
||||
catch( IllegalAccessException ex ) {
|
||||
//logger.fatal("processArgs: cannot convert field " + field.toString());
|
||||
throw new StingException("processArgs: Failed conversion " + ex.getMessage(), ex);
|
||||
if( definition.source.clazz.isAssignableFrom(object.getClass()) ) {
|
||||
if( !definition.source.isFlag() ) {
|
||||
String[] tokens = match.values().toArray(new String[0]);
|
||||
FieldParser fieldParser = FieldParser.create(definition.source.field);
|
||||
definition.source.setValue( object, fieldParser.parse(tokens) );
|
||||
}
|
||||
else
|
||||
definition.source.setValue( object, true );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -280,13 +288,25 @@ public class ParsingEngine {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns true if the argument is a flag (a 0-valued argument).
|
||||
* @param definition Argument definition.
|
||||
* @return True if argument is a flag; false otherwise.
|
||||
* Extract all the argument sources from a given object.
|
||||
* @param sourceClass class to act as sources for other arguments.
|
||||
* @param recursive Whether to recursively look for argument collections and add their contents.
|
||||
* @return A list of sources associated with this object and its aggregated objects.
|
||||
*/
|
||||
private boolean isArgumentFlag( ArgumentDefinition definition ) {
|
||||
return (definition.sourceField.getType() == Boolean.class) || (definition.sourceField.getType() == Boolean.TYPE);
|
||||
}
|
||||
private List<ArgumentSource> extractArgumentSources( Class sourceClass, boolean recursive ) {
|
||||
List<ArgumentSource> argumentSources = new ArrayList<ArgumentSource>();
|
||||
while( sourceClass != null ) {
|
||||
Field[] fields = sourceClass.getDeclaredFields();
|
||||
for( Field field: fields ) {
|
||||
if( field.isAnnotationPresent(Argument.class) )
|
||||
argumentSources.add( new ArgumentSource(sourceClass,field) );
|
||||
if( field.isAnnotationPresent(ArgumentCollection.class) && recursive )
|
||||
argumentSources.addAll( extractArgumentSources(field.getType(),recursive) );
|
||||
}
|
||||
sourceClass = sourceClass.getSuperclass();
|
||||
}
|
||||
return argumentSources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a token looks like the name of an argument.
|
||||
|
|
@ -320,49 +340,6 @@ public class ParsingEngine {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the argument portions of the string and assemble them into a data structure.
|
||||
* @param tokens List of tokens from which to find arguments.
|
||||
* @return Set of argument matches.
|
||||
*/
|
||||
private ArgumentMatches parseArguments( String[] tokens ) {
|
||||
ArgumentMatches argumentMatches = new ArgumentMatches();
|
||||
|
||||
for( int i = 0; i < tokens.length; i++ ) {
|
||||
String token = tokens[i];
|
||||
if( isArgumentForm(token) ) {
|
||||
ArgumentMatch argumentMatch = parseArgument( token, i );
|
||||
if( argumentMatch != null )
|
||||
argumentMatches.mergeInto( argumentMatch );
|
||||
}
|
||||
}
|
||||
|
||||
return argumentMatches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fit the options presented on the command line to the given arguments.
|
||||
* @param argumentMatches List of arguments already matched to data.
|
||||
* @param tokens The command-line input.
|
||||
*/
|
||||
private void fitValuesToArguments( ArgumentMatches argumentMatches, String[] tokens ) {
|
||||
for( int i = 0; i < tokens.length; i++ ) {
|
||||
// If this is the site of a successfully matched argument, pass it over.
|
||||
if( argumentMatches.hasMatch(i) )
|
||||
continue;
|
||||
|
||||
// tokens[i] must be an argument value. Match it with the previous argument.
|
||||
String value = tokens[i];
|
||||
int argumentSite = i - 1;
|
||||
|
||||
// If the argument is present and doesn't already have a value associated with the given site, add the value.
|
||||
if( argumentMatches.hasMatch(argumentSite) && !argumentMatches.getMatch(argumentSite).hasValueAtSite(argumentSite))
|
||||
argumentMatches.getMatch(argumentSite).addValue( argumentSite, value );
|
||||
else
|
||||
argumentMatches.MissingArgument.addValue( i, value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a command-line argument given a string and field.
|
||||
* @param f Field type from which to infer the type.
|
||||
|
|
@ -370,125 +347,8 @@ public class ParsingEngine {
|
|||
* @return Parsed object of the inferred type.
|
||||
*/
|
||||
private Object constructFromString(Field f, List<String> strs) {
|
||||
Class type = f.getType();
|
||||
|
||||
if( Collection.class.isAssignableFrom(type) ) {
|
||||
Collection collection = null;
|
||||
Class containedType = null;
|
||||
|
||||
// If this is a parameterized collection, find the contained type. If blow up if only one type exists.
|
||||
if( f.getGenericType() instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType)f.getGenericType();
|
||||
if( parameterizedType.getActualTypeArguments().length > 1 )
|
||||
throw new IllegalArgumentException("Unable to determine collection type of field: " + f.toString());
|
||||
containedType = (Class)parameterizedType.getActualTypeArguments()[0];
|
||||
}
|
||||
else
|
||||
containedType = String.class;
|
||||
|
||||
// If this is a generic interface, pick a concrete implementation to create and pass back.
|
||||
// Because of type erasure, don't worry about creating one of exactly the correct type.
|
||||
if( Modifier.isInterface(type.getModifiers()) || Modifier.isAbstract(type.getModifiers()) )
|
||||
{
|
||||
if( java.util.List.class.isAssignableFrom(type) ) type = ArrayList.class;
|
||||
else if( java.util.Queue.class.isAssignableFrom(type) ) type = java.util.ArrayDeque.class;
|
||||
else if( java.util.Set.class.isAssignableFrom(type) ) type = java.util.TreeSet.class;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
collection = (Collection)type.newInstance();
|
||||
}
|
||||
catch( Exception ex ) {
|
||||
// Runtime exceptions are definitely unexpected parsing simple collection classes.
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
|
||||
for( String str: strs )
|
||||
collection.add( constructSingleElement(f,containedType,str) );
|
||||
|
||||
return collection;
|
||||
}
|
||||
else if( type.isArray() ) {
|
||||
Class containedType = type.getComponentType();
|
||||
|
||||
Object arr = Array.newInstance(containedType,strs.size());
|
||||
for( int i = 0; i < strs.size(); i++ )
|
||||
Array.set( arr,i,constructSingleElement(f,containedType,strs.get(i)) );
|
||||
return arr;
|
||||
}
|
||||
else {
|
||||
if( strs.size() != 1 )
|
||||
throw new IllegalArgumentException("Passed multiple arguments to an object expecting a single value.");
|
||||
return constructSingleElement(f,type,strs.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a single element of the given type.
|
||||
* @param f Implies type of data to construct.
|
||||
* @param str String representation of data.
|
||||
* @return parsed form of String.
|
||||
*/
|
||||
private Object constructSingleElement(Field f, Class type, String str) {
|
||||
if( customArgumentFactory != null ) {
|
||||
Object instance = customArgumentFactory.createArgument(type, str);
|
||||
if( instance != null )
|
||||
return instance;
|
||||
}
|
||||
|
||||
// lets go through the types we support
|
||||
if (type == Boolean.TYPE) {
|
||||
boolean b = false;
|
||||
if (str.toLowerCase().equals("true")) {
|
||||
b = true;
|
||||
}
|
||||
Boolean bool = new Boolean(b);
|
||||
return bool;
|
||||
} else if (type == Byte.TYPE) {
|
||||
Byte b = Byte.valueOf(str);
|
||||
return b;
|
||||
} else if (type == Short.TYPE) {
|
||||
Short s = Short.valueOf(str);
|
||||
return s;
|
||||
} else if (type == Integer.TYPE) {
|
||||
Integer in = Integer.valueOf(str);
|
||||
return in;
|
||||
} else if (type == Long.TYPE) {
|
||||
Long l = Long.valueOf(str);
|
||||
return l;
|
||||
} else if (type == Float.TYPE) {
|
||||
Float fl = Float.valueOf(str);
|
||||
return fl;
|
||||
} else if (type == Double.TYPE) {
|
||||
Double db = Double.valueOf(str);
|
||||
return db;
|
||||
} else if (type == Character.TYPE) {
|
||||
if( str.trim().length() != 1 )
|
||||
throw new StingException("Unable to parse argument '" + str + "' into a character.");
|
||||
Character c = str.trim().charAt(0);
|
||||
return c;
|
||||
} else if (type.isEnum()) {
|
||||
return Enum.valueOf(type,str.toUpperCase().trim());
|
||||
} else {
|
||||
Constructor ctor = null;
|
||||
try {
|
||||
ctor = type.getConstructor(String.class);
|
||||
return ctor.newInstance(str);
|
||||
} catch (NoSuchMethodException e) {
|
||||
logger.fatal("constructFromString:NoSuchMethodException: cannot convert field " + f.toString());
|
||||
throw new RuntimeException("constructFromString:NoSuchMethodException: Failed conversion " + e.getMessage());
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.fatal("constructFromString:IllegalAccessException: cannot convert field " + f.toString());
|
||||
throw new RuntimeException("constructFromString:IllegalAccessException: Failed conversion " + e.getMessage());
|
||||
} catch (InvocationTargetException e) {
|
||||
logger.fatal("constructFromString:InvocationTargetException: cannot convert field " + f.toString());
|
||||
throw new RuntimeException("constructFromString:InvocationTargetException: Failed conversion " + e.getMessage());
|
||||
} catch (InstantiationException e) {
|
||||
logger.fatal("constructFromString:InstantiationException: cannot convert field " + f.toString());
|
||||
throw new RuntimeException("constructFromString:InstantiationException: Failed conversion " + e.getMessage());
|
||||
}
|
||||
}
|
||||
FieldParser fieldParser = FieldParser.create(f);
|
||||
return fieldParser.parse( strs.toArray(new String[0]) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,6 +100,25 @@ public class ParsingEngineTest extends BaseTest {
|
|||
Assert.assertEquals("Argument is not correctly initialized", "na12878.bam", argProvider.inputFile );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void primitiveArgumentTest() {
|
||||
final String[] commandLine = new String[] {"--foo", "5"};
|
||||
|
||||
parsingEngine.addArgumentSource( PrimitiveArgProvider.class );
|
||||
parsingEngine.parse( commandLine );
|
||||
parsingEngine.validate();
|
||||
|
||||
PrimitiveArgProvider argProvider = new PrimitiveArgProvider();
|
||||
parsingEngine.loadArgumentsIntoObject( argProvider );
|
||||
|
||||
Assert.assertEquals("Argument is not correctly initialized", 5, argProvider.foo );
|
||||
}
|
||||
|
||||
private class PrimitiveArgProvider {
|
||||
@Argument(doc="simple integer")
|
||||
int foo;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flagTest() {
|
||||
final String[] commandLine = new String[] {"--all_loci"};
|
||||
|
|
|
|||
Loading…
Reference in New Issue