Ticket #16866: remove-getopt.2.patch

File remove-getopt.2.patch, 120.5 KB (added by michael2402, 7 years ago)

... missed the test file

  • src/gnu/getopt/Getopt.java

     
    1 /*
    2 /* Copyright (c) 1987-1997 Free Software Foundation, Inc.
    3 /* Java Port Copyright (c) 1998 by Aaron M. Renn (arenn@urbanophile.com)
    4 /*
    5 /* This program is free software; you can redistribute it and/or modify
    6 /* it under the terms of the GNU Library General Public License as published
    7 /* by  the Free Software Foundation; either version 2 of the License or
    8 /* (at your option) any later version.
    9 /*
    10 /* This program is distributed in the hope that it will be useful, but
    11 /* WITHOUT ANY WARRANTY; without even the implied warranty of
    12 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13 /* GNU Library General Public License for more details.
    14 /*
    15 /* You should have received a copy of the GNU Library General Public License
    16 /* along with this program; see the file COPYING.LIB.  If not, write to
    17 /* the Free Software Foundation Inc., 59 Temple Place - Suite 330,
    18 /* Boston, MA  02111-1307 USA
    19 /**************************************************************************/
    20 
    21 package gnu.getopt;
    22 
    23 import java.text.MessageFormat;
    24 import java.util.HashMap;
    25 import java.util.Map;
    26 import java.util.function.Function;
    27 
    28 /**************************************************************************/
    29 
    30 /**
    31   * This is a Java port of GNU getopt, a class for parsing command line
    32   * arguments passed to programs.  It it based on the C getopt() functions
    33   * in glibc 2.0.6 and should parse options in a 100% compatible manner.
    34   * If it does not, that is a bug.  The programmer's interface is also
    35   * very compatible.
    36   * <p>
    37   * To use Getopt, create a Getopt object with a argv array passed to the
    38   * main method, then call the getopt() method in a loop.  It will return an
    39   * int that contains the value of the option character parsed from the
    40   * command line.  When there are no more options to be parsed, it
    41   * returns -1.
    42   * <p>
    43   * A command line option can be defined to take an argument.  If an
    44   * option has an argument, the value of that argument is stored in an
    45   * instance variable called optarg, which can be accessed using the
    46   * getOptarg() method.  If an option that requires an argument is
    47   * found, but there is no argument present, then an error message is
    48   * printed. Normally getopt() returns a '?' in this situation, but
    49   * that can be changed as described below.
    50   * <p>
    51   * If an invalid option is encountered, an error message is printed
    52   * to the standard error and getopt() returns a '?'.  The value of the
    53   * invalid option encountered is stored in the instance variable optopt
    54   * which can be retrieved using the getOptopt() method.  To suppress
    55   * the printing of error messages for this or any other error, set
    56   * the value of the opterr instance variable to false using the
    57   * setOpterr() method.
    58   * <p>
    59   * Between calls to getopt(), the instance variable optind is used to
    60   * keep track of where the object is in the parsing process.  After all
    61   * options have been returned, optind is the index in argv of the first
    62   * non-option argument.  This variable can be accessed with the getOptind()
    63   * method.
    64   * <p>
    65   * Note that this object expects command line options to be passed in the
    66   * traditional Unix manner.  That is, proceeded by a '-' character.
    67   * Multiple options can follow the '-'.  For example "-abc" is equivalent
    68   * to "-a -b -c".  If an option takes a required argument, the value
    69   * of the argument can immediately follow the option character or be
    70   * present in the next argv element.  For example, "-cfoo" and "-c foo"
    71   * both represent an option character of 'c' with an argument of "foo"
    72   * assuming c takes a required argument.  If an option takes an argument
    73   * that is not required, then any argument must immediately follow the
    74   * option character in the same argv element.  For example, if c takes
    75   * a non-required argument, then "-cfoo" represents option character 'c'
    76   * with an argument of "foo" while "-c foo" represents the option
    77   * character 'c' with no argument, and a first non-option argv element
    78   * of "foo".
    79   * <p>
    80   * The user can stop getopt() from scanning any further into a command line
    81   * by using the special argument "--" by itself.  For example:
    82   * "-a -- -d" would return an option character of 'a', then return -1
    83   * The "--" is discarded and "-d" is pointed to by optind as the first
    84   * non-option argv element.
    85   * <p>
    86   * Here is a basic example of using Getopt:
    87   * <p>
    88   * <pre>
    89   * Getopt g = new Getopt("testprog", argv, "ab:c::d");
    90   * //
    91   * int c;
    92   * String arg;
    93   * while ((c = g.getopt()) != -1)
    94   *   {
    95   *     switch(c)
    96   *       {
    97   *          case 'a':
    98   *          case 'd':
    99   *            System.out.print("You picked " + (char)c + "\n");
    100   *            break;
    101   *            //
    102   *          case 'b':
    103   *          case 'c':
    104   *            arg = g.getOptarg();
    105   *            System.out.print("You picked " + (char)c +
    106   *                             " with an argument of " +
    107   *                             ((arg != null) ? arg : "null") + "\n");
    108   *            break;
    109   *            //
    110   *          case '?':
    111   *            break; // getopt() already printed an error
    112   *            //
    113   *          default:
    114   *            System.out.print("getopt() returned " + c + "\n");
    115   *       }
    116   *   }
    117   * </pre>
    118   * <p>
    119   * In this example, a new Getopt object is created with three params.
    120   * The first param is the program name.  This is for printing error
    121   * messages in the form "program: error message".  In the C version, this
    122   * value is taken from argv[0], but in Java the program name is not passed
    123   * in that element, thus the need for this parameter.  The second param is
    124   * the argument list that was passed to the main() method.  The third
    125   * param is the list of valid options.  Each character represents a valid
    126   * option.  If the character is followed by a single colon, then that
    127   * option has a required argument.  If the character is followed by two
    128   * colons, then that option has an argument that is not required.
    129   * <p>
    130   * Note in this example that the value returned from getopt() is cast to
    131   * a char prior to printing.  This is required in order to make the value
    132   * display correctly as a character instead of an integer.
    133   * <p>
    134   * If the first character in the option string is a colon, for example
    135   * ":abc::d", then getopt() will return a ':' instead of a '?' when it
    136   * encounters an option with a missing required argument.  This allows the
    137   * caller to distinguish between invalid options and valid options that
    138   * are simply incomplete.
    139   * <p>
    140   * In the traditional Unix getopt(), -1 is returned when the first non-option
    141   * charcter is encountered.  In GNU getopt(), the default behavior is to
    142   * allow options to appear anywhere on the command line.  The getopt()
    143   * method permutes the argument to make it appear to the caller that all
    144   * options were at the beginning of the command line, and all non-options
    145   * were at the end.  For example, calling getopt() with command line args
    146   * of "-a foo bar -d" returns options 'a' and 'd', then sets optind to
    147   * point to "foo".  The program would read the last two argv elements as
    148   * "foo" and "bar", just as if the user had typed "-a -d foo bar".
    149   * <p>
    150   * The user can force getopt() to stop scanning the command line with
    151   * the special argument "--" by itself.  Any elements occuring before the
    152   * "--" are scanned and permuted as normal.  Any elements after the "--"
    153   * are returned as is as non-option argv elements.  For example,
    154   * "foo -a -- bar -d" would return  option 'a' then -1.  optind would point
    155   * to "foo", "bar" and "-d" as the non-option argv elements.  The "--"
    156   * is discarded by getopt().
    157   * <p>
    158   * There are two ways this default behavior can be modified.  The first is
    159   * to specify traditional Unix getopt() behavior (which is also POSIX
    160   * behavior) in which scanning stops when the first non-option argument
    161   * encountered.  (Thus "-a foo bar -d" would return 'a' as an option and
    162   * have "foo", "bar", and "-d" as non-option elements).  The second is to
    163   * allow options anywhere, but to return all elements in the order they
    164   * occur on the command line.  When a non-option element is ecountered,
    165   * an integer 1 is returned and the value of the non-option element is
    166   * stored in optarg is if it were the argument to that option.  For
    167   * example, "-a foo -d", returns first 'a', then 1 (with optarg set to
    168   * "foo") then 'd' then -1.  When this "return in order" functionality
    169   * is enabled, the only way to stop getopt() from scanning all command
    170   * line elements is to use the special "--" string by itself as described
    171   * above.  An example is "-a foo -b -- bar", which would return 'a', then
    172   * integer 1 with optarg set to "foo", then 'b', then -1.  optind would
    173   * then point to "bar" as the first non-option argv element.  The "--"
    174   * is discarded.
    175   * <p>
    176   * The POSIX/traditional behavior is enabled by either setting the
    177   * property "gnu.posixly_correct" or by putting a '+' sign as the first
    178   * character of the option string.  The difference between the two
    179   * methods is that setting the gnu.posixly_correct property also forces
    180   * certain error messages to be displayed in POSIX format.  To enable
    181   * the "return in order" functionality, put a '-' as the first character
    182   * of the option string.  Note that after determining the proper
    183   * behavior, Getopt strips this leading '+' or '-', meaning that a ':'
    184   * placed as the second character after one of those two will still cause
    185   * getopt() to return a ':' instead of a '?' if a required option
    186   * argument is missing.
    187   * <p>
    188   * In addition to traditional single character options, GNU Getopt also
    189   * supports long options.  These are preceeded by a "--" sequence and
    190   * can be as long as desired.  Long options provide a more user-friendly
    191   * way of entering command line options.  For example, in addition to a
    192   * "-h" for help, a program could support also "--help".
    193   * <p>
    194   * Like short options, long options can also take a required or non-required
    195   * argument.  Required arguments can either be specified by placing an
    196   * equals sign after the option name, then the argument, or by putting the
    197   * argument in the next argv element.  For example: "--outputdir=foo" and
    198   * "--outputdir foo" both represent an option of "outputdir" with an
    199   * argument of "foo", assuming that outputdir takes a required argument.
    200   * If a long option takes a non-required argument, then the equals sign
    201   * form must be used to specify the argument.  In this case,
    202   * "--outputdir=foo" would represent option outputdir with an argument of
    203   * "foo" while "--outputdir foo" would represent the option outputdir
    204   * with no argument and a first non-option argv element of "foo".
    205   * <p>
    206   * Long options can also be specified using a special POSIX argument
    207   * format (one that I highly discourage).  This form of entry is
    208   * enabled by placing a "W;" (yes, 'W' then a semi-colon) in the valid
    209   * option string.  This causes getopt to treat the name following the
    210   * "-W" as the name of the long option.  For example, "-W outputdir=foo"
    211   * would be equivalent to "--outputdir=foo".  The name can immediately
    212   * follow the "-W" like so: "-Woutputdir=foo".  Option arguments are
    213   * handled identically to normal long options.  If a string follows the
    214   * "-W" that does not represent a valid long option, then getopt() returns
    215   * 'W' and the caller must decide what to do.  Otherwise getopt() returns
    216   * a long option value as described below.
    217   * <p>
    218   * While long options offer convenience, they can also be tedious to type
    219   * in full.  So it is permissible to abbreviate the option name to as
    220   * few characters as required to uniquely identify it.  If the name can
    221   * represent multiple long options, then an error message is printed and
    222   * getopt() returns a '?'.
    223   * <p>
    224   * If an invalid option is specified or a required option argument is
    225   * missing, getopt() prints an error and returns a '?' or ':' exactly
    226   * as for short options.  Note that when an invalid long option is
    227   * encountered, the optopt variable is set to integer 0 and so cannot
    228   * be used to identify the incorrect option the user entered.
    229   * <p>
    230   * Long options are defined by LongOpt objects.  These objects are created
    231   * with a contructor that takes four params: a String representing the
    232   * object name, a integer specifying what arguments the option takes
    233   * (the value is one of LongOpt.NO_ARGUMENT, LongOpt.REQUIRED_ARGUMENT,
    234   * or LongOpt.OPTIONAL_ARGUMENT), a StringBuffer flag object (described
    235   * below), and an integer value (described below).
    236   * <p>
    237   * To enable long option parsing, create an array of LongOpt's representing
    238   * the legal options and pass it to the Getopt() constructor.  WARNING: If
    239   * all elements of the array are not populated with LongOpt objects, the
    240   * getopt() method will throw a NullPointerException.
    241   * <p>
    242   * When getopt() is called and a long option is encountered, one of two
    243   * things can be returned.  If the flag field in the LongOpt object
    244   * representing the long option is non-null, then the integer value field
    245   * is stored there and an integer 0 is returned to the caller.  The val
    246   * field can then be retrieved from the flag field.  Note that since the
    247   * flag field is a StringBuffer, the appropriate String to integer converions
    248   * must be performed in order to get the actual int value stored there.
    249   * If the flag field in the LongOpt object is null, then the value field
    250   * of the LongOpt is returned.  This can be the character of a short option.
    251   * This allows an app to have both a long and short option sequence
    252   * (say, "-h" and "--help") that do the exact same thing.
    253   * <p>
    254   * With long options, there is an alternative method of determining
    255   * which option was selected.  The method getLongind() will return the
    256   * the index in the long option array (NOT argv) of the long option found.
    257   * So if multiple long options are configured to return the same value,
    258   * the application can use getLongind() to distinguish between them.
    259   * <p>
    260   * Here is an expanded Getopt example using long options and various
    261   * techniques described above:
    262   * <p>
    263   * <pre>
    264   * int c;
    265   * String arg;
    266   * LongOpt[] longopts = new LongOpt[3];
    267   * //
    268   * StringBuffer sb = new StringBuffer();
    269   * longopts[0] = new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h');
    270   * longopts[1] = new LongOpt("outputdir", LongOpt.REQUIRED_ARGUMENT, sb, 'o');
    271   * longopts[2] = new LongOpt("maximum", LongOpt.OPTIONAL_ARGUMENT, null, 2);
    272   * //
    273   * Getopt g = new Getopt("testprog", argv, "-:bc::d:hW;", longopts);
    274   * g.setOpterr(false); // We'll do our own error handling
    275   * //
    276   * while ((c = g.getopt()) != -1)
    277   *   switch (c)
    278   *     {
    279   *        case 0:
    280   *          arg = g.getOptarg();
    281   *          System.out.println("Got long option with value '" +
    282   *                             (char)(new Integer(sb.toString())).intValue()
    283   *                             + "' with argument " +
    284   *                             ((arg != null) ? arg : "null"));
    285   *          break;
    286   *          //
    287   *        case 1:
    288   *          System.out.println("I see you have return in order set and that " +
    289   *                             "a non-option argv element was just found " +
    290   *                             "with the value '" + g.getOptarg() + "'");
    291   *          break;
    292   *          //
    293   *        case 2:
    294   *          arg = g.getOptarg();
    295   *          System.out.println("I know this, but pretend I didn't");
    296   *          System.out.println("We picked option " +
    297   *                             longopts[g.getLongind()].getName() +
    298   *                           " with value " +
    299   *                           ((arg != null) ? arg : "null"));
    300   *          break;
    301   *          //
    302   *        case 'b':
    303   *          System.out.println("You picked plain old option " + (char)c);
    304   *          break;
    305   *          //
    306   *        case 'c':
    307   *        case 'd':
    308   *          arg = g.getOptarg();
    309   *          System.out.println("You picked option '" + (char)c +
    310   *                             "' with argument " +
    311   *                             ((arg != null) ? arg : "null"));
    312   *          break;
    313   *          //
    314   *        case 'h':
    315   *          System.out.println("I see you asked for help");
    316   *          break;
    317   *          //
    318   *        case 'W':
    319   *          System.out.println("Hmmm. You tried a -W with an incorrect long " +
    320   *                             "option name");
    321   *          break;
    322   *          //
    323   *        case ':':
    324   *          System.out.println("Doh! You need an argument for option " +
    325   *                             (char)g.getOptopt());
    326   *          break;
    327   *          //
    328   *        case '?':
    329   *          System.out.println("The option '" + (char)g.getOptopt() +
    330   *                           "' is not valid");
    331   *          break;
    332   *          //
    333   *        default:
    334   *          System.out.println("getopt() returned " + c);
    335   *          break;
    336   *     }
    337   * //
    338   * for (int i = g.getOptind(); i < argv.length ; i++)
    339   *   System.out.println("Non option argv element: " + argv[i] + "\n");
    340   * </pre>
    341   * <p>
    342   * There is an alternative form of the constructor used for long options
    343   * above.  This takes a trailing boolean flag.  If set to false, Getopt
    344   * performs identically to the example, but if the boolean flag is true
    345   * then long options are allowed to start with a single '-' instead of
    346   * "--".  If the first character of the option is a valid short option
    347   * character, then the option is treated as if it were the short option.
    348   * Otherwise it behaves as if the option is a long option.  Note that
    349   * the name given to this option - long_only - is very counter-intuitive.
    350   * It does not cause only long options to be parsed but instead enables
    351   * the behavior described above.
    352   * <p>
    353   * Note that the functionality and variable names used are driven from
    354   * the C lib version as this object is a port of the C code, not a
    355   * new implementation.  This should aid in porting existing C/C++ code,
    356   * as well as helping programmers familiar with the glibc version to
    357   * adapt to the Java version even if it seems very non-Java at times.
    358   * <p>
    359   * In this release I made all instance variables protected due to
    360   * overwhelming public demand.  Any code which relied on optarg,
    361   * opterr, optind, or optopt being public will need to be modified to
    362   * use the appropriate access methods.
    363   * <p>
    364   * Please send all bug reports, requests, and comments to
    365   * <a href="mailto:arenn@urbanophile.com">arenn@urbanophile.com</a>.
    366   *
    367   * @version 1.0.7
    368   *
    369   * @author Roland McGrath (roland@gnu.ai.mit.edu)
    370   * @author Ulrich Drepper (drepper@cygnus.com)
    371   * @author Aaron M. Renn (arenn@urbanophile.com)
    372   *
    373   * @see LongOpt
    374   */
    375 public class Getopt extends Object
    376 {
    377 
    378 /**************************************************************************/
    379 
    380 /*
    381  * Class Variables
    382  */
    383 
    384 /**
    385   * Describe how to deal with options that follow non-option ARGV-elements.
    386   *
    387   * If the caller did not specify anything,
    388   * the default is REQUIRE_ORDER if the property
    389   * gnu.posixly_correct is defined, PERMUTE otherwise.
    390   *
    391   * The special argument `--' forces an end of option-scanning regardless
    392   * of the value of `ordering'.  In the case of RETURN_IN_ORDER, only
    393   * `--' can cause `getopt' to return -1 with `optind' != ARGC.
    394   *
    395   * REQUIRE_ORDER means don't recognize them as options;
    396   * stop option processing when the first non-option is seen.
    397   * This is what Unix does.
    398   * This mode of operation is selected by either setting the property
    399   * gnu.posixly_correct, or using `+' as the first character
    400   * of the list of option characters.
    401   */
    402 protected static final int REQUIRE_ORDER = 1;
    403 
    404 /**
    405   * PERMUTE is the default.  We permute the contents of ARGV as we scan,
    406   * so that eventually all the non-options are at the end.  This allows options
    407   * to be given in any order, even with programs that were not written to
    408   * expect this.
    409   */
    410 protected static final int PERMUTE = 2;
    411 
    412 /**
    413   * RETURN_IN_ORDER is an option available to programs that were written
    414   * to expect options and other ARGV-elements in any order and that care about
    415   * the ordering of the two.  We describe each non-option ARGV-element
    416   * as if it were the argument of an option with character code 1.
    417   * Using `-' as the first character of the list of option characters
    418   * selects this mode of operation.
    419   */
    420 protected static final int RETURN_IN_ORDER = 3;
    421 
    422 /**************************************************************************/
    423 
    424 /*
    425  * Instance Variables
    426  */
    427 
    428 /**
    429   * For communication from `getopt' to the caller.
    430   * When `getopt' finds an option that takes an argument,
    431   * the argument value is returned here.
    432   * Also, when `ordering' is RETURN_IN_ORDER,
    433   * each non-option ARGV-element is returned here.
    434   */
    435 protected String optarg;
    436 
    437 /**
    438   *  Index in ARGV of the next element to be scanned.
    439   *  This is used for communication to and from the caller
    440   *  and for communication between successive calls to `getopt'.
    441   *
    442   *  On entry to `getopt', zero means this is the first call; initialize.
    443   *
    444   *  When `getopt' returns -1, this is the index of the first of the
    445   *  non-option elements that the caller should itself scan.
    446   *
    447   *  Otherwise, `optind' communicates from one call to the next
    448   *  how much of ARGV has been scanned so far.
    449   */
    450 protected int optind = 0;
    451 
    452 /**
    453   * Callers store false here to inhibit the error message
    454   * for unrecognized options.
    455   */
    456 protected boolean opterr = true;
    457 
    458 /**
    459   * When an unrecognized option is encountered, getopt will return a '?'
    460   * and store the value of the invalid option here.
    461   */
    462 protected int optopt = '?';
    463 
    464 /**
    465   * The next char to be scanned in the option-element
    466   * in which the last option character we returned was found.
    467   * This allows us to pick up the scan where we left off.
    468   *
    469   * If this is zero, or a null string, it means resume the scan
    470   * by advancing to the next ARGV-element.
    471   */
    472 protected String nextchar;
    473 
    474 /**
    475   * This is the string describing the valid short options.
    476   */
    477 protected String optstring;
    478 
    479 /**
    480   * This is an array of LongOpt objects which describ the valid long
    481   * options.
    482   */
    483 protected LongOpt[] long_options;
    484 
    485 /**
    486   * This flag determines whether or not we are parsing only long args
    487   */
    488 protected boolean long_only;
    489 
    490 /**
    491   * Stores the index into the long_options array of the long option found
    492   */
    493 protected int longind;
    494 
    495 /**
    496   * The flag determines whether or not we operate in strict POSIX compliance
    497   */
    498 protected boolean posixly_correct;
    499 
    500 /**
    501   * A flag which communicates whether or not checkLongOption() did all
    502   * necessary processing for the current option
    503   */
    504 protected boolean longopt_handled;
    505 
    506 /**
    507   * The index of the first non-option in argv[]
    508   */
    509 protected int first_nonopt = 1;
    510 
    511 /**
    512   * The index of the last non-option in argv[]
    513   */
    514 protected int last_nonopt = 1;
    515 
    516 /**
    517   * Flag to tell getopt to immediately return -1 the next time it is
    518   * called.
    519   */
    520 private boolean endparse = false;
    521 
    522 /**
    523   * Saved argument list passed to the program
    524   */
    525 protected String[] argv;
    526 
    527 /**
    528   * Determines whether we permute arguments or not
    529   */
    530 protected int ordering;
    531 
    532 /**
    533   * Name to print as the program name in error messages.  This is necessary
    534   * since Java does not place the program name in argv[0]
    535   */
    536 protected String progname;
    537 
    538 /**
    539   * The localized strings are kept in a separate file
    540   */
    541 private OptI18n _messages = new OptI18n(); // ResourceBundle.getBundle("gnu/getopt/MessagesBundle", Locale.getDefault());
    542 
    543 /**************************************************************************/
    544 
    545 /*
    546  * Constructors
    547  */
    548 
    549 /**
    550   * Construct a basic Getopt instance with the given input data.  Note that
    551   * this handles "short" options only.
    552   *
    553   * @param progname The name to display as the program name when printing errors
    554   * @param argv The String array passed as the command line to the program.
    555   * @param optstring A String containing a description of the valid args for this program
    556   */
    557 public
    558 Getopt(String progname, String[] argv, String optstring)
    559 {
    560   this(progname, argv, optstring, null, false);
    561 }
    562 
    563 /**************************************************************************/
    564 
    565 /**
    566   * Construct a Getopt instance with given input data that is capable of
    567   * parsing long options as well as short.
    568   *
    569   * @param progname The name to display as the program name when printing errors
    570   * @param argv The String array passed as the command ilne to the program
    571   * @param optstring A String containing a description of the valid short args for this program
    572   * @param long_options An array of LongOpt objects that describes the valid long args for this program
    573   */
    574 public
    575 Getopt(String progname, String[] argv, String optstring,
    576        LongOpt[] long_options)
    577 {
    578   this(progname, argv, optstring, long_options, false);
    579 }
    580 
    581 /**************************************************************************/
    582 
    583 private static Function<String, String> tr = Function.identity();
    584 
    585 /**
    586  * Set the global translation handler for Getopt.
    587  *
    588  * This needs to be done before any call to {@link Getopt} or {@link LongOpt}
    589  * constructor.
    590  * @param tr function that takes messages in English and returns the localized message
    591  */
    592 public static void setI18nHandler(Function<String, String> tr) {
    593     Getopt.tr = tr;
    594 }
    595 
    596 static class OptI18n {
    597 
    598     private final Map<String, String> trns = new HashMap<>();
    599 
    600     public  OptI18n() {
    601         add("getopt.ambigious", tr("{0}: option ''{1}'' is ambiguous"));
    602         add("getopt.arguments1", tr("{0}: option ''--{1}'' does not allow an argument"));
    603         add("getopt.arguments2", tr("{0}: option ''{1}{2}'' does not allow an argument"));
    604         add("getopt.requires", tr("{0}: option ''{1}'' requires an argument"));
    605         add("getopt.unrecognized", tr("{0}: unrecognized option ''--{1}''"));
    606         add("getopt.unrecognized2", tr("{0}: unrecognized option ''{1}{2}''"));
    607         add("getopt.illegal", tr("{0}: illegal option -- {1}"));
    608         add("getopt.invalid", tr("{0}: invalid option -- {1}"));
    609         add("getopt.requires2", tr("{0}: option requires an argument -- {1}"));
    610         add("getopt.invalidValue", tr("Invalid value {0} for parameter ''has_arg''"));
    611     }
    612 
    613     private String tr(String s) {
    614         return Getopt.tr.apply(s);
    615     }
    616 
    617     private void add(String key, String value) {
    618         trns.put(key, value);
    619     }
    620 
    621     public String getString(String s) {
    622         String val = trns.get(s);
    623         if (val == null) throw new IllegalArgumentException();
    624         return val.replace("'", "''");
    625     }
    626 }
    627 
    628 /**
    629   * Construct a Getopt instance with given input data that is capable of
    630   * parsing long options and short options.  Contrary to what you might
    631   * think, the flag 'long_only' does not determine whether or not we
    632   * scan for only long arguments.  Instead, a value of true here allows
    633   * long arguments to start with a '-' instead of '--' unless there is a
    634   * conflict with a short option name.
    635   *
    636   * @param progname The name to display as the program name when printing errors
    637   * @param argv The String array passed as the command ilne to the program
    638   * @param optstring A String containing a description of the valid short args for this program
    639   * @param long_options An array of LongOpt objects that describes the valid long args for this program
    640   * @param long_only true if long options that do not conflict with short options can start with a '-' as well as '--'
    641   */
    642 public
    643 Getopt(String progname, String[] argv, String optstring,
    644        LongOpt[] long_options, boolean long_only)
    645 {
    646   if (optstring.length() == 0)
    647     optstring = " ";
    648 
    649   // This function is essentially _getopt_initialize from GNU getopt
    650   this.progname = progname;
    651   this.argv = argv;
    652   this.optstring = optstring;
    653   this.long_options = long_options;
    654   this.long_only = long_only;
    655 
    656   // Check for property "gnu.posixly_correct" to determine whether to
    657   // strictly follow the POSIX standard.  This replaces the "POSIXLY_CORRECT"
    658   // environment variable in the C version
    659   try {
    660   if (System.getProperty("gnu.posixly_correct", null) == null)
    661     posixly_correct = false;
    662   else
    663     {
    664       posixly_correct = true;
    665       _messages = new OptI18n();//ResourceBundle.getBundle("gnu/getopt/MessagesBundle",
    666                              //                      Locale.US);
    667     }
    668   } catch (SecurityException e) {
    669     System.err.println(e.getMessage());
    670   }
    671 
    672   // Determine how to handle the ordering of options and non-options
    673   if (optstring.charAt(0) == '-')
    674     {
    675       ordering = RETURN_IN_ORDER;
    676       if (optstring.length() > 1)
    677         this.optstring = optstring.substring(1);
    678     }
    679   else if (optstring.charAt(0) == '+')
    680     {
    681       ordering = REQUIRE_ORDER;
    682       if (optstring.length() > 1)
    683         this.optstring = optstring.substring(1);
    684     }
    685   else if (posixly_correct)
    686     {
    687       ordering = REQUIRE_ORDER;
    688     }
    689   else
    690     {
    691       ordering = PERMUTE; // The normal default case
    692     }
    693 }
    694 
    695 /**************************************************************************/
    696 
    697 /*
    698  * Instance Methods
    699  */
    700 
    701 /**
    702   * In GNU getopt, it is possible to change the string containg valid options
    703   * on the fly because it is passed as an argument to getopt() each time.  In
    704   * this version we do not pass the string on every call.  In order to allow
    705   * dynamic option string changing, this method is provided.
    706   *
    707   * @param optstring The new option string to use
    708   */
    709 public void
    710 setOptstring(String optstring)
    711 {
    712   if (optstring.length() == 0)
    713     optstring = " ";
    714 
    715   this.optstring = optstring;
    716 }
    717 
    718 /**************************************************************************/
    719 
    720 /**
    721   * optind it the index in ARGV of the next element to be scanned.
    722   * This is used for communication to and from the caller
    723   * and for communication between successive calls to `getopt'.
    724   *
    725   * When `getopt' returns -1, this is the index of the first of the
    726   * non-option elements that the caller should itself scan.
    727   *
    728   * Otherwise, `optind' communicates from one call to the next
    729   * how much of ARGV has been scanned so far.
    730   */
    731 public int
    732 getOptind()
    733 {
    734   return(optind);
    735 }
    736 
    737 /**************************************************************************/
    738 
    739 /**
    740   * This method allows the optind index to be set manually.  Normally this
    741   * is not necessary (and incorrect usage of this method can lead to serious
    742   * lossage), but optind is a public symbol in GNU getopt, so this method
    743   * was added to allow it to be modified by the caller if desired.
    744   *
    745   * @param optind The new value of optind
    746   */
    747 public void
    748 setOptind(int optind)
    749 {
    750   this.optind = optind;
    751 }
    752 
    753 /**************************************************************************/
    754 
    755 /**
    756   * Since in GNU getopt() the argument vector is passed back in to the
    757   * function every time, the caller can swap out argv on the fly.  Since
    758   * passing argv is not required in the Java version, this method allows
    759   * the user to override argv.  Note that incorrect use of this method can
    760   * lead to serious lossage.
    761   *
    762   * @param argv New argument list
    763   */
    764 public void
    765 setArgv(String[] argv)
    766 {
    767   this.argv = argv;
    768 }
    769 
    770 /**************************************************************************/
    771 
    772 /**
    773   * For communication from `getopt' to the caller.
    774   * When `getopt' finds an option that takes an argument,
    775   * the argument value is returned here.
    776   * Also, when `ordering' is RETURN_IN_ORDER,
    777   * each non-option ARGV-element is returned here.
    778   * No set method is provided because setting this variable has no effect.
    779   */
    780 public String
    781 getOptarg()
    782 {
    783   return(optarg);
    784 }
    785 
    786 /**************************************************************************/
    787 
    788 /**
    789   * Normally Getopt will print a message to the standard error when an
    790   * invalid option is encountered.  This can be suppressed (or re-enabled)
    791   * by calling this method.  There is no get method for this variable
    792   * because if you can't remember the state you set this to, why should I?
    793   */
    794 public void
    795 setOpterr(boolean opterr)
    796 {
    797   this.opterr = opterr;
    798 }
    799 
    800 /**************************************************************************/
    801 
    802 /**
    803   * When getopt() encounters an invalid option, it stores the value of that
    804   * option in optopt which can be retrieved with this method.  There is
    805   * no corresponding set method because setting this variable has no effect.
    806   */
    807 public int
    808 getOptopt()
    809 {
    810   return(optopt);
    811 }
    812 
    813 /**************************************************************************/
    814 
    815 /**
    816   * Returns the index into the array of long options (NOT argv) representing
    817   * the long option that was found.
    818   */
    819 public int
    820 getLongind()
    821 {
    822   return(longind);
    823 }
    824 
    825 /**************************************************************************/
    826 
    827 /**
    828   * Exchange the shorter segment with the far end of the longer segment.
    829   * That puts the shorter segment into the right place.
    830   * It leaves the longer segment in the right place overall,
    831   * but it consists of two parts that need to be swapped next.
    832   * This method is used by getopt() for argument permutation.
    833   */
    834 protected void
    835 exchange(String[] argv)
    836 {
    837   int bottom = first_nonopt;
    838   int middle = last_nonopt;
    839   int top = optind;
    840   String tem;
    841 
    842   while (top > middle && middle > bottom)
    843     {
    844       if (top - middle > middle - bottom)
    845         {
    846           // Bottom segment is the short one.
    847           int len = middle - bottom;
    848           int i;
    849 
    850           // Swap it with the top part of the top segment.
    851           for (i = 0; i < len; i++)
    852             {
    853               tem = argv[bottom + i];
    854               argv[bottom + i] = argv[top - (middle - bottom) + i];
    855               argv[top - (middle - bottom) + i] = tem;
    856             }
    857           // Exclude the moved bottom segment from further swapping.
    858           top -= len;
    859         }
    860       else
    861         {
    862           // Top segment is the short one.
    863           int len = top - middle;
    864           int i;
    865 
    866           // Swap it with the bottom part of the bottom segment.
    867           for (i = 0; i < len; i++)
    868             {
    869               tem = argv[bottom + i];
    870               argv[bottom + i] = argv[middle + i];
    871               argv[middle + i] = tem;
    872             }
    873           // Exclude the moved top segment from further swapping.
    874           bottom += len;
    875         }
    876     }
    877 
    878   // Update records for the slots the non-options now occupy.
    879 
    880   first_nonopt += (optind - last_nonopt);
    881   last_nonopt = optind;
    882 }
    883 
    884 /**************************************************************************/
    885 
    886 /**
    887   * Check to see if an option is a valid long option.  Called by getopt().
    888   * Put in a separate method because this needs to be done twice.  (The
    889   * C getopt authors just copy-pasted the code!).
    890   *
    891   * @param longind A buffer in which to store the 'val' field of found LongOpt
    892   *
    893   * @return Various things depending on circumstances
    894   */
    895 protected int
    896 checkLongOption()
    897 {
    898   LongOpt pfound = null;
    899   int nameend;
    900   boolean ambig;
    901   boolean exact;
    902 
    903   longopt_handled = true;
    904   ambig = false;
    905   exact = false;
    906   longind = -1;
    907 
    908   nameend = nextchar.indexOf("=");
    909   if (nameend == -1)
    910     nameend = nextchar.length();
    911 
    912   // Test all lnog options for either exact match or abbreviated matches
    913   for (int i = 0; i < long_options.length; i++)
    914     {
    915       if (long_options[i].getName().startsWith(nextchar.substring(0, nameend)))
    916         {
    917           if (long_options[i].getName().equals(nextchar.substring(0, nameend)))
    918             {
    919               // Exact match found
    920               pfound = long_options[i];
    921               longind = i;
    922               exact = true;
    923               break;
    924             }
    925           else if (pfound == null)
    926             {
    927               // First nonexact match found
    928               pfound = long_options[i];
    929               longind = i;
    930             }
    931           else
    932             {
    933               // Second or later nonexact match found
    934               ambig = true;
    935             }
    936         }
    937     } // for
    938 
    939   // Print out an error if the option specified was ambiguous
    940   if (ambig && !exact)
    941     {
    942       if (opterr)
    943         {
    944           Object[] msgArgs = { progname, argv[optind] };
    945           System.err.println(MessageFormat.format(
    946                              _messages.getString("getopt.ambigious"),
    947                              msgArgs));
    948         }
    949 
    950        nextchar = "";
    951        optopt = 0;
    952        ++optind;
    953 
    954        return('?');
    955     }
    956 
    957   if (pfound != null)
    958     {
    959       ++optind;
    960 
    961       if (nameend != nextchar.length())
    962         {
    963           if (pfound.has_arg != LongOpt.NO_ARGUMENT)
    964             {
    965               if (nextchar.substring(nameend).length() > 1)
    966                 optarg = nextchar.substring(nameend+1);
    967               else
    968                 optarg = "";
    969             }
    970           else
    971             {
    972               if (opterr)
    973                 {
    974                   // -- option
    975                   if (argv[optind - 1].startsWith("--"))
    976                     {
    977                       Object[] msgArgs = { progname, pfound.name };
    978                       System.err.println(MessageFormat.format(
    979                                   _messages.getString("getopt.arguments1"),
    980                                   msgArgs));
    981                     }
    982                   // +option or -option
    983                   else
    984                     {
    985                       Object[] msgArgs = { progname,
    986                                Character.toString(argv[optind-1].charAt(0)),
    987                                pfound.name };
    988                       System.err.println(MessageFormat.format(
    989                                _messages.getString("getopt.arguments2"),
    990                                msgArgs));
    991                     }
    992                  }
    993 
    994               nextchar = "";
    995               optopt = pfound.val;
    996 
    997               return('?');
    998             }
    999         } // if (nameend)
    1000       else if (pfound.has_arg == LongOpt.REQUIRED_ARGUMENT)
    1001         {
    1002           if (optind < argv.length)
    1003             {
    1004                optarg = argv[optind];
    1005                ++optind;
    1006             }
    1007           else
    1008             {
    1009               if (opterr)
    1010                 {
    1011                   Object[] msgArgs = { progname, argv[optind-1] };
    1012                   System.err.println(MessageFormat.format(
    1013                                      _messages.getString("getopt.requires"),
    1014                                      msgArgs));
    1015                 }
    1016 
    1017               nextchar = "";
    1018               optopt = pfound.val;
    1019               if (optstring.charAt(0) == ':')
    1020                 return(':');
    1021               else
    1022                 return('?');
    1023             }
    1024         } // else if (pfound)
    1025 
    1026       nextchar = "";
    1027 
    1028       if (pfound.flag != null)
    1029         {
    1030           pfound.flag.setLength(0);
    1031           pfound.flag.append(pfound.val);
    1032 
    1033           return(0);
    1034         }
    1035 
    1036       return(pfound.val);
    1037    } // if (pfound != null)
    1038 
    1039   longopt_handled = false;
    1040 
    1041   return(0);
    1042 }
    1043 
    1044 /**************************************************************************/
    1045 
    1046 /**
    1047   * This method returns a char that is the current option that has been
    1048   * parsed from the command line.  If the option takes an argument, then
    1049   * the internal variable 'optarg' is set which is a String representing
    1050   * the the value of the argument.  This value can be retrieved by the
    1051   * caller using the getOptarg() method.  If an invalid option is found,
    1052   * an error message is printed and a '?' is returned.  The name of the
    1053   * invalid option character can be retrieved by calling the getOptopt()
    1054   * method.  When there are no more options to be scanned, this method
    1055   * returns -1.  The index of first non-option element in argv can be
    1056   * retrieved with the getOptind() method.
    1057   *
    1058   * @return Various things as described above
    1059   */
    1060 public int
    1061 getopt()
    1062 {
    1063   optarg = null;
    1064 
    1065   if (endparse == true)
    1066     return(-1);
    1067 
    1068   if ((nextchar == null) || (nextchar.equals("")))
    1069     {
    1070       // If we have just processed some options following some non-options,
    1071       //  exchange them so that the options come first.
    1072       if (last_nonopt > optind)
    1073         last_nonopt = optind;
    1074       if (first_nonopt > optind)
    1075         first_nonopt = optind;
    1076 
    1077       if (ordering == PERMUTE)
    1078         {
    1079           // If we have just processed some options following some non-options,
    1080           // exchange them so that the options come first.
    1081           if ((first_nonopt != last_nonopt) && (last_nonopt != optind))
    1082             exchange(argv);
    1083           else if (last_nonopt != optind)
    1084             first_nonopt = optind;
    1085 
    1086           // Skip any additional non-options
    1087           // and extend the range of non-options previously skipped.
    1088           while ((optind < argv.length) && (argv[optind].equals("") ||
    1089             (argv[optind].charAt(0) != '-') || argv[optind].equals("-")))
    1090             {
    1091               optind++;
    1092             }
    1093 
    1094           last_nonopt = optind;
    1095         }
    1096 
    1097       // The special ARGV-element `--' means premature end of options.
    1098       // Skip it like a null option,
    1099       // then exchange with previous non-options as if it were an option,
    1100       // then skip everything else like a non-option.
    1101       if ((optind != argv.length) && argv[optind].equals("--"))
    1102         {
    1103           optind++;
    1104 
    1105           if ((first_nonopt != last_nonopt) && (last_nonopt != optind))
    1106             exchange (argv);
    1107           else if (first_nonopt == last_nonopt)
    1108             first_nonopt = optind;
    1109 
    1110           last_nonopt = argv.length;
    1111 
    1112           optind = argv.length;
    1113         }
    1114 
    1115       // If we have done all the ARGV-elements, stop the scan
    1116       // and back over any non-options that we skipped and permuted.
    1117       if (optind == argv.length)
    1118         {
    1119           // Set the next-arg-index to point at the non-options
    1120           // that we previously skipped, so the caller will digest them.
    1121           if (first_nonopt != last_nonopt)
    1122             optind = first_nonopt;
    1123 
    1124           return(-1);
    1125         }
    1126 
    1127       // If we have come to a non-option and did not permute it,
    1128       // either stop the scan or describe it to the caller and pass it by.
    1129       if (argv[optind].equals("") || (argv[optind].charAt(0) != '-') ||
    1130           argv[optind].equals("-"))
    1131         {
    1132           if (ordering == REQUIRE_ORDER)
    1133             return(-1);
    1134 
    1135             optarg = argv[optind++];
    1136             return(1);
    1137         }
    1138 
    1139       // We have found another option-ARGV-element.
    1140       // Skip the initial punctuation.
    1141       if (argv[optind].startsWith("--"))
    1142         nextchar = argv[optind].substring(2);
    1143       else
    1144         nextchar = argv[optind].substring(1);
    1145    }
    1146 
    1147   // Decode the current option-ARGV-element.
    1148 
    1149   /* Check whether the ARGV-element is a long option.
    1150 
    1151      If long_only and the ARGV-element has the form "-f", where f is
    1152      a valid short option, don't consider it an abbreviated form of
    1153      a long option that starts with f.  Otherwise there would be no
    1154      way to give the -f short option.
    1155 
    1156      On the other hand, if there's a long option "fubar" and
    1157      the ARGV-element is "-fu", do consider that an abbreviation of
    1158      the long option, just like "--fu", and not "-f" with arg "u".
    1159 
    1160      This distinction seems to be the most useful approach.  */
    1161   if ((long_options != null) && (argv[optind].startsWith("--")
    1162       || (long_only && ((argv[optind].length()  > 2) ||
    1163       (optstring.indexOf(argv[optind].charAt(1)) == -1)))))
    1164     {
    1165        int c = checkLongOption();
    1166 
    1167        if (longopt_handled)
    1168          return(c);
    1169 
    1170       // Can't find it as a long option.  If this is not getopt_long_only,
    1171       // or the option starts with '--' or is not a valid short
    1172       // option, then it's an error.
    1173       // Otherwise interpret it as a short option.
    1174       if (!long_only || argv[optind].startsWith("--")
    1175         || (optstring.indexOf(nextchar.charAt(0)) == -1))
    1176         {
    1177           if (opterr)
    1178             {
    1179               if (argv[optind].startsWith("--"))
    1180                 {
    1181                   Object[] msgArgs = { progname, nextchar };
    1182                   System.err.println(MessageFormat.format(
    1183                                    _messages.getString("getopt.unrecognized"),
    1184                                    msgArgs));
    1185                 }
    1186               else
    1187                 {
    1188                   Object[] msgArgs = { progname,
    1189                                  Character.toString(argv[optind].charAt(0)),
    1190                                  nextchar };
    1191                   System.err.println(MessageFormat.format(
    1192                                  _messages.getString("getopt.unrecognized2"),
    1193                                  msgArgs));
    1194                 }
    1195             }
    1196 
    1197           nextchar = "";
    1198           ++optind;
    1199           optopt = 0;
    1200 
    1201           return('?');
    1202         }
    1203     } // if (longopts)
    1204 
    1205   // Look at and handle the next short option-character */
    1206   int c = nextchar.charAt(0); //**** Do we need to check for empty str?
    1207   if (nextchar.length() > 1)
    1208     nextchar = nextchar.substring(1);
    1209   else
    1210     nextchar = "";
    1211 
    1212   String temp = null;
    1213   if (optstring.indexOf(c) != -1)
    1214     temp = optstring.substring(optstring.indexOf(c));
    1215 
    1216   if (nextchar.equals(""))
    1217     ++optind;
    1218 
    1219   if ((temp == null) || (c == ':'))
    1220     {
    1221       if (opterr)
    1222         {
    1223           if (posixly_correct)
    1224             {
    1225               // 1003.2 specifies the format of this message
    1226               Object[] msgArgs = { progname,
    1227                                    Character.toString((char)c) };
    1228               System.err.println(MessageFormat.format(
    1229                             _messages.getString("getopt.illegal"), msgArgs));
    1230             }
    1231           else
    1232             {
    1233               Object[] msgArgs = { progname,
    1234                                    Character.toString((char)c) };
    1235               System.err.println(MessageFormat.format(
    1236                             _messages.getString("getopt.invalid"), msgArgs));
    1237             }
    1238         }
    1239 
    1240       optopt = c;
    1241 
    1242       return('?');
    1243     }
    1244 
    1245   // Convenience. Treat POSIX -W foo same as long option --foo
    1246   if ((temp.charAt(0) == 'W') && (temp.length() > 1) && (temp.charAt(1) == ';'))
    1247     {
    1248       if (!nextchar.equals(""))
    1249         {
    1250           optarg = nextchar;
    1251         }
    1252       // No further cars in this argv element and no more argv elements
    1253       else if (optind == argv.length)
    1254         {
    1255           if (opterr)
    1256             {
    1257               // 1003.2 specifies the format of this message.
    1258               Object[] msgArgs = { progname,
    1259                                    Character.toString((char)c) };
    1260               System.err.println(MessageFormat.format(
    1261                             _messages.getString("getopt.requires2"), msgArgs));
    1262             }
    1263 
    1264           optopt = c;
    1265           if (optstring.charAt(0) == ':')
    1266             return(':');
    1267           else
    1268             return('?');
    1269         }
    1270       else
    1271         {
    1272           // We already incremented `optind' once;
    1273           // increment it again when taking next ARGV-elt as argument.
    1274           nextchar = argv[optind];
    1275           optarg  = argv[optind];
    1276         }
    1277 
    1278       c = checkLongOption();
    1279 
    1280       if (longopt_handled)
    1281         return(c);
    1282       else
    1283         // Let the application handle it
    1284         {
    1285           nextchar = null;
    1286           ++optind;
    1287           return('W');
    1288         }
    1289     }
    1290 
    1291   if ((temp.length() > 1) && (temp.charAt(1) == ':'))
    1292     {
    1293       if ((temp.length() > 2) && (temp.charAt(2) == ':'))
    1294         // This is an option that accepts and argument optionally
    1295         {
    1296           if (!nextchar.equals(""))
    1297             {
    1298                optarg = nextchar;
    1299                ++optind;
    1300             }
    1301           else
    1302             {
    1303               optarg = null;
    1304             }
    1305 
    1306           nextchar = null;
    1307         }
    1308       else
    1309         {
    1310           if (!nextchar.equals(""))
    1311             {
    1312               optarg = nextchar;
    1313               ++optind;
    1314             }
    1315           else if (optind == argv.length)
    1316             {
    1317               if (opterr)
    1318                 {
    1319                   // 1003.2 specifies the format of this message
    1320                   Object[] msgArgs = { progname,
    1321                                        Character.toString((char)c) };
    1322                   System.err.println(MessageFormat.format(
    1323                             _messages.getString("getopt.requires2"), msgArgs));
    1324                 }
    1325 
    1326               optopt = c;
    1327 
    1328               if (optstring.charAt(0) == ':')
    1329                 return(':');
    1330               else
    1331                 return('?');
    1332             }
    1333           else
    1334             {
    1335               optarg = argv[optind];
    1336               ++optind;
    1337 
    1338               // Ok, here's an obscure Posix case.  If we have o:, and
    1339               // we get -o -- foo, then we're supposed to skip the --,
    1340               // end parsing of options, and make foo an operand to -o.
    1341               // Only do this in Posix mode.
    1342               if ((posixly_correct) && optarg.equals("--"))
    1343                 {
    1344                   // If end of argv, error out
    1345                   if (optind == argv.length)
    1346                     {
    1347                       if (opterr)
    1348                         {
    1349                           // 1003.2 specifies the format of this message
    1350                           Object[] msgArgs = { progname,
    1351                                                Character.toString((char)c) };
    1352                           System.err.println(MessageFormat.format(
    1353                              _messages.getString("getopt.requires2"), msgArgs));
    1354                         }
    1355 
    1356                       optopt = c;
    1357 
    1358                       if (optstring.charAt(0) == ':')
    1359                         return(':');
    1360                       else
    1361                         return('?');
    1362                     }
    1363 
    1364                   // Set new optarg and set to end
    1365                   // Don't permute as we do on -- up above since we
    1366                   // know we aren't in permute mode because of Posix.
    1367                   optarg = argv[optind];
    1368                   ++optind;
    1369                   first_nonopt = optind;
    1370                   last_nonopt = argv.length;
    1371                   endparse = true;
    1372                 }
    1373             }
    1374 
    1375           nextchar = null;
    1376         }
    1377     }
    1378 
    1379   return(c);
    1380 }
    1381 
    1382 } // Class Getopt
    1383 
    1384 
  • src/gnu/getopt/LongOpt.java

     
    1 /**************************************************************************
    2 /* LongOpt.java -- Long option object for Getopt
    3 /*
    4 /* Copyright (c) 1998 by Aaron M. Renn (arenn@urbanophile.com)
    5 /*
    6 /* This program is free software; you can redistribute it and/or modify
    7 /* it under the terms of the GNU Library General Public License as published
    8 /* by  the Free Software Foundation; either version 2 of the License or
    9 /* (at your option) any later version.
    10 /*
    11 /* This program is distributed in the hope that it will be useful, but
    12 /* WITHOUT ANY WARRANTY; without even the implied warranty of
    13 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14 /* GNU Library General Public License for more details.
    15 /*
    16 /* You should have received a copy of the GNU Library General Public License
    17 /* along with this program; see the file COPYING.LIB.  If not, write to
    18 /* the Free Software Foundation Inc., 59 Temple Place - Suite 330,
    19 /* Boston, MA  02111-1307 USA
    20 /**************************************************************************/
    21 
    22 package gnu.getopt;
    23 
    24 import java.text.MessageFormat;
    25 
    26 /**************************************************************************/
    27 
    28 /**
    29   * This object represents the definition of a long option in the Java port
    30   * of GNU getopt.  An array of LongOpt objects is passed to the Getopt
    31   * object to define the list of valid long options for a given parsing
    32   * session.  Refer to the getopt documentation for details on the
    33   * format of long options.
    34   *
    35   * @version 1.0.5
    36   * @author Aaron M. Renn (arenn@urbanophile.com)
    37   *
    38   * @see Getopt
    39   */
    40 public class LongOpt extends Object
    41 {
    42 
    43 /**************************************************************************/
    44 
    45 /*
    46  * Class Variables
    47  */
    48 
    49 /**
    50   * Constant value used for the "has_arg" constructor argument.  This
    51   * value indicates that the option takes no argument.
    52   */
    53 public static final int NO_ARGUMENT = 0;
    54 
    55 /**
    56   * Constant value used for the "has_arg" constructor argument.  This
    57   * value indicates that the option takes an argument that is required.
    58   */
    59 public static final int REQUIRED_ARGUMENT = 1;
    60 
    61 /**
    62   * Constant value used for the "has_arg" constructor argument.  This
    63   * value indicates that the option takes an argument that is optional.
    64   */
    65 public static final int OPTIONAL_ARGUMENT = 2;
    66 
    67 /**************************************************************************/
    68 
    69 /*
    70  * Instance Variables
    71  */
    72 
    73 /**
    74   * The name of the long option
    75   */
    76 protected String name;
    77 
    78 /**
    79   * Indicates whether the option has no argument, a required argument, or
    80   * an optional argument.
    81   */
    82 protected int has_arg;
    83 
    84 /**
    85   * If this variable is not null, then the value stored in "val" is stored
    86   * here when this long option is encountered.  If this is null, the value
    87   * stored in "val" is treated as the name of an equivalent short option.
    88   */
    89 protected StringBuffer flag;
    90 
    91 /**
    92   * The value to store in "flag" if flag is not null, otherwise the
    93   * equivalent short option character for this long option.
    94   */
    95 protected int val;
    96 
    97 /**
    98   * Localized strings for error messages
    99   */
    100 private Getopt.OptI18n _messages = new Getopt.OptI18n(); // ResourceBundle.getBundle("gnu/getopt/MessagesBundle", Locale.getDefault());
    101 
    102 /**************************************************************************/
    103 
    104 /*
    105  * Constructors
    106  */
    107 
    108 /**
    109   * Create a new LongOpt object with the given parameter values.  If the
    110   * value passed as has_arg is not valid, then an exception is thrown.
    111   *
    112   * @param name The long option String.
    113   * @param has_arg Indicates whether the option has no argument (NO_ARGUMENT), a required argument (REQUIRED_ARGUMENT) or an optional argument (OPTIONAL_ARGUMENT).
    114   * @param flag If non-null, this is a location to store the value of "val" when this option is encountered, otherwise "val" is treated as the equivalent short option character.
    115   * @param val The value to return for this long option, or the equivalent single letter option to emulate if flag is null.
    116   *
    117   * @exception IllegalArgumentException If the has_arg param is not one of NO_ARGUMENT, REQUIRED_ARGUMENT or OPTIONAL_ARGUMENT.
    118   */
    119 public
    120 LongOpt(String name, int has_arg,
    121         StringBuffer flag, int val) throws IllegalArgumentException
    122 {
    123   // Validate has_arg
    124   if ((has_arg != NO_ARGUMENT) && (has_arg != REQUIRED_ARGUMENT)
    125      && (has_arg != OPTIONAL_ARGUMENT))
    126     {
    127       Object[] msgArgs = { Integer.toString(has_arg) };
    128       throw new IllegalArgumentException(MessageFormat.format(
    129                     _messages.getString("getopt.invalidValue"), msgArgs));
    130     }
    131 
    132   // Store off values
    133   this.name = name;
    134   this.has_arg = has_arg;
    135   this.flag = flag;
    136   this.val = val;
    137 }
    138 
    139 /**************************************************************************/
    140 
    141 /**
    142   * Returns the name of this LongOpt as a String
    143   *
    144   * @return Then name of the long option
    145   */
    146 public String
    147 getName()
    148 {
    149   return(name);
    150 }
    151 
    152 /**************************************************************************/
    153 
    154 /**
    155   * Returns the value set for the 'has_arg' field for this long option
    156   *
    157   * @return The value of 'has_arg'
    158   */
    159 public int
    160 getHasArg()
    161 {
    162   return(has_arg);
    163 }
    164 
    165 /**************************************************************************/
    166 
    167 /**
    168   * Returns the value of the 'flag' field for this long option
    169   *
    170   * @return The value of 'flag'
    171   */
    172 public StringBuffer
    173 getFlag()
    174 {
    175   return(flag);
    176 }
    177 
    178 /**
    179   * Returns the value of the 'val' field for this long option
    180   *
    181   * @return The value of 'val'
    182   */
    183 public int
    184 getVal()
    185 {
    186   return(val);
    187 }
    188 
    189 /**************************************************************************/
    190 
    191 } // Class LongOpt
    192 
  • src/org/openstreetmap/josm/data/projection/ProjectionCLI.java

     
    1111import java.nio.file.Files;
    1212import java.nio.file.Paths;
    1313import java.util.ArrayList;
     14import java.util.Arrays;
    1415import java.util.List;
    1516import java.util.function.Function;
    1617
     
    1819import org.openstreetmap.josm.data.coor.EastNorth;
    1920import org.openstreetmap.josm.data.coor.LatLon;
    2021import org.openstreetmap.josm.data.coor.conversion.LatLonParser;
    21 import org.openstreetmap.josm.tools.I18n;
     22import org.openstreetmap.josm.tools.OptionParser;
    2223import org.openstreetmap.josm.tools.Utils;
    2324
    24 import gnu.getopt.Getopt;
    25 import gnu.getopt.LongOpt;
    26 
    2725/**
    2826 * Command line interface for projecting coordinates.
    2927 * @since 12792
     
    4341
    4442    @Override
    4543    public void processArguments(String[] argArray) {
    46         Getopt.setI18nHandler(I18n::tr);
    47         Getopt getopt = new Getopt("JOSM projection", argArray, "Irh", new LongOpt[] {
    48                 new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h')});
     44        List<String> positionalArguments = new OptionParser("JOSM projection")
     45            .addFlagParameter("help", this::showHelp)
     46            .addShortAlias("help", "h")
     47            .addFlagParameter("inverse", () -> argInverse = true)
     48            .addShortAlias("inverse", "I")
     49            .addFlagParameter("switch-input", () -> argSwitchInput = true)
     50            .addShortAlias("switch-input", "r")
     51            .addFlagParameter("switch-output", () -> argSwitchOutput = true)
     52            .addShortAlias("switch-output", "s")
     53            .parseOptionsOrExit(Arrays.asList(argArray));
    4954
    50         int c;
    51         while ((c = getopt.getopt()) != -1) {
    52             switch (c) {
    53             case 'h':
    54                 showHelp();
    55                 System.exit(0);
    56             case 'I':
    57                 argInverse = true;
    58                 break;
    59             case 'r':
    60                 argSwitchInput = true;
    61                 break;
    62             case 's':
    63                 argSwitchOutput = true;
    64                 break;
    65             default:
    66                 // ignore
    67             }
    68         }
    69 
    7055        List<String> projParamFrom = new ArrayList<>();
    7156        List<String> projParamTo = new ArrayList<>();
    7257        List<String> otherPositional = new ArrayList<>();
    7358        boolean toTokenSeen = false;
    7459        // positional arguments:
    75         for (int i = getopt.getOptind(); i < argArray.length; ++i) {
    76             String arg = argArray[i];
     60        for (String arg: positionalArguments) {
    7761            if (arg.isEmpty()) throw new IllegalArgumentException("non-empty argument expected");
    7862            if (arg.startsWith("+")) {
    7963                if ("+to".equals(arg)) {
     
    9983    /**
    10084     * Displays help on the console
    10185     */
    102     public static void showHelp() {
     86    private void showHelp() {
    10387        System.out.println(getHelp());
     88        System.exit(0);
    10489    }
    10590
    10691    private static String getHelp() {
  • src/org/openstreetmap/josm/gui/ProgramArguments.java

     
    22package org.openstreetmap.josm.gui;
    33
    44import java.util.ArrayList;
     5import java.util.Arrays;
    56import java.util.Collection;
    67import java.util.Collections;
    78import java.util.EnumMap;
     
    1314import java.util.logging.Level;
    1415import java.util.stream.Stream;
    1516
    16 import org.openstreetmap.josm.tools.I18n;
    1717import org.openstreetmap.josm.tools.Logging;
     18import org.openstreetmap.josm.tools.OptionParser;
     19import org.openstreetmap.josm.tools.OptionParser.OptionCount;
    1820
    19 import gnu.getopt.Getopt;
    20 import gnu.getopt.LongOpt;
    21 
    2221/**
    2322 * This class holds the arguments passed on to {@link MainApplication#main}.
    2423 * @author Michael Zangl
     
    9089        public boolean requiresArgument() {
    9190            return requiresArg;
    9291        }
    93 
    94         LongOpt toLongOpt() {
    95             return new LongOpt(getName(), requiresArgument() ? LongOpt.REQUIRED_ARGUMENT : LongOpt.NO_ARGUMENT, null, 0);
    96         }
    9792    }
    9893
    9994    private final Map<Option, List<String>> argMap = new EnumMap<>(Option.class);
     
    113108     * @param args command-line arguments array
    114109     */
    115110    private void buildCommandLineArgumentMap(String... args) {
    116         Getopt.setI18nHandler(I18n::tr);
    117         LongOpt[] los = Stream.of(Option.values()).map(Option::toLongOpt).toArray(LongOpt[]::new);
    118         Getopt g = new Getopt("JOSM", args, "hv", los);
    119 
    120         int c;
    121         while ((c = g.getopt()) != -1) {
    122             Option opt;
    123             switch (c) {
    124             case 'h':
    125                 opt = Option.HELP;
    126                 break;
    127             case 'v':
    128                 opt = Option.VERSION;
    129                 break;
    130             case 0:
    131                 opt = Option.values()[g.getLongind()];
    132                 break;
    133             default:
    134                 opt = null;
     111        OptionParser parser = new OptionParser("JOSM");
     112        for (Option o : Option.values()) {
     113            if (o.requiresArgument()) {
     114                parser.addArgumentParameter(o.getName(), OptionCount.MULTIPLE, p -> addOption(o, p));
     115            } else {
     116                parser.addFlagParameter(o.getName(), () -> addOption(o, ""));
    135117            }
    136             if (opt != null) {
    137                 addOption(opt, g.getOptarg());
    138             } else
    139                 throw new IllegalArgumentException("Invalid option: "+ (char) c);
    140118        }
     119
     120        parser.addShortAlias(Option.HELP.getName(), "h");
     121        parser.addShortAlias(Option.VERSION.getName(), "v");
     122
     123        List<String> remaining = parser.parseOptionsOrExit(Arrays.asList(args));
     124
    141125        // positional arguments are a shortcut for the --download ... option
    142         for (int i = g.getOptind(); i < args.length; ++i) {
    143             addOption(Option.DOWNLOAD, args[i]);
     126        for (String arg : remaining) {
     127            addOption(Option.DOWNLOAD, arg);
    144128        }
    145129    }
    146130
  • src/org/openstreetmap/josm/gui/mappaint/RenderingCLI.java

     
    1111import java.nio.file.Files;
    1212import java.nio.file.Paths;
    1313import java.util.ArrayList;
     14import java.util.Arrays;
    1415import java.util.List;
    1516import java.util.Locale;
    1617import java.util.Optional;
     
    3738import org.openstreetmap.josm.io.OsmReader;
    3839import org.openstreetmap.josm.spi.preferences.Config;
    3940import org.openstreetmap.josm.spi.preferences.MemoryPreferences;
    40 import org.openstreetmap.josm.tools.I18n;
    4141import org.openstreetmap.josm.tools.JosmDecimalFormatSymbolsProvider;
    4242import org.openstreetmap.josm.tools.Logging;
     43import org.openstreetmap.josm.tools.OptionParser;
     44import org.openstreetmap.josm.tools.OptionParser.OptionCount;
     45import org.openstreetmap.josm.tools.OptionParser.OptionParseException;
    4346import org.openstreetmap.josm.tools.RightAndLefthandTraffic;
    4447
    45 import gnu.getopt.Getopt;
    46 import gnu.getopt.LongOpt;
    47 
    4848/**
    4949 * Command line interface for rendering osm data to an image file.
    5050 *
     
    7676    private String argProjection;
    7777    private Integer argMaxImageSize;
    7878
     79    private StyleData argCurrentStyle;
     80
    7981    private enum Option {
    80         HELP(false, 'h'),
    81         DEBUG(false, '*'),
    82         TRACE(false, '*'),
    83         INPUT(true, 'i'),
    84         STYLE(true, 's'),
    85         SETTING(true, '*'),
    86         OUTPUT(true, 'o'),
    87         ZOOM(true, 'z'),
    88         SCALE(true, '*'),
    89         BOUNDS(true, 'b'),
    90         ANCHOR(true, '*'),
    91         WIDTH_M(true, '*'),
    92         HEIGHT_M(true, '*'),
    93         WIDTH_PX(true, '*'),
    94         HEIGHT_PX(true, '*'),
    95         PROJECTION(true, '*'),
    96         MAX_IMAGE_SIZE(true, '*');
     82        HELP(false, 'h'), DEBUG(false, '*'), TRACE(false, '*'), INPUT(true, 'i'), STYLE(true, 's'), SETTING(true,
     83                '*'), OUTPUT(true, 'o'), ZOOM(true, 'z'), SCALE(true, '*'), BOUNDS(true, 'b'), ANCHOR(true,
     84                        '*'), WIDTH_M(true, '*'), HEIGHT_M(true, '*'), WIDTH_PX(true,
     85                                '*'), HEIGHT_PX(true, '*'), PROJECTION(true, '*'), MAX_IMAGE_SIZE(true, '*');
    9786
    9887        private final String name;
    9988        private final boolean requiresArg;
     
    128117        public char getShortOption() {
    129118            return shortOption;
    130119        }
    131 
    132         LongOpt toLongOpt() {
    133             return new LongOpt(getName(), requiresArgument() ? LongOpt.REQUIRED_ARGUMENT : LongOpt.NO_ARGUMENT, null, getShortOption());
    134         }
    135120    }
    136121
    137122    /**
     
    187172     * @param argArray the arguments array
    188173     */
    189174    void parseArguments(String[] argArray) {
    190         Getopt.setI18nHandler(I18n::tr);
    191175        Logging.setLogLevel(Level.INFO);
    192176
    193         LongOpt[] opts = new LongOpt[Option.values().length];
    194         StringBuilder optString = new StringBuilder();
     177        OptionParser parser = new OptionParser("JOSM rendering");
    195178        for (Option o : Option.values()) {
    196             opts[o.ordinal()] = o.toLongOpt();
     179            if (o.requiresArgument()) {
     180                parser.addArgumentParameter(o.getName(),
     181                        o == Option.SETTING ? OptionCount.MULTIPLE : OptionCount.OPTIONAL,
     182                        arg -> handleOption(o, arg));
     183            } else {
     184                parser.addFlagParameter(o.getName(), () -> handleOption(o));
     185            }
    197186            if (o.getShortOption() != '*') {
    198                 optString.append(o.getShortOption());
    199                 if (o.requiresArgument()) {
    200                     optString.append(':');
    201                 }
     187                parser.addShortAlias(o.getName(), o.getShortOption() + "");
    202188            }
    203189        }
    204190
    205         Getopt getopt = new Getopt("JOSM rendering", argArray, optString.toString(), opts);
    206 
    207         StyleData currentStyle = new StyleData();
     191        argCurrentStyle = new StyleData();
    208192        argStyles = new ArrayList<>();
    209193
    210         int c;
    211         while ((c = getopt.getopt()) != -1) {
    212             switch (c) {
    213             case 'h':
    214                 showHelp();
    215                 System.exit(0);
    216             case 'i':
    217                 argInput = getopt.getOptarg();
    218                 break;
    219             case 's':
    220                 if (currentStyle.styleUrl != null) {
    221                     argStyles.add(currentStyle);
    222                     currentStyle = new StyleData();
    223                 }
    224                 currentStyle.styleUrl = getopt.getOptarg();
    225                 break;
    226             case 'o':
    227                 argOutput = getopt.getOptarg();
    228                 break;
    229             case 'z':
     194        parser.parseOptionsOrExit(Arrays.asList(argArray));
     195
     196        if (argCurrentStyle.styleUrl != null) {
     197            argStyles.add(argCurrentStyle);
     198        }
     199    }
     200
     201    private void handleOption(Option o) {
     202        switch (o) {
     203        case HELP:
     204            showHelp();
     205            System.exit(0);
     206            break;
     207        case DEBUG:
     208            argDebug = true;
     209            break;
     210        case TRACE:
     211            argTrace = true;
     212            break;
     213        default:
     214            throw new AssertionError("Unexpected option index: " + o);
     215        }
     216    }
     217
     218    private void handleOption(Option o, String arg) {
     219        switch (o) {
     220        case INPUT:
     221            argInput = arg;
     222            break;
     223        case STYLE:
     224            if (argCurrentStyle.styleUrl != null) {
     225                argStyles.add(argCurrentStyle);
     226                argCurrentStyle = new StyleData();
     227            }
     228            argCurrentStyle.styleUrl = arg;
     229            break;
     230        case OUTPUT:
     231            argOutput = arg;
     232            break;
     233        case ZOOM:
     234            try {
     235                argZoom = Integer.valueOf(arg);
     236            } catch (NumberFormatException nfe) {
     237                throw new OptionParseException(
     238                        tr("Expected integer number for option {0}, but got ''{1}''", "--zoom", arg), nfe);
     239            }
     240            if (argZoom < 0) {
     241                throw new OptionParseException(
     242                        tr("Expected integer number >= 0 for option {0}, but got ''{1}''", "--zoom", arg));
     243            }
     244            break;
     245        case BOUNDS:
     246            if (!"auto".equals(arg)) {
    230247                try {
    231                     argZoom = Integer.valueOf(getopt.getOptarg());
    232                 } catch (NumberFormatException nfe) {
    233                     throw new IllegalArgumentException(
    234                             tr("Expected integer number for option {0}, but got ''{1}''", "--zoom", getopt.getOptarg()), nfe);
     248                    argBounds = new Bounds(arg, ",", Bounds.ParseMethod.LEFT_BOTTOM_RIGHT_TOP, false);
     249                } catch (IllegalArgumentException iae) { // NOPMD
     250                    throw new OptionParseException(
     251                            tr("Unable to parse {0} parameter: {1}", "--bounds", iae.getMessage()), iae);
    235252                }
    236                 if (argZoom < 0)
    237                     throw new IllegalArgumentException(
    238                             tr("Expected integer number >= 0 for option {0}, but got ''{1}''", "--zoom", getopt.getOptarg()));
    239                 break;
    240             case 'b':
    241                 if (!"auto".equals(getopt.getOptarg())) {
    242                     try {
    243                         argBounds = new Bounds(getopt.getOptarg(), ",", Bounds.ParseMethod.LEFT_BOTTOM_RIGHT_TOP, false);
    244                     } catch (IllegalArgumentException iae) { // NOPMD
    245                         throw new IllegalArgumentException(tr("Unable to parse {0} parameter: {1}", "--bounds", iae.getMessage()), iae);
    246                     }
    247                 }
    248                 break;
    249             case '*':
    250                 switch (Option.values()[getopt.getLongind()]) {
    251                 case DEBUG:
    252                     argDebug = true;
    253                     break;
    254                 case TRACE:
    255                     argTrace = true;
    256                     break;
    257                 case SETTING:
    258                     String keyval = getopt.getOptarg();
    259                     String[] comp = keyval.split(":");
    260                     if (comp.length != 2)
    261                         throw new IllegalArgumentException(
    262                                 tr("Expected key and value, separated by '':'' character for option {0}, but got ''{1}''",
    263                                         "--setting", getopt.getOptarg()));
    264                     currentStyle.settings.put(comp[0].trim(), comp[1].trim());
    265                     break;
    266                 case SCALE:
    267                     try {
    268                         argScale = JosmDecimalFormatSymbolsProvider.parseDouble(getopt.getOptarg());
    269                     } catch (NumberFormatException nfe) {
    270                         throw new IllegalArgumentException(
    271                                 tr("Expected floating point number for option {0}, but got ''{1}''", "--scale", getopt.getOptarg()), nfe);
    272                     }
    273                     break;
    274                 case ANCHOR:
    275                     String[] parts = getopt.getOptarg().split(",");
    276                     if (parts.length != 2)
    277                         throw new IllegalArgumentException(
    278                                 tr("Expected two coordinates, separated by comma, for option {0}, but got ''{1}''",
    279                                 "--anchor", getopt.getOptarg()));
    280                     try {
    281                         double lon = LatLonParser.parseCoordinate(parts[0]);
    282                         double lat = LatLonParser.parseCoordinate(parts[1]);
    283                         argAnchor = new LatLon(lat, lon);
    284                     } catch (IllegalArgumentException iae) { // NOPMD
    285                         throw new IllegalArgumentException(tr("In option {0}: {1}", "--anchor", iae.getMessage()), iae);
    286                     }
    287                     break;
    288                 case WIDTH_M:
    289                     try {
    290                         argWidthM = JosmDecimalFormatSymbolsProvider.parseDouble(getopt.getOptarg());
    291                     } catch (NumberFormatException nfe) {
    292                         throw new IllegalArgumentException(
    293                                 tr("Expected floating point number for option {0}, but got ''{1}''", "--width-m", getopt.getOptarg()), nfe);
    294                     }
    295                     if (argWidthM <= 0) throw new IllegalArgumentException(
    296                             tr("Expected floating point number > 0 for option {0}, but got ''{1}''", "--width-m", getopt.getOptarg()));
    297                     break;
    298                 case HEIGHT_M:
    299                     try {
    300                         argHeightM = JosmDecimalFormatSymbolsProvider.parseDouble(getopt.getOptarg());
    301                     } catch (NumberFormatException nfe) {
    302                         throw new IllegalArgumentException(
    303                                 tr("Expected floating point number for option {0}, but got ''{1}''", "--height-m", getopt.getOptarg()), nfe);
    304                     }
    305                     if (argHeightM <= 0) throw new IllegalArgumentException(
    306                             tr("Expected floating point number > 0 for option {0}, but got ''{1}''", "--width-m", getopt.getOptarg()));
    307                     break;
    308                 case WIDTH_PX:
    309                     try {
    310                         argWidthPx = Integer.valueOf(getopt.getOptarg());
    311                     } catch (NumberFormatException nfe) {
    312                         throw new IllegalArgumentException(
    313                                 tr("Expected integer number for option {0}, but got ''{1}''", "--width-px", getopt.getOptarg()), nfe);
    314                     }
    315                     if (argWidthPx <= 0) throw new IllegalArgumentException(
    316                             tr("Expected integer number > 0 for option {0}, but got ''{1}''", "--width-px", getopt.getOptarg()));
    317                     break;
    318                 case HEIGHT_PX:
    319                     try {
    320                         argHeightPx = Integer.valueOf(getopt.getOptarg());
    321                     } catch (NumberFormatException nfe) {
    322                         throw new IllegalArgumentException(
    323                                 tr("Expected integer number for option {0}, but got ''{1}''", "--height-px", getopt.getOptarg()), nfe);
    324                     }
    325                     if (argHeightPx <= 0) throw new IllegalArgumentException(
    326                             tr("Expected integer number > 0 for option {0}, but got ''{1}''", "--height-px", getopt.getOptarg()));
    327                     break;
    328                 case PROJECTION:
    329                     argProjection = getopt.getOptarg();
    330                     break;
    331                 case MAX_IMAGE_SIZE:
    332                     try {
    333                         argMaxImageSize = Integer.valueOf(getopt.getOptarg());
    334                     } catch (NumberFormatException nfe) {
    335                         throw new IllegalArgumentException(
    336                                 tr("Expected integer number for option {0}, but got ''{1}''", "--max-image-size", getopt.getOptarg()), nfe);
    337                     }
    338                     if (argMaxImageSize < 0) throw new IllegalArgumentException(
    339                             tr("Expected integer number >= 0 for option {0}, but got ''{1}''", "--max-image-size", getopt.getOptarg()));
    340                     break;
    341                 default:
    342                     throw new AssertionError("Unexpected option index: " + getopt.getLongind());
    343                 }
    344                 break;
    345             case '?':
    346                 throw new IllegalArgumentException();   // getopt error
    347             default:
    348                 throw new AssertionError("Unrecognized option: " + c);
    349253            }
     254            break;
     255
     256        case SETTING:
     257            String keyval = arg;
     258            String[] comp = keyval.split(":", 2);
     259            if (comp.length != 2) {
     260                throw new OptionParseException(
     261                        tr("Expected key and value, separated by '':'' character for option {0}, but got ''{1}''",
     262                                "--setting", arg));
     263            }
     264            argCurrentStyle.settings.put(comp[0].trim(), comp[1].trim());
     265            break;
     266        case SCALE:
     267            try {
     268                argScale = JosmDecimalFormatSymbolsProvider.parseDouble(arg);
     269            } catch (NumberFormatException nfe) {
     270                throw new OptionParseException(
     271                        tr("Expected floating point number for option {0}, but got ''{1}''", "--scale", arg), nfe);
     272            }
     273            break;
     274        case ANCHOR:
     275            String[] parts = arg.split(",");
     276            if (parts.length != 2)
     277                throw new OptionParseException(
     278                        tr("Expected two coordinates, separated by comma, for option {0}, but got ''{1}''", "--anchor",
     279                                arg));
     280            try {
     281                double lon = LatLonParser.parseCoordinate(parts[0]);
     282                double lat = LatLonParser.parseCoordinate(parts[1]);
     283                argAnchor = new LatLon(lat, lon);
     284            } catch (IllegalArgumentException iae) { // NOPMD
     285                throw new OptionParseException(tr("In option {0}: {1}", "--anchor", iae.getMessage()), iae);
     286            }
     287            break;
     288        case WIDTH_M:
     289            try {
     290                argWidthM = JosmDecimalFormatSymbolsProvider.parseDouble(arg);
     291            } catch (NumberFormatException nfe) {
     292                throw new OptionParseException(
     293                        tr("Expected floating point number for option {0}, but got ''{1}''", "--width-m", arg), nfe);
     294            }
     295            if (argWidthM <= 0)
     296                throw new OptionParseException(
     297                        tr("Expected floating point number > 0 for option {0}, but got ''{1}''", "--width-m", arg));
     298            break;
     299        case HEIGHT_M:
     300            try {
     301                argHeightM = JosmDecimalFormatSymbolsProvider.parseDouble(arg);
     302            } catch (NumberFormatException nfe) {
     303                throw new OptionParseException(
     304                        tr("Expected floating point number for option {0}, but got ''{1}''", "--height-m", arg), nfe);
     305            }
     306            if (argHeightM <= 0)
     307                throw new OptionParseException(
     308                        tr("Expected floating point number > 0 for option {0}, but got ''{1}''", "--width-m", arg));
     309            break;
     310        case WIDTH_PX:
     311            try {
     312                argWidthPx = Integer.valueOf(arg);
     313            } catch (NumberFormatException nfe) {
     314                throw new OptionParseException(
     315                        tr("Expected integer number for option {0}, but got ''{1}''", "--width-px", arg), nfe);
     316            }
     317            if (argWidthPx <= 0)
     318                throw new OptionParseException(
     319                        tr("Expected integer number > 0 for option {0}, but got ''{1}''", "--width-px", arg));
     320            break;
     321        case HEIGHT_PX:
     322            try {
     323                argHeightPx = Integer.valueOf(arg);
     324            } catch (NumberFormatException nfe) {
     325                throw new OptionParseException(
     326                        tr("Expected integer number for option {0}, but got ''{1}''", "--height-px", arg), nfe);
     327            }
     328            if (argHeightPx <= 0) {
     329                throw new OptionParseException(
     330                        tr("Expected integer number > 0 for option {0}, but got ''{1}''", "--height-px", arg));
     331            }
     332            break;
     333        case PROJECTION:
     334            argProjection = arg;
     335            break;
     336        case MAX_IMAGE_SIZE:
     337            try {
     338                argMaxImageSize = Integer.valueOf(arg);
     339            } catch (NumberFormatException nfe) {
     340                throw new OptionParseException(
     341                        tr("Expected integer number for option {0}, but got ''{1}''", "--max-image-size", arg), nfe);
     342            }
     343            if (argMaxImageSize < 0) {
     344                throw new OptionParseException(
     345                        tr("Expected integer number >= 0 for option {0}, but got ''{1}''", "--max-image-size", arg));
     346            }
     347            break;
     348        default:
     349            throw new AssertionError("Unexpected option index: " + o);
    350350        }
    351         if (currentStyle.styleUrl != null) {
    352             argStyles.add(currentStyle);
    353         }
    354351    }
    355352
    356353    /**
     
    361358    }
    362359
    363360    private static String getHelp() {
    364         return tr("JOSM rendering command line interface")+"\n\n"+
    365                 tr("Usage")+":\n"+
    366                 "\tjava -jar josm.jar render <options>\n\n"+
    367                 tr("Description")+":\n"+
    368                 tr("Renders data and saves the result to an image file.")+"\n\n"+
    369                 tr("Options")+":\n"+
    370                 "\t--help|-h                 "+tr("Show this help")+"\n"+
    371                 "\t--input|-i <file>         "+tr("Input data file name (.osm)")+"\n"+
    372                 "\t--output|-o <file>        "+tr("Output image file name (.png); defaults to ''{0}''", "out.png")+"\n"+
    373                 "\t--style|-s <file>         "+tr("Style file to use for rendering (.mapcss or .zip)")+"\n"+
    374                 "\t                          "+tr("This option can be repeated to load multiple styles.")+"\n"+
    375                 "\t--setting <key>:<value>   "+tr("Style setting (in JOSM accessible in the style list dialog right click menu)")+"\n"+
    376                 "\t                          "+tr("Applies to the last style loaded with the {0} option.", "--style")+"\n"+
    377                 "\t--zoom|-z <lvl>           "+tr("Select zoom level to render. (integer value, 0=entire earth, 18=street level)")+"\n"+
    378                 "\t--scale <scale>           "+tr("Select the map scale")+"\n"+
    379                 "\t                          "+tr("A value of 10000 denotes a scale of 1:10000 (1 cm on the map equals 100 m on the ground; "
    380                                                 + "display resolution: 96 dpi)")+"\n"+
    381                 "\t                          "+tr("Options {0} and {1} are mutually exclusive.", "--zoom", "--scale")+"\n"+
    382                 "\t--bounds|-b auto|<min_lon>,<min_lat>,<max_lon>,<max_lat>\n"+
    383                 "\t                          "+tr("Area to render, default value is ''{0}''", "auto")+"\n"+
    384                 "\t                          "+tr("With keyword ''{0}'', the downloaded area in the .osm input file will be used (if recorded).",
    385                                                      "auto")+"\n"+
    386                 "\t--anchor <lon>,<lat>      "+tr("Specify bottom left corner of the rendering area")+"\n"+
    387                 "\t                          "+tr("Used in combination with width and height options to determine the area to render.")+"\n"+
    388                 "\t--width-m <number>        "+tr("Width of the rendered area, in meter")+"\n"+
    389                 "\t--height-m <number>       "+tr("Height of the rendered area, in meter")+"\n"+
    390                 "\t--width-px <number>       "+tr("Width of the target image, in pixel")+"\n"+
    391                 "\t--height-px <number>      "+tr("Height of the target image, in pixel")+"\n"+
    392                 "\t--projection <code>       "+tr("Projection to use, default value ''{0}'' (web-Mercator)", "epsg:3857")+"\n"+
    393                 "\t--max-image-size <number> "+tr("Maximum image width/height in pixel (''{0}'' means no limit), default value: {1}",
    394                                                     0, Integer.toString(DEFAULT_MAX_IMAGE_SIZE))+"\n"+
    395                 "\n"+
    396                 tr("To specify the rendered area and scale, the options can be combined in various ways")+":\n"+
    397                 "  * --bounds (--zoom|--scale|--width-px|--height-px)\n"+
    398                 "  * --anchor (--width-m|--width-px) (--height-m|--height-px) (--zoom|--scale)\n"+
    399                 "  * --anchor --width-m --height-m (--width-px|--height-px)\n"+
    400                 "  * --anchor --width-px --height-px (--width-m|--height-m)\n"+
    401                 tr("If neither ''{0}'' nor ''{1}'' is given, the default value {2} takes effect "
    402                         + "and the bounds of the download area in the .osm input file are used.",
    403                         "bounds", "anchor", "--bounds=auto")+"\n\n"+
    404                 tr("Examples")+":\n"+
    405                 "  java -jar josm.jar render -i data.osm -s style.mapcss -z 16\n"+
    406                 "  josm render -i data.osm -s style.mapcss --scale 5000\n"+
    407                 "  josm render -i data.osm -s style.mapcss -z 16 -o image.png\n"+
    408                 "  josm render -i data.osm -s elemstyles.mapcss --setting hide_icons:false -z 16\n"+
    409                 "  josm render -i data.osm -s style.mapcss -s another_style.mapcss -z 16 -o image.png\n"+
    410                 "  josm render -i data.osm -s style.mapcss --bounds 21.151,51.401,21.152,51.402 -z 16\n"+
    411                 "  josm render -i data.osm -s style.mapcss --anchor 21.151,51.401 --width-m 500 --height-m 300 -z 16\n"+
    412                 "  josm render -i data.osm -s style.mapcss --anchor 21.151,51.401 --width-m 500 --height-m 300 --width-px 1800\n"+
    413                 "  josm render -i data.osm -s style.mapcss --scale 5000 --projection epsg:4326\n";
     361        return tr("JOSM rendering command line interface") + "\n\n" + tr("Usage") + ":\n"
     362                + "\tjava -jar josm.jar render <options>\n\n" + tr("Description") + ":\n"
     363                + tr("Renders data and saves the result to an image file.") + "\n\n" + tr("Options") + ":\n"
     364                + "\t--help|-h                 " + tr("Show this help") + "\n" + "\t--input|-i <file>         "
     365                + tr("Input data file name (.osm)") + "\n" + "\t--output|-o <file>        "
     366                + tr("Output image file name (.png); defaults to ''{0}''", "out.png") + "\n"
     367                + "\t--style|-s <file>         " + tr("Style file to use for rendering (.mapcss or .zip)") + "\n"
     368                + "\t                          " + tr("This option can be repeated to load multiple styles.") + "\n"
     369                + "\t--setting <key>:<value>   "
     370                + tr("Style setting (in JOSM accessible in the style list dialog right click menu)") + "\n"
     371                + "\t                          "
     372                + tr("Applies to the last style loaded with the {0} option.", "--style") + "\n"
     373                + "\t--zoom|-z <lvl>           "
     374                + tr("Select zoom level to render. (integer value, 0=entire earth, 18=street level)") + "\n"
     375                + "\t--scale <scale>           " + tr("Select the map scale") + "\n" + "\t                          "
     376                + tr("A value of 10000 denotes a scale of 1:10000 (1 cm on the map equals 100 m on the ground; "
     377                        + "display resolution: 96 dpi)")
     378                + "\n" + "\t                          "
     379                + tr("Options {0} and {1} are mutually exclusive.", "--zoom", "--scale") + "\n"
     380                + "\t--bounds|-b auto|<min_lon>,<min_lat>,<max_lon>,<max_lat>\n" + "\t                          "
     381                + tr("Area to render, default value is ''{0}''", "auto") + "\n" + "\t                          "
     382                + tr("With keyword ''{0}'', the downloaded area in the .osm input file will be used (if recorded).",
     383                        "auto")
     384                + "\n" + "\t--anchor <lon>,<lat>      " + tr("Specify bottom left corner of the rendering area") + "\n"
     385                + "\t                          "
     386                + tr("Used in combination with width and height options to determine the area to render.") + "\n"
     387                + "\t--width-m <number>        " + tr("Width of the rendered area, in meter") + "\n"
     388                + "\t--height-m <number>       " + tr("Height of the rendered area, in meter") + "\n"
     389                + "\t--width-px <number>       " + tr("Width of the target image, in pixel") + "\n"
     390                + "\t--height-px <number>      " + tr("Height of the target image, in pixel") + "\n"
     391                + "\t--projection <code>       "
     392                + tr("Projection to use, default value ''{0}'' (web-Mercator)", "epsg:3857") + "\n"
     393                + "\t--max-image-size <number> "
     394                + tr("Maximum image width/height in pixel (''{0}'' means no limit), default value: {1}", 0,
     395                        Integer.toString(DEFAULT_MAX_IMAGE_SIZE))
     396                + "\n" + "\n"
     397                + tr("To specify the rendered area and scale, the options can be combined in various ways") + ":\n"
     398                + "  * --bounds (--zoom|--scale|--width-px|--height-px)\n"
     399                + "  * --anchor (--width-m|--width-px) (--height-m|--height-px) (--zoom|--scale)\n"
     400                + "  * --anchor --width-m --height-m (--width-px|--height-px)\n"
     401                + "  * --anchor --width-px --height-px (--width-m|--height-m)\n"
     402                + tr("If neither ''{0}'' nor ''{1}'' is given, the default value {2} takes effect "
     403                        + "and the bounds of the download area in the .osm input file are used.", "bounds", "anchor",
     404                        "--bounds=auto")
     405                + "\n\n" + tr("Examples") + ":\n" + "  java -jar josm.jar render -i data.osm -s style.mapcss -z 16\n"
     406                + "  josm render -i data.osm -s style.mapcss --scale 5000\n"
     407                + "  josm render -i data.osm -s style.mapcss -z 16 -o image.png\n"
     408                + "  josm render -i data.osm -s elemstyles.mapcss --setting hide_icons:false -z 16\n"
     409                + "  josm render -i data.osm -s style.mapcss -s another_style.mapcss -z 16 -o image.png\n"
     410                + "  josm render -i data.osm -s style.mapcss --bounds 21.151,51.401,21.152,51.402 -z 16\n"
     411                + "  josm render -i data.osm -s style.mapcss --anchor 21.151,51.401 --width-m 500 --height-m 300 -z 16\n"
     412                + "  josm render -i data.osm -s style.mapcss --anchor 21.151,51.401 --width-m 500 --height-m 300 --width-px 1800\n"
     413                + "  josm render -i data.osm -s style.mapcss --scale 5000 --projection epsg:4326\n";
    414414    }
    415415
    416416    /**
     
    451451        Projection proj = ProjectionRegistry.getProjection();
    452452        Double scale = null; // scale in east-north units per pixel
    453453        if (argZoom != null) {
    454             scale = OsmMercator.EARTH_RADIUS * Math.PI * 2 / Math.pow(2, argZoom) / OsmMercator.DEFAUL_TILE_SIZE / proj.getMetersPerUnit();
     454            scale = OsmMercator.EARTH_RADIUS * Math.PI * 2 / Math.pow(2, argZoom) / OsmMercator.DEFAUL_TILE_SIZE
     455                    / proj.getMetersPerUnit();
    455456        }
    456457        Bounds bounds = argBounds;
    457458        ProjectionBounds pb = null;
     
    463464                double enPerMeter = Double.NaN;
    464465                DoubleSupplier getEnPerMeter = () -> {
    465466                    double shiftMeter = 10;
    466                     EastNorth projAnchorShifted = projAnchor.add(
    467                             shiftMeter / proj.getMetersPerUnit(), shiftMeter / proj.getMetersPerUnit());
     467                    EastNorth projAnchorShifted = projAnchor.add(shiftMeter / proj.getMetersPerUnit(),
     468                            shiftMeter / proj.getMetersPerUnit());
    468469                    LatLon anchorShifted = proj.eastNorth2latlon(projAnchorShifted);
    469470                    return projAnchor.distance(projAnchorShifted) / argAnchor.greatCircleDistance(anchorShifted);
    470471                };
     
    481482                        scale = argHeightM / argHeightPx * enPerMeter;
    482483                    } else {
    483484                        throw new IllegalArgumentException(
    484                                 tr("Argument {0} given, but scale cannot be determined from remaining arguments", "--anchor"));
     485                                tr("Argument {0} given, but scale cannot be determined from remaining arguments",
     486                                        "--anchor"));
    485487                    }
    486488                }
    487489
     
    516518                bounds.extend(proj.eastNorth2latlon(pb.getMax()));
    517519            } else {
    518520                if (ds.getDataSourceBounds().isEmpty()) {
    519                     throw new IllegalArgumentException(tr("{0} mode, but no bounds found in osm data input file", "--bounds=auto"));
     521                    throw new IllegalArgumentException(
     522                            tr("{0} mode, but no bounds found in osm data input file", "--bounds=auto"));
    520523                }
    521524                bounds = ds.getDataSourceBounds().get(0);
    522525            }
     
    530533
    531534        if (scale == null) {
    532535            if (argScale != null) {
    533                 double enPerMeter = pb.getMin().distance(pb.getMax()) / bounds.getMin().greatCircleDistance(bounds.getMax());
     536                double enPerMeter = pb.getMin().distance(pb.getMax())
     537                        / bounds.getMin().greatCircleDistance(bounds.getMax());
    534538                scale = argScale * enPerMeter / PIXEL_PER_METER;
    535539            } else if (argWidthPx != null) {
    536540                scale = (pb.maxEast - pb.minEast) / argWidthPx;
     
    538542                scale = (pb.maxNorth - pb.minNorth) / argHeightPx;
    539543            } else {
    540544                throw new IllegalArgumentException(
    541                         tr("Unable to determine scale, one of the options {0}, {1}, {2} or {3} expected",
    542                                 "--zoom", "--scale", "--width-px", "--height-px"));
     545                        tr("Unable to determine scale, one of the options {0}, {1}, {2} or {3} expected", "--zoom",
     546                                "--scale", "--width-px", "--height-px"));
    543547            }
    544548        }
    545549
  • src/org/openstreetmap/josm/tools/OptionParser.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.util.HashMap;
     7import java.util.LinkedList;
     8import java.util.List;
     9import java.util.Map.Entry;
     10import java.util.Objects;
     11import java.util.function.Consumer;
     12import java.util.stream.Collectors;
     13
     14/**
     15 * A replacement of getopt.
     16 * <p>
     17 * Allows parsing command line options
     18 *
     19 * @author Michael Zangl
     20 */
     21public class OptionParser {
     22
     23    private HashMap<String, AvailableOption> availableOptions = new HashMap<>();
     24    private final String program;
     25
     26    /**
     27     * Create a new option parser.
     28     * @param program The program name.
     29     */
     30    public OptionParser(String program) {
     31        Objects.requireNonNull(program, "program name must be provided");
     32        this.program = program;
     33    }
     34
     35    /**
     36     * Adds an alias for the long option --optionName to the short version -name
     37     * @param optionName The long option
     38     * @param shortName The short version
     39      * @return this {@link OptionParser}
     40    */
     41    public OptionParser addShortAlias(String optionName, String shortName) {
     42        if (!shortName.matches("\\w")) {
     43            throw new IllegalArgumentException("Short name " + shortName + " must be one character");
     44        }
     45        if (availableOptions.containsKey("-" + shortName)) {
     46            throw new IllegalArgumentException("Short name " + shortName + " is already used");
     47        }
     48        AvailableOption longDefinition = availableOptions.get("--" + optionName);
     49        if (longDefinition == null) {
     50            throw new IllegalArgumentException("No long definition for " + optionName
     51                    + " was defined. Define the long definition first before creating " + "a short definition for it.");
     52        }
     53        availableOptions.put("-" + shortName, longDefinition);
     54        return this;
     55    }
     56
     57    /**
     58     * Adds an option that may be used as a flag, e.g. --debug
     59     * @param optionName The parameter name
     60     * @param handler The handler that is called when the flag is encountered.
     61     * @return this {@link OptionParser}
     62     */
     63    public OptionParser addFlagParameter(String optionName, Runnable handler) {
     64        checkOptionName(optionName);
     65        availableOptions.put("--" + optionName, new AvailableOption() {
     66            @Override
     67            public void runFor(String parameter) {
     68                handler.run();
     69            }
     70        });
     71        return this;
     72    }
     73
     74    private void checkOptionName(String optionName) {
     75        if (!optionName.matches("\\w([\\w-]*\\w)?")) {
     76            throw new IllegalArgumentException("Illegal option name: " + optionName);
     77        }
     78        if (availableOptions.containsKey("--" + optionName)) {
     79            throw new IllegalArgumentException("The option --" + optionName + " is already registered");
     80        }
     81    }
     82
     83    /**
     84     * Add a parameter that expects a string attribute. E.g.: --config=/path/to/file
     85     * @param optionName The name of the parameter.
     86     * @param count The number of times the parameter may occur.
     87     * @param handler A function that gets the current object and the parameter. It should throw an {@link OptionParseException} if the parameter cannot be handled / is invalid.
     88     * @return this {@link OptionParser}
     89     */
     90    public OptionParser addArgumentParameter(String optionName, OptionCount count, Consumer<String> handler) {
     91        checkOptionName(optionName);
     92        availableOptions.put("--" + optionName, new AvailableOption() {
     93            @Override
     94            public boolean requiresParameter() {
     95                return true;
     96            }
     97
     98            @Override
     99            public OptionCount getRequiredCount() {
     100                return count;
     101            }
     102
     103            @Override
     104            public void runFor(String parameter) {
     105                Objects.requireNonNull(parameter, "parameter");
     106                handler.accept(parameter);
     107            }
     108        });
     109        return this;
     110    }
     111
     112    /**
     113     * Same as {@link #parseOptions(List)}, but exits if option parsing fails.
     114     * @param arguments The options
     115     * @return The remaining program arguments that are no options.
     116     */
     117    public List<String> parseOptionsOrExit(List<String> arguments) {
     118        try {
     119            return parseOptions(arguments);
     120        } catch (OptionParseException e) {
     121            System.err.println(e.getLocalizedMessage());
     122            System.exit(1);
     123            // unreachable, but makes compilers happy
     124            throw e;
     125        }
     126    }
     127
     128    /**
     129     * Parses the options.
     130     * <p>
     131     * It first checks if all required options are present, if all options are known and validates the option count.
     132     * <p>
     133     * Then, all option handlers are called in the order in which the options are encountered.
     134     * @param arguments Program arguments
     135     * @return The remaining program arguments that are no options.
     136     * @throws OptionParseException The error to display if option parsing failed.
     137     */
     138    public List<String> parseOptions(List<String> arguments) {
     139        LinkedList<String> toHandle = new LinkedList<>(arguments);
     140        List<String> remainingArguments = new LinkedList<>();
     141        boolean argumentOnlyMode = false;
     142        List<FoundOption> options = new LinkedList<>();
     143
     144        while (!toHandle.isEmpty()) {
     145            String next = toHandle.removeFirst();
     146            if (argumentOnlyMode || !next.matches("-.+")) {
     147                // argument found, add it to arguments list
     148                remainingArguments.add(next);
     149            } else if ("--".equals(next)) {
     150                // we are done, the remaining should be used as arguments.
     151                argumentOnlyMode = true;
     152            } else {
     153                if (next.matches("-\\w\\w+")) {
     154                    // special case: concatenated short options like -hv
     155                    // We handle them as if the user wrote -h -v by just scheduling the remainder for the next loop.
     156                    toHandle.addFirst("-" + next.substring(2));
     157                    next = next.substring(0, 2);
     158                }
     159
     160                String[] split = next.split("=", 2);
     161                String optionName = split[0];
     162                AvailableOption definition = findParameter(optionName);
     163                String parameter = null;
     164                if (definition.requiresParameter()) {
     165                    if (split.length > 1) {
     166                        parameter = split[1];
     167                    } else {
     168                        if (toHandle.isEmpty() || toHandle.getFirst().equals("--")) {
     169                            throw new OptionParseException(tr("{0}: option ''{1}'' requires an argument", program));
     170                        }
     171                        parameter = toHandle.removeFirst();
     172                    }
     173                } else if (split.length > 1) {
     174                    throw new OptionParseException(
     175                            tr("{0}: option ''{1}'' does not allow an argument", program, optionName));
     176                }
     177                options.add(new FoundOption(optionName, definition, parameter));
     178            }
     179        }
     180
     181        // Count how often they are used
     182        availableOptions.values().stream().distinct().forEach(def -> {
     183            long count = options.stream().filter(p -> def.equals(p.option)).count();
     184            if (count < def.getRequiredCount().min) {
     185                // min may be 0 or 1 at the moment
     186                throw new OptionParseException(tr("{0}: option ''{1}'' is required"));
     187            } else if (count > def.getRequiredCount().max) {
     188                // max may be 1 or MAX_INT at the moment
     189                throw new OptionParseException(tr("{0}: option ''{1}'' may not appear multiple times"));
     190            }
     191        });
     192
     193        // Actually apply the parameters.
     194        for (FoundOption option : options) {
     195            try {
     196                option.option.runFor(option.parameter);
     197            } catch (OptionParseException e) {
     198                String message;
     199                // Just add a nicer error message
     200                if (option.parameter == null) {
     201                    message = tr("{0}: Error while handling option ''{1}''", program, option.optionName);
     202                } else {
     203                    message = tr("{0}: Invalid value {2} for option ''{1}''", program, option.optionName,
     204                            option.parameter);
     205                }
     206                if (!e.getLocalizedMessage().isEmpty()) {
     207                    message += ": " + e.getLocalizedMessage().isEmpty();
     208                }
     209                throw new OptionParseException(message);
     210            }
     211        }
     212        return remainingArguments;
     213    }
     214
     215    private AvailableOption findParameter(String optionName) {
     216        AvailableOption exactMatch = availableOptions.get(optionName);
     217        if (exactMatch != null) {
     218            return exactMatch;
     219        } else if (optionName.startsWith("--")) {
     220            List<AvailableOption> alternatives = availableOptions.entrySet().stream()
     221                    .filter(entry -> entry.getKey().startsWith(optionName)).map(Entry::getValue).distinct()
     222                    .collect(Collectors.toList());
     223
     224            if (alternatives.size() == 1) {
     225                return alternatives.get(0);
     226            } else if (alternatives.size() > 1) {
     227                throw new OptionParseException(tr("{0}: option ''{1}'' is ambiguous", program));
     228            }
     229        }
     230        throw new OptionParseException(tr("{0}: unrecognized option ''{1}''", program, optionName));
     231    }
     232
     233    /**
     234     * How often an option may / must be specified on the command line.
     235     * @author Michael Zangl
     236     */
     237    public enum OptionCount {
     238        /**
     239         * The option may be specified once
     240         */
     241        OPTIONAL(0, 1),
     242        /**
     243         * The option is required exactly once
     244         */
     245        REQUIRED(1, 1),
     246        /**
     247         * The option may be specified multiple times
     248         */
     249        MULTIPLE(0, Integer.MAX_VALUE);
     250
     251        private int min;
     252        private int max;
     253
     254        OptionCount(int min, int max) {
     255            this.min = min;
     256            this.max = max;
     257
     258        }
     259    }
     260
     261    protected abstract class AvailableOption {
     262
     263        public boolean requiresParameter() {
     264            return false;
     265        }
     266
     267        public OptionCount getRequiredCount() {
     268            return OptionCount.OPTIONAL;
     269        }
     270
     271        /**
     272         * Called once if the parameter is encountered, afer basic validation.
     273         * @param parameter The parameter if {@link #requiresParameter()} is true, <code>null</code> otherwise.
     274         */
     275        public abstract void runFor(String parameter);
     276
     277    }
     278
     279    private static class FoundOption {
     280        private final String optionName;
     281        private final AvailableOption option;
     282        private final String parameter;
     283
     284        FoundOption(String optionName, AvailableOption option, String parameter) {
     285            this.optionName = optionName;
     286            this.option = option;
     287            this.parameter = parameter;
     288        }
     289    }
     290
     291    /**
     292     * @author Michael Zangl
     293     */
     294    public static class OptionParseException extends RuntimeException {
     295        // Don't rely on JAVA handling this correctly.
     296        private final String localizedMessage;
     297
     298        /**
     299         * Create an empty error with no description
     300         */
     301        public OptionParseException() {
     302            super();
     303            localizedMessage = "";
     304        }
     305
     306        /**
     307         * @param localizedMessage
     308         */
     309        public OptionParseException(String localizedMessage) {
     310            super(localizedMessage);
     311            this.localizedMessage = localizedMessage;
     312        }
     313
     314        /**
     315         * @param localizedMessage
     316         * @param t
     317         */
     318        public OptionParseException(String localizedMessage, Throwable t) {
     319            super(localizedMessage, t);
     320            this.localizedMessage = localizedMessage;
     321        }
     322
     323        @Override
     324        public String getLocalizedMessage() {
     325            return localizedMessage;
     326        }
     327    }
     328}
  • test/unit/org/openstreetmap/josm/tools/OptionParserTest.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools;
     3
     4import static org.junit.Assert.assertEquals;
     5
     6import java.util.ArrayList;
     7import java.util.Arrays;
     8import java.util.List;
     9import java.util.concurrent.atomic.AtomicBoolean;
     10import java.util.concurrent.atomic.AtomicReference;
     11
     12import org.junit.Test;
     13import org.openstreetmap.josm.tools.OptionParser.OptionParseException;
     14import org.openstreetmap.josm.tools.OptionParser.OptionCount;
     15
     16/**
     17 * Test for {@link OptionParser}
     18 * @author Michael Zangl
     19 */
     20public class OptionParserTest {
     21
     22    // A reason for moving to jupter...
     23    @Test(expected = OptionParseException.class)
     24    public void testEmptyParserRejectsLongopt() {
     25        new OptionParser("test").parseOptions(Arrays.asList("--long"));
     26    }
     27
     28    @Test(expected = OptionParseException.class)
     29    public void testEmptyParserRejectsShortopt() {
     30        new OptionParser("test").parseOptions(Arrays.asList("-s"));
     31    }
     32
     33    @Test(expected = OptionParseException.class)
     34    public void testParserRejectsWrongShortopt() {
     35        new OptionParser("test").addFlagParameter("test", this::nop).addShortAlias("test", "t")
     36                .parseOptions(Arrays.asList("-s"));
     37    }
     38
     39    @Test(expected = OptionParseException.class)
     40    public void testParserRejectsWrongLongopt() {
     41        new OptionParser("test").addFlagParameter("test", this::nop).parseOptions(Arrays.asList("--wrong"));
     42    }
     43
     44    @Test
     45    public void testparserOption() {
     46        AtomicReference<String> argFound = new AtomicReference<>();
     47        OptionParser parser = new OptionParser("test")
     48                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set);
     49
     50        parser.parseOptions(Arrays.asList("--test", "arg"));
     51        assertEquals("arg", argFound.get());
     52    }
     53
     54    @Test(expected = OptionParseException.class)
     55    public void testparserOptionFailsIfMissing() {
     56        AtomicReference<String> argFound = new AtomicReference<>();
     57        OptionParser parser = new OptionParser("test")
     58                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set);
     59
     60        parser.parseOptions(Arrays.asList("--test2", "arg"));
     61    }
     62
     63    @Test(expected = OptionParseException.class)
     64    public void testparserOptionFailsIfMissingArgument() {
     65        AtomicReference<String> argFound = new AtomicReference<>();
     66        OptionParser parser = new OptionParser("test")
     67                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set);
     68
     69        parser.parseOptions(Arrays.asList("--test2", "--other"));
     70    }
     71
     72    @Test(expected = OptionParseException.class)
     73    public void testparserOptionFailsIfMissing2() {
     74        AtomicReference<String> argFound = new AtomicReference<>();
     75        OptionParser parser = new OptionParser("test")
     76                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set);
     77
     78        parser.parseOptions(Arrays.asList("--", "--test", "arg"));
     79    }
     80
     81    @Test(expected = OptionParseException.class)
     82    public void testparserOptionFailsIfTwice() {
     83        AtomicReference<String> argFound = new AtomicReference<>();
     84        OptionParser parser = new OptionParser("test")
     85                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set);
     86
     87        parser.parseOptions(Arrays.asList("--test", "arg", "--test", "arg"));
     88    }
     89
     90    @Test(expected = OptionParseException.class)
     91    public void testparserOptionFailsIfTwiceForAlias() {
     92        AtomicReference<String> argFound = new AtomicReference<>();
     93        OptionParser parser = new OptionParser("test")
     94                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set)
     95                .addShortAlias("test", "t");
     96
     97        parser.parseOptions(Arrays.asList("--test", "arg", "-t", "arg"));
     98    }
     99
     100    @Test(expected = OptionParseException.class)
     101    public void testOptionalOptionFailsIfTwice() {
     102        OptionParser parser = new OptionParser("test")
     103                .addFlagParameter("test", this::nop);
     104        parser.parseOptions(Arrays.asList("--test", "--test"));
     105    }
     106
     107    @Test(expected = OptionParseException.class)
     108    public void testOptionalOptionFailsIfTwiceForAlias() {
     109        OptionParser parser = new OptionParser("test")
     110                .addFlagParameter("test", this::nop)
     111                .addShortAlias("test", "t");
     112        parser.parseOptions(Arrays.asList("-t", "-t"));
     113    }
     114
     115    @Test(expected = OptionParseException.class)
     116    public void testOptionalOptionFailsIfTwiceForAlias2() {
     117        OptionParser parser = new OptionParser("test")
     118                .addFlagParameter("test", this::nop)
     119                .addShortAlias("test", "t");
     120        parser.parseOptions(Arrays.asList("-tt"));
     121    }
     122
     123    @Test
     124    public void testLongArgumentsUsingEqualSign() {
     125        AtomicReference<String> argFound = new AtomicReference<>();
     126        OptionParser parser = new OptionParser("test")
     127                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set);
     128
     129        List<String> remaining = parser.parseOptions(Arrays.asList("--test=arg", "value"));
     130
     131        assertEquals(Arrays.asList("value"), remaining);
     132        assertEquals("arg", argFound.get());
     133
     134        remaining = parser.parseOptions(Arrays.asList("--test=", "value"));
     135
     136        assertEquals(Arrays.asList("value"), remaining);
     137        assertEquals("", argFound.get());
     138
     139        remaining = parser.parseOptions(Arrays.asList("--test=with space and=equals", "value"));
     140
     141        assertEquals(Arrays.asList("value"), remaining);
     142        assertEquals("with space and=equals", argFound.get());
     143    }
     144
     145    @Test(expected = OptionParseException.class)
     146    public void testLongArgumentsMissingOption() {
     147        OptionParser parser = new OptionParser("test")
     148                .addArgumentParameter("test", OptionCount.REQUIRED, this::nop);
     149
     150        parser.parseOptions(Arrays.asList("--test"));
     151    }
     152
     153    @Test(expected = OptionParseException.class)
     154    public void testLongArgumentsMissingOption2() {
     155        OptionParser parser = new OptionParser("test")
     156                .addArgumentParameter("test", OptionCount.REQUIRED, this::nop);
     157
     158        parser.parseOptions(Arrays.asList("--test", "--", "x"));
     159    }
     160
     161
     162    @Test(expected = OptionParseException.class)
     163    public void testShortArgumentsMissingOption() {
     164        OptionParser parser = new OptionParser("test")
     165                .addArgumentParameter("test", OptionCount.REQUIRED, this::nop)
     166                .addShortAlias("test", "t");
     167
     168        parser.parseOptions(Arrays.asList("-t"));
     169    }
     170
     171    @Test(expected = OptionParseException.class)
     172    public void testShortArgumentsMissingOption2() {
     173        OptionParser parser = new OptionParser("test")
     174                .addArgumentParameter("test", OptionCount.REQUIRED, this::nop)
     175                .addShortAlias("test", "t");
     176
     177        parser.parseOptions(Arrays.asList("-t", "--", "x"));
     178    }
     179
     180    @Test(expected = OptionParseException.class)
     181    public void testLongFlagHasOption() {
     182        OptionParser parser = new OptionParser("test")
     183                .addFlagParameter("test", this::nop);
     184
     185        parser.parseOptions(Arrays.asList("--test=arg"));
     186    }
     187
     188    @Test(expected = OptionParseException.class)
     189    public void testShortFlagHasOption() {
     190        OptionParser parser = new OptionParser("test")
     191                .addFlagParameter("test", this::nop)
     192                .addShortAlias("test", "t");
     193
     194        parser.parseOptions(Arrays.asList("-t=arg"));
     195    }
     196
     197    @Test
     198    public void testShortArgumentsUsingEqualSign() {
     199        AtomicReference<String> argFound = new AtomicReference<>();
     200        OptionParser parser = new OptionParser("test")
     201                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set)
     202                .addShortAlias("test", "t");
     203
     204        List<String> remaining = parser.parseOptions(Arrays.asList("-t=arg", "value"));
     205
     206        assertEquals(Arrays.asList("value"), remaining);
     207        assertEquals("arg", argFound.get());
     208    }
     209
     210    @Test
     211    public void testMultipleArguments() {
     212        AtomicReference<String> argFound = new AtomicReference<>();
     213        List<String> multiFound = new ArrayList<>();
     214        AtomicBoolean usedFlag = new AtomicBoolean();
     215        AtomicBoolean unusedFlag = new AtomicBoolean();
     216        OptionParser parser = new OptionParser("test")
     217                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set)
     218                .addShortAlias("test", "t")
     219                .addArgumentParameter("multi", OptionCount.MULTIPLE, multiFound::add)
     220                .addFlagParameter("flag", () -> usedFlag.set(true))
     221                .addFlagParameter("flag2", () -> unusedFlag.set(true));
     222
     223        List<String> remaining = parser.parseOptions(Arrays.asList("-t=arg", "--multi", "m1", "x1", "--flag", "--multi", "m2", "x2", "--", "x3", "--x4", "x5"));
     224
     225        assertEquals(Arrays.asList("x1", "x2", "x3", "--x4", "x5"), remaining);
     226        assertEquals("arg", argFound.get());
     227        assertEquals(Arrays.asList("m1", "m2"), multiFound);
     228        assertEquals(true, usedFlag.get());
     229        assertEquals(false, unusedFlag.get());
     230    }
     231
     232    @Test
     233    public void testUseAlternatives() {
     234        AtomicReference<String> argFound = new AtomicReference<>();
     235        AtomicBoolean usedFlag = new AtomicBoolean();
     236        AtomicBoolean unusedFlag = new AtomicBoolean();
     237
     238        OptionParser parser = new OptionParser("test")
     239                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set)
     240                .addFlagParameter("flag", () -> usedFlag.set(true))
     241                .addFlagParameter("fleg", () -> unusedFlag.set(true));
     242
     243        List<String> remaining = parser.parseOptions(Arrays.asList("--te=arg", "--fla"));
     244
     245        assertEquals(Arrays.asList(), remaining);
     246        assertEquals("arg", argFound.get());
     247        assertEquals(true, usedFlag.get());
     248        assertEquals(false, unusedFlag.get());
     249    }
     250
     251    @Test(expected = OptionParseException.class)
     252    public void testAbigiousAlternatives() {
     253        AtomicReference<String> argFound = new AtomicReference<>();
     254        AtomicBoolean usedFlag = new AtomicBoolean();
     255        AtomicBoolean unusedFlag = new AtomicBoolean();
     256
     257        OptionParser parser = new OptionParser("test")
     258                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set)
     259                .addFlagParameter("flag", () -> usedFlag.set(true))
     260                .addFlagParameter("fleg", () -> unusedFlag.set(true));
     261
     262        parser.parseOptions(Arrays.asList("--te=arg", "--fl"));
     263    }
     264
     265    @Test(expected = OptionParseException.class)
     266    public void testMultipleShort() {
     267        AtomicReference<String> argFound = new AtomicReference<>();
     268        AtomicBoolean usedFlag = new AtomicBoolean();
     269        AtomicBoolean unusedFlag = new AtomicBoolean();
     270
     271        OptionParser parser = new OptionParser("test")
     272                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set)
     273                .addShortAlias("test", "t")
     274                .addFlagParameter("flag", () -> usedFlag.set(true))
     275                .addShortAlias("flag", "f")
     276                .addFlagParameter("fleg", () -> unusedFlag.set(true));
     277
     278        List<String> remaining = parser.parseOptions(Arrays.asList("-ft=arg", "x"));
     279
     280        assertEquals(Arrays.asList("x"), remaining);
     281        assertEquals("arg", argFound.get());
     282        assertEquals(true, usedFlag.get());
     283        assertEquals(false, unusedFlag.get());
     284
     285        remaining = parser.parseOptions(Arrays.asList("-ft", "arg", "x"));
     286
     287        assertEquals(Arrays.asList("x"), remaining);
     288        assertEquals("arg", argFound.get());
     289        assertEquals(true, usedFlag.get());
     290        assertEquals(false, unusedFlag.get());
     291
     292        remaining = parser.parseOptions(Arrays.asList("-f", "-t=arg", "x"));
     293
     294        assertEquals(Arrays.asList("x"), remaining);
     295        assertEquals("arg", argFound.get());
     296        assertEquals(true, usedFlag.get());
     297        assertEquals(false, unusedFlag.get());
     298    }
     299
     300
     301    @Test(expected = IllegalArgumentException.class)
     302    public void testIllegalOptionName() {
     303        new OptionParser("test").addFlagParameter("", this::nop);
     304    }
     305
     306    @Test(expected = IllegalArgumentException.class)
     307    public void testIllegalOptionName2() {
     308        new OptionParser("test").addFlagParameter("-", this::nop);
     309    }
     310
     311    @Test(expected = IllegalArgumentException.class)
     312    public void testIllegalOptionName3() {
     313        new OptionParser("test").addFlagParameter("-test", this::nop);
     314    }
     315
     316    @Test(expected = IllegalArgumentException.class)
     317    public void testIllegalOptionName4() {
     318        new OptionParser("test").addFlagParameter("$", this::nop);
     319    }
     320
     321    @Test(expected = IllegalArgumentException.class)
     322    public void testDupplicateOptionName() {
     323        new OptionParser("test").addFlagParameter("test", this::nop).addFlagParameter("test", this::nop);
     324    }
     325
     326    @Test(expected = IllegalArgumentException.class)
     327    public void testDupplicateOptionName2() {
     328        new OptionParser("test").addFlagParameter("test", this::nop)
     329        .addArgumentParameter("test", OptionCount.OPTIONAL,  this::nop);
     330    }
     331
     332    @Test(expected = IllegalArgumentException.class)
     333    public void testInvalidShortAlias() {
     334        new OptionParser("test").addFlagParameter("test", this::nop).addShortAlias("test", "$");
     335    }
     336
     337    @Test(expected = IllegalArgumentException.class)
     338    public void testInvalidShortAlias2() {
     339        new OptionParser("test").addFlagParameter("test", this::nop).addShortAlias("test", "");
     340    }
     341
     342    @Test(expected = IllegalArgumentException.class)
     343    public void testInvalidShortAlias3() {
     344        new OptionParser("test").addFlagParameter("test", this::nop).addShortAlias("test", "xx");
     345    }
     346
     347    @Test(expected = IllegalArgumentException.class)
     348    public void testDupplicateShortAlias() {
     349        new OptionParser("test").addFlagParameter("test", this::nop)
     350        .addFlagParameter("test2", this::nop)
     351        .addShortAlias("test", "t")
     352        .addShortAlias("test2", "t");
     353    }
     354
     355    @Test(expected = IllegalArgumentException.class)
     356    public void testInvalidShortNoLong() {
     357        new OptionParser("test").addFlagParameter("test", this::nop).addShortAlias("test2", "t");
     358    }
     359
     360    private void nop() {
     361        // nop
     362    }
     363
     364    private void nop(String arg) {
     365        // nop
     366    }
     367}