From 96e848e880fdae119af439fc413f2cec8907726c Mon Sep 17 00:00:00 2001 From: sk89q Date: Sun, 2 Jan 2011 16:59:50 -0800 Subject: [PATCH] Add a very rudimentary command line program that will check the integrity (a very basic integrity check) of a world. --- build.xml | 1 + src/com/sk89q/worldedit/cli/Main.java | 94 ++++ src/com/sk89q/worldedit/cli/WorldChecker.java | 92 ++++ src/joptsimple/AbstractOptionSpec.java | 124 +++++ src/joptsimple/AlternativeLongOptionSpec.java | 57 ++ .../ArgumentAcceptingOptionSpec.java | 301 +++++++++++ src/joptsimple/ArgumentList.java | 60 +++ src/joptsimple/HelpFormatter.java | 149 ++++++ .../IllegalOptionClusterException.java | 48 ++ .../IllegalOptionSpecificationException.java | 48 ++ .../MultipleArgumentsForOptionException.java | 48 ++ src/joptsimple/NoArgumentOptionSpec.java | 79 +++ .../OptionArgumentConversionException.java | 56 ++ src/joptsimple/OptionException.java | 95 ++++ ...ptionMissingRequiredArgumentException.java | 48 ++ src/joptsimple/OptionParser.java | 496 ++++++++++++++++++ src/joptsimple/OptionParserState.java | 69 +++ src/joptsimple/OptionSet.java | 301 +++++++++++ src/joptsimple/OptionSpec.java | 95 ++++ src/joptsimple/OptionSpecBuilder.java | 101 ++++ src/joptsimple/OptionSpecTokenizer.java | 119 +++++ src/joptsimple/OptionSpecVisitor.java | 42 ++ .../OptionalArgumentOptionSpec.java | 75 +++ src/joptsimple/ParserRules.java | 88 ++++ .../RequiredArgumentOptionSpec.java | 58 ++ .../UnrecognizedOptionException.java | 47 ++ src/joptsimple/ValueConversionException.java | 56 ++ src/joptsimple/ValueConverter.java | 61 +++ src/joptsimple/internal/AbbreviationMap.java | 239 +++++++++ src/joptsimple/internal/Classes.java | 51 ++ src/joptsimple/internal/Column.java | 132 +++++ .../internal/ColumnWidthCalculator.java | 42 ++ src/joptsimple/internal/ColumnarData.java | 162 ++++++ .../ConstructorInvokingValueConverter.java | 56 ++ .../MethodInvokingValueConverter.java | 58 ++ src/joptsimple/internal/Objects.java | 51 ++ src/joptsimple/internal/Reflection.java | 142 +++++ .../internal/ReflectionException.java | 40 ++ src/joptsimple/internal/Strings.java | 124 +++++ src/joptsimple/util/DateConverter.java | 101 ++++ src/joptsimple/util/KeyValuePair.java | 84 +++ src/joptsimple/util/RegexMatcher.java | 86 +++ 42 files changed, 4276 insertions(+) create mode 100644 src/com/sk89q/worldedit/cli/Main.java create mode 100644 src/com/sk89q/worldedit/cli/WorldChecker.java create mode 100644 src/joptsimple/AbstractOptionSpec.java create mode 100644 src/joptsimple/AlternativeLongOptionSpec.java create mode 100644 src/joptsimple/ArgumentAcceptingOptionSpec.java create mode 100644 src/joptsimple/ArgumentList.java create mode 100644 src/joptsimple/HelpFormatter.java create mode 100644 src/joptsimple/IllegalOptionClusterException.java create mode 100644 src/joptsimple/IllegalOptionSpecificationException.java create mode 100644 src/joptsimple/MultipleArgumentsForOptionException.java create mode 100644 src/joptsimple/NoArgumentOptionSpec.java create mode 100644 src/joptsimple/OptionArgumentConversionException.java create mode 100644 src/joptsimple/OptionException.java create mode 100644 src/joptsimple/OptionMissingRequiredArgumentException.java create mode 100644 src/joptsimple/OptionParser.java create mode 100644 src/joptsimple/OptionParserState.java create mode 100644 src/joptsimple/OptionSet.java create mode 100644 src/joptsimple/OptionSpec.java create mode 100644 src/joptsimple/OptionSpecBuilder.java create mode 100644 src/joptsimple/OptionSpecTokenizer.java create mode 100644 src/joptsimple/OptionSpecVisitor.java create mode 100644 src/joptsimple/OptionalArgumentOptionSpec.java create mode 100644 src/joptsimple/ParserRules.java create mode 100644 src/joptsimple/RequiredArgumentOptionSpec.java create mode 100644 src/joptsimple/UnrecognizedOptionException.java create mode 100644 src/joptsimple/ValueConversionException.java create mode 100644 src/joptsimple/ValueConverter.java create mode 100644 src/joptsimple/internal/AbbreviationMap.java create mode 100644 src/joptsimple/internal/Classes.java create mode 100644 src/joptsimple/internal/Column.java create mode 100644 src/joptsimple/internal/ColumnWidthCalculator.java create mode 100644 src/joptsimple/internal/ColumnarData.java create mode 100644 src/joptsimple/internal/ConstructorInvokingValueConverter.java create mode 100644 src/joptsimple/internal/MethodInvokingValueConverter.java create mode 100644 src/joptsimple/internal/Objects.java create mode 100644 src/joptsimple/internal/Reflection.java create mode 100644 src/joptsimple/internal/ReflectionException.java create mode 100644 src/joptsimple/internal/Strings.java create mode 100644 src/joptsimple/util/DateConverter.java create mode 100644 src/joptsimple/util/KeyValuePair.java create mode 100644 src/joptsimple/util/RegexMatcher.java diff --git a/build.xml b/build.xml index 46f8e130e..44ba86de0 100644 --- a/build.xml +++ b/build.xml @@ -33,6 +33,7 @@ + diff --git a/src/com/sk89q/worldedit/cli/Main.java b/src/com/sk89q/worldedit/cli/Main.java new file mode 100644 index 000000000..d4c4e9551 --- /dev/null +++ b/src/com/sk89q/worldedit/cli/Main.java @@ -0,0 +1,94 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit.cli; + +import static java.util.Arrays.*; +import java.util.List; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; + +/** + * + * @author sk89q + */ +public class Main { + /** + * @param args + */ + public static void main(String[] _args) throws Throwable { + OptionParser parser = new OptionParser(); + OptionSpec world = + parser.accepts("world").withRequiredArg().ofType(String.class) + .describedAs("world directory").defaultsTo("world"); + parser.acceptsAll(asList("?", "h"), "show help"); + + OptionSet options = parser.parse(_args); + List args = options.nonOptionArguments(); + + if (args.size() != 1 || options.has("?")) { + System.err.println("worldedit "); + System.err.println(); + parser.printHelpOn(System.err); + return; + } + + System.err.println("WorldEdit v" + getVersion()); + System.err.println("Copyright (c) 2010-2011 sk89q "); + System.err.println(); + + String act = args.get(0); + String worldPath = options.valueOf(world); + + if (act.equalsIgnoreCase("check")) { + new WorldChecker(worldPath); + } else { + System.err.println("Only valid action is 'check'."); + } + } + + /** + * Get the version. + * + * @return + */ + public static String getVersion() { + Package p = com.sk89q.worldedit.cli.Main.class.getPackage(); + + if (p == null) { + p = Package.getPackage("com.sk89q.worldedit"); + } + + String version; + + if (p == null) { + return "(unknown)"; + } else { + version = p.getImplementationVersion(); + + if (version == null) { + return "(unknown)"; + } + } + + return version; + } + +} diff --git a/src/com/sk89q/worldedit/cli/WorldChecker.java b/src/com/sk89q/worldedit/cli/WorldChecker.java new file mode 100644 index 000000000..eb4b3f35b --- /dev/null +++ b/src/com/sk89q/worldedit/cli/WorldChecker.java @@ -0,0 +1,92 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit.cli; + +import java.io.*; +import java.nio.*; +import java.util.regex.Pattern; + +import org.jnbt.NBTInputStream; +import org.jnbt.Tag; + +public class WorldChecker { + private File worldPath; + + public WorldChecker(String path) { + worldPath = new File(path); + + checkLevelDat(); + checkFiles(); + + System.out.println("Done."); + } + + public void checkLevelDat() { + try { + checkNBT(new File(worldPath, "level.dat")); + } catch (IOException e) { + System.out.println("BAD: level.dat: " + e.getMessage()); + } + } + + public void checkFiles() { + final Pattern chunkFilePattern = Pattern.compile("^c\\..*\\.dat$"); + + FileFilter folderFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return f.isDirectory(); + } + }; + + FileFilter chunkFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return f.isFile() + && chunkFilePattern.matcher(f.getName()).matches(); + } + }; + + for (File l1 : worldPath.listFiles(folderFilter)) { + for (File l2 : l1.listFiles(folderFilter)) { + for (File chunkFile : l2.listFiles(chunkFilter)) { + checkChunkFile(chunkFile, + l1.getName(), l2.getName(), chunkFile.getName()); + } + } + } + } + + public void checkChunkFile(File f, String a, String b, String c) { + String id = a + "/" + b + "/" + c; + + try { + checkNBT(f); + } catch (IOException e) { + System.out.println("BAD: " + id); + } + } + + public void checkNBT(File file) throws IOException { + FileInputStream stream = new FileInputStream(file); + NBTInputStream nbt = new NBTInputStream(stream); + Tag tag = nbt.readTag(); + } +} diff --git a/src/joptsimple/AbstractOptionSpec.java b/src/joptsimple/AbstractOptionSpec.java new file mode 100644 index 000000000..74be60a83 --- /dev/null +++ b/src/joptsimple/AbstractOptionSpec.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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import static java.util.Collections.*; + +import static joptsimple.internal.Strings.*; + +/** + * @param represents the type of the arguments this option accepts + * @author Paul Holser + * @version $Id: AbstractOptionSpec.java,v 1.19 2009/11/28 21:31:53 pholser Exp $ + */ +abstract class AbstractOptionSpec implements OptionSpec { + private final List options = new ArrayList(); + private final String description; + + protected AbstractOptionSpec( String option ) { + this( singletonList( option ), EMPTY ); + } + + protected AbstractOptionSpec( Collection options, String description ) { + arrangeOptions( options ); + + this.description = description; + } + + public final Collection options() { + return unmodifiableCollection( options ); + } + + public final List values( OptionSet detectedOptions ) { + return detectedOptions.valuesOf( this ); + } + + public final V value( OptionSet detectedOptions ) { + return detectedOptions.valueOf( this ); + } + + abstract List defaultValues(); + + String description() { + return description; + } + + protected abstract V convert( String argument ); + + abstract void handleOption( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions, + String detectedArgument ); + + abstract boolean acceptsArguments(); + + abstract boolean requiresArgument(); + + abstract void accept( OptionSpecVisitor visitor ); + + private void arrangeOptions( Collection unarranged ) { + if ( unarranged.size() == 1 ) { + options.addAll( unarranged ); + return; + } + + List shortOptions = new ArrayList(); + List longOptions = new ArrayList(); + + for ( String each : unarranged ) { + if ( each.length() == 1 ) + shortOptions.add( each ); + else + longOptions.add( each ); + } + + sort( shortOptions ); + sort( longOptions ); + + options.addAll( shortOptions ); + options.addAll( longOptions ); + } + + @Override + public boolean equals( Object that ) { + if ( !( that instanceof AbstractOptionSpec ) ) + return false; + + AbstractOptionSpec other = (AbstractOptionSpec) that; + return options.equals( other.options ); + } + + @Override + public int hashCode() { + return options.hashCode(); + } + + @Override + public String toString() { + return options.toString(); + } +} diff --git a/src/joptsimple/AlternativeLongOptionSpec.java b/src/joptsimple/AlternativeLongOptionSpec.java new file mode 100644 index 000000000..2b5b4ffac --- /dev/null +++ b/src/joptsimple/AlternativeLongOptionSpec.java @@ -0,0 +1,57 @@ +/* + 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.*; + +import static joptsimple.ParserRules.*; + +/** + *

Represents the "-W" form of long option specification.

+ * + * @author Paul Holser + * @version $Id: AlternativeLongOptionSpec.java,v 1.13 2009/09/28 01:12:48 pholser Exp $ + */ +class AlternativeLongOptionSpec extends ArgumentAcceptingOptionSpec { + AlternativeLongOptionSpec() { + super( singletonList( RESERVED_FOR_EXTENSIONS ), true, "Alternative form of long options" ); + + describedAs( "opt=value" ); + } + + @Override + protected void detectOptionArgument( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions ) { + if ( !arguments.hasMore() ) + throw new OptionMissingRequiredArgumentException( options() ); + + arguments.treatNextAsLongOption(); + } + + @Override + void accept( OptionSpecVisitor visitor ) { + visitor.visit( this ); + } +} diff --git a/src/joptsimple/ArgumentAcceptingOptionSpec.java b/src/joptsimple/ArgumentAcceptingOptionSpec.java new file mode 100644 index 000000000..60cb209ce --- /dev/null +++ b/src/joptsimple/ArgumentAcceptingOptionSpec.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.Collection; +import java.util.List; +import java.util.StringTokenizer; +import static java.util.Collections.*; + +import joptsimple.internal.ReflectionException; +import static joptsimple.internal.Objects.*; +import static joptsimple.internal.Reflection.*; +import static joptsimple.internal.Strings.*; + +/** + *

Specification of an option that accepts an argument.

+ * + *

Instances are returned from {@link OptionSpecBuilder} methods 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 treat its argument as a {@link String}.

+ * + * @param represents the type of the arguments this option accepts + * @author Paul Holser + * @version $Id: ArgumentAcceptingOptionSpec.java,v 1.43 2009/10/25 18:37:06 pholser Exp $ + */ +public abstract class ArgumentAcceptingOptionSpec extends AbstractOptionSpec { + private static final char NIL_VALUE_SEPARATOR = '\u0000'; + + private final boolean argumentRequired; + private ValueConverter converter; + private String argumentDescription = ""; + private String valueSeparator = String.valueOf( NIL_VALUE_SEPARATOR ); + private final List defaultValues = new ArrayList(); + + ArgumentAcceptingOptionSpec( String option, boolean argumentRequired ) { + super( option ); + + this.argumentRequired = argumentRequired; + } + + ArgumentAcceptingOptionSpec( Collection 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:

+ * + *
    + *
  1. 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.
  2. + * + *
  3. a public constructor which accepts a single argument of type + * {@link String}.
  4. + *
+ * + *

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.

+ * + *
    + *
  • Short options begin with a single hyphen ("-") followed + * by a single letter or digit, or question mark ("?"), or dot + * (".").
  • + * + *
  • Short options can accept single arguments. The argument can be made required or + * optional. The option's argument can occur: + *
      + *
    • in the slot after the option, as in -d /tmp
    • + *
    • right up against the option, as in -d/tmp
    • + *
    • right up against the option separated by an equals sign ("="), + * as in -d=/tmp
    • + *
    + * To specify n arguments for an option, specify the option n + * times, once for each argument, as in -d /tmp -d /var -d /opt; or, when + * using the {@linkplain ArgumentAcceptingOptionSpec#withValuesSeparatedBy(char) + * "separated values"} clause of the "fluent interface" (see below), give multiple + * values separated by a given character as a single argument to the option.
  • + * + *
  • Short options can be clustered, so that -abc is treated as + * -a -b -c, if none of those options can accept arguments.
  • + * + *
  • An argument consisting only of two hyphens ("--") signals that the + * remaining arguments are to be treated as non-options.
  • + * + *
  • An argument consisting only of a single hyphen is considered a non-option + * argument (though it can be an argument of an option). Many Unix programs treat + * single hyphens as stand-ins for the standard input or standard output streams.
  • + * + *
  • Long options begin with two hyphens ("--"), followed + * by multiple letters, digits, hyphens, question marks, or dots. A hyphen cannot be + * the first character of a long option specification when configuring the parser.
  • + * + *
  • You can abbreviate long options, so long as the abbreviation is unique.
  • + * + *
  • Long options can accept single arguments. The argument can be made required or + * optional. The option's argument can occur: + *
      + *
    • in the slot after the option, as in --directory /tmp
    • + *
    • right up against the option separated by an equals sign ("="), + * as in --directory=/tmp + *
    + * Specify multiple arguments for a long option in the same manner as for short options + * (see above).
  • + * + *
  • You can use a single hyphen ("-") instead of a double hyphen + * ("--") for a long option.
  • + * + *
  • The option -W is reserved. If you tell the parser to {@linkplain + * #recognizeAlternativeLongOptions(boolean) recognize alternative long options}, then + * it will treat, for example, -W foo=bar as the long option + * foo with argument bar, as though you had written + * --foo=bar.
  • + * + *
  • You can specify -W as a valid short option, or use it as an + * abbreviation for a long option, but {@linkplain + * #recognizeAlternativeLongOptions(boolean) recognizing alternative long options} will + * always supersede this behavior.
  • + * + *
  • You can specify a given short or long option multiple times on a single command + * line. The parser collects any arguments specified for those options as a list.
  • + * + *
  • If the parser detects an option whose argument is optional, and the next argument + * "looks like" an option, that argument is not treated as the argument to the option, + * but as a potentially valid option. If, on the other hand, the optional argument is + * typed as a derivative of {@link Number}, then that argument is treated as the + * negative number argument of the option, even if the parser recognizes the + * corresponding numeric option. For example: + *
    
    + *     OptionParser parser = new OptionParser();
    + *     parser.accepts( "a" ).withOptionalArg().ofType( Integer.class );
    + *     parser.accepts( "2" );
    + *     OptionSet options = parser.parse( "-a", "-2" );
    + *   
    + * In this case, the option set contains "a" with argument -2, + * not both "a" and "2". Swapping the elements in the + * args array gives the latter.
  • + *
+ * + *

There are two ways to tell the parser what options to recognize:

+ * + *
    + *
  1. 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.
  2. + * + *
  3. 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.
    • + *
    + *
  4. + *
+ * + *

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:

+ * + *
    + *
  1. using the method {@link #posixlyCorrect(boolean)}, or
  2. + * + *
  3. using the {@linkplain #OptionParser(String) constructor} with an argument whose + * first character is a plus sign ("+")
  4. + *
+ * + * @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(); + } +}