package org.broadinstitute.sting.utils.cmdLine; import org.broadinstitute.sting.utils.StingException; import java.util.Set; import java.util.HashSet; import java.util.Collection; import java.util.List; import java.util.ArrayList; import java.util.Iterator; import java.util.Collections; /** * Created by IntelliJ IDEA. * User: mhanna * Date: May 3, 2009 * Time: 6:02:04 PM *

* The Broad Institute * SOFTWARE COPYRIGHT NOTICE AGREEMENT * This software and its documentation are copyright 2009 by the * Broad Institute/Massachusetts Institute of Technology. All rights are reserved. *

* This software is supplied without any warranty or guaranteed support whatsoever. Neither * the Broad Institute nor MIT can be responsible for its use, misuse, or functionality. */ /** * A collection of argument definitions. */ class ArgumentDefinitions implements Iterable { /** * Backing data set of argument stored by short name and long name. */ private Set argumentDefinitions = new HashSet(); /** * The groupings of argument definitions. Used mainly for help output. */ 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( definition.shortName != null && hasArgumentDefinition( definition.shortName, ShortNameDefinitionMatcher ) ) throw new StingException("Duplicate definition of argument with short name: " + definition.shortName); argumentDefinitions.add( definition ); } // Find an existing argument definition group with this name. // If one exists, merge this group into the other. Iterator definitionGroupIterator = argumentDefinitionGroups.iterator(); while( definitionGroupIterator.hasNext() ) { ArgumentDefinitionGroup candidate = definitionGroupIterator.next(); if( candidate.groupNameMatches(argumentDefinitionGroup) ) { argumentDefinitionGroup = candidate.merge(argumentDefinitionGroup); definitionGroupIterator.remove(); } } // Otherwise, add the new group. argumentDefinitionGroups.add( argumentDefinitionGroup ); } /** * Are there any argument definitions matching the given property? * @param property Property to find. * @param matcher Method of matching a given property. * @return True if one or multiple argument definitions match; false otherwise. */ public boolean hasArgumentDefinition( Object property, DefinitionMatcher matcher ) { return findArgumentDefinitions( property, matcher ).size() > 0; } /** * Find the given definition matching this property. * @param property Property to find. * @param matcher Method of matching a given property. * @return The ArgumentDefinition matching the given property. Null if none matches. * @throws IllegalArgumentException if multiple arguments match this definition. */ public ArgumentDefinition findArgumentDefinition( Object property, DefinitionMatcher matcher ) { Collection selectedDefinitions = findArgumentDefinitions( property, matcher ); if( selectedDefinitions.size() > 1 ) throw new IllegalArgumentException("Multiple argument definitions match the selected property: " + property); if( selectedDefinitions.size() == 0 ) return null; return selectedDefinitions.iterator().next(); } /** * Find all argument definitions matching a certain category. * @param property Property to inspect. * @param matcher Test to see whether property matches. * @return All argument definitions matching a certain object. */ public Collection findArgumentDefinitions( Object property, DefinitionMatcher matcher ) { Set selectedArgumentDefinitions = new HashSet(); for( ArgumentDefinition argumentDefinition: argumentDefinitions ) { if( matcher.matches( argumentDefinition, property ) ) selectedArgumentDefinitions.add( argumentDefinition ); } 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 an iterator over command-line arguments. */ public Iterator iterator() { return argumentDefinitions.iterator(); } /** * Match the full name of a definition. */ static DefinitionMatcher FullNameDefinitionMatcher = new DefinitionMatcher() { public boolean matches( ArgumentDefinition definition, Object key ) { if( definition.fullName == null ) return key == null; else return definition.fullName.equals( key ); } }; /** * Match the short name of a definition. */ static DefinitionMatcher ShortNameDefinitionMatcher = new DefinitionMatcher() { public boolean matches( ArgumentDefinition definition, Object key ) { if( definition.shortName == null ) return key == null; else return definition.shortName.equals( key ); } }; /** * Find all required definitions. */ static DefinitionMatcher RequiredDefinitionMatcher = new DefinitionMatcher() { public boolean matches( ArgumentDefinition definition, Object key ) { if( !(key instanceof Boolean) ) throw new IllegalArgumentException("RequiredDefinitionMatcher requires boolean key"); return definition.required == (Boolean)key; } }; static DefinitionMatcher VerifiableDefinitionMatcher = new DefinitionMatcher() { public boolean matches( ArgumentDefinition definition, Object key ) { return definition.validation != null; } }; } /** * 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 List argumentDefinitions; public ArgumentDefinitionGroup( String groupName, List argumentDefinitions ) { this.groupName = groupName; this.argumentDefinitions = Collections.unmodifiableList( argumentDefinitions ); } /** * Does the name of this argument group match the name of another? */ public boolean groupNameMatches( ArgumentDefinitionGroup other ) { if( this.groupName == null && other.groupName == null ) return true; if( this.groupName == null && other.groupName != null ) return false; return this.groupName.equals(other.groupName); } /** * Merges another argument group into this argument group. Return a new * group since argument groups are supposed to be immutable. Asserts that * both argument groups have the same name. */ public ArgumentDefinitionGroup merge( ArgumentDefinitionGroup other ) { if( !groupNameMatches(other) ) throw new StingException("Unable to merge two argument groups with differing names."); // Create a merged definition group. List mergedDefinitions = new ArrayList(); mergedDefinitions.addAll(this.argumentDefinitions); mergedDefinitions.addAll(other.argumentDefinitions); return new ArgumentDefinitionGroup(groupName,mergedDefinitions); } /** * 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. */ class ArgumentDefinition { /** * Full name of the argument. Must have a value. */ public final String fullName; /** * Short name of the argument. Can be null. */ public final String shortName; /** * Doc string for the argument. Displayed in help. */ public final String doc; /** * Is this argument required? */ public final boolean required; /** * Is this argument exclusive of other arguments? */ public final String exclusiveOf; /** * Can we validate this regular expression? */ public final String validation; /** * The target into which to inject arguments meeting this definition. */ public final ArgumentSource source; /** * Creates a new argument definition. * @param source Source information for defining the argument. */ public ArgumentDefinition( ArgumentSource source, String fullName, String shortName, String doc, boolean required, String exclusiveOf, String validation ) { this.source = source; this.fullName = fullName; this.shortName = shortName; this.doc = doc; this.required = required; this.exclusiveOf = exclusiveOf; this.validation = validation; } } /** * A Comparator-esque interface for finding argument definitions within a collection. */ interface DefinitionMatcher { /** * Does the given definition match the provided key? * @param definition The definition to inspect. * @param key The value to match. * @return True if the key matches the definition, false otherwise. */ boolean matches( ArgumentDefinition definition, Object key ); }