Coverage Report - joptsimple.ArgumentAcceptingOptionSpec
 
Classes in this File Line Coverage Branch Coverage Complexity
ArgumentAcceptingOptionSpec
100%
49/49
93%
26/28
1.941
 
 1  
 /*
 2  
  Copyright 2004-2008 Paul R. Holser, Jr.  All rights reserved.
 3  
  Licensed under the Academic Free License version 3.0
 4  
  */
 5  
 
 6  
 package joptsimple;
 7  
 
 8  
 import java.lang.reflect.Constructor;
 9  
 import java.lang.reflect.Member;
 10  
 import java.lang.reflect.Method;
 11  
 import java.util.List;
 12  
 import java.util.StringTokenizer;
 13  
 
 14  
 import joptsimple.internal.Reflection;
 15  
 import joptsimple.internal.ReflectionException;
 16  
 import joptsimple.internal.Strings;
 17  
 
 18  
 /**
 19  
  * <p>Specification of an option that accepts an argument.</p>
 20  
  *
 21  
  * <p>Instances are returned from {@link OptionSpecBuilder} methods to allow the formation
 22  
  * of parser directives as sentences in a domain-specific language.  For example:</p>
 23  
  *
 24  
  * <pre>
 25  
  *   <code>
 26  
  *   OptionParser parser = new OptionParser();
 27  
  *   parser.accepts( "c" ).withRequiredArg().<strong>ofType( Integer.class )</strong>;
 28  
  *   </code>
 29  
  * </pre>
 30  
  *
 31  
  * <p>If no methods are invoked on an instance of this class, then that instance's option
 32  
  * will treat its argument as a {@link String}.</p>
 33  
  *
 34  
  * @since 1.0
 35  
  * @author <a href="mailto:pholser@alumni.rice.edu">Paul Holser</a>
 36  
  * @version $Id: ArgumentAcceptingOptionSpec.java,v 1.9 2008/05/23 15:02:43 pholser Exp $
 37  
  */
 38  
 public abstract class ArgumentAcceptingOptionSpec extends OptionSpec {
 39  
     private static final char NIL_VALUE_SEPARATOR = '\u0000';
 40  
     private final boolean argumentRequired;
 41  
     private Member converter;
 42  496
     private String argumentDescription = "";
 43  496
     private String valueSeparator = String.valueOf( NIL_VALUE_SEPARATOR );
 44  
 
 45  
     ArgumentAcceptingOptionSpec( String option, boolean argumentRequired ) {
 46  194
         super( option );
 47  
 
 48  194
         this.argumentRequired = argumentRequired;
 49  194
     }
 50  
 
 51  
     ArgumentAcceptingOptionSpec( List options, boolean argumentRequired,
 52  
         String description ) {
 53  
 
 54  302
         super( options, description );
 55  
 
 56  302
         this.argumentRequired = argumentRequired;
 57  302
     }
 58  
 
 59  
     /**
 60  
      * <p>Specifies a type to which arguments of this spec's option are to be
 61  
      * converted.</p>
 62  
      *
 63  
      * <p>JOpt Simple accepts types that have either:</p>
 64  
      *
 65  
      * <ol>
 66  
      *   <li>a public static method called <code>valueOf</code> which accepts a single
 67  
      *   argument of type {@link String} and whose return type is the same as the class
 68  
      *   on which the method is declared.  The <code>java.lang</code> primitive wrapper
 69  
      *   classes have such methods.</li>
 70  
      *
 71  
      *   <li>a public constructor which accepts a single argument of type
 72  
      *   {@link String}.</li>
 73  
      * </ol>
 74  
      *
 75  
      * <p>This class converts arguments using those methods in that order; that is,
 76  
      * <code>valueOf</code> would be invoked before a one-{@link String}-arg constructor
 77  
      * would.</p>
 78  
      *
 79  
      * @param argumentType desired type of arguments to this spec's option
 80  
      * @return self, so that the caller can add clauses to the domain-specific sentence
 81  
      * @throws NullPointerException if the type is <code>null</code>
 82  
      * @throws IllegalArgumentException if the type does not have the standard conversion
 83  
      * methods
 84  
      */
 85  
     public ArgumentAcceptingOptionSpec ofType( Class argumentType ) {
 86  132
         converter = Reflection.findConverter( argumentType );
 87  124
         return this;
 88  
     }
 89  
 
 90  
     /**
 91  
      * <p>Specifies a description for the argument of the option that this spec
 92  
      * represents.  This description is used when generating help information about
 93  
      * the parser.</p>
 94  
      *
 95  
      * @param description describes the nature of the argument of this spec's
 96  
      * option
 97  
      * @return self, so that the caller can add clauses to the domain-specific sentence
 98  
      */
 99  
     public final ArgumentAcceptingOptionSpec describedAs( String description ) {
 100  34
         argumentDescription = description;
 101  
 
 102  34
         return this;
 103  
     }
 104  
 
 105  
     /**
 106  
      * <p>Specifies a value separator for the argument of the option that this spec
 107  
      * represents.  This allows a single option argument to represent multiple values
 108  
      * for the option.  For example:</p>
 109  
      *
 110  
      * <pre>
 111  
      *   <code>
 112  
      *   parser.accepts( "z" ).withRequiredArg()
 113  
      *       .<strong>withValuesSeparatedBy( ',' )</strong>;
 114  
      *   OptionSet options = parser.parse( new String[] { "-z", "foo,bar,baz", "-z",
 115  
      *       "fizz", "buzz" } );
 116  
      *   </code>
 117  
      * </pre>
 118  
      *
 119  
      * <p>Then <code>options.valuesOf( "z" )</code> would yield the list <code>[foo, bar,
 120  
      * baz, fizz, buzz]</code>.</p>
 121  
      *
 122  
      * <p>You cannot use Unicode U+0000 as the separator.</p>
 123  
      *
 124  
      * @param separator a character separator
 125  
      * @return self, so that the caller can add clauses to the domain-specific sentence
 126  
      * @throws IllegalArgumentException if the separator is Unicode U+0000
 127  
      */
 128  
     public final ArgumentAcceptingOptionSpec withValuesSeparatedBy( char separator ) {
 129  24
         if ( separator == NIL_VALUE_SEPARATOR )
 130  4
             throw new IllegalArgumentException( "cannot use U+0000 as separator" );
 131  
 
 132  20
         this.valueSeparator = String.valueOf( separator );
 133  20
         return this;
 134  
     }
 135  
 
 136  
     final void handleOption( OptionParser parser, ArgumentList arguments,
 137  
         OptionSet detectedOptions, String detectedArgument ) {
 138  
 
 139  274
         if ( Strings.isNullOrEmpty( detectedArgument ) )
 140  202
             detectOptionArgument( parser, arguments, detectedOptions );
 141  
         else {
 142  72
             addArguments( detectedOptions, detectedArgument );
 143  
         }
 144  264
     }
 145  
 
 146  
     protected void addArguments( OptionSet detectedOptions, String detectedArgument ) {
 147  220
         StringTokenizer lexer = new StringTokenizer( detectedArgument, valueSeparator );
 148  476
         while ( lexer.hasMoreTokens() )
 149  260
             detectedOptions.addAllWithArgument( options(), convert( lexer.nextToken() ) );
 150  216
     }
 151  
 
 152  
     protected abstract void detectOptionArgument( OptionParser parser,
 153  
         ArgumentList arguments, OptionSet detectedOptions );
 154  
 
 155  
     protected final Object convert( String argument ) {
 156  274
         if ( converter == null )
 157  154
             return argument;
 158  
 
 159  120
         Object[] args = new Object[] { argument };
 160  
 
 161  
         try {
 162  120
             if ( converter instanceof Constructor )
 163  40
                 return Reflection.instantiate( (Constructor) converter, args );
 164  
 
 165  80
             return Reflection.invoke( (Method) converter, args );
 166  
         }
 167  8
         catch ( ReflectionException ignored ) {
 168  8
             throw new OptionArgumentConversionException(
 169  
                 options(), argument, converter.getDeclaringClass() );
 170  
         }
 171  
     }
 172  
 
 173  
     protected boolean canConvertArgument( String argument ) {
 174  12
         StringTokenizer lexer = new StringTokenizer( argument, valueSeparator );
 175  
 
 176  
         try {
 177  20
             while ( lexer.hasMoreTokens() )
 178  12
                 convert( lexer.nextToken() );
 179  8
             return true;
 180  
         }
 181  4
         catch ( OptionException ignored ) {
 182  4
             return false;
 183  
         }
 184  
     }
 185  
 
 186  
     protected boolean isArgumentOfNumberType() {
 187  24
         return converter != null
 188  4
             && Number.class.isAssignableFrom( converter.getDeclaringClass() );
 189  
     }
 190  
 
 191  
     boolean acceptsArguments() {
 192  112
         return true;
 193  
     }
 194  
 
 195  
     boolean requiresArgument() {
 196  1746
         return argumentRequired;
 197  
     }
 198  
 
 199  
     String argumentDescription() {
 200  40
         return argumentDescription;
 201  
     }
 202  
 
 203  
     Class argumentType() {
 204  38
         return converter == null ? String.class : converter.getDeclaringClass();
 205  
     }
 206  
 
 207  
     /**
 208  
      * {@inheritDoc}
 209  
      */
 210  
     public boolean equals( Object that ) {
 211  1478
         if ( !super.equals( that ) )
 212  614
             return false;
 213  
 
 214  864
         ArgumentAcceptingOptionSpec other = (ArgumentAcceptingOptionSpec) that;
 215  864
         return requiresArgument() == other.requiresArgument();
 216  
     }
 217  
 
 218  
     /**
 219  
      * {@inheritDoc}
 220  
      */
 221  
     public int hashCode() {
 222  360
         return super.hashCode() ^ ( argumentRequired ? 0 : 1 );
 223  
     }
 224  
 }