Index: trunk/src/org/openstreetmap/josm/data/projection/ProjectionCLI.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/ProjectionCLI.java	(revision 14414)
+++ trunk/src/org/openstreetmap/josm/data/projection/ProjectionCLI.java	(revision 14415)
@@ -12,4 +12,5 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.Function;
@@ -19,9 +20,6 @@
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.coor.conversion.LatLonParser;
-import org.openstreetmap.josm.tools.I18n;
+import org.openstreetmap.josm.tools.OptionParser;
 import org.openstreetmap.josm.tools.Utils;
-
-import gnu.getopt.Getopt;
-import gnu.getopt.LongOpt;
 
 /**
@@ -44,27 +42,14 @@
     @Override
     public void processArguments(String[] argArray) {
-        Getopt.setI18nHandler(I18n::tr);
-        Getopt getopt = new Getopt("JOSM projection", argArray, "Irh", new LongOpt[] {
-                new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h')});
-
-        int c;
-        while ((c = getopt.getopt()) != -1) {
-            switch (c) {
-            case 'h':
-                showHelp();
-                System.exit(0);
-            case 'I':
-                argInverse = true;
-                break;
-            case 'r':
-                argSwitchInput = true;
-                break;
-            case 's':
-                argSwitchOutput = true;
-                break;
-            default:
-                // ignore
-            }
-        }
+        List<String> positionalArguments = new OptionParser("JOSM projection")
+            .addFlagParameter("help", this::showHelp)
+            .addShortAlias("help", "h")
+            .addFlagParameter("inverse", () -> argInverse = true)
+            .addShortAlias("inverse", "I")
+            .addFlagParameter("switch-input", () -> argSwitchInput = true)
+            .addShortAlias("switch-input", "r")
+            .addFlagParameter("switch-output", () -> argSwitchOutput = true)
+            .addShortAlias("switch-output", "s")
+            .parseOptionsOrExit(Arrays.asList(argArray));
 
         List<String> projParamFrom = new ArrayList<>();
@@ -73,6 +58,5 @@
         boolean toTokenSeen = false;
         // positional arguments:
-        for (int i = getopt.getOptind(); i < argArray.length; ++i) {
-            String arg = argArray[i];
+        for (String arg: positionalArguments) {
             if (arg.isEmpty()) throw new IllegalArgumentException("non-empty argument expected");
             if (arg.startsWith("+")) {
@@ -100,6 +84,7 @@
      * Displays help on the console
      */
