options, boolean argumentRequired, String description ) {
+ super( options, description );
+
+ this.argumentRequired = argumentRequired;
+ }
+
+ /**
+ * Specifies a type to which arguments of this spec's option are to be
+ * converted.
+ *
+ * JOpt Simple accepts types that have either:
+ *
+ *
+ * - a public static method called {@code valueOf} which accepts a single
+ * argument of type {@link String} and whose return type is the same as the class
+ * on which the method is declared. The {@code java.lang} primitive wrapper
+ * classes have such methods.
+ *
+ * - a public constructor which accepts a single argument of type
+ * {@link String}.
+ *
+ *
+ * This class converts arguments using those methods in that order; that is,
+ * {@code valueOf} would be invoked before a one-{@link String}-arg constructor
+ * would.
+ *
+ * Invoking this method will trump any previous calls to this method or to
+ * {@link #withValuesConvertedBy(ValueConverter)}.
+ *
+ * @param represents the runtime class of the desired option argument type
+ * @param argumentType desired type of arguments to this spec's option
+ * @return self, so that the caller can add clauses to the fluent interface sentence
+ * @throws NullPointerException if the type is {@code null}
+ * @throws IllegalArgumentException if the type does not have the standard conversion
+ * methods
+ */
+ public final ArgumentAcceptingOptionSpec ofType( Class argumentType ) {
+ return withValuesConvertedBy( findConverter( argumentType ) );
+ }
+
+ /**
+ * Specifies a converter to use to translate arguments of this spec's option into
+ * Java objects. This is useful when converting to types that do not have the
+ * requisite factory method or constructor for {@link #ofType(Class)}.
+ *
+ * Invoking this method will trump any previous calls to this method or to
+ * {@link #ofType(Class)}.
+ *
+ * @param represents the runtime class of the desired option argument type
+ * @param aConverter the converter to use
+ * @return self, so that the caller can add clauses to the fluent interface sentence
+ * @throws NullPointerException if the converter is {@code null}
+ */
+ @SuppressWarnings( "unchecked" )
+ public final ArgumentAcceptingOptionSpec withValuesConvertedBy( ValueConverter aConverter ) {
+ if ( aConverter == null )
+ throw new NullPointerException( "illegal null converter" );
+
+ converter = (ValueConverter) aConverter;
+ return (ArgumentAcceptingOptionSpec) this;
+ }
+
+ /**
+ * Specifies a description for the argument of the option that this spec
+ * represents. This description is used when generating help information about
+ * the parser.
+ *
+ * @param description describes the nature of the argument of this spec's
+ * option
+ * @return self, so that the caller can add clauses to the fluent interface sentence
+ */
+ public final ArgumentAcceptingOptionSpec describedAs( String description ) {
+ argumentDescription = description;
+ return this;
+ }
+
+ /**
+ * Specifies a value separator for the argument of the option that this spec
+ * represents. This allows a single option argument to represent multiple values
+ * for the option. For example:
+ *
+ *
+ *
+ * parser.accepts( "z" ).withRequiredArg()
+ * .withValuesSeparatedBy( ',' );
+ * OptionSet options = parser.parse( new String[] { "-z", "foo,bar,baz", "-z",
+ * "fizz", "-z", "buzz" } );
+ *
+ *
+ *
+ * Then {@code options.valuesOf( "z" )} would yield the list {@code [foo, bar,
+ * baz, fizz, buzz]}.
+ *
+ * You cannot use Unicode U+0000 as the separator.
+ *
+ * @param separator a character separator
+ * @return self, so that the caller can add clauses to the fluent interface sentence
+ * @throws IllegalArgumentException if the separator is Unicode U+0000
+ */
+ public final ArgumentAcceptingOptionSpec withValuesSeparatedBy( char separator ) {
+ if ( separator == NIL_VALUE_SEPARATOR )
+ throw new IllegalArgumentException( "cannot use U+0000 as separator" );
+
+ valueSeparator = String.valueOf( separator );
+ return this;
+ }
+
+ /**
+ * Specifies a set of default values for the argument of the option that this spec
+ * represents.
+ *
+ * @param value the first in the set of default argument values for this spec's option
+ * @param values the (optional) remainder of the set of default argument values for this spec's option
+ * @return self, so that the caller can add clauses to the fluent interface sentence
+ * @throws NullPointerException if {@code value}, {@code values}, or any elements of {@code values} are
+ * {@code null}
+ */
+ public ArgumentAcceptingOptionSpec defaultsTo( V value, V... values ) {
+ addDefaultValue( value );
+ for ( V each : values )
+ addDefaultValue( each );
+
+ return this;
+ }
+
+ private void addDefaultValue( V value ) {
+ ensureNotNull( value );
+ defaultValues.add( value );
+ }
+
+ @Override
+ final void handleOption( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions,
+ String detectedArgument ) {
+
+ if ( isNullOrEmpty( detectedArgument ) )
+ detectOptionArgument( parser, arguments, detectedOptions );
+ else
+ addArguments( detectedOptions, detectedArgument );
+ }
+
+ protected void addArguments( OptionSet detectedOptions, String detectedArgument ) {
+ StringTokenizer lexer = new StringTokenizer( detectedArgument, valueSeparator );
+ if ( !lexer.hasMoreTokens() )
+ detectedOptions.addWithArgument( this, detectedArgument );
+ else {
+ while ( lexer.hasMoreTokens() )
+ detectedOptions.addWithArgument( this, lexer.nextToken() );
+ }
+ }
+
+ protected abstract void detectOptionArgument( OptionParser parser, ArgumentList arguments,
+ OptionSet detectedOptions );
+
+ @SuppressWarnings( "unchecked" )
+ @Override
+ protected final V convert( String argument ) {
+ if ( converter == null )
+ return (V) argument;
+
+ try {
+ return converter.convert( argument );
+ }
+ catch ( ReflectionException ex ) {
+ throw new OptionArgumentConversionException( options(), argument, converter.valueType(), ex );
+ }
+ catch ( ValueConversionException ex ) {
+ throw new OptionArgumentConversionException( options(), argument, converter.valueType(), ex );
+ }
+ }
+
+ protected boolean canConvertArgument( String argument ) {
+ StringTokenizer lexer = new StringTokenizer( argument, valueSeparator );
+
+ try {
+ while ( lexer.hasMoreTokens() )
+ convert( lexer.nextToken() );
+ return true;
+ }
+ catch ( OptionException ignored ) {
+ return false;
+ }
+ }
+
+ protected boolean isArgumentOfNumberType() {
+ return converter != null && Number.class.isAssignableFrom( converter.valueType() );
+ }
+
+ @Override
+ boolean acceptsArguments() {
+ return true;
+ }
+
+ @Override
+ boolean requiresArgument() {
+ return argumentRequired;
+ }
+
+ String argumentDescription() {
+ return argumentDescription;
+ }
+
+ String typeIndicator() {
+ if ( converter == null )
+ return null;
+
+ String pattern = converter.valuePattern();
+ return pattern == null ? converter.valueType().getName() : pattern;
+ }
+
+ @Override
+ List defaultValues() {
+ return unmodifiableList( defaultValues );
+ }
+
+ @Override
+ public boolean equals( Object that ) {
+ if ( !super.equals( that ) )
+ return false;
+
+ ArgumentAcceptingOptionSpec> other = (ArgumentAcceptingOptionSpec>) that;
+ return requiresArgument() == other.requiresArgument();
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() ^ ( argumentRequired ? 0 : 1 );
+ }
+}
diff --git a/src/joptsimple/ArgumentList.java b/src/joptsimple/ArgumentList.java
new file mode 100644
index 000000000..53c04224b
--- /dev/null
+++ b/src/joptsimple/ArgumentList.java
@@ -0,0 +1,60 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import static joptsimple.ParserRules.*;
+
+/**
+ * Wrapper for an array of command line arguments.
+ *
+ * @author Paul Holser
+ * @version $Id: ArgumentList.java,v 1.7 2009/08/13 00:34:35 pholser Exp $
+ */
+class ArgumentList {
+ private final String[] arguments;
+ private int currentIndex;
+
+ ArgumentList( String... arguments ) {
+ this.arguments = arguments.clone();
+ }
+
+ boolean hasMore() {
+ return currentIndex < arguments.length;
+ }
+
+ String next() {
+ return arguments[ currentIndex++ ];
+ }
+
+ String peek() {
+ return arguments[ currentIndex ];
+ }
+
+ void treatNextAsLongOption() {
+ if ( HYPHEN_CHAR != arguments[ currentIndex ].charAt( 0 ) )
+ arguments[ currentIndex ] = DOUBLE_HYPHEN + arguments[ currentIndex ];
+ }
+}
diff --git a/src/joptsimple/HelpFormatter.java b/src/joptsimple/HelpFormatter.java
new file mode 100644
index 000000000..e04ea38b1
--- /dev/null
+++ b/src/joptsimple/HelpFormatter.java
@@ -0,0 +1,149 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import joptsimple.internal.ColumnarData;
+import static joptsimple.ParserRules.*;
+import static joptsimple.internal.Classes.*;
+import static joptsimple.internal.Strings.*;
+
+/**
+ * Produces text for a help screen given a set of options.
+ *
+ * @author Paul Holser
+ * @version $Id: HelpFormatter.java,v 1.19 2009/10/04 00:13:41 pholser Exp $
+ */
+class HelpFormatter implements OptionSpecVisitor {
+ private final ColumnarData grid;
+
+ HelpFormatter() {
+ grid = new ColumnarData( "Option", "Description" );
+ }
+
+ String format( Map> options ) {
+ if ( options.isEmpty() )
+ return "No options specified";
+
+ grid.clear();
+
+ Comparator> comparator =
+ new Comparator>() {
+ public int compare( AbstractOptionSpec> first, AbstractOptionSpec> second ) {
+ return first.options().iterator().next().compareTo( second.options().iterator().next() );
+ }
+ };
+
+ Set> sorted = new TreeSet>( comparator );
+ sorted.addAll( options.values() );
+
+ for ( AbstractOptionSpec> each : sorted )
+ each.accept( this );
+
+ return grid.format();
+ }
+
+ void addHelpLineFor( AbstractOptionSpec> spec, String additionalInfo ) {
+ grid.addRow( createOptionDisplay( spec ) + additionalInfo, createDescriptionDisplay( spec ) );
+ }
+
+ public void visit( NoArgumentOptionSpec spec ) {
+ addHelpLineFor( spec, "" );
+ }
+
+ public void visit( RequiredArgumentOptionSpec> spec ) {
+ visit( spec, '<', '>' );
+ }
+
+ public void visit( OptionalArgumentOptionSpec> spec ) {
+ visit( spec, '[', ']' );
+ }
+
+ public void visit( AlternativeLongOptionSpec spec ) {
+ addHelpLineFor( spec, ' ' + surround( spec.argumentDescription(), '<', '>' ) );
+ }
+
+ private void visit( ArgumentAcceptingOptionSpec> spec, char begin, char end ) {
+ String argDescription = spec.argumentDescription();
+ String typeIndicator = typeIndicator( spec );
+ StringBuilder collector = new StringBuilder();
+
+ if ( typeIndicator.length() > 0 ) {
+ collector.append( typeIndicator );
+
+ if ( argDescription.length() > 0 )
+ collector.append( ": " ).append( argDescription );
+ }
+ else if ( argDescription.length() > 0 )
+ collector.append( argDescription );
+
+ String helpLine = collector.length() == 0
+ ? ""
+ : ' ' + surround( collector.toString(), begin, end );
+ addHelpLineFor( spec, helpLine );
+ }
+
+ private String createOptionDisplay( AbstractOptionSpec> spec ) {
+ StringBuilder buffer = new StringBuilder();
+
+ for ( Iterator iter = spec.options().iterator(); iter.hasNext(); ) {
+ String option = iter.next();
+ buffer.append( option.length() > 1 ? DOUBLE_HYPHEN : HYPHEN );
+ buffer.append( option );
+
+ if ( iter.hasNext() )
+ buffer.append( ", " );
+ }
+
+ return buffer.toString();
+ }
+
+ private String createDescriptionDisplay( AbstractOptionSpec> spec ) {
+ List> defaultValues = spec.defaultValues();
+ if ( defaultValues.isEmpty() )
+ return spec.description();
+
+ String defaultValuesDisplay = createDefaultValuesDisplay( defaultValues );
+ return spec.description() + ' ' + surround( "default: " + defaultValuesDisplay, '(', ')' );
+ }
+
+ private String createDefaultValuesDisplay( List> defaultValues ) {
+ return defaultValues.size() == 1 ? defaultValues.get( 0 ).toString() : defaultValues.toString();
+ }
+
+ private static String typeIndicator( ArgumentAcceptingOptionSpec> spec ) {
+ String indicator = spec.typeIndicator();
+ return indicator == null || String.class.getName().equals( indicator )
+ ? ""
+ : shortNameOf( indicator );
+ }
+}
diff --git a/src/joptsimple/IllegalOptionClusterException.java b/src/joptsimple/IllegalOptionClusterException.java
new file mode 100644
index 000000000..09a40abb7
--- /dev/null
+++ b/src/joptsimple/IllegalOptionClusterException.java
@@ -0,0 +1,48 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import static java.util.Collections.*;
+
+/**
+ * Thrown when the option parser discovers a cluster of short options in which
+ * at least one of the short options can accept arguments.
+ *
+ * @author Paul Holser
+ * @version $Id: IllegalOptionClusterException.java,v 1.11 2009/10/25 18:37:06 pholser Exp $
+ */
+class IllegalOptionClusterException extends OptionException {
+ private static final long serialVersionUID = -1L;
+
+ IllegalOptionClusterException( String option ) {
+ super( singletonList( option ) );
+ }
+
+ @Override
+ public String getMessage() {
+ return "Option cluster containing " + singleOptionMessage() + " is illegal";
+ }
+}
diff --git a/src/joptsimple/IllegalOptionSpecificationException.java b/src/joptsimple/IllegalOptionSpecificationException.java
new file mode 100644
index 000000000..677dec777
--- /dev/null
+++ b/src/joptsimple/IllegalOptionSpecificationException.java
@@ -0,0 +1,48 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import static java.util.Collections.*;
+
+/**
+ * Thrown when the option parser is asked to recognize an option with illegal
+ * characters in it.
+ *
+ * @author Paul Holser
+ * @version $Id: IllegalOptionSpecificationException.java,v 1.11 2009/10/25 18:37:06 pholser Exp $
+ */
+class IllegalOptionSpecificationException extends OptionException {
+ private static final long serialVersionUID = -1L;
+
+ IllegalOptionSpecificationException( String option ) {
+ super( singletonList( option ) );
+ }
+
+ @Override
+ public String getMessage() {
+ return singleOptionMessage() + " is not a legal option character";
+ }
+}
diff --git a/src/joptsimple/MultipleArgumentsForOptionException.java b/src/joptsimple/MultipleArgumentsForOptionException.java
new file mode 100644
index 000000000..69105c3bd
--- /dev/null
+++ b/src/joptsimple/MultipleArgumentsForOptionException.java
@@ -0,0 +1,48 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.util.Collection;
+
+/**
+ * Thrown when asking an {@link OptionSet} for a single argument of an option when
+ * many have been specified.
+ *
+ * @author Paul Holser
+ * @version $Id: MultipleArgumentsForOptionException.java,v 1.14 2009/10/25 18:37:06 pholser Exp $
+ */
+class MultipleArgumentsForOptionException extends OptionException {
+ private static final long serialVersionUID = -1L;
+
+ MultipleArgumentsForOptionException( Collection options ) {
+ super( options );
+ }
+
+ @Override
+ public String getMessage() {
+ return "Found multiple arguments for option " + multipleOptionMessage() + ", but you asked for only one";
+ }
+}
diff --git a/src/joptsimple/NoArgumentOptionSpec.java b/src/joptsimple/NoArgumentOptionSpec.java
new file mode 100644
index 000000000..69685ae3e
--- /dev/null
+++ b/src/joptsimple/NoArgumentOptionSpec.java
@@ -0,0 +1,79 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.util.Collection;
+import java.util.List;
+
+import static java.util.Collections.*;
+
+/**
+ * A specification for an option that does not accept arguments.
+ *
+ * @author Paul Holser
+ * @version $Id: NoArgumentOptionSpec.java,v 1.16 2009/10/04 00:13:41 pholser Exp $
+ */
+class NoArgumentOptionSpec extends AbstractOptionSpec {
+ NoArgumentOptionSpec( String option ) {
+ this( singletonList( option ), "" );
+ }
+
+ NoArgumentOptionSpec( Collection options, String description ) {
+ super( options, description );
+ }
+
+ @Override
+ void handleOption( OptionParser parser, ArgumentList arguments,
+ OptionSet detectedOptions, String detectedArgument ) {
+
+ detectedOptions.add( this );
+ }
+
+ @Override
+ boolean acceptsArguments() {
+ return false;
+ }
+
+ @Override
+ boolean requiresArgument() {
+ return false;
+ }
+
+ @Override
+ void accept( OptionSpecVisitor visitor ) {
+ visitor.visit( this );
+ }
+
+ @Override
+ protected Void convert( String argument ) {
+ return null;
+ }
+
+ @Override
+ List defaultValues() {
+ return emptyList();
+ }
+}
diff --git a/src/joptsimple/OptionArgumentConversionException.java b/src/joptsimple/OptionArgumentConversionException.java
new file mode 100644
index 000000000..9defed612
--- /dev/null
+++ b/src/joptsimple/OptionArgumentConversionException.java
@@ -0,0 +1,56 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.util.Collection;
+
+/**
+ * Thrown when a problem occurs converting an argument of an option from {@link String}
+ * to another type.
+ *
+ * @author Paul Holser
+ * @version $Id: OptionArgumentConversionException.java,v 1.16 2009/10/25 18:37:06 pholser Exp $
+ */
+class OptionArgumentConversionException extends OptionException {
+ private static final long serialVersionUID = -1L;
+
+ private final String argument;
+ private final Class> valueType;
+
+ OptionArgumentConversionException( Collection options, String argument, Class> valueType,
+ Throwable cause ) {
+
+ super( options, cause );
+
+ this.argument = argument;
+ this.valueType = valueType;
+ }
+
+ @Override
+ public String getMessage() {
+ return "Cannot convert argument '" + argument + "' of option " + multipleOptionMessage() + " to " + valueType;
+ }
+}
diff --git a/src/joptsimple/OptionException.java b/src/joptsimple/OptionException.java
new file mode 100644
index 000000000..3d9d0cc2d
--- /dev/null
+++ b/src/joptsimple/OptionException.java
@@ -0,0 +1,95 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import static java.util.Collections.*;
+
+import static joptsimple.internal.Strings.*;
+
+/**
+ * Thrown when a problem occurs during option parsing.
+ *
+ * @author Paul Holser
+ * @version $Id: OptionException.java,v 1.21 2009/08/13 00:34:35 pholser Exp $
+ */
+public abstract class OptionException extends RuntimeException {
+ private static final long serialVersionUID = -1L;
+
+ private final List options = new ArrayList();
+
+ protected OptionException( Collection options ) {
+ this.options.addAll( options );
+ }
+
+ protected OptionException( Collection options, Throwable cause ) {
+ super( cause );
+
+ this.options.addAll( options );
+ }
+
+ /**
+ * Gives the option being considered when the exception was created.
+ *
+ * @return the option being considered when the exception was created
+ */
+ public Collection options() {
+ return unmodifiableCollection( options );
+ }
+
+ protected final String singleOptionMessage() {
+ return singleOptionMessage( options.get( 0 ) );
+ }
+
+ protected final String singleOptionMessage( String option ) {
+ return SINGLE_QUOTE + option + SINGLE_QUOTE;
+ }
+
+ protected final String multipleOptionMessage() {
+ StringBuilder buffer = new StringBuilder( "[" );
+
+ for ( Iterator iter = options.iterator(); iter.hasNext(); ) {
+ buffer.append( singleOptionMessage( iter.next() ) );
+ if ( iter.hasNext() )
+ buffer.append( ", " );
+ }
+
+ buffer.append( ']' );
+
+ return buffer.toString();
+ }
+
+ static OptionException illegalOptionCluster( String option ) {
+ return new IllegalOptionClusterException( option );
+ }
+
+ static OptionException unrecognizedOption( String option ) {
+ return new UnrecognizedOptionException( option );
+ }
+}
diff --git a/src/joptsimple/OptionMissingRequiredArgumentException.java b/src/joptsimple/OptionMissingRequiredArgumentException.java
new file mode 100644
index 000000000..201ee9d44
--- /dev/null
+++ b/src/joptsimple/OptionMissingRequiredArgumentException.java
@@ -0,0 +1,48 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.util.Collection;
+
+/**
+ * Thrown when the option parser discovers an option that requires an argument,
+ * but that argument is missing.
+ *
+ * @author Paul Holser
+ * @version $Id: OptionMissingRequiredArgumentException.java,v 1.12 2009/10/25 18:37:06 pholser Exp $
+ */
+class OptionMissingRequiredArgumentException extends OptionException {
+ private static final long serialVersionUID = -1L;
+
+ OptionMissingRequiredArgumentException( Collection options ) {
+ super( options );
+ }
+
+ @Override
+ public String getMessage() {
+ return "Option " + multipleOptionMessage() + " requires an argument";
+ }
+}
diff --git a/src/joptsimple/OptionParser.java b/src/joptsimple/OptionParser.java
new file mode 100644
index 000000000..c0029e5b2
--- /dev/null
+++ b/src/joptsimple/OptionParser.java
@@ -0,0 +1,496 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Collections.*;
+
+import joptsimple.internal.AbbreviationMap;
+import joptsimple.util.KeyValuePair;
+import static joptsimple.OptionException.*;
+import static joptsimple.OptionParserState.*;
+import static joptsimple.ParserRules.*;
+
+/**
+ * Parses command line arguments, using a syntax that attempts to take from the best
+ * of POSIX {@code getopt()} and GNU {@code getopt_long()}.
+ *
+ * This parser supports short options and long options.
+ *
+ *
+ *
+ * There are two ways to tell the parser what options to recognize:
+ *
+ *
+ * - A "fluent interface"-style API for specifying options, available since
+ * version 2. Sentences in this fluent interface language begin with a call to
+ * {@link #accepts(String) accepts} or {@link #acceptsAll(Collection) acceptsAll}
+ * methods; calls on the ensuing chain of objects describe whether the options can take
+ * an argument, whether the argument is required or optional, to what type arguments of
+ * the options should be converted if any, etc. Since version 3, these calls return
+ * an instance of {@link OptionSpec}, which can subsequently be used to retrieve the
+ * arguments of the associated option in a type-safe manner.
+ *
+ * - Since version 1, a more concise way of specifying short options has been to use
+ * the special {@linkplain #OptionParser(String) constructor}. Arguments of options
+ * specified in this manner will be of type {@link String}. Here are the rules for the
+ * format of the specification strings this constructor accepts:
+ *
+ *
+ * - Any letter or digit is treated as an option character.
+ *
+ * - If an option character is followed by a single colon (":"),
+ * then the option requires an argument.
+ *
+ * - If an option character is followed by two colons ("::"), then
+ * the option accepts an optional argument.
+ *
+ * - Otherwise, the option character accepts no argument.
+ *
+ * - If the option specification string begins with a plus sign ("+"),
+ * the parser will behave "POSIX-ly correct".
+ *
+ * - If the option specification string contains the sequence "W;"
+ * (capital W followed by a semicolon), the parser will recognize the alternative
+ * form of long options.
+ *
+ *
+ *
+ *
+ * Each of the options in a list of options given to {@link #acceptsAll(Collection)
+ * acceptsAll} is treated as a synonym of the others. For example:
+ *
+ *
+ * OptionParser parser = new OptionParser();
+ * parser.acceptsAll( asList( "w", "interactive", "confirmation" ) );
+ * OptionSet options = parser.parse( "-w" );
+ *
+ *
+ * In this case, options.{@link OptionSet#has(String) has}
would answer
+ * {@code true} when given arguments "w", "interactive", and
+ * "confirmation". The {@link OptionSet} would give the same responses to
+ * these arguments for its other methods as well.
+ *
+ * By default, as with GNU {@code getopt()}, the parser allows intermixing of options
+ * and non-options. If, however, the parser has been created to be "POSIX-ly correct",
+ * then the first argument that does not look lexically like an option, and is not a
+ * required argument of a preceding option, signals the end of options. You can still
+ * bind optional arguments to their options using the abutting (for short options) or
+ * = syntax.
+ *
+ * Unlike GNU {@code getopt()}, this parser does not honor the environment variable
+ * {@code POSIXLY_CORRECT}. "POSIX-ly correct" parsers are configured by either:
+ *
+ *
+ * - using the method {@link #posixlyCorrect(boolean)}, or
+ *
+ * - using the {@linkplain #OptionParser(String) constructor} with an argument whose
+ * first character is a plus sign ("+")
+ *
+ *
+ * @author Paul Holser
+ * @version $Id: OptionParser.java,v 1.38 2009/10/25 18:37:06 pholser Exp $
+ * @see The GNU C Library
+ */
+public class OptionParser {
+ private final AbbreviationMap> recognizedOptions;
+ private OptionParserState state;
+ private boolean posixlyCorrect;
+
+ /**
+ * Creates an option parser that initially recognizes no options, and does not
+ * exhibit "POSIX-ly correct" behavior.
+ */
+ public OptionParser() {
+ recognizedOptions = new AbbreviationMap>();
+ state = moreOptions( false );
+ }
+
+ /**
+ * Creates an option parser and configures it to recognize the short options
+ * specified in the given string.
+ *
+ * Arguments of options specified this way will be of type {@link String}.
+ *
+ * @param optionSpecification an option specification
+ * @throws NullPointerException if optionSpecification is
+ * {@code null}
+ * @throws OptionException if the option specification contains illegal characters
+ * or otherwise cannot be recognized
+ */
+ public OptionParser( String optionSpecification ) {
+ this();
+
+ new OptionSpecTokenizer( optionSpecification ).configure( this );
+ }
+
+ /**
+ * Tells the parser to recognize the given option.
+ *
+ * This method returns an instance of {@link OptionSpecBuilder} to allow the
+ * formation of parser directives as sentences in a fluent interface language.
+ * For example:
+ *
+ *
+ * OptionParser parser = new OptionParser();
+ * parser.accepts( "c" ).withRequiredArg().ofType( Integer.class );
+ *
+ *
+ * If no methods are invoked on the returned {@link OptionSpecBuilder}, then the
+ * parser treats the option as accepting no argument.
+ *
+ * @param option the option to recognize
+ * @return an object that can be used to flesh out more detail about the option
+ * @throws OptionException if the option contains illegal characters
+ * @throws NullPointerException if the option is {@code null}
+ */
+ public OptionSpecBuilder accepts( String option ) {
+ return acceptsAll( singletonList( option ) );
+ }
+
+ /**
+ * Tells the parser to recognize the given option.
+ *
+ * @see #accepts(String)
+ * @param option the option to recognize
+ * @param description a string that describes the purpose of the option. This is
+ * used when generating help information about the parser.
+ * @return an object that can be used to flesh out more detail about the option
+ * @throws OptionException if the option contains illegal characters
+ * @throws NullPointerException if the option is {@code null}
+ */
+ public OptionSpecBuilder accepts( String option, String description ) {
+ return acceptsAll( singletonList( option ), description );
+ }
+
+ /**
+ * Tells the parser to recognize the given options, and treat them as
+ * synonymous.
+ *
+ * @see #accepts(String)
+ * @param options the options to recognize and treat as synonymous
+ * @return an object that can be used to flesh out more detail about the options
+ * @throws OptionException if any of the options contain illegal characters
+ * @throws NullPointerException if the option list or any of its elements are
+ * {@code null}
+ */
+ public OptionSpecBuilder acceptsAll( Collection options ) {
+ return acceptsAll( options, "" );
+ }
+
+ /**
+ * Tells the parser to recognize the given options, and treat them as
+ * synonymous.
+ *
+ * @see #acceptsAll(Collection)
+ * @param options the options to recognize and treat as synonymous
+ * @param description a string that describes the purpose of the option. This is
+ * used when generating help information about the parser.
+ * @return an object that can be used to flesh out more detail about the options
+ * @throws OptionException if any of the options contain illegal characters
+ * @throws NullPointerException if the option list or any of its elements are
+ * {@code null}
+ * @throws IllegalArgumentException if the option list is empty
+ */
+ public OptionSpecBuilder acceptsAll( Collection options,
+ String description ) {
+
+ if ( options.isEmpty() )
+ throw new IllegalArgumentException( "need at least one option" );
+
+ ensureLegalOptions( options );
+
+ return new OptionSpecBuilder( this, options, description );
+ }
+
+ /**
+ * Tells the parser whether or not to behave "POSIX-ly correct"-ly.
+ *
+ * @param setting {@code true} if the parser should behave "POSIX-ly correct"-ly
+ */
+ public void posixlyCorrect( boolean setting ) {
+ posixlyCorrect = setting;
+ state = moreOptions( setting );
+ }
+
+ boolean posixlyCorrect() {
+ return posixlyCorrect;
+ }
+
+ /**
+ * Tells the parser either to recognize or ignore "-W"-style long
+ * options.
+ *
+ * @param recognize {@code true} if the parser is to recognize the special style
+ * of long options
+ */
+ public void recognizeAlternativeLongOptions( boolean recognize ) {
+ if ( recognize )
+ recognize( new AlternativeLongOptionSpec() );
+ else
+ recognizedOptions.remove( String.valueOf( RESERVED_FOR_EXTENSIONS ) );
+ }
+
+ void recognize( AbstractOptionSpec> spec ) {
+ recognizedOptions.putAll( spec.options(), spec );
+ }
+
+ /**
+ * Writes information about the options this parser recognizes to the given output
+ * sink.
+ *
+ * The output sink is flushed, but not closed.
+ *
+ * @param sink the sink to write information to
+ * @throws IOException if there is a problem writing to the sink
+ * @throws NullPointerException if sink is {@code null}
+ * @see #printHelpOn(Writer)
+ */
+ public void printHelpOn( OutputStream sink ) throws IOException {
+ printHelpOn( new OutputStreamWriter( sink ) );
+ }
+
+ /**
+ * Writes information about the options this parser recognizes to the given output
+ * sink.
+ *
+ * The output sink is flushed, but not closed.
+ *
+ * @param sink the sink to write information to
+ * @throws IOException if there is a problem writing to the sink
+ * @throws NullPointerException if sink is {@code null}
+ * @see #printHelpOn(OutputStream)
+ */
+ public void printHelpOn( Writer sink ) throws IOException {
+ sink.write( new HelpFormatter().format( recognizedOptions.toJavaUtilMap() ) );
+ sink.flush();
+ }
+
+ /**
+ * Parses the given command line arguments according to the option specifications
+ * given to the parser.
+ *
+ * @param arguments arguments to parse
+ * @return an {@link OptionSet} describing the parsed options, their arguments, and
+ * any non-option arguments found
+ * @throws OptionException if problems are detected while parsing
+ * @throws NullPointerException if the argument list is {@code null}
+ */
+ public OptionSet parse( String... arguments ) {
+ ArgumentList argumentList = new ArgumentList( arguments );
+ OptionSet detected = new OptionSet( defaultValues() );
+
+ while ( argumentList.hasMore() )
+ state.handleArgument( this, argumentList, detected );
+
+ reset();
+ return detected;
+ }
+
+ void handleLongOptionToken( String candidate, ArgumentList arguments, OptionSet detected ) {
+ KeyValuePair optionAndArgument = parseLongOptionWithArgument( candidate );
+
+ if ( !isRecognized( optionAndArgument.key ) )
+ throw unrecognizedOption( optionAndArgument.key );
+
+ AbstractOptionSpec> optionSpec = specFor( optionAndArgument.key );
+ optionSpec.handleOption( this, arguments, detected, optionAndArgument.value );
+ }
+
+ void handleShortOptionToken( String candidate, ArgumentList arguments, OptionSet detected ) {
+ KeyValuePair optionAndArgument = parseShortOptionWithArgument( candidate );
+
+ if ( isRecognized( optionAndArgument.key ) ) {
+ specFor( optionAndArgument.key ).handleOption( this, arguments, detected, optionAndArgument.value );
+ }
+ else
+ handleShortOptionCluster( candidate, arguments, detected );
+ }
+
+ private void handleShortOptionCluster( String candidate, ArgumentList arguments, OptionSet detected ) {
+ char[] options = extractShortOptionsFrom( candidate );
+ validateOptionCharacters( options );
+
+ AbstractOptionSpec> optionSpec = specFor( options[ 0 ] );
+
+ if ( optionSpec.acceptsArguments() && options.length > 1 ) {
+ String detectedArgument = String.valueOf( options, 1, options.length - 1 );
+ optionSpec.handleOption( this, arguments, detected, detectedArgument );
+ }
+ else {
+ for ( char each : options )
+ specFor( each ).handleOption( this, arguments, detected, null );
+ }
+ }
+
+ void noMoreOptions() {
+ state = OptionParserState.noMoreOptions();
+ }
+
+ boolean looksLikeAnOption( String argument ) {
+ return isShortOptionToken( argument ) || isLongOptionToken( argument );
+ }
+
+ private boolean isRecognized( String option ) {
+ return recognizedOptions.contains( option );
+ }
+
+ private AbstractOptionSpec> specFor( char option ) {
+ return specFor( String.valueOf( option ) );
+ }
+
+ private AbstractOptionSpec> specFor( String option ) {
+ return recognizedOptions.get( option );
+ }
+
+ private void reset() {
+ state = moreOptions( posixlyCorrect );
+ }
+
+ private static char[] extractShortOptionsFrom( String argument ) {
+ char[] options = new char[ argument.length() - 1 ];
+ argument.getChars( 1, argument.length(), options, 0 );
+
+ return options;
+ }
+
+ private void validateOptionCharacters( char[] options ) {
+ for ( int i = 0; i < options.length; ++i ) {
+ String option = String.valueOf( options[ i ] );
+
+ if ( !isRecognized( option ) )
+ throw unrecognizedOption( option );
+
+ if ( specFor( option ).acceptsArguments() ) {
+ if ( i > 0 )
+ throw illegalOptionCluster( option );
+
+ // remainder of chars are the argument to the option at char 0
+ return;
+ }
+ }
+ }
+
+ private static KeyValuePair parseLongOptionWithArgument( String argument ) {
+ return KeyValuePair.valueOf( argument.substring( 2 ) );
+ }
+
+ private static KeyValuePair parseShortOptionWithArgument( String argument ) {
+ return KeyValuePair.valueOf( argument.substring( 1 ) );
+ }
+
+ private Map> defaultValues() {
+ Map> defaults = new HashMap>();
+ for ( Map.Entry> each : recognizedOptions.toJavaUtilMap().entrySet() )
+ defaults.put( each.getKey(), each.getValue().defaultValues() );
+ return defaults;
+ }
+}
diff --git a/src/joptsimple/OptionParserState.java b/src/joptsimple/OptionParserState.java
new file mode 100644
index 000000000..d011a25a8
--- /dev/null
+++ b/src/joptsimple/OptionParserState.java
@@ -0,0 +1,69 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import static joptsimple.ParserRules.*;
+
+/**
+ * Abstraction of parser state; mostly serves to model how a parser behaves depending
+ * on whether end-of-options has been detected.
+ *
+ * @author Paul Holser
+ * @version $Id: OptionParserState.java,v 1.8 2009/09/28 01:12:48 pholser Exp $
+ */
+abstract class OptionParserState {
+ static OptionParserState noMoreOptions() {
+ return new OptionParserState() {
+ @Override
+ protected void handleArgument( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions ) {
+ detectedOptions.addNonOptionArgument( arguments.next() );
+ }
+ };
+ }
+
+ static OptionParserState moreOptions( final boolean posixlyCorrect ) {
+ return new OptionParserState() {
+ @Override
+ protected void handleArgument( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions ) {
+ String candidate = arguments.next();
+ if ( isOptionTerminator( candidate ) )
+ parser.noMoreOptions();
+ else if ( isLongOptionToken( candidate ) )
+ parser.handleLongOptionToken( candidate, arguments, detectedOptions );
+ else if ( isShortOptionToken( candidate ) )
+ parser.handleShortOptionToken( candidate, arguments, detectedOptions );
+ else {
+ if ( posixlyCorrect )
+ parser.noMoreOptions();
+
+ detectedOptions.addNonOptionArgument( candidate );
+ }
+ }
+ };
+ }
+
+ protected abstract void handleArgument( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions );
+}
diff --git a/src/joptsimple/OptionSet.java b/src/joptsimple/OptionSet.java
new file mode 100644
index 000000000..e043893b4
--- /dev/null
+++ b/src/joptsimple/OptionSet.java
@@ -0,0 +1,301 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import static java.util.Collections.*;
+
+import static joptsimple.internal.Objects.*;
+
+/**
+ * Representation of a group of detected command line options, their arguments, and
+ * non-option arguments.
+ *
+ * @author Paul Holser
+ * @version $Id: OptionSet.java,v 1.26 2009/10/25 18:37:05 pholser Exp $
+ */
+public class OptionSet {
+ private final Map> detectedOptions;
+ private final Map, List> optionsToArguments;
+ private final List nonOptionArguments;
+ private final Map> defaultValues;
+
+ /**
+ * Package-private because clients don't create these.
+ */
+ OptionSet( Map> defaults ) {
+ detectedOptions = new HashMap>();
+ optionsToArguments = new IdentityHashMap, List>();
+ nonOptionArguments = new ArrayList();
+ defaultValues = new HashMap>( defaults );
+ }
+
+ /**
+ * Tells whether the given option was detected.
+ *
+ * @param option the option to search for
+ * @return {@code true} if the option was detected
+ * @see #has(OptionSpec)
+ */
+ public boolean has( String option ) {
+ return detectedOptions.containsKey( option );
+ }
+
+ /**
+ * Tells whether the given option was detected.
+ *
+ * This method recognizes only instances of options returned from the fluent
+ * interface methods.
+ *
+ * Specifying a {@linkplain ArgumentAcceptingOptionSpec#defaultsTo(Object, Object[])} default argument value}
+ * for an option does not cause this method to return {@code true} if the option was not detected on the command
+ * line.
+ *
+ * @param option the option to search for
+ * @return {@code true} if the option was detected
+ * @see #has(String)
+ */
+ public boolean has( OptionSpec> option ) {
+ return optionsToArguments.containsKey( option );
+ }
+
+ /**
+ * Tells whether there are any arguments associated with the given option.
+ *
+ * @param option the option to search for
+ * @return {@code true} if the option was detected and at least one argument was
+ * detected for the option
+ * @see #hasArgument(OptionSpec)
+ */
+ public boolean hasArgument( String option ) {
+ AbstractOptionSpec> spec = detectedOptions.get( option );
+ return spec != null && hasArgument( spec );
+ }
+
+ /**
+ * Tells whether there are any arguments associated with the given option.
+ *
+ * This method recognizes only instances of options returned from the fluent
+ * interface methods.
+ *
+ * Specifying a {@linkplain ArgumentAcceptingOptionSpec#defaultsTo(Object, Object[]) default argument value}
+ * for an option does not cause this method to return {@code true} if the option was not detected on the command
+ * line, or if the option can take an optional argument but did not have one on the command line.
+ *
+ * @param option the option to search for
+ * @return {@code true} if the option was detected and at least one argument was
+ * detected for the option
+ * @throws NullPointerException if {@code option} is {@code null}
+ * @see #hasArgument(String)
+ */
+ public boolean hasArgument( OptionSpec> option ) {
+ ensureNotNull( option );
+
+ List values = optionsToArguments.get( option );
+ return values != null && !values.isEmpty();
+ }
+
+ /**
+ * Gives the argument associated with the given option. If the option was given
+ * an argument type, the argument will take on that type; otherwise, it will be a
+ * {@link String}.
+ *
+ * Specifying a {@linkplain ArgumentAcceptingOptionSpec#defaultsTo(Object, Object[]) default argument value}
+ * for an option will cause this method to return that default value even if the option was not detected on the
+ * command line, or if the option can take an optional argument but did not have one on the command line.
+ *
+ * @param option the option to search for
+ * @return the argument of the given option; {@code null} if no argument is
+ * present, or that option was not detected
+ * @throws NullPointerException if {@code option} is {@code null}
+ * @throws OptionException if more than one argument was detected for the option
+ */
+ public Object valueOf( String option ) {
+ ensureNotNull( option );
+
+ AbstractOptionSpec> spec = detectedOptions.get( option );
+ if ( spec == null ) {
+ List> defaults = defaultValuesFor( option );
+ return defaults.isEmpty() ? null : defaults.get( 0 );
+ }
+
+ return valueOf( spec );
+ }
+
+ /**
+ * Gives the argument associated with the given option.
+ *
+ * This method recognizes only instances of options returned from the fluent
+ * interface methods.
+ *
+ * @param represents the type of the arguments the given option accepts
+ * @param option the option to search for
+ * @return the argument of the given option; {@code null} if no argument is
+ * present, or that option was not detected
+ * @throws OptionException if more than one argument was detected for the option
+ * @throws NullPointerException if {@code option} is {@code null}
+ * @throws ClassCastException if the arguments of this option are not of the
+ * expected type
+ */
+ public V valueOf( OptionSpec option ) {
+ ensureNotNull( option );
+
+ List values = valuesOf( option );
+ switch ( values.size() ) {
+ case 0:
+ return null;
+ case 1:
+ return values.get( 0 );
+ default:
+ throw new MultipleArgumentsForOptionException( option.options() );
+ }
+ }
+
+ /**
+ * Gives any arguments associated with the given option. If the option was given
+ * an argument type, the arguments will take on that type; otherwise, they will be
+ * {@link String}s.
+ *
+ * @param option the option to search for
+ * @return the arguments associated with the option, as a list of objects of the
+ * type given to the arguments; an empty list if no such arguments are present, or if
+ * the option was not detected
+ * @throws NullPointerException if {@code option} is {@code null}
+ */
+ public List> valuesOf( String option ) {
+ ensureNotNull( option );
+
+ AbstractOptionSpec> spec = detectedOptions.get( option );
+ return spec == null ? defaultValuesFor( option ) : valuesOf( spec );
+ }
+
+ /**
+ * Gives any arguments associated with the given option. If the option was given
+ * an argument type, the arguments will take on that type; otherwise, they will be
+ * {@link String}s.
+ *
+ * This method recognizes only instances of options returned from the fluent
+ * interface methods.
+ *
+ * @param represents the type of the arguments the given option accepts
+ * @param option the option to search for
+ * @return the arguments associated with the option; an empty list if no such
+ * arguments are present, or if the option was not detected
+ * @throws NullPointerException if {@code option} is {@code null}
+ * @throws OptionException if there is a problem converting the option's arguments to
+ * the desired type; for example, if the type does not implement a correct conversion
+ * constructor or method
+ */
+ public List valuesOf( OptionSpec option ) {
+ ensureNotNull( option );
+
+ List values = optionsToArguments.get( option );
+ if ( values == null || values.isEmpty() )
+ return defaultValueFor( option );
+
+ AbstractOptionSpec spec = (AbstractOptionSpec) option;
+ List convertedValues = new ArrayList();
+ for ( String each : values )
+ convertedValues.add( spec.convert( each ) );
+
+ return unmodifiableList( convertedValues );
+ }
+
+ /**
+ * @return the detected non-option arguments
+ */
+ public List nonOptionArguments() {
+ return unmodifiableList( nonOptionArguments );
+ }
+
+ void add( AbstractOptionSpec> option ) {
+ addWithArgument( option, null );
+ }
+
+ void addWithArgument( AbstractOptionSpec> option, String argument ) {
+ for ( String each : option.options() )
+ detectedOptions.put( each, option );
+
+ List optionArguments = optionsToArguments.get( option );
+
+ if ( optionArguments == null ) {
+ optionArguments = new ArrayList();
+ optionsToArguments.put( option, optionArguments );
+ }
+
+ if ( argument != null )
+ optionArguments.add( argument );
+ }
+
+ void addNonOptionArgument( String argument ) {
+ nonOptionArguments.add( argument );
+ }
+
+ @Override
+ public boolean equals( Object that ) {
+ if ( this == that )
+ return true;
+
+ if ( that == null || !getClass().equals( that.getClass() ) )
+ return false;
+
+ OptionSet other = (OptionSet) that;
+ Map, List> thisOptionsToArguments =
+ new HashMap, List>( optionsToArguments );
+ Map, List> otherOptionsToArguments =
+ new HashMap, List>( other.optionsToArguments );
+ return detectedOptions.equals( other.detectedOptions )
+ && thisOptionsToArguments.equals( otherOptionsToArguments )
+ && nonOptionArguments.equals( other.nonOptionArguments() );
+ }
+
+ @Override
+ public int hashCode() {
+ Map, List> thisOptionsToArguments =
+ new HashMap, List>( optionsToArguments );
+ return detectedOptions.hashCode()
+ ^ thisOptionsToArguments.hashCode()
+ ^ nonOptionArguments.hashCode();
+ }
+
+ private List defaultValuesFor( String option ) {
+ if ( defaultValues.containsKey( option ) ) {
+ @SuppressWarnings( "unchecked" )
+ List defaults = (List) defaultValues.get( option );
+ return defaults;
+ }
+
+ return emptyList();
+ }
+
+ private List defaultValueFor( OptionSpec option ) {
+ return defaultValuesFor( option.options().iterator().next() );
+ }
+}
diff --git a/src/joptsimple/OptionSpec.java b/src/joptsimple/OptionSpec.java
new file mode 100644
index 000000000..c7032f80c
--- /dev/null
+++ b/src/joptsimple/OptionSpec.java
@@ -0,0 +1,95 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Describes options that an option parser recognizes.
+ *
+ * Instances of this interface are returned by the "fluent interface" methods to allow
+ * retrieval of option arguments in a type-safe manner. Here's an example:
+ *
+ * OptionParser parser = new OptionParser();
+ * OptionSpec<Integer> count =
+ * parser.accepts( "count" ).withRequiredArg().ofType( Integer.class );
+ * OptionSet options = parser.parse( "--count", "2" );
+ * assert options.has( count );
+ * int countValue = options.valueOf( count );
+ * assert countValue == count.value( options );
+ * List<Integer> countValues = options.valuesOf( count );
+ * assert countValues.equals( count.values( options ) );
+ *
+ *
+ * @param represents the type of the arguments this option accepts
+ * @author Paul Holser
+ * @version $Id: OptionSpec.java,v 1.25 2009/10/25 18:37:06 pholser Exp $
+ */
+public interface OptionSpec {
+ /**
+ * Gives any arguments associated with the given option in the given set of
+ * detected options.
+ *
+ * Specifying a {@linkplain ArgumentAcceptingOptionSpec#defaultsTo(Object, Object[]) default argument value}
+ * for this option will cause this method to return that default value even if this option was not detected on the
+ * command line, or if this option can take an optional argument but did not have one on the command line.
+ *
+ * @param detectedOptions the detected options to search in
+ * @return the arguments associated with this option; an empty list if no such
+ * arguments are present, or if this option was not detected
+ * @throws OptionException if there is a problem converting this option's arguments
+ * to the desired type; for example, if the type does not implement a correct
+ * conversion constructor or method
+ * @throws NullPointerException if {@code detectedOptions} is {@code null}
+ * @see OptionSet#valuesOf(OptionSpec)
+ */
+ List values( OptionSet detectedOptions );
+
+ /**
+ * Gives the argument associated with the given option in the given set of
+ * detected options.
+ *
+ * Specifying a {@linkplain ArgumentAcceptingOptionSpec#defaultsTo(Object, Object[]) default argument value}
+ * for this option will cause this method to return that default value even if this option was not detected on the
+ * command line, or if this option can take an optional argument but did not have one on the command line.
+ *
+ * @param detectedOptions the detected options to search in
+ * @return the argument of the this option; {@code null} if no argument is present,
+ * or that option was not detected
+ * @throws OptionException if more than one argument was detected for the option
+ * @throws NullPointerException if {@code detectedOptions} is {@code null}
+ * @throws ClassCastException if the arguments of this option are not of the
+ * expected type
+ * @see OptionSet#valueOf(OptionSpec)
+ */
+ V value( OptionSet detectedOptions );
+
+ /**
+ * @return the string representations of this option
+ */
+ Collection options();
+}
diff --git a/src/joptsimple/OptionSpecBuilder.java b/src/joptsimple/OptionSpecBuilder.java
new file mode 100644
index 000000000..c1095ef57
--- /dev/null
+++ b/src/joptsimple/OptionSpecBuilder.java
@@ -0,0 +1,101 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.util.Collection;
+
+/**
+ * Allows callers to specify whether a given option accepts arguments (required or
+ * optional).
+ *
+ * Instances are returned from {@link OptionParser#accepts(String)} to allow the
+ * formation of parser directives as sentences in a "fluent interface" language. For
+ * example:
+ *
+ *
+ * OptionParser parser = new OptionParser();
+ * parser.accepts( "c" ).withRequiredArg().ofType( Integer.class );
+ *
+ *
+ * If no methods are invoked on an instance of this class, then that instance's option
+ * will accept no argument.
+ *
+ * Note that you should not use the fluent interface clauses in a way that would
+ * defeat the typing of option arguments:
+ *
+ *
+ * OptionParser parser = new OptionParser();
+ * ArgumentAcceptingOptionSpec<String> optionC =
+ * parser.accepts( "c" ).withRequiredArg();
+ * optionC.ofType( Integer.class ); // DON'T THROW AWAY THE TYPE!
+ *
+ * String value = parser.parse( "-c", "2" ).valueOf( optionC ); // ClassCastException
+ *
+ *
+ * @author Paul Holser
+ * @version $Id: OptionSpecBuilder.java,v 1.19 2009/10/25 18:37:06 pholser Exp $
+ */
+public class OptionSpecBuilder extends NoArgumentOptionSpec {
+ private final OptionParser parser;
+
+ OptionSpecBuilder( OptionParser parser, Collection options, String description ) {
+ super( options, description );
+
+ this.parser = parser;
+ attachToParser();
+ }
+
+ private void attachToParser() {
+ parser.recognize( this );
+ }
+
+ /**
+ * Informs an option parser that this builder's option requires an argument.
+ *
+ * @return a specification for the option
+ */
+ public ArgumentAcceptingOptionSpec withRequiredArg() {
+ ArgumentAcceptingOptionSpec newSpec =
+ new RequiredArgumentOptionSpec( options(), description() );
+ parser.recognize( newSpec );
+
+ return newSpec;
+ }
+
+ /**
+ * Informs an option parser that this builder's option accepts an optional
+ * argument.
+ *
+ * @return a specification for the option
+ */
+ public ArgumentAcceptingOptionSpec withOptionalArg() {
+ ArgumentAcceptingOptionSpec newSpec =
+ new OptionalArgumentOptionSpec( options(), description() );
+ parser.recognize( newSpec );
+
+ return newSpec;
+ }
+}
diff --git a/src/joptsimple/OptionSpecTokenizer.java b/src/joptsimple/OptionSpecTokenizer.java
new file mode 100644
index 000000000..c83535b22
--- /dev/null
+++ b/src/joptsimple/OptionSpecTokenizer.java
@@ -0,0 +1,119 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.util.NoSuchElementException;
+
+import static joptsimple.ParserRules.*;
+
+/**
+ * Tokenizes a short option specification string.
+ *
+ * @author Paul Holser
+ * @version $Id: OptionSpecTokenizer.java,v 1.14 2009/10/25 18:37:06 pholser Exp $
+ */
+class OptionSpecTokenizer {
+ private static final char POSIXLY_CORRECT_MARKER = '+';
+
+ private String specification;
+ private int index;
+
+ OptionSpecTokenizer( String specification ) {
+ if ( specification == null )
+ throw new NullPointerException( "null option specification" );
+
+ this.specification = specification;
+ }
+
+ boolean hasMore() {
+ return index < specification.length();
+ }
+
+ AbstractOptionSpec> next() {
+ if ( !hasMore() )
+ throw new NoSuchElementException();
+
+
+ String optionCandidate = String.valueOf( specification.charAt( index ) );
+ index++;
+
+ AbstractOptionSpec> spec;
+ if ( RESERVED_FOR_EXTENSIONS.equals( optionCandidate ) ) {
+ spec = handleReservedForExtensionsToken();
+
+ if ( spec != null )
+ return spec;
+ }
+
+ ensureLegalOption( optionCandidate );
+
+ if ( hasMore() )
+ spec = specification.charAt( index ) == ':'
+ ? handleArgumentAcceptingOption( optionCandidate )
+ : new NoArgumentOptionSpec( optionCandidate );
+ else
+ spec = new NoArgumentOptionSpec( optionCandidate );
+
+ return spec;
+ }
+
+ void configure( OptionParser parser ) {
+ adjustForPosixlyCorrect( parser );
+
+ while ( hasMore() )
+ parser.recognize( next() );
+ }
+
+ private void adjustForPosixlyCorrect( OptionParser parser ) {
+ if ( POSIXLY_CORRECT_MARKER == specification.charAt( 0 ) ) {
+ parser.posixlyCorrect( true );
+ specification = specification.substring( 1 );
+ }
+ }
+
+ private AbstractOptionSpec> handleReservedForExtensionsToken() {
+ if ( !hasMore() )
+ return new NoArgumentOptionSpec( RESERVED_FOR_EXTENSIONS );
+
+ if ( specification.charAt( index ) == ';' ) {
+ ++index;
+ return new AlternativeLongOptionSpec();
+ }
+
+ return null;
+ }
+
+ private AbstractOptionSpec> handleArgumentAcceptingOption( String candidate ) {
+ index++;
+
+ if ( hasMore() && specification.charAt( index ) == ':' ) {
+ index++;
+ return new OptionalArgumentOptionSpec( candidate );
+ }
+
+ return new RequiredArgumentOptionSpec( candidate );
+ }
+}
diff --git a/src/joptsimple/OptionSpecVisitor.java b/src/joptsimple/OptionSpecVisitor.java
new file mode 100644
index 000000000..ff88bad7a
--- /dev/null
+++ b/src/joptsimple/OptionSpecVisitor.java
@@ -0,0 +1,42 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+/**
+ * Visitor interface for option specifications.
+ *
+ * @author Paul Holser
+ * @version $Id: OptionSpecVisitor.java,v 1.6 2009/08/13 00:34:35 pholser Exp $
+ */
+interface OptionSpecVisitor {
+ void visit( NoArgumentOptionSpec spec );
+
+ void visit( RequiredArgumentOptionSpec> spec );
+
+ void visit( OptionalArgumentOptionSpec> spec );
+
+ void visit( AlternativeLongOptionSpec spec );
+}
diff --git a/src/joptsimple/OptionalArgumentOptionSpec.java b/src/joptsimple/OptionalArgumentOptionSpec.java
new file mode 100644
index 000000000..bf572ce45
--- /dev/null
+++ b/src/joptsimple/OptionalArgumentOptionSpec.java
@@ -0,0 +1,75 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.util.Collection;
+
+/**
+ * Specification of an option that accepts an optional argument.
+ *
+ * @param represents the type of the arguments this option accepts
+ * @author Paul Holser
+ * @version $Id: OptionalArgumentOptionSpec.java,v 1.17 2009/09/28 01:12:48 pholser Exp $
+ */
+class OptionalArgumentOptionSpec extends ArgumentAcceptingOptionSpec {
+ OptionalArgumentOptionSpec( String option ) {
+ super( option, false );
+ }
+
+ OptionalArgumentOptionSpec( Collection options, String description ) {
+ super( options, false, description );
+ }
+
+ @Override
+ protected void detectOptionArgument( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions ) {
+ if ( arguments.hasMore() ) {
+ String nextArgument = arguments.peek();
+
+ if ( !parser.looksLikeAnOption( nextArgument ) )
+ handleOptionArgument( parser, detectedOptions, arguments );
+ else if ( isArgumentOfNumberType() && canConvertArgument( nextArgument ) )
+ addArguments( detectedOptions, arguments.next() );
+ else
+ detectedOptions.add( this );
+ }
+ else
+ detectedOptions.add( this );
+ }
+
+ private void handleOptionArgument( OptionParser parser, OptionSet detectedOptions, ArgumentList arguments ) {
+ if ( parser.posixlyCorrect() ) {
+ detectedOptions.add( this );
+ parser.noMoreOptions();
+ }
+ else
+ addArguments( detectedOptions, arguments.next() );
+ }
+
+ @Override
+ void accept( OptionSpecVisitor visitor ) {
+ visitor.visit( this );
+ }
+}
diff --git a/src/joptsimple/ParserRules.java b/src/joptsimple/ParserRules.java
new file mode 100644
index 000000000..83799787b
--- /dev/null
+++ b/src/joptsimple/ParserRules.java
@@ -0,0 +1,88 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.util.Collection;
+import static java.lang.Character.*;
+
+/**
+ * Can tell whether or not options are well-formed.
+ *
+ * @author Paul Holser
+ * @version $Id: ParserRules.java,v 1.14 2009/09/23 00:39:14 pholser Exp $
+ */
+final class ParserRules {
+ static final char HYPHEN_CHAR = '-';
+ static final String HYPHEN = String.valueOf( HYPHEN_CHAR );
+ static final String DOUBLE_HYPHEN = "--";
+ static final String OPTION_TERMINATOR = DOUBLE_HYPHEN;
+ static final String RESERVED_FOR_EXTENSIONS = "W";
+
+ static {
+ new ParserRules();
+ }
+
+ private ParserRules() {
+ // nothing to do here
+ }
+
+ static boolean isShortOptionToken( String argument ) {
+ return argument.startsWith( HYPHEN )
+ && !HYPHEN.equals( argument )
+ && !isLongOptionToken( argument );
+ }
+
+ static boolean isLongOptionToken( String argument ) {
+ return argument.startsWith( DOUBLE_HYPHEN ) && !isOptionTerminator( argument );
+ }
+
+ static boolean isOptionTerminator( String argument ) {
+ return OPTION_TERMINATOR.equals( argument );
+ }
+
+ static void ensureLegalOption( String option ) {
+ if ( option.startsWith( HYPHEN ) )
+ throw new IllegalOptionSpecificationException( String.valueOf( option ) );
+
+ for ( int i = 0; i < option.length(); ++i )
+ ensureLegalOptionCharacter( option.charAt( i ) );
+ }
+
+ static void ensureLegalOptions( Collection options ) {
+ for ( String each : options )
+ ensureLegalOption( each );
+ }
+
+ private static void ensureLegalOptionCharacter( char option ) {
+ if ( !( isLetterOrDigit( option ) || isAllowedPunctuation( option ) ) )
+ throw new IllegalOptionSpecificationException( String.valueOf( option ) );
+ }
+
+ private static boolean isAllowedPunctuation( char option ) {
+ String allowedPunctuation = "?." + HYPHEN_CHAR;
+ return allowedPunctuation.indexOf( option ) != -1;
+ }
+}
diff --git a/src/joptsimple/RequiredArgumentOptionSpec.java b/src/joptsimple/RequiredArgumentOptionSpec.java
new file mode 100644
index 000000000..ba5d296a6
--- /dev/null
+++ b/src/joptsimple/RequiredArgumentOptionSpec.java
@@ -0,0 +1,58 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import java.util.Collection;
+
+/**
+ * Specification of an option that accepts a required argument.
+ *
+ * @param represents the type of the arguments this option accepts
+ * @author Paul Holser
+ * @version $Id: RequiredArgumentOptionSpec.java,v 1.16 2009/09/28 01:12:48 pholser Exp $
+ */
+class RequiredArgumentOptionSpec extends ArgumentAcceptingOptionSpec {
+ RequiredArgumentOptionSpec( String option ) {
+ super( option, true );
+ }
+
+ RequiredArgumentOptionSpec( Collection options, String description ) {
+ super( options, true, description );
+ }
+
+ @Override
+ protected void detectOptionArgument( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions ) {
+ if ( !arguments.hasMore() )
+ throw new OptionMissingRequiredArgumentException( options() );
+
+ addArguments( detectedOptions, arguments.next() );
+ }
+
+ @Override
+ void accept( OptionSpecVisitor visitor ) {
+ visitor.visit( this );
+ }
+}
diff --git a/src/joptsimple/UnrecognizedOptionException.java b/src/joptsimple/UnrecognizedOptionException.java
new file mode 100644
index 000000000..d1862d1c8
--- /dev/null
+++ b/src/joptsimple/UnrecognizedOptionException.java
@@ -0,0 +1,47 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+import static java.util.Collections.*;
+
+/**
+ * Thrown when the option parser encounters an unrecognized option.
+ *
+ * @author Paul Holser
+ * @version $Id: UnrecognizedOptionException.java,v 1.10 2009/10/25 18:37:06 pholser Exp $
+ */
+class UnrecognizedOptionException extends OptionException {
+ private static final long serialVersionUID = -1L;
+
+ UnrecognizedOptionException( String option ) {
+ super( singletonList( option ) );
+ }
+
+ @Override
+ public String getMessage() {
+ return singleOptionMessage() + " is not a recognized option";
+ }
+}
diff --git a/src/joptsimple/ValueConversionException.java b/src/joptsimple/ValueConversionException.java
new file mode 100644
index 000000000..d07f7ffc7
--- /dev/null
+++ b/src/joptsimple/ValueConversionException.java
@@ -0,0 +1,56 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+/**
+ * Thrown by {@link ValueConverter}s when problems occur in converting string values to
+ * other Java types.
+ *
+ * @author Paul Holser
+ * @version $Id: ValueConversionException.java,v 1.5 2009/08/13 00:34:35 pholser Exp $
+ */
+public class ValueConversionException extends RuntimeException {
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * Creates a new exception with the specified detail message.
+ *
+ * @param message the detail message
+ */
+ public ValueConversionException( String message ) {
+ this( message, null );
+ }
+
+ /**
+ * Creates a new exception with the specified detail message and cause.
+ *
+ * @param message the detail message
+ * @param cause the original exception
+ */
+ public ValueConversionException( String message, Throwable cause ) {
+ super( message, cause );
+ }
+}
diff --git a/src/joptsimple/ValueConverter.java b/src/joptsimple/ValueConverter.java
new file mode 100644
index 000000000..b167de2e9
--- /dev/null
+++ b/src/joptsimple/ValueConverter.java
@@ -0,0 +1,61 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple;
+
+/**
+ * Instances of this interface are used to convert arguments of options into specific
+ * Java types.
+ *
+ * @param constraint on the type of values being converted to
+ * @author Paul Holser
+ * @version $Id: ValueConverter.java,v 1.9 2009/08/13 00:34:35 pholser Exp $
+ */
+public interface ValueConverter {
+ /**
+ * Converts the given string value into a Java type.
+ *
+ * @param value the string to convert
+ * @return the converted value
+ * @throws ValueConversionException if a problem occurs while converting the value
+ */
+ V convert( String value );
+
+ /**
+ * Gives the class of the type of values this converter converts to.
+ *
+ * @return the target class for conversion
+ */
+ Class valueType();
+
+ /**
+ * Gives a string that describes the pattern of the values this converter expects,
+ * if any. For example, a date converter can respond with a
+ * {@link java.text.SimpleDateFormat date format string}.
+ *
+ * @return a value pattern, or {@code null} if there's nothing interesting here
+ */
+ String valuePattern();
+}
diff --git a/src/joptsimple/internal/AbbreviationMap.java b/src/joptsimple/internal/AbbreviationMap.java
new file mode 100644
index 000000000..b239943f8
--- /dev/null
+++ b/src/joptsimple/internal/AbbreviationMap.java
@@ -0,0 +1,239 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.internal;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A map whose keys are strings; when a key/value pair is added to the map,
+ * the longest unique abbreviations of that key are added as well, and associated with
+ * the value. Thus:
+ *
+ *
+ *
+ * abbreviations.put( "good", "bye" );
+ *
+ *
+ *
+ * would make it such that you could retrieve the value {@code "bye"} from the map
+ * using the keys {@code "good"}, {@code "goo"}, {@code "go"}, and {@code "g"}.
+ * A subsequent invocation of:
+ *
+ *
+ * abbreviations.put( "go", "fish" );
+ *
+ *
+ *
+ * would make it such that you could retrieve the value {@code "bye"} using the keys
+ * {@code "good"} and {@code "goo"}, and the value {@code "fish"} using the key
+ * {@code "go"}. The key {@code "g"} would yield {@code null}, since it would no longer
+ * be a unique abbreviation.
+ *
+ * The data structure is much like a "trie".
+ *
+ * @param a constraint on the types of the values in the map
+ * @author Paul Holser
+ * @version $Id: AbbreviationMap.java,v 1.14 2009/10/25 18:37:08 pholser Exp $
+ * @see Perl's
+ * Text::Abbrev module
+ */
+public class AbbreviationMap {
+ private String key;
+ private V value;
+ private final Map> children = new TreeMap>();
+ private int keysBeyond;
+
+ /**
+ * Tells whether the given key is in the map, or whether the given key is a unique
+ * abbreviation of a key that is in the map.
+ *
+ * @param aKey key to look up
+ * @return {@code true} if {@code key} is present in the map
+ * @throws NullPointerException if {@code key} is {@code null}
+ */
+ public boolean contains( String aKey ) {
+ return get( aKey ) != null;
+ }
+
+ /**
+ * Answers the value associated with the given key. The key can be a unique
+ * abbreviation of a key that is in the map.
+ *
+ * @param aKey key to look up
+ * @return the value associated with {@code aKey}; or {@code null} if there is no
+ * such value or {@code aKey} is not a unique abbreviation of a key in the map
+ * @throws NullPointerException if {@code aKey} is {@code null}
+ */
+ public V get( String aKey ) {
+ char[] chars = charsOf( aKey );
+
+ AbbreviationMap child = this;
+ for ( char each : chars ) {
+ child = child.children.get( each );
+ if ( child == null )
+ return null;
+ }
+
+ return child.value;
+ }
+
+ /**
+ * Associates a given value with a given key. If there was a previous
+ * association, the old value is replaced with the new one.
+ *
+ * @param aKey key to create in the map
+ * @param newValue value to associate with the key
+ * @throws NullPointerException if {@code aKey} or {@code newValue} is {@code null}
+ * @throws IllegalArgumentException if {@code aKey} is a zero-length string
+ */
+ public void put( String aKey, V newValue ) {
+ if ( newValue == null )
+ throw new NullPointerException();
+ if ( aKey.length() == 0 )
+ throw new IllegalArgumentException();
+
+ char[] chars = charsOf( aKey );
+ add( chars, newValue, 0, chars.length );
+ }
+
+ /**
+ * Associates a given value with a given set of keys. If there was a previous
+ * association, the old value is replaced with the new one.
+ *
+ * @param keys keys to create in the map
+ * @param newValue value to associate with the key
+ * @throws NullPointerException if {@code keys} or {@code newValue} is {@code null}
+ * @throws IllegalArgumentException if any of {@code keys} is a zero-length string
+ */
+ public void putAll( Iterable keys, V newValue ) {
+ for ( String each : keys )
+ put( each, newValue );
+ }
+
+ private boolean add( char[] chars, V newValue, int offset, int length ) {
+ if ( offset == length ) {
+ value = newValue;
+ boolean wasAlreadyAKey = key != null;
+ key = new String( chars );
+ return !wasAlreadyAKey;
+ }
+
+ char nextChar = chars[ offset ];
+ AbbreviationMap child = children.get( nextChar );
+ if ( child == null ) {
+ child = new AbbreviationMap();
+ children.put( nextChar, child );
+ }
+
+ boolean newKeyAdded = child.add( chars, newValue, offset + 1, length );
+
+ if ( newKeyAdded )
+ ++keysBeyond;
+
+ if ( key == null )
+ value = keysBeyond > 1 ? null : newValue;
+
+ return newKeyAdded;
+ }
+
+ /**
+ * If the map contains the given key, dissociates the key from its value.
+ *
+ * @param aKey key to remove
+ * @throws NullPointerException if {@code aKey} is {@code null}
+ * @throws IllegalArgumentException if {@code aKey} is a zero-length string
+ */
+ public void remove( String aKey ) {
+ if ( aKey.length() == 0 )
+ throw new IllegalArgumentException();
+
+ char[] keyChars = charsOf( aKey );
+ remove( keyChars, 0, keyChars.length );
+ }
+
+ private boolean remove( char[] aKey, int offset, int length ) {
+ if ( offset == length )
+ return removeAtEndOfKey();
+
+ char nextChar = aKey[ offset ];
+ AbbreviationMap child = children.get( nextChar );
+ if ( child == null || !child.remove( aKey, offset + 1, length ) )
+ return false;
+
+ --keysBeyond;
+ if ( child.keysBeyond == 0 )
+ children.remove( nextChar );
+ if ( keysBeyond == 1 && key == null )
+ setValueToThatOfOnlyChild();
+
+ return true;
+ }
+
+ private void setValueToThatOfOnlyChild() {
+ Map.Entry> entry = children.entrySet().iterator().next();
+ AbbreviationMap onlyChild = entry.getValue();
+ value = onlyChild.value;
+ }
+
+ private boolean removeAtEndOfKey() {
+ if ( key == null )
+ return false;
+
+ key = null;
+ if ( keysBeyond == 1 )
+ setValueToThatOfOnlyChild();
+ else
+ value = null;
+
+ return true;
+ }
+
+ /**
+ * Gives a Java map representation of this abbreviation map.
+ *
+ * @return a Java map corresponding to this abbreviation map
+ */
+ public Map toJavaUtilMap() {
+ Map mappings = new TreeMap();
+ addToMappings( mappings );
+ return mappings;
+ }
+
+ private void addToMappings( Map mappings ) {
+ if ( key != null )
+ mappings.put( key, value );
+
+ for ( AbbreviationMap each : children.values() )
+ each.addToMappings( mappings );
+ }
+
+ private static char[] charsOf( String aKey ) {
+ char[] chars = new char[ aKey.length() ];
+ aKey.getChars( 0, aKey.length(), chars, 0 );
+ return chars;
+ }
+}
diff --git a/src/joptsimple/internal/Classes.java b/src/joptsimple/internal/Classes.java
new file mode 100644
index 000000000..097409fc0
--- /dev/null
+++ b/src/joptsimple/internal/Classes.java
@@ -0,0 +1,51 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.internal;
+
+/**
+ * @author Paul Holser
+ * @version $Id: Classes.java,v 1.10 2009/08/13 01:05:35 pholser Exp $
+ */
+public final class Classes {
+ static {
+ new Classes();
+ }
+
+ private Classes() {
+ // nothing to do here
+ }
+
+ /**
+ * Gives the "short version" of the given class name. Somewhat naive to inner
+ * classes.
+ *
+ * @param className class name to chew on
+ * @return the short name of the class
+ */
+ public static String shortNameOf( String className ) {
+ return className.substring( className.lastIndexOf( '.' ) + 1 );
+ }
+}
diff --git a/src/joptsimple/internal/Column.java b/src/joptsimple/internal/Column.java
new file mode 100644
index 000000000..b53269f0f
--- /dev/null
+++ b/src/joptsimple/internal/Column.java
@@ -0,0 +1,132 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.internal;
+
+import java.text.BreakIterator;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import static java.lang.System.*;
+import static java.text.BreakIterator.*;
+
+import static joptsimple.internal.Strings.*;
+
+/**
+ * @author Paul Holser
+ * @version $Id: Column.java,v 1.16 2009/10/25 18:37:08 pholser Exp $
+ */
+public class Column {
+ static final Comparator BY_HEIGHT = new Comparator() {
+ public int compare( Column first, Column second ) {
+ if ( first.height() < second.height() )
+ return -1;
+ return first.height() == second.height() ? 0 : 1;
+ }
+ };
+
+ private final String header;
+ private final List data;
+ private final int width;
+ private int height;
+
+ Column( String header, int width ) {
+ this.header = header;
+ this.width = Math.max( width, header.length() );
+ data = new LinkedList();
+ height = 0;
+ }
+
+ int addCells( Object cellCandidate ) {
+ int originalHeight = height;
+
+ String source = String.valueOf( cellCandidate ).trim();
+ for ( String eachPiece : source.split( getProperty( "line.separator" ) ) )
+ processNextEmbeddedLine( eachPiece );
+
+ return height - originalHeight;
+ }
+
+ private void processNextEmbeddedLine( String line ) {
+ BreakIterator words = BreakIterator.getLineInstance( Locale.US );
+ words.setText( line );
+
+ StringBuilder nextCell = new StringBuilder();
+
+ int start = words.first();
+ for ( int end = words.next(); end != DONE; start = end, end = words.next() )
+ nextCell = processNextWord( line, nextCell, start, end );
+
+ if ( nextCell.length() > 0 )
+ addCell( nextCell.toString() );
+ }
+
+ private StringBuilder processNextWord( String source, StringBuilder nextCell, int start, int end ) {
+ StringBuilder augmented = nextCell;
+
+ String word = source.substring( start, end );
+ if ( augmented.length() + word.length() > width ) {
+ addCell( augmented.toString() );
+ augmented = new StringBuilder( " " ).append( word );
+ }
+ else
+ augmented.append( word );
+
+ return augmented;
+ }
+
+ void addCell( String newCell ) {
+ data.add( newCell );
+ ++height;
+ }
+
+ void writeHeaderOn( StringBuilder buffer, boolean appendSpace ) {
+ buffer.append( header ).append( repeat( ' ', width - header.length() ) );
+
+ if ( appendSpace )
+ buffer.append( ' ' );
+ }
+
+ void writeSeparatorOn( StringBuilder buffer, boolean appendSpace ) {
+ buffer.append( repeat( '-', header.length() ) ).append( repeat( ' ', width - header.length() ) );
+ if ( appendSpace )
+ buffer.append( ' ' );
+ }
+
+ void writeCellOn( int index, StringBuilder buffer, boolean appendSpace ) {
+ if ( index < data.size() ) {
+ String item = data.get( index );
+
+ buffer.append( item ).append( repeat( ' ', width - item.length() ) );
+ if ( appendSpace )
+ buffer.append( ' ' );
+ }
+ }
+
+ int height() {
+ return height;
+ }
+}
diff --git a/src/joptsimple/internal/ColumnWidthCalculator.java b/src/joptsimple/internal/ColumnWidthCalculator.java
new file mode 100644
index 000000000..0a3d28fd7
--- /dev/null
+++ b/src/joptsimple/internal/ColumnWidthCalculator.java
@@ -0,0 +1,42 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.internal;
+
+/**
+ * @author Paul Holser
+ * @version $Id: ColumnWidthCalculator.java,v 1.4 2009/08/13 00:34:36 pholser Exp $
+ */
+class ColumnWidthCalculator {
+ int calculate( int totalWidth, int numberOfColumns ) {
+ if ( numberOfColumns == 1 )
+ return totalWidth;
+
+ int remainder = totalWidth % numberOfColumns;
+ if ( remainder == numberOfColumns - 1 )
+ return totalWidth / numberOfColumns;
+ return totalWidth / numberOfColumns - 1;
+ }
+}
diff --git a/src/joptsimple/internal/ColumnarData.java b/src/joptsimple/internal/ColumnarData.java
new file mode 100644
index 000000000..fdc1c8021
--- /dev/null
+++ b/src/joptsimple/internal/ColumnarData.java
@@ -0,0 +1,162 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.internal;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import static java.lang.Integer.*;
+import static java.lang.System.*;
+import static java.util.Collections.*;
+
+import static joptsimple.internal.Column.*;
+import static joptsimple.internal.Strings.*;
+
+/**
+ * A means to display data in a text grid.
+ *
+ * @author Paul Holser
+ * @version $Id: ColumnarData.java,v 1.17 2009/10/25 18:37:08 pholser Exp $
+ */
+public class ColumnarData {
+ private static final String LINE_SEPARATOR = getProperty( "line.separator" );
+ private static final int TOTAL_WIDTH = 80;
+
+ private final ColumnWidthCalculator widthCalculator;
+ private final List columns;
+ private final String[] headers;
+
+ /**
+ * Creates a new grid with the given column headers.
+ *
+ * @param headers column headers
+ */
+ public ColumnarData( String... headers ) {
+ this.headers = headers.clone();
+ widthCalculator = new ColumnWidthCalculator();
+ columns = new LinkedList();
+
+ clear();
+ }
+
+ /**
+ * Adds a row to the grid. The data will fall under the corresponding headers.
+ * There can be fewer elements in the row than headers. Any data in columns outside
+ * of the number of headers will not be added to the grid.
+ *
+ * @param rowData row data to add
+ */
+ public void addRow( Object... rowData ) {
+ int[] numberOfCellsAddedAt = addRowCells( rowData );
+ addPaddingCells( numberOfCellsAddedAt );
+ }
+
+ /**
+ * Gives a string that represents the data formatted in columns.
+ *
+ * @return the formatted grid
+ */
+ public String format() {
+ StringBuilder buffer = new StringBuilder();
+
+ writeHeadersOn( buffer );
+ writeSeparatorsOn( buffer );
+ writeRowsOn( buffer );
+
+ return buffer.toString();
+ }
+
+ /**
+ * Removes all data from the grid, but preserves the headers.
+ */
+ public final void clear() {
+ columns.clear();
+
+ int desiredColumnWidth = widthCalculator.calculate( TOTAL_WIDTH, headers.length );
+ for ( String each : headers )
+ columns.add( new Column( each, desiredColumnWidth ) );
+ }
+
+ private void writeHeadersOn( StringBuilder buffer ) {
+ for ( Iterator iter = columns.iterator(); iter.hasNext(); )
+ iter.next().writeHeaderOn( buffer, iter.hasNext() );
+
+ buffer.append( LINE_SEPARATOR );
+ }
+
+ private void writeSeparatorsOn( StringBuilder buffer ) {
+ for ( Iterator iter = columns.iterator(); iter.hasNext(); )
+ iter.next().writeSeparatorOn( buffer, iter.hasNext() );
+
+ buffer.append( LINE_SEPARATOR );
+ }
+
+ private void writeRowsOn( StringBuilder buffer ) {
+ int maxHeight = max( columns, BY_HEIGHT ).height();
+
+ for ( int i = 0; i < maxHeight; ++i )
+ writeRowOn( buffer, i );
+ }
+
+ private void writeRowOn( StringBuilder buffer, int rowIndex ) {
+ for ( Iterator iter = columns.iterator(); iter.hasNext(); )
+ iter.next().writeCellOn( rowIndex, buffer, iter.hasNext() );
+
+ buffer.append( LINE_SEPARATOR );
+ }
+
+ private int arrayMax( int[] numbers ) {
+ int maximum = MIN_VALUE;
+
+ for ( int each : numbers )
+ maximum = Math.max( maximum, each );
+
+ return maximum;
+ }
+
+ private int[] addRowCells( Object... rowData ) {
+ int[] cellsAddedAt = new int[ rowData.length ];
+
+ Iterator iter = columns.iterator();
+ for ( int i = 0; iter.hasNext() && i < rowData.length; ++i )
+ cellsAddedAt[ i ] = iter.next().addCells( rowData[ i ] );
+
+ return cellsAddedAt;
+ }
+
+ private void addPaddingCells( int... numberOfCellsAddedAt ) {
+ int maxHeight = arrayMax( numberOfCellsAddedAt );
+
+ Iterator iter = columns.iterator();
+ for ( int i = 0; iter.hasNext() && i < numberOfCellsAddedAt.length; ++i )
+ addPaddingCellsForColumn( iter.next(), maxHeight, numberOfCellsAddedAt[ i ] );
+ }
+
+ private void addPaddingCellsForColumn( Column column, int maxHeight, int numberOfCellsAdded ) {
+ for ( int i = 0; i < maxHeight - numberOfCellsAdded; ++i )
+ column.addCell( EMPTY );
+ }
+}
diff --git a/src/joptsimple/internal/ConstructorInvokingValueConverter.java b/src/joptsimple/internal/ConstructorInvokingValueConverter.java
new file mode 100644
index 000000000..f3b53f5e3
--- /dev/null
+++ b/src/joptsimple/internal/ConstructorInvokingValueConverter.java
@@ -0,0 +1,56 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.internal;
+
+import java.lang.reflect.Constructor;
+
+import joptsimple.ValueConverter;
+import static joptsimple.internal.Reflection.*;
+
+/**
+ * @param constraint on the type of values being converted to
+ * @author Paul Holser
+ * @version $Id: ConstructorInvokingValueConverter.java,v 1.4 2009/10/25 18:37:08 pholser Exp $
+ */
+class ConstructorInvokingValueConverter implements ValueConverter {
+ private final Constructor ctor;
+
+ ConstructorInvokingValueConverter( Constructor ctor ) {
+ this.ctor = ctor;
+ }
+
+ public V convert( String value ) {
+ return instantiate( ctor, value );
+ }
+
+ public Class valueType() {
+ return ctor.getDeclaringClass();
+ }
+
+ public String valuePattern() {
+ return null;
+ }
+}
diff --git a/src/joptsimple/internal/MethodInvokingValueConverter.java b/src/joptsimple/internal/MethodInvokingValueConverter.java
new file mode 100644
index 000000000..ffb2834ee
--- /dev/null
+++ b/src/joptsimple/internal/MethodInvokingValueConverter.java
@@ -0,0 +1,58 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.internal;
+
+import java.lang.reflect.Method;
+
+import joptsimple.ValueConverter;
+import static joptsimple.internal.Reflection.*;
+
+/**
+ * @param constraint on the type of values being converted to
+ * @author Paul Holser
+ * @version $Id: MethodInvokingValueConverter.java,v 1.4 2009/10/25 18:37:08 pholser Exp $
+ */
+class MethodInvokingValueConverter implements ValueConverter {
+ private final Method method;
+ private final Class clazz;
+
+ MethodInvokingValueConverter( Method method, Class clazz ) {
+ this.method = method;
+ this.clazz = clazz;
+ }
+
+ public V convert( String value ) {
+ return clazz.cast( invoke( method, value ) );
+ }
+
+ public Class valueType() {
+ return clazz;
+ }
+
+ public String valuePattern() {
+ return null;
+ }
+}
diff --git a/src/joptsimple/internal/Objects.java b/src/joptsimple/internal/Objects.java
new file mode 100644
index 000000000..683ce4f6c
--- /dev/null
+++ b/src/joptsimple/internal/Objects.java
@@ -0,0 +1,51 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.internal;
+
+/**
+ * @author Paul Holser
+ * @version $Id: Objects.java,v 1.2 2009/10/25 18:37:08 pholser Exp $
+ */
+public final class Objects {
+ static {
+ new Objects();
+ }
+
+ private Objects() {
+ // nothing to do here
+ }
+
+ /**
+ * Rejects {@code null} references.
+ *
+ * @param target reference to check
+ * @throws NullPointerException if {@code target} is {@code null}
+ */
+ public static void ensureNotNull( Object target ) {
+ if ( target == null )
+ throw new NullPointerException();
+ }
+}
diff --git a/src/joptsimple/internal/Reflection.java b/src/joptsimple/internal/Reflection.java
new file mode 100644
index 000000000..8d124d2b5
--- /dev/null
+++ b/src/joptsimple/internal/Reflection.java
@@ -0,0 +1,142 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.internal;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import static java.lang.reflect.Modifier.*;
+
+import joptsimple.ValueConverter;
+
+/**
+ * Helper methods for reflection.
+ *
+ * @author Paul Holser
+ * @version $Id: Reflection.java,v 1.20 2009/09/28 01:12:48 pholser Exp $
+ */
+public final class Reflection {
+ static {
+ new Reflection();
+ }
+
+ private Reflection() {
+ // nothing to do here
+ }
+
+ /**
+ * Finds an appropriate value converter for the given class.
+ *
+ * @param a constraint on the class object to introspect
+ * @param clazz class to introspect on
+ * @return a converter method or constructor
+ */
+ public static ValueConverter findConverter( Class clazz ) {
+ ValueConverter valueOf = valueOfConverter( clazz );
+ if ( valueOf != null )
+ return valueOf;
+
+ ValueConverter constructor = constructorConverter( clazz );
+ if ( constructor != null )
+ return constructor;
+
+ throw new IllegalArgumentException( clazz + " is not a value type" );
+ }
+
+ private static ValueConverter valueOfConverter( Class clazz ) {
+ try {
+ Method valueOf = clazz.getDeclaredMethod( "valueOf", String.class );
+ if ( !meetsConverterRequirements( valueOf, clazz ) )
+ return null;
+
+ return new MethodInvokingValueConverter( valueOf, clazz );
+ }
+ catch ( NoSuchMethodException ignored ) {
+ return null;
+ }
+ }
+
+ private static ValueConverter constructorConverter( Class clazz ) {
+ try {
+ return new ConstructorInvokingValueConverter(
+ clazz.getConstructor( String.class ) );
+ }
+ catch ( NoSuchMethodException ignored ) {
+ return null;
+ }
+ }
+
+ /**
+ * Invokes the given constructor with the given arguments.
+ *
+ * @param constraint on the type of the objects yielded by the constructor
+ * @param constructor constructor to invoke
+ * @param args arguments to hand to the constructor
+ * @return the result of invoking the constructor
+ * @throws ReflectionException in lieu of the gaggle of reflection-related exceptions
+ */
+ public static T instantiate( Constructor constructor, Object... args ) {
+ try {
+ return constructor.newInstance( args );
+ }
+ catch ( Exception ex ) {
+ throw reflectionException( ex );
+ }
+ }
+
+ /**
+ * Invokes the given static method with the given arguments.
+ *
+ * @param method method to invoke
+ * @param args arguments to hand to the method
+ * @return the result of invoking the method
+ * @throws ReflectionException in lieu of the gaggle of reflection-related exceptions
+ */
+ public static Object invoke( Method method, Object... args ) {
+ try {
+ return method.invoke( null, args );
+ }
+ catch ( Exception ex ) {
+ throw reflectionException( ex );
+ }
+ }
+
+ private static boolean meetsConverterRequirements( Method method, Class> expectedReturnType ) {
+ int modifiers = method.getModifiers();
+ return isPublic( modifiers ) && isStatic( modifiers ) && expectedReturnType.equals( method.getReturnType() );
+ }
+
+ private static RuntimeException reflectionException( Exception ex ) {
+ if ( ex instanceof IllegalArgumentException )
+ return new ReflectionException( ex );
+ if ( ex instanceof InvocationTargetException )
+ return new ReflectionException( ex.getCause() );
+ if ( ex instanceof RuntimeException )
+ return (RuntimeException) ex;
+
+ return new ReflectionException( ex );
+ }
+}
diff --git a/src/joptsimple/internal/ReflectionException.java b/src/joptsimple/internal/ReflectionException.java
new file mode 100644
index 000000000..e6e842426
--- /dev/null
+++ b/src/joptsimple/internal/ReflectionException.java
@@ -0,0 +1,40 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.internal;
+
+/**
+ * This unchecked exception wraps reflection-oriented exceptions.
+ *
+ * @author Paul Holser
+ * @version $Id: ReflectionException.java,v 1.5 2009/08/13 00:34:36 pholser Exp $
+ */
+public class ReflectionException extends RuntimeException {
+ private static final long serialVersionUID = -2L;
+
+ ReflectionException( Throwable cause ) {
+ super( cause.toString() );
+ }
+}
diff --git a/src/joptsimple/internal/Strings.java b/src/joptsimple/internal/Strings.java
new file mode 100644
index 000000000..55d5a9764
--- /dev/null
+++ b/src/joptsimple/internal/Strings.java
@@ -0,0 +1,124 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.internal;
+
+import java.util.Iterator;
+import java.util.List;
+import static java.lang.System.*;
+import static java.util.Arrays.*;
+
+/**
+ * @author Paul Holser
+ * @version $Id: Strings.java,v 1.16 2009/08/13 01:05:35 pholser Exp $
+ */
+public final class Strings {
+ public static final String EMPTY = "";
+ public static final String SINGLE_QUOTE = "'";
+ public static final String LINE_SEPARATOR = getProperty( "line.separator" );
+
+ static {
+ new Strings();
+ }
+
+ private Strings() {
+ // nothing to do here
+ }
+
+ /**
+ * Gives a string consisting of the given character repeated the given number of
+ * times.
+ *
+ * @param ch the character to repeat
+ * @param count how many times to repeat the character
+ * @return the resultant string
+ */
+ public static String repeat( char ch, int count ) {
+ StringBuilder buffer = new StringBuilder();
+
+ for ( int i = 0; i < count; ++i )
+ buffer.append( ch );
+
+ return buffer.toString();
+ }
+
+ /**
+ * Tells whether the given string is either {@code} or consists solely of
+ * whitespace characters.
+ *
+ * @param target string to check
+ * @return {@code true} if the target string is null or empty
+ */
+ public static boolean isNullOrEmpty( String target ) {
+ return target == null || EMPTY.equals( target );
+ }
+
+
+ /**
+ * Gives a string consisting of a given string prepended and appended with
+ * surrounding characters.
+ *
+ * @param target a string
+ * @param begin character to prepend
+ * @param end character to append
+ * @return the surrounded string
+ */
+ public static String surround( String target, char begin, char end ) {
+ return begin + target + end;
+ }
+
+ /**
+ * Gives a string consisting of the elements of a given array of strings, each
+ * separated by a given separator string.
+ *
+ * @param pieces the strings to join
+ * @param separator the separator
+ * @return the joined string
+ */
+ public static String join( String[] pieces, String separator ) {
+ return join( asList( pieces ), separator );
+ }
+
+ /**
+ * Gives a string consisting of the string representations of the elements of a
+ * given array of objects, each separated by a given separator string.
+ *
+ * @param pieces the elements whose string representations are to be joined
+ * @param separator the separator
+ * @return the joined string
+ */
+ public static String join( List pieces, String separator ) {
+ StringBuilder buffer = new StringBuilder();
+
+ for ( Iterator iter = pieces.iterator(); iter.hasNext(); ) {
+ buffer.append( iter.next() );
+
+ if ( iter.hasNext() )
+ buffer.append( separator );
+ }
+
+ return buffer.toString();
+ }
+}
diff --git a/src/joptsimple/util/DateConverter.java b/src/joptsimple/util/DateConverter.java
new file mode 100644
index 000000000..021afff37
--- /dev/null
+++ b/src/joptsimple/util/DateConverter.java
@@ -0,0 +1,101 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.util;
+
+import java.text.DateFormat;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import joptsimple.ValueConversionException;
+import joptsimple.ValueConverter;
+
+/**
+ * Converts values to {@link Date}s using a {@link DateFormat} object.
+ *
+ * @author Paul Holser
+ * @version $Id: DateConverter.java,v 1.6 2009/10/25 18:37:09 pholser Exp $
+ */
+public class DateConverter implements ValueConverter {
+ private final DateFormat formatter;
+
+ /**
+ * Creates a converter that uses the given date formatter/parser.
+ *
+ * @param formatter the formatter/parser to use
+ * @throws NullPointerException if {@code formatter} is {@code null}
+ */
+ public DateConverter( DateFormat formatter ) {
+ if ( formatter == null )
+ throw new NullPointerException( "illegal null formatter" );
+
+ this.formatter = formatter;
+ }
+
+ /**
+ * Creates a converter that uses a {@link SimpleDateFormat} with the given date/time
+ * pattern. The date formatter created is not
+ * {@link SimpleDateFormat#setLenient(boolean) lenient}.
+ *
+ * @param pattern expected date/time pattern
+ * @return the new converter
+ * @throws NullPointerException if {@code pattern} is {@code null}
+ * @throws IllegalArgumentException if {@code pattern} is invalid
+ */
+ public static DateConverter datePattern( String pattern ) {
+ SimpleDateFormat formatter = new SimpleDateFormat( pattern );
+ formatter.setLenient( false );
+
+ return new DateConverter( formatter );
+ }
+
+ public Date convert( String value ) {
+ ParsePosition position = new ParsePosition( 0 );
+
+ Date date = formatter.parse( value, position );
+ if ( position.getIndex() != value.length() )
+ throw new ValueConversionException( message( value ) );
+
+ return date;
+ }
+
+ public Class valueType() {
+ return Date.class;
+ }
+
+ public String valuePattern() {
+ return formatter instanceof SimpleDateFormat
+ ? ( (SimpleDateFormat) formatter ).toLocalizedPattern()
+ : "";
+ }
+
+ private String message( String value ) {
+ String message = "Value [" + value + "] does not match date/time pattern";
+ if ( formatter instanceof SimpleDateFormat )
+ message += " [" + ( (SimpleDateFormat) formatter ).toLocalizedPattern() + ']';
+
+ return message;
+ }
+}
diff --git a/src/joptsimple/util/KeyValuePair.java b/src/joptsimple/util/KeyValuePair.java
new file mode 100644
index 000000000..7a4a9c0b5
--- /dev/null
+++ b/src/joptsimple/util/KeyValuePair.java
@@ -0,0 +1,84 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.util;
+
+import static joptsimple.internal.Strings.*;
+
+/**
+ * A simple string key/string value pair.
+ *
+ * This is useful as an argument type for options whose values take on the form
+ * key=value, such as JVM command line system properties.
+ *
+ * @author Paul Holser
+ * @version $Id: KeyValuePair.java,v 1.10 2009/10/25 18:37:09 pholser Exp $
+ */
+public final class KeyValuePair {
+ public final String key;
+ public final String value;
+
+ private KeyValuePair( String key, String value ) {
+ this.key = key;
+ this.value = value;
+ }
+
+ /**
+ * Parses a string assumed to be of the form key=value into its parts.
+ *
+ * @param asString key-value string
+ * @return a key-value pair
+ * @throws NullPointerException if {@code stringRepresentation} is {@code null}
+ */
+ public static KeyValuePair valueOf( String asString ) {
+ int equalsIndex = asString.indexOf( '=' );
+ if ( equalsIndex == -1 )
+ return new KeyValuePair( asString, EMPTY );
+
+ String aKey = asString.substring( 0, equalsIndex );
+ String aValue = equalsIndex == asString.length() - 1 ? EMPTY : asString.substring( equalsIndex + 1 );
+
+ return new KeyValuePair( aKey, aValue );
+ }
+
+ @Override
+ public boolean equals( Object that ) {
+ if ( !( that instanceof KeyValuePair ) )
+ return false;
+
+ KeyValuePair other = (KeyValuePair) that;
+ return key.equals( other.key ) && value.equals( other.value );
+ }
+
+ @Override
+ public int hashCode() {
+ return key.hashCode() ^ value.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return key + '=' + value;
+ }
+}
diff --git a/src/joptsimple/util/RegexMatcher.java b/src/joptsimple/util/RegexMatcher.java
new file mode 100644
index 000000000..6d1ad33a3
--- /dev/null
+++ b/src/joptsimple/util/RegexMatcher.java
@@ -0,0 +1,86 @@
+/*
+ The MIT License
+
+ Copyright (c) 2009 Paul R. Holser, Jr.
+
+ 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 joptsimple.util;
+
+import java.util.regex.Pattern;
+import static java.util.regex.Pattern.*;
+
+import joptsimple.ValueConversionException;
+import joptsimple.ValueConverter;
+
+/**
+ * Ensures that values entirely match a regular expression.
+ *
+ * @author Paul Holser
+ * @version $Id: RegexMatcher.java,v 1.6 2009/10/25 18:37:09 pholser Exp $
+ */
+public class RegexMatcher implements ValueConverter {
+ private final Pattern pattern;
+
+ /**
+ * Creates a matcher that uses the given regular expression, modified by the given
+ * flags.
+ *
+ * @param pattern the regular expression pattern
+ * @param flags modifying regex flags
+ * @throws IllegalArgumentException if bit values other than those corresponding to
+ * the defined match flags are set in {@code flags}
+ * @throws java.util.regex.PatternSyntaxException if the expression's syntax is
+ * invalid
+ */
+ public RegexMatcher( String pattern, int flags ) {
+ this.pattern = compile( pattern, flags );
+ }
+
+ /**
+ * Gives a matcher that uses the given regular expression.
+ *
+ * @param pattern the regular expression pattern
+ * @return the new converter
+ * @throws java.util.regex.PatternSyntaxException if the expression's syntax is
+ * invalid
+ */
+ public static ValueConverter regex( String pattern ) {
+ return new RegexMatcher( pattern, 0 );
+ }
+
+ public String convert( String value ) {
+ if ( !pattern.matcher( value ).matches() ) {
+ throw new ValueConversionException(
+ "Value [" + value + "] did not match regex [" + pattern.pattern() + ']' );
+ }
+
+ return value;
+ }
+
+ public Class valueType() {
+ return String.class;
+ }
+
+ public String valuePattern() {
+ return pattern.pattern();
+ }
+}