-    public static void showHelp() {
+    private void showHelp() {
         System.out.println(getHelp());
+        System.exit(0);
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/ProgramArguments.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/ProgramArguments.java	(revision 14414)
+++ trunk/src/org/openstreetmap/josm/gui/ProgramArguments.java	(revision 14415)
@@ -3,4 +3,5 @@
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -14,9 +15,7 @@
 import java.util.stream.Stream;
 
-import org.openstreetmap.josm.tools.I18n;
 import org.openstreetmap.josm.tools.Logging;
-
-import gnu.getopt.Getopt;
-import gnu.getopt.LongOpt;
+import org.openstreetmap.josm.tools.OptionParser;
+import org.openstreetmap.josm.tools.OptionParser.OptionCount;
 
 /**
@@ -91,8 +90,4 @@
             return requiresArg;
         }
-
-        LongOpt toLongOpt() {
-            return new LongOpt(getName(), requiresArgument() ? LongOpt.REQUIRED_ARGUMENT : LongOpt.NO_ARGUMENT, null, 0);
-        }
     }
 
@@ -114,32 +109,21 @@
      */
     private void buildCommandLineArgumentMap(String... args) {
-        Getopt.setI18nHandler(I18n::tr);
-        LongOpt[] los = Stream.of(Option.values()).map(Option::toLongOpt).toArray(LongOpt[]::new);
-        Getopt g = new Getopt("JOSM", args, "hv", los);
-
-        int c;
-        while ((c = g.getopt()) != -1) {
-            Option opt;
-            switch (c) {
-            case 'h':
-                opt = Option.HELP;
-                break;
-            case 'v':
-                opt = Option.VERSION;
-                break;
-            case 0:
-                opt = Option.values()[g.getLongind()];
-                break;
-            default:
-                opt = null;
+        OptionParser parser = new OptionParser("JOSM");
+        for (Option o : Option.values()) {
+            if (o.requiresArgument()) {
+                parser.addArgumentParameter(o.getName(), OptionCount.MULTIPLE, p -> addOption(o, p));
+            } else {
+                parser.addFlagParameter(o.getName(), () -> addOption(o, ""));
             }
-            if (opt != null) {
-                addOption(opt, g.getOptarg());
-            } else
-                throw new IllegalArgumentException("Invalid option: "+ (char) c);
-        }
+        }
+
+        parser.addShortAlias(Option.HELP.getName(), "h");
+        parser.addShortAlias(Option.VERSION.getName(), "v");
+
+        List<String> remaining = parser.parseOptionsOrExit(Arrays.asList(args));
+
         // positional arguments are a shortcut for the --download ... option
-        for (int i = g.getOptind(); i < args.length; ++i) {
-            addOption(Option.DOWNLOAD, args[i]);
+        for (String arg : remaining) {
+            addOption(Option.DOWNLOAD, arg);
         }
     }
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/RenderingCLI.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/RenderingCLI.java	(revision 14414)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/RenderingCLI.java	(revision 14415)
@@ -12,4 +12,5 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
@@ -38,11 +39,10 @@
 import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.spi.preferences.MemoryPreferences;
-import org.openstreetmap.josm.tools.I18n;
 import org.openstreetmap.josm.tools.JosmDecimalFormatSymbolsProvider;
 import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.OptionParser;
+import org.openstreetmap.josm.tools.OptionParser.OptionCount;
+import org.openstreetmap.josm.tools.OptionParser.OptionParseException;
 import org.openstreetmap.josm.tools.RightAndLefthandTraffic;
-
-import gnu.getopt.Getopt;
-import gnu.getopt.LongOpt;
 
 /**
@@ -76,4 +76,6 @@
     private String argProjection;
     private Integer argMaxImageSize;
+
+    private StyleData argCurrentStyle;
 
     private enum Option {
@@ -129,8 +131,4 @@
             return shortOption;
         }
-
-        LongOpt toLongOpt() {
-            return new LongOpt(getName(), requiresArgument() ? LongOpt.REQUIRED_ARGUMENT : LongOpt.NO_ARGUMENT, null, getShortOption());
-        }
     }
 
@@ -188,167 +186,179 @@
      */
     void parseArguments(String[] argArray) {
-        Getopt.setI18nHandler(I18n::tr);
         Logging.setLogLevel(Level.INFO);
 
-        LongOpt[] opts = new LongOpt[Option.values().length];
-        StringBuilder optString = new StringBuilder();
+        OptionParser parser = new OptionParser("JOSM rendering");
         for (Option o : Option.values()) {
-            opts[o.ordinal()] = o.toLongOpt();
+            if (o.requiresArgument()) {
+                parser.addArgumentParameter(o.getName(),
+                        o == Option.SETTING ? OptionCount.MULTIPLE : OptionCount.OPTIONAL,
+                        arg -> handleOption(o, arg));
+            } else {
+                parser.addFlagParameter(o.getName(), () -> handleOption(o));
+            }
             if (o.getShortOption() != '*') {
-                optString.append(o.getShortOption());
-                if (o.requiresArgument()) {
-                    optString.append(':');
+                parser.addShortAlias(o.getName(), o.getShortOption() + "");
+            }
+        }
+
+        argCurrentStyle = new StyleData();
+        argStyles = new ArrayList<>();
+
+        parser.parseOptionsOrExit(Arrays.asList(argArray));
+
+        if (argCurrentStyle.styleUrl != null) {
+            argStyles.add(argCurrentStyle);
+        }
+    }
+
+    private void handleOption(Option o) {
+        switch (o) {
+        case HELP:
+            showHelp();
+            System.exit(0);
+            break;
+        case DEBUG:
+            argDebug = true;
+            break;
+        case TRACE:
+            argTrace = true;
+            break;
+        default:
+            throw new AssertionError("Unexpected option index: " + o);
+        }
+    }
+
+    private void handleOption(Option o, String arg) {
+        switch (o) {
+        case INPUT:
+            argInput = arg;
+            break;
+        case STYLE:
+            if (argCurrentStyle.styleUrl != null) {
+                argStyles.add(argCurrentStyle);
+                argCurrentStyle = new StyleData();
+            }
+            argCurrentStyle.styleUrl = arg;
+            break;
+        case OUTPUT:
+            argOutput = arg;
+            break;
+        case ZOOM:
+            try {
+                argZoom = Integer.valueOf(arg);
+            } catch (NumberFormatException nfe) {
+                throw new OptionParseException(
+                        tr("Expected integer number for option {0}, but got ''{1}''", "--zoom", arg), nfe);
+            }
+            if (argZoom < 0) {
+                throw new OptionParseException(
+                        tr("Expected integer number >= 0 for option {0}, but got ''{1}''", "--zoom", arg));
+            }
+            break;
+        case BOUNDS:
+            if (!"auto".equals(arg)) {
+                try {
+                    argBounds = new Bounds(arg, ",", Bounds.ParseMethod.LEFT_BOTTOM_RIGHT_TOP, false);
+                } catch (IllegalArgumentException iae) { // NOPMD
+                    throw new OptionParseException(
+                            tr("Unable to parse {0} parameter: {1}", "--bounds", iae.getMessage()), iae);
                 }
             }
-        }
-
-        Getopt getopt = new Getopt("JOSM rendering", argArray, optString.toString(), opts);
-
-        StyleData currentStyle = new StyleData();
-        argStyles = new ArrayList<>();
-
-        int c;
-        while ((c = getopt.getopt()) != -1) {
-            switch (c) {
-            case 'h':
-                showHelp();
-                System.exit(0);
-            case 'i':
-                argInput = getopt.getOptarg();
-                break;
-            case 's':
-                if (currentStyle.styleUrl != null) {
-                    argStyles.add(currentStyle);
-                    currentStyle = new StyleData();
-                }
-                currentStyle.styleUrl = getopt.getOptarg();
-                break;
-            case 'o':
-                argOutput = getopt.getOptarg();
-                break;
-            case 'z':
-                try {
-                    argZoom = Integer.valueOf(getopt.getOptarg());
-                } catch (NumberFormatException nfe) {
-                    throw new IllegalArgumentException(
-                            tr("Expected integer number for option {0}, but got ''{1}''", "--zoom", getopt.getOptarg()), nfe);
-                }
-                if (argZoom < 0)
-                    throw new IllegalArgumentException(
-                            tr("Expected integer number >= 0 for option {0}, but got ''{1}''", "--zoom", getopt.getOptarg()));
-                break;
-            case 'b':
-                if (!"auto".equals(getopt.getOptarg())) {
-                    try {
-                        argBounds = new Bounds(getopt.getOptarg(), ",", Bounds.ParseMethod.LEFT_BOTTOM_RIGHT_TOP, false);
-                    } catch (IllegalArgumentException iae) { // NOPMD
-                        throw new IllegalArgumentException(tr("Unable to parse {0} parameter: {1}", "--bounds", iae.getMessage()), iae);
-                    }
-                }
-                break;
-            case '*':
-                switch (Option.values()[getopt.getLongind()]) {
-                case DEBUG:
-                    argDebug = true;
-                    break;
-                case TRACE:
-                    argTrace = true;
-                    break;
-                case SETTING:
-                    String keyval = getopt.getOptarg();
-                    String[] comp = keyval.split(":");
-                    if (comp.length != 2)
-                        throw new IllegalArgumentException(
-                                tr("Expected key and value, separated by '':'' character for option {0}, but got ''{1}''",
-                                        "--setting", getopt.getOptarg()));
-                    currentStyle.settings.put(comp[0].trim(), comp[1].trim());
-                    break;
-                case SCALE:
-                    try {
-                        argScale = JosmDecimalFormatSymbolsProvider.parseDouble(getopt.getOptarg());
-                    } catch (NumberFormatException nfe) {
-                        throw new IllegalArgumentException(
-                                tr("Expected floating point number for option {0}, but got ''{1}''", "--scale", getopt.getOptarg()), nfe);
-                    }
-                    break;
-                case ANCHOR:
-                    String[] parts = getopt.getOptarg().split(",");
-                    if (parts.length != 2)
-                        throw new IllegalArgumentException(
-                                tr("Expected two coordinates, separated by comma, for option {0}, but got ''{1}''",
-                                "--anchor", getopt.getOptarg()));
-                    try {
-                        double lon = LatLonParser.parseCoordinate(parts[0]);
-                        double lat = LatLonParser.parseCoordinate(parts[1]);
-                        argAnchor = new LatLon(lat, lon);
-                    } catch (IllegalArgumentException iae) { // NOPMD
-                        throw new IllegalArgumentException(tr("In option {0}: {1}", "--anchor", iae.getMessage()), iae);
-                    }
-                    break;
-                case WIDTH_M:
-                    try {
-                        argWidthM = JosmDecimalFormatSymbolsProvider.parseDouble(getopt.getOptarg());
-                    } catch (NumberFormatException nfe) {
-                        throw new IllegalArgumentException(
-                                tr("Expected floating point number for option {0}, but got ''{1}''", "--width-m", getopt.getOptarg()), nfe);
-                    }
-                    if (argWidthM <= 0) throw new IllegalArgumentException(
-                            tr("Expected floating point number > 0 for option {0}, but got ''{1}''", "--width-m", getopt.getOptarg()));
-                    break;
-                case HEIGHT_M:
-                    try {
-                        argHeightM = JosmDecimalFormatSymbolsProvider.parseDouble(getopt.getOptarg());
-                    } catch (NumberFormatException nfe) {
-                        throw new IllegalArgumentException(
-                                tr("Expected floating point number for option {0}, but got ''{1}''", "--height-m", getopt.getOptarg()), nfe);
-                    }
-                    if (argHeightM <= 0) throw new IllegalArgumentException(
-                            tr("Expected floating point number > 0 for option {0}, but got ''{1}''", "--width-m", getopt.getOptarg()));
-                    break;
-                case WIDTH_PX:
-                    try {
-                        argWidthPx = Integer.valueOf(getopt.getOptarg());
-                    } catch (NumberFormatException nfe) {
-                        throw new IllegalArgumentException(
-                                tr("Expected integer number for option {0}, but got ''{1}''", "--width-px", getopt.getOptarg()), nfe);
-                    }
-                    if (argWidthPx <= 0) throw new IllegalArgumentException(
-                            tr("Expected integer number > 0 for option {0}, but got ''{1}''", "--width-px", getopt.getOptarg()));
-                    break;
-                case HEIGHT_PX:
-                    try {
-                        argHeightPx = Integer.valueOf(getopt.getOptarg());
-                    } catch (NumberFormatException nfe) {
-                        throw new IllegalArgumentException(
-                                tr("Expected integer number for option {0}, but got ''{1}''", "--height-px", getopt.getOptarg()), nfe);
-                    }
-                    if (argHeightPx <= 0) throw new IllegalArgumentException(
-                            tr("Expected integer number > 0 for option {0}, but got ''{1}''", "--height-px", getopt.getOptarg()));
-                    break;
-                case PROJECTION:
-                    argProjection = getopt.getOptarg();
-                    break;
-                case MAX_IMAGE_SIZE:
-                    try {
-                        argMaxImageSize = Integer.valueOf(getopt.getOptarg());
-                    } catch (NumberFormatException nfe) {
-                        throw new IllegalArgumentException(
-                                tr("Expected integer number for option {0}, but got ''{1}''", "--max-image-size", getopt.getOptarg()), nfe);
-                    }
-                    if (argMaxImageSize < 0) throw new IllegalArgumentException(
-                            tr("Expected integer number >= 0 for option {0}, but got ''{1}''", "--max-image-size", getopt.getOptarg()));
-                    break;
-                default:
-                    throw new AssertionError("Unexpected option index: " + getopt.getLongind());
-                }
-                break;
-            case '?':
-                throw new IllegalArgumentException();   // getopt error
-            default:
-                throw new AssertionError("Unrecognized option: " + c);
-            }
-        }
-        if (currentStyle.styleUrl != null) {
-            argStyles.add(currentStyle);
+            break;
+
+        case SETTING:
+            String keyval = arg;
+            String[] comp = keyval.split(":", 2);
+            if (comp.length != 2) {
+                throw new OptionParseException(
+                        tr("Expected key and value, separated by '':'' character for option {0}, but got ''{1}''",
+                                "--setting", arg));
+            }
+            argCurrentStyle.settings.put(comp[0].trim(), comp[1].trim());
+            break;
+        case SCALE:
+            try {
+                argScale = JosmDecimalFormatSymbolsProvider.parseDouble(arg);
+            } catch (NumberFormatException nfe) {
+                throw new OptionParseException(
+                        tr("Expected floating point number for option {0}, but got ''{1}''", "--scale", arg), nfe);
+            }
+            break;
+        case ANCHOR:
+            String[] parts = arg.split(",");
+            if (parts.length != 2)
+                throw new OptionParseException(
+                        tr("Expected two coordinates, separated by comma, for option {0}, but got ''{1}''", "--anchor",
+                                arg));
+            try {
+                double lon = LatLonParser.parseCoordinate(parts[0]);
+                double lat = LatLonParser.parseCoordinate(parts[1]);
+                argAnchor = new LatLon(lat, lon);
+            } catch (IllegalArgumentException iae) { // NOPMD
+                throw new OptionParseException(tr("In option {0}: {1}", "--anchor", iae.getMessage()), iae);
+            }
+            break;
+        case WIDTH_M:
+            try {
+                argWidthM = JosmDecimalFormatSymbolsProvider.parseDouble(arg);
+            } catch (NumberFormatException nfe) {
+                throw new OptionParseException(
+                        tr("Expected floating point number for option {0}, but got ''{1}''", "--width-m", arg), nfe);
+            }
+            if (argWidthM <= 0)
+                throw new OptionParseException(
+                        tr("Expected floating point number > 0 for option {0}, but got ''{1}''", "--width-m", arg));
+            break;
+        case HEIGHT_M:
+            try {
+                argHeightM = JosmDecimalFormatSymbolsProvider.parseDouble(arg);
+            } catch (NumberFormatException nfe) {
+                throw new OptionParseException(
+                        tr("Expected floating point number for option {0}, but got ''{1}''", "--height-m", arg), nfe);
+            }
+            if (argHeightM <= 0)
+                throw new OptionParseException(
+                        tr("Expected floating point number > 0 for option {0}, but got ''{1}''", "--width-m", arg));
+            break;
+        case WIDTH_PX:
+            try {
+                argWidthPx = Integer.valueOf(arg);
+            } catch (NumberFormatException nfe) {
+                throw new OptionParseException(
+                        tr("Expected integer number for option {0}, but got ''{1}''", "--width-px", arg), nfe);
+            }
+            if (argWidthPx <= 0)
+                throw new OptionParseException(
+                        tr("Expected integer number > 0 for option {0}, but got ''{1}''", "--width-px", arg));
+            break;
+        case HEIGHT_PX:
+            try {
+                argHeightPx = Integer.valueOf(arg);
+            } catch (NumberFormatException nfe) {
+                throw new OptionParseException(
+                        tr("Expected integer number for option {0}, but got ''{1}''", "--height-px", arg), nfe);
+            }
+            if (argHeightPx <= 0) {
+                throw new OptionParseException(
+                        tr("Expected integer number > 0 for option {0}, but got ''{1}''", "--height-px", arg));
+            }
+            break;
+        case PROJECTION:
+            argProjection = arg;
+            break;
+        case MAX_IMAGE_SIZE:
+            try {
+                argMaxImageSize = Integer.valueOf(arg);
+            } catch (NumberFormatException nfe) {
+                throw new OptionParseException(
+                        tr("Expected integer number for option {0}, but got ''{1}''", "--max-image-size", arg), nfe);
+            }
+            if (argMaxImageSize < 0) {
+                throw new OptionParseException(
+                        tr("Expected integer number >= 0 for option {0}, but got ''{1}''", "--max-image-size", arg));
+            }
+            break;
+        default:
+            throw new AssertionError("Unexpected option index: " + o);
         }
     }
@@ -383,5 +393,5 @@
                 "\t                          "+tr("Area to render, default value is ''{0}''", "auto")+"\n"+
                 "\t                          "+tr("With keyword ''{0}'', the downloaded area in the .osm input file will be used (if recorded).",
-                                                     "auto")+"\n"+
+                                                  "auto")+"\n"+
                 "\t--anchor <lon>,<lat>      "+tr("Specify bottom left corner of the rendering area")+"\n"+
                 "\t                          "+tr("Used in combination with width and height options to determine the area to render.")+"\n"+
@@ -392,5 +402,5 @@
                 "\t--projection <code>       "+tr("Projection to use, default value ''{0}'' (web-Mercator)", "epsg:3857")+"\n"+
                 "\t--max-image-size <number> "+tr("Maximum image width/height in pixel (''{0}'' means no limit), default value: {1}",
-                                                    0, Integer.toString(DEFAULT_MAX_IMAGE_SIZE))+"\n"+
+                                                   0, Integer.toString(DEFAULT_MAX_IMAGE_SIZE))+"\n"+
                 "\n"+
                 tr("To specify the rendered area and scale, the options can be combined in various ways")+":\n"+
@@ -452,5 +462,6 @@
         Double scale = null; // scale in east-north units per pixel
         if (argZoom != null) {
-            scale = OsmMercator.EARTH_RADIUS * Math.PI * 2 / Math.pow(2, argZoom) / OsmMercator.DEFAUL_TILE_SIZE / proj.getMetersPerUnit();
+            scale = OsmMercator.EARTH_RADIUS * Math.PI * 2 / Math.pow(2, argZoom) / OsmMercator.DEFAUL_TILE_SIZE
+                    / proj.getMetersPerUnit();
         }
         Bounds bounds = argBounds;
@@ -464,6 +475,6 @@
                 DoubleSupplier getEnPerMeter = () -> {
                     double shiftMeter = 10;
-                    EastNorth projAnchorShifted = projAnchor.add(
-                            shiftMeter / proj.getMetersPerUnit(), shiftMeter / proj.getMetersPerUnit());
+                    EastNorth projAnchorShifted = projAnchor.add(shiftMeter / proj.getMetersPerUnit(),
+                            shiftMeter / proj.getMetersPerUnit());
                     LatLon anchorShifted = proj.eastNorth2latlon(projAnchorShifted);
                     return projAnchor.distance(projAnchorShifted) / argAnchor.greatCircleDistance(anchorShifted);
@@ -482,5 +493,6 @@
                     } else {
                         throw new IllegalArgumentException(
-                                tr("Argument {0} given, but scale cannot be determined from remaining arguments", "--anchor"));
+                                tr("Argument {0} given, but scale cannot be determined from remaining arguments",
+                                        "--anchor"));
                     }
                 }
@@ -517,5 +529,6 @@
             } else {
                 if (ds.getDataSourceBounds().isEmpty()) {
-                    throw new IllegalArgumentException(tr("{0} mode, but no bounds found in osm data input file", "--bounds=auto"));
+                    throw new IllegalArgumentException(
+                            tr("{0} mode, but no bounds found in osm data input file", "--bounds=auto"));
                 }
                 bounds = ds.getDataSourceBounds().get(0);
@@ -531,5 +544,6 @@
         if (scale == null) {
             if (argScale != null) {
-                double enPerMeter = pb.getMin().distance(pb.getMax()) / bounds.getMin().greatCircleDistance(bounds.getMax());
+                double enPerMeter = pb.getMin().distance(pb.getMax())
+                        / bounds.getMin().greatCircleDistance(bounds.getMax());
                 scale = argScale * enPerMeter / PIXEL_PER_METER;
             } else if (argWidthPx != null) {
@@ -539,6 +553,6 @@
             } else {
                 throw new IllegalArgumentException(
-                        tr("Unable to determine scale, one of the options {0}, {1}, {2} or {3} expected",
-                                "--zoom", "--scale", "--width-px", "--height-px"));
+                        tr("Unable to determine scale, one of the options {0}, {1}, {2} or {3} expected", "--zoom",
+                                "--scale", "--width-px", "--height-px"));
             }
         }
Index: trunk/src/org/openstreetmap/josm/tools/OptionParser.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/OptionParser.java	(revision 14415)
+++ trunk/src/org/openstreetmap/josm/tools/OptionParser.java	(revision 14415)
@@ -0,0 +1,330 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.tools;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+/**
+ * A replacement of getopt.
+ * <p>
+ * Allows parsing command line options
+ *
+ * @author Michael Zangl
+ * @since 14415
+ */
+public class OptionParser {
+
+    private HashMap<String, AvailableOption> availableOptions = new HashMap<>();
+    private final String program;
+
+    /**
+     * Create a new option parser.
+     * @param program The program name.
+     */
+    public OptionParser(String program) {
+        Objects.requireNonNull(program, "program name must be provided");
+        this.program = program;
+    }
+
+    /**
+     * Adds an alias for the long option --optionName to the short version -name
+     * @param optionName The long option
+     * @param shortName The short version
+      * @return this {@link OptionParser}
+    */
+    public OptionParser addShortAlias(String optionName, String shortName) {
+        if (!shortName.matches("\\w")) {
+            throw new IllegalArgumentException("Short name " + shortName + " must be one character");
+        }
+        if (availableOptions.containsKey("-" + shortName)) {
+            throw new IllegalArgumentException("Short name " + shortName + " is already used");
+        }
+        AvailableOption longDefinition = availableOptions.get("--" + optionName);
+        if (longDefinition == null) {
+            throw new IllegalArgumentException("No long definition for " + optionName
+                    + " was defined. Define the long definition first before creating " + "a short definition for it.");
+        }
+        availableOptions.put("-" + shortName, longDefinition);
+        return this;
+    }
+
+    /**
+     * Adds an option that may be used as a flag, e.g. --debug
+     * @param optionName The parameter name
+     * @param handler The handler that is called when the flag is encountered.
+     * @return this {@link OptionParser}
+     */
+    public OptionParser addFlagParameter(String optionName, Runnable handler) {
+        checkOptionName(optionName);
+        availableOptions.put("--" + optionName, new AvailableOption() {
+            @Override
+            public void runFor(String parameter) {
+                handler.run();
+            }
+        });
+        return this;
+    }
+
+    private void checkOptionName(String optionName) {
+        if (!optionName.matches("\\w([\\w-]*\\w)?")) {
+            throw new IllegalArgumentException("Illegal option name: " + optionName);
+        }
+        if (availableOptions.containsKey("--" + optionName)) {
+            throw new IllegalArgumentException("The option --" + optionName + " is already registered");
+        }
+    }
+
+    /**
+     * Add a parameter that expects a string attribute. E.g.: --config=/path/to/file
+     * @param optionName The name of the parameter.
+     * @param count The number of times the parameter may occur.
+     * @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.
+     * @return this {@link OptionParser}
+     */
+    public OptionParser addArgumentParameter(String optionName, OptionCount count, Consumer<String> handler) {
+        checkOptionName(optionName);
+        availableOptions.put("--" + optionName, new AvailableOption() {
+            @Override
+            public boolean requiresParameter() {
+                return true;
+            }
+
+            @Override
+            public OptionCount getRequiredCount() {
+                return count;
+            }
+
+            @Override
+            public void runFor(String parameter) {
+                Objects.requireNonNull(parameter, "parameter");
+                handler.accept(parameter);
+            }
+        });
+        return this;
+    }
+
+    /**
+     * Same as {@link #parseOptions(List)}, but exits if option parsing fails.
+     * @param arguments The options
+     * @return The remaining program arguments that are no options.
+     */
+    public List<String> parseOptionsOrExit(List<String> arguments) {
+        try {
+            return parseOptions(arguments);
+        } catch (OptionParseException e) {
+            System.err.println(e.getLocalizedMessage());
+            System.exit(1);
+            // unreachable, but makes compilers happy
+            throw e;
+        }
+    }
+
+    /**
+     * Parses the options.
+     * <p>
+     * It first checks if all required options are present, if all options are known and validates the option count.
+     * <p>
+     * Then, all option handlers are called in the order in which the options are encountered.
+     * @param arguments Program arguments
+     * @return The remaining program arguments that are no options.
+     * @throws OptionParseException The error to display if option parsing failed.
+     */
+    public List<String> parseOptions(List<String> arguments) {
+        LinkedList<String> toHandle = new LinkedList<>(arguments);
+        List<String> remainingArguments = new LinkedList<>();
+        boolean argumentOnlyMode = false;
+        List<FoundOption> options = new LinkedList<>();
+
+        while (!toHandle.isEmpty()) {
+            String next = toHandle.removeFirst();
+            if (argumentOnlyMode || !next.matches("-.+")) {
+                // argument found, add it to arguments list
+                remainingArguments.add(next);
+            } else if ("--".equals(next)) {
+                // we are done, the remaining should be used as arguments.
+                argumentOnlyMode = true;
+            } else {
+                if (next.matches("-\\w\\w+")) {
+                    // special case: concatenated short options like -hv
+                    // We handle them as if the user wrote -h -v by just scheduling the remainder for the next loop.
+                    toHandle.addFirst("-" + next.substring(2));
+                    next = next.substring(0, 2);
+                }
+
+                String[] split = next.split("=", 2);
+                String optionName = split[0];
+                AvailableOption definition = findParameter(optionName);
+                String parameter = null;
+                if (definition.requiresParameter()) {
+                    if (split.length > 1) {
+                        parameter = split[1];
+                    } else {
+                        if (toHandle.isEmpty() || toHandle.getFirst().equals("--")) {
+                            throw new OptionParseException(tr("{0}: option ''{1}'' requires an argument", program));
+                        }
+                        parameter = toHandle.removeFirst();
+                    }
+                } else if (split.length > 1) {
+                    throw new OptionParseException(
+                            tr("{0}: option ''{1}'' does not allow an argument", program, optionName));
+                }
+                options.add(new FoundOption(optionName, definition, parameter));
+            }
+        }
+
+        // Count how often they are used
+        availableOptions.values().stream().distinct().forEach(def -> {
+            long count = options.stream().filter(p -> def.equals(p.option)).count();
+            if (count < def.getRequiredCount().min) {
+                // min may be 0 or 1 at the moment
+                throw new OptionParseException(tr("{0}: option ''{1}'' is required"));
+            } else if (count > def.getRequiredCount().max) {
+                // max may be 1 or MAX_INT at the moment
+                throw new OptionParseException(tr("{0}: option ''{1}'' may not appear multiple times"));
+            }
+        });
+
+        // Actually apply the parameters.
+        for (FoundOption option : options) {
+            try {
+                option.option.runFor(option.parameter);
+            } catch (OptionParseException e) {
+                String message;
+                // Just add a nicer error message
+                if (option.parameter == null) {
+                    message = tr("{0}: Error while handling option ''{1}''", program, option.optionName);
+                } else {
+                    message = tr("{0}: Invalid value {2} for option ''{1}''", program, option.optionName,
+                            option.parameter);
+                }
+                if (!e.getLocalizedMessage().isEmpty()) {
+                    message += ": " + e.getLocalizedMessage().isEmpty();
+                }
+                throw new OptionParseException(message);
+            }
+        }
+        return remainingArguments;
+    }
+
+    private AvailableOption findParameter(String optionName) {
+        AvailableOption exactMatch = availableOptions.get(optionName);
+        if (exactMatch != null) {
+            return exactMatch;
+        } else if (optionName.startsWith("--")) {
+            List<AvailableOption> alternatives = availableOptions.entrySet().stream()
+                    .filter(entry -> entry.getKey().startsWith(optionName)).map(Entry::getValue).distinct()
+                    .collect(Collectors.toList());
+
+            if (alternatives.size() == 1) {
+                return alternatives.get(0);
+            } else if (alternatives.size() > 1) {
+                throw new OptionParseException(tr("{0}: option ''{1}'' is ambiguous", program));
+            }
+        }
+        throw new OptionParseException(tr("{0}: unrecognized option ''{1}''", program, optionName));
+    }
+
+    /**
+     * How often an option may / must be specified on the command line.
+     * @author Michael Zangl
+     */
+    public enum OptionCount {
+        /**
+         * The option may be specified once
+         */
+        OPTIONAL(0, 1),
+        /**
+         * The option is required exactly once
+         */
+        REQUIRED(1, 1),
+        /**
+         * The option may be specified multiple times
+         */
+        MULTIPLE(0, Integer.MAX_VALUE);
+
+        private int min;
+        private int max;
+
+        OptionCount(int min, int max) {
+            this.min = min;
+            this.max = max;
+
+        }
+    }
+
+    protected abstract class AvailableOption {
+
+        public boolean requiresParameter() {
+            return false;
+        }
+
+        public OptionCount getRequiredCount() {
+            return OptionCount.OPTIONAL;
+        }
+
+        /**
+         * Called once if the parameter is encountered, afer basic validation.
+         * @param parameter The parameter if {@link #requiresParameter()} is true, <code>null</code> otherwise.
+         */
+        public abstract void runFor(String parameter);
+
+    }
+
+    private static class FoundOption {
+        private final String optionName;
+        private final AvailableOption option;
+        private final String parameter;
+
+        FoundOption(String optionName, AvailableOption option, String parameter) {
+            this.optionName = optionName;
+            this.option = option;
+            this.parameter = parameter;
+        }
+    }
+
+    /**
+     * @author Michael Zangl
+     */
+    public static class OptionParseException extends RuntimeException {
+        // Don't rely on JAVA handling this correctly.
+        private final String localizedMessage;
+
+        /**
+         * Create an empty error with no description
+         */
+        public OptionParseException() {
+            super();
+            localizedMessage = "";
+        }
+
+        /**
+         * @param localizedMessage The message to display to the user.
+         */
+        public OptionParseException(String localizedMessage) {
+            super(localizedMessage);
+            this.localizedMessage = localizedMessage;
+        }
+
+        /**
+         * @param localizedMessage The message to display to the user.
+         * @param t The error that caused this message to be displayed.
+         */
+        public OptionParseException(String localizedMessage, Throwable t) {
+            super(localizedMessage, t);
+            this.localizedMessage = localizedMessage;
+        }
+
+        @Override
+        public String getLocalizedMessage() {
+            return localizedMessage;
+        }
+    }
+}
Index: trunk/test/unit/org/openstreetmap/josm/tools/OptionParserTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/tools/OptionParserTest.java	(revision 14415)
+++ trunk/test/unit/org/openstreetmap/josm/tools/OptionParserTest.java	(revision 14415)
@@ -0,0 +1,366 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.tools;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.Test;
+import org.openstreetmap.josm.tools.OptionParser.OptionCount;
+import org.openstreetmap.josm.tools.OptionParser.OptionParseException;
+
+/**
+ * Test for {@link OptionParser}
+ * @author Michael Zangl
+ */
+public class OptionParserTest {
+
+    // A reason for moving to jupter...
+    @Test(expected = OptionParseException.class)
+    public void testEmptyParserRejectsLongopt() {
+        new OptionParser("test").parseOptions(Arrays.asList("--long"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testEmptyParserRejectsShortopt() {
+        new OptionParser("test").parseOptions(Arrays.asList("-s"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testParserRejectsWrongShortopt() {
+        new OptionParser("test").addFlagParameter("test", this::nop).addShortAlias("test", "t")
+                .parseOptions(Arrays.asList("-s"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testParserRejectsWrongLongopt() {
+        new OptionParser("test").addFlagParameter("test", this::nop).parseOptions(Arrays.asList("--wrong"));
+    }
+
+    @Test
+    public void testparserOption() {
+        AtomicReference<String> argFound = new AtomicReference<>();
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set);
+
+        parser.parseOptions(Arrays.asList("--test", "arg"));
+        assertEquals("arg", argFound.get());
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testparserOptionFailsIfMissing() {
+        AtomicReference<String> argFound = new AtomicReference<>();
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set);
+
+        parser.parseOptions(Arrays.asList("--test2", "arg"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testparserOptionFailsIfMissingArgument() {
+        AtomicReference<String> argFound = new AtomicReference<>();
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set);
+
+        parser.parseOptions(Arrays.asList("--test2", "--other"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testparserOptionFailsIfMissing2() {
+        AtomicReference<String> argFound = new AtomicReference<>();
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set);
+
+        parser.parseOptions(Arrays.asList("--", "--test", "arg"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testparserOptionFailsIfTwice() {
+        AtomicReference<String> argFound = new AtomicReference<>();
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set);
+
+        parser.parseOptions(Arrays.asList("--test", "arg", "--test", "arg"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testparserOptionFailsIfTwiceForAlias() {
+        AtomicReference<String> argFound = new AtomicReference<>();
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set)
+                .addShortAlias("test", "t");
+
+        parser.parseOptions(Arrays.asList("--test", "arg", "-t", "arg"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testOptionalOptionFailsIfTwice() {
+        OptionParser parser = new OptionParser("test")
+                .addFlagParameter("test", this::nop);
+        parser.parseOptions(Arrays.asList("--test", "--test"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testOptionalOptionFailsIfTwiceForAlias() {
+        OptionParser parser = new OptionParser("test")
+                .addFlagParameter("test", this::nop)
+                .addShortAlias("test", "t");
+        parser.parseOptions(Arrays.asList("-t", "-t"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testOptionalOptionFailsIfTwiceForAlias2() {
+        OptionParser parser = new OptionParser("test")
+                .addFlagParameter("test", this::nop)
+                .addShortAlias("test", "t");
+        parser.parseOptions(Arrays.asList("-tt"));
+    }
+
+    @Test
+    public void testLongArgumentsUsingEqualSign() {
+        AtomicReference<String> argFound = new AtomicReference<>();
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set);
+
+        List<String> remaining = parser.parseOptions(Arrays.asList("--test=arg", "value"));
+
+        assertEquals(Arrays.asList("value"), remaining);
+        assertEquals("arg", argFound.get());
+
+        remaining = parser.parseOptions(Arrays.asList("--test=", "value"));
+
+        assertEquals(Arrays.asList("value"), remaining);
+        assertEquals("", argFound.get());
+
+        remaining = parser.parseOptions(Arrays.asList("--test=with space and=equals", "value"));
+
+        assertEquals(Arrays.asList("value"), remaining);
+        assertEquals("with space and=equals", argFound.get());
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testLongArgumentsMissingOption() {
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, this::nop);
+
+        parser.parseOptions(Arrays.asList("--test"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testLongArgumentsMissingOption2() {
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, this::nop);
+
+        parser.parseOptions(Arrays.asList("--test", "--", "x"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testShortArgumentsMissingOption() {
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, this::nop)
+                .addShortAlias("test", "t");
+
+        parser.parseOptions(Arrays.asList("-t"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testShortArgumentsMissingOption2() {
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, this::nop)
+                .addShortAlias("test", "t");
+
+        parser.parseOptions(Arrays.asList("-t", "--", "x"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testLongFlagHasOption() {
+        OptionParser parser = new OptionParser("test")
+                .addFlagParameter("test", this::nop);
+
+        parser.parseOptions(Arrays.asList("--test=arg"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testShortFlagHasOption() {
+        OptionParser parser = new OptionParser("test")
+                .addFlagParameter("test", this::nop)
+                .addShortAlias("test", "t");
+
+        parser.parseOptions(Arrays.asList("-t=arg"));
+    }
+
+    @Test
+    public void testShortArgumentsUsingEqualSign() {
+        AtomicReference<String> argFound = new AtomicReference<>();
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set)
+                .addShortAlias("test", "t");
+
+        List<String> remaining = parser.parseOptions(Arrays.asList("-t=arg", "value"));
+
+        assertEquals(Arrays.asList("value"), remaining);
+        assertEquals("arg", argFound.get());
+    }
+
+    @Test
+    public void testMultipleArguments() {
+        AtomicReference<String> argFound = new AtomicReference<>();
+        List<String> multiFound = new ArrayList<>();
+        AtomicBoolean usedFlag = new AtomicBoolean();
+        AtomicBoolean unusedFlag = new AtomicBoolean();
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set)
+                .addShortAlias("test", "t")
+                .addArgumentParameter("multi", OptionCount.MULTIPLE, multiFound::add)
+                .addFlagParameter("flag", () -> usedFlag.set(true))
+                .addFlagParameter("flag2", () -> unusedFlag.set(true));
+
+        List<String> remaining = parser.parseOptions(Arrays.asList(
+                "-t=arg", "--multi", "m1", "x1", "--flag", "--multi", "m2", "x2", "--", "x3", "--x4", "x5"));
+
+        assertEquals(Arrays.asList("x1", "x2", "x3", "--x4", "x5"), remaining);
+        assertEquals("arg", argFound.get());
+        assertEquals(Arrays.asList("m1", "m2"), multiFound);
+        assertEquals(true, usedFlag.get());
+        assertEquals(false, unusedFlag.get());
+    }
+
+    @Test
+    public void testUseAlternatives() {
+        AtomicReference<String> argFound = new AtomicReference<>();
+        AtomicBoolean usedFlag = new AtomicBoolean();
+        AtomicBoolean unusedFlag = new AtomicBoolean();
+
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set)
+                .addFlagParameter("flag", () -> usedFlag.set(true))
+                .addFlagParameter("fleg", () -> unusedFlag.set(true));
+
+        List<String> remaining = parser.parseOptions(Arrays.asList("--te=arg", "--fla"));
+
+        assertEquals(Arrays.asList(), remaining);
+        assertEquals("arg", argFound.get());
+        assertEquals(true, usedFlag.get());
+        assertEquals(false, unusedFlag.get());
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testAbigiousAlternatives() {
+        AtomicReference<String> argFound = new AtomicReference<>();
+        AtomicBoolean usedFlag = new AtomicBoolean();
+        AtomicBoolean unusedFlag = new AtomicBoolean();
+
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set)
+                .addFlagParameter("flag", () -> usedFlag.set(true))
+                .addFlagParameter("fleg", () -> unusedFlag.set(true));
+
+        parser.parseOptions(Arrays.asList("--te=arg", "--fl"));
+    }
+
+    @Test(expected = OptionParseException.class)
+    public void testMultipleShort() {
+        AtomicReference<String> argFound = new AtomicReference<>();
+        AtomicBoolean usedFlag = new AtomicBoolean();
+        AtomicBoolean unusedFlag = new AtomicBoolean();
+
+        OptionParser parser = new OptionParser("test")
+                .addArgumentParameter("test", OptionCount.REQUIRED, argFound::set)
+                .addShortAlias("test", "t")
+                .addFlagParameter("flag", () -> usedFlag.set(true))
+                .addShortAlias("flag", "f")
+                .addFlagParameter("fleg", () -> unusedFlag.set(true));
+
+        List<String> remaining = parser.parseOptions(Arrays.asList("-ft=arg", "x"));
+
+        assertEquals(Arrays.asList("x"), remaining);
+        assertEquals("arg", argFound.get());
+        assertEquals(true, usedFlag.get());
+        assertEquals(false, unusedFlag.get());
+
+        remaining = parser.parseOptions(Arrays.asList("-ft", "arg", "x"));
+
+        assertEquals(Arrays.asList("x"), remaining);
+        assertEquals("arg", argFound.get());
+        assertEquals(true, usedFlag.get());
+        assertEquals(false, unusedFlag.get());
+
+        remaining = parser.parseOptions(Arrays.asList("-f", "-t=arg", "x"));
+
+        assertEquals(Arrays.asList("x"), remaining);
+        assertEquals("arg", argFound.get());
+        assertEquals(true, usedFlag.get());
+        assertEquals(false, unusedFlag.get());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testIllegalOptionName() {
+        new OptionParser("test").addFlagParameter("", this::nop);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testIllegalOptionName2() {
+        new OptionParser("test").addFlagParameter("-", this::nop);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testIllegalOptionName3() {
+        new OptionParser("test").addFlagParameter("-test", this::nop);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testIllegalOptionName4() {
+        new OptionParser("test").addFlagParameter("$", this::nop);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDupplicateOptionName() {
+        new OptionParser("test").addFlagParameter("test", this::nop).addFlagParameter("test", this::nop);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDupplicateOptionName2() {
+        new OptionParser("test").addFlagParameter("test", this::nop)
+            .addArgumentParameter("test", OptionCount.OPTIONAL, this::nop);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidShortAlias() {
+        new OptionParser("test").addFlagParameter("test", this::nop).addShortAlias("test", "$");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidShortAlias2() {
+        new OptionParser("test").addFlagParameter("test", this::nop).addShortAlias("test", "");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidShortAlias3() {
+        new OptionParser("test").addFlagParameter("test", this::nop).addShortAlias("test", "xx");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDupplicateShortAlias() {
+        new OptionParser("test").addFlagParameter("test", this::nop)
+        .addFlagParameter("test2", this::nop)
+        .addShortAlias("test", "t")
+        .addShortAlias("test2", "t");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidShortNoLong() {
+        new OptionParser("test").addFlagParameter("test", this::nop).addShortAlias("test2", "t");
+    }
+
+    private void nop() {
+        // nop
+    }
+
+    private void nop(String arg) {
+        // nop
+    }
+}
