17850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com/*
27850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Copyright (C) 2010 The Android Open Source Project
37850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
47850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Licensed under the Apache License, Version 2.0 (the "License");
57850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * you may not use this file except in compliance with the License.
67850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * You may obtain a copy of the License at
77850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
87850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *      http://www.apache.org/licenses/LICENSE-2.0
97850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
107850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Unless required by applicable law or agreed to in writing, software
117850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * distributed under the License is distributed on an "AS IS" BASIS,
127850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
137850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * See the License for the specific language governing permissions and
147850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * limitations under the License.
157850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */
167850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
177850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.compackage vogar;
187850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
198918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.comimport com.google.common.collect.Lists;
207850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.io.File;
218918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.comimport java.io.IOException;
227850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.lang.reflect.Field;
237850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.lang.reflect.ParameterizedType;
247850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.lang.reflect.Type;
257850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.util.ArrayList;
267850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.util.Arrays;
277850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.util.Collection;
287850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.util.HashMap;
297850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.util.Iterator;
307850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.util.List;
314cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.comimport java.util.Map;
32969d5622a380e2f2f9ebdfbf7a22cbb3e031125bjessewilson@google.comimport vogar.util.Strings;
337850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
347850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com/**
357850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Parses command line options.
367850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
377850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Strings in the passed-in String[] are parsed left-to-right. Each
387850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * String is classified as a short option (such as "-v"), a long
397850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * option (such as "--verbose"), an argument to an option (such as
407850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * "out.txt" in "-f out.txt"), or a non-option positional argument.
417850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
427850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * A simple short option is a "-" followed by a short option
437850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * character. If the option requires an argument (which is true of any
447850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * non-boolean option), it may be written as a separate parameter, but
457850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * need not be. That is, "-f out.txt" and "-fout.txt" are both
467850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * acceptable.
477850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
487850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * It is possible to specify multiple short options after a single "-"
497850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * as long as all (except possibly the last) do not require arguments.
507850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
517850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * A long option begins with "--" followed by several characters. If
527850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * the option requires an argument, it may be written directly after
537850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * the option name, separated by "=", or as the next argument. (That
547850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * is, "--file=out.txt" or "--file out.txt".)
557850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
567850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * A boolean long option '--name' automatically gets a '--no-name'
577850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * companion. Given an option "--flag", then, "--flag", "--no-flag",
587850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * "--flag=true" and "--flag=false" are all valid, though neither
597850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * "--flag true" nor "--flag false" are allowed (since "--flag" by
607850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * itself is sufficient, the following "true" or "false" is
617850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * interpreted separately). You can use "yes" and "no" as synonyms for
627850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * "true" and "false".
637850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
647850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Each String not starting with a "-" and not a required argument of
657850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * a previous option is a non-option positional argument, as are all
667850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * successive Strings. Each String after a "--" is a non-option
677850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * positional argument.
687850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
697850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Parsing of numeric fields such byte, short, int, long, float, and
707850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * double fields is supported. This includes both unboxed and boxed
717850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * versions (e.g. int vs Integer). If there is a problem parsing the
727850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * argument to match the desired type, a runtime exception is thrown.
737850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
747850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * File option fields are supported by simply wrapping the string
757850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * argument in a File object without testing for the existance of the
767850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * file.
777850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
787850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Parameterized Collection fields such as List<File> and Set<String>
797850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * are supported as long as the parameter type is otherwise supported
807850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * by the option parser. The collection field should be initialized
817850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * with an appropriate collection instance.
827850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
832bd3a577a29660020d11bef1cefb1057fccec4fajessewilson@google.com * Enum types are supported. Input may be in either CONSTANT_CASE or
842bd3a577a29660020d11bef1cefb1057fccec4fajessewilson@google.com * lower_case.
8531acbb7dcb87286cdd93b2327b3a7def569ad389bdc@google.com *
867850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * The fields corresponding to options are updated as their options
877850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * are processed. Any remaining positional arguments are returned as a
887850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * List<String>.
897850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
907850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Here's a simple example:
917850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
927850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * // This doesn't need to be a separate class, if your application doesn't warrant it.
937850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * // Non-@Option fields will be ignored.
947850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * class Options {
957850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     @Option(names = { "-q", "--quiet" })
967850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     boolean quiet = false;
977850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
987850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     // Boolean options require a long name if it's to be possible to explicitly turn them off.
997850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     // Here the user can use --no-color.
1007850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     @Option(names = { "--color" })
1017850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     boolean color = true;
1027850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
1037850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     @Option(names = { "-m", "--mode" })
1047850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     String mode = "standard; // Supply a default just by setting the field.
1057850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
1067850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     @Option(names = { "-p", "--port" })
1077850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     int portNumber = 8888;
1087850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
1097850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     // There's no need to offer a short name for rarely-used options.
1107850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     @Option(names = { "--timeout" })
1117850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     double timeout = 1.0;
1127850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
1137850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     @Option(names = { "-o", "--output-file" })
1147850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     File output;
1157850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
1167850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     // Multiple options are added to the collection.
1177850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     // The collection field itself must be non-null.
1187850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     @Option(names = { "-i", "--input-file" })
1197850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     List<File> inputs = new ArrayList<File>();
1207850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
1217850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * }
1227850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
1237850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * class Main {
1247850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     public static void main(String[] args) {
1257850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *         Options options = new Options();
1267850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *         List<String> inputFilenames = new OptionParser(options).parse(args);
1277850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *         for (String inputFilename : inputFilenames) {
1287850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *             if (!options.quiet) {
1297850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *                 ...
1307850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *             }
1317850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *             ...
1327850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *         }
1337850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *     }
1347850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * }
1357850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
1367850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * See also:
1377850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
1387850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *  the getopt(1) man page
1397850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *  Python's "optparse" module (http://docs.python.org/library/optparse.html)
1407850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *  the POSIX "Utility Syntax Guidelines" (http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap12.html#tag_12_02)
1417850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *  the GNU "Standards for Command Line Interfaces" (http://www.gnu.org/prep/standards/standards.html#Command_002dLine-Interfaces)
1427850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */
1437850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.compublic class OptionParser {
1447850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    private static final HashMap<Class<?>, Handler> handlers = new HashMap<Class<?>, Handler>();
1457850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    static {
1467850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(boolean.class, new BooleanHandler());
1477850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(Boolean.class, new BooleanHandler());
1487850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
1497850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(byte.class, new ByteHandler());
1507850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(Byte.class, new ByteHandler());
1517850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(short.class, new ShortHandler());
1527850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(Short.class, new ShortHandler());
1537850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(int.class, new IntegerHandler());
1547850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(Integer.class, new IntegerHandler());
1557850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(long.class, new LongHandler());
1567850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(Long.class, new LongHandler());
1577850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
1587850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(float.class, new FloatHandler());
1597850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(Float.class, new FloatHandler());
1607850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(double.class, new DoubleHandler());
1617850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(Double.class, new DoubleHandler());
1627850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
1637850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(String.class, new StringHandler());
1647850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        handlers.put(File.class, new FileHandler());
1657850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
1667850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    Handler getHandler(Type type) {
1677850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        if (type instanceof ParameterizedType) {
1687850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            ParameterizedType parameterizedType = (ParameterizedType) type;
1697850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            Class rawClass = (Class<?>) parameterizedType.getRawType();
1707850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            if (!Collection.class.isAssignableFrom(rawClass)) {
1717850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                throw new RuntimeException("cannot handle non-collection parameterized type " + type);
1727850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
1737850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            Type actualType = parameterizedType.getActualTypeArguments()[0];
1747850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            if (!(actualType instanceof Class)) {
1757850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                throw new RuntimeException("cannot handle nested parameterized type " + type);
1767850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
1777850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            return getHandler(actualType);
1787850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
1797850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        if (type instanceof Class) {
180d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com            Class<?> classType = (Class) type;
181d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com            if (Collection.class.isAssignableFrom(classType)) {
1827850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                // could handle by just having a default of treating
1837850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                // contents as String but consciously decided this
1847850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                // should be an error
1857850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                throw new RuntimeException(
1867850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                        "cannot handle non-parameterized collection " + type + ". " +
1877850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                        "use a generic Collection to specify a desired element type");
1887850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
189d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com            if (classType.isEnum()) {
190d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com                return new EnumHandler(classType);
191d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com            }
192d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com            return handlers.get(classType);
1937850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
1947850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        throw new RuntimeException("cannot handle unknown field type " + type);
1957850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
1967850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
1977850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    private final Object optionSource;
1987850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    private final HashMap<String, Field> optionMap;
1994cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com    private final Map<Field, Object> defaultOptionMap;
2007850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
2017850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    /**
2027850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     * Constructs a new OptionParser for setting the @Option fields of 'optionSource'.
2037850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     */
2047850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    public OptionParser(Object optionSource) {
2057850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        this.optionSource = optionSource;
2067850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        this.optionMap = makeOptionMap();
2074cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com        this.defaultOptionMap = new HashMap<Field, Object>();
2087850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
2097850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
210c7b173425beec5784c669388345eb3b7b96fc341enh@google.com    public static String[] readFile(File configFile) {
21131acbb7dcb87286cdd93b2327b3a7def569ad389bdc@google.com        if (!configFile.exists()) {
2128918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            return new String[0];
2138918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com        }
2148918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com
2158918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com        List<String> configFileLines;
2168918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com        try {
2178918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            configFileLines = Strings.readFileLines(configFile);
2188918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com        } catch (IOException e) {
2198918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            throw new RuntimeException(e);
2208918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com        }
2218918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com
2228918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com        List<String> argsList = Lists.newArrayList();
2238918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com        for (String rawLine : configFileLines) {
2244ac80fd21b440a8e9debe995e73c71e0fed8735cjsharpe@google.com            String line = rawLine.trim();
2258918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com
2268918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            // allow comments and blank lines
2278918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            if (line.startsWith("#") || line.isEmpty()) {
2288918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com                continue;
2298918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            }
2308918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            int space = line.indexOf(' ');
2318918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            if (space == -1) {
2328918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com                argsList.add(line);
2338918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            } else {
2348918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com                argsList.add(line.substring(0, space));
2358918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com                argsList.add(line.substring(space + 1).trim());
2368918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com            }
2378918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com        }
2388918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com
2398918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com        return argsList.toArray(new String[argsList.size()]);
2408918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com    }
2418918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com
2427850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    /**
2437850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     * Parses the command-line arguments 'args', setting the @Option fields of the 'optionSource' provided to the constructor.
2447850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     * Returns a list of the positional arguments left over after processing all options.
2457850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com     */
2467850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    public List<String> parse(String[] args) {
2477850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        return parseOptions(Arrays.asList(args).iterator());
2487850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
2497850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
2507850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    private List<String> parseOptions(Iterator<String> args) {
2517850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        final List<String> leftovers = new ArrayList<String>();
2527850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
2537850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        // Scan 'args'.
2547850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        while (args.hasNext()) {
2557850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            final String arg = args.next();
2567850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            if (arg.equals("--")) {
2577850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                // "--" marks the end of options and the beginning of positional arguments.
2587850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                break;
2597850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            } else if (arg.startsWith("--")) {
2607850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                // A long option.
2617850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                parseLongOption(arg, args);
2627850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            } else if (arg.startsWith("-")) {
2637850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                // A short option.
2647850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                parseGroupedShortOptions(arg, args);
2657850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            } else {
2667850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                // The first non-option marks the end of options.
2677850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                leftovers.add(arg);
2687850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                break;
2697850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
2707850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
2717850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
2727850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        // Package up the leftovers.
2737850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        while (args.hasNext()) {
2747850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            leftovers.add(args.next());
2757850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
2767850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        return leftovers;
2777850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
2787850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
2797850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    private Field fieldForArg(String name) {
2807850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        final Field field = optionMap.get(name);
2817850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        if (field == null) {
2827850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            throw new RuntimeException("unrecognized option '" + name + "'");
2837850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
2847850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        return field;
2857850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
2867850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
2877850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    private void parseLongOption(String arg, Iterator<String> args) {
2887850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        String name = arg.replaceFirst("^--no-", "--");
2897850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        String value = null;
2907850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
2917850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        // Support "--name=value" as well as "--name value".
2927850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        final int equalsIndex = name.indexOf('=');
2937850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        if (equalsIndex != -1) {
2947850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            value = name.substring(equalsIndex + 1);
2957850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            name = name.substring(0, equalsIndex);
2967850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
2977850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
2987850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        final Field field = fieldForArg(name);
2997850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        final Handler handler = getHandler(field.getGenericType());
3007850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        if (value == null) {
3017850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            if (handler.isBoolean()) {
3027850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                value = arg.startsWith("--no-") ? "false" : "true";
3037850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            } else {
3047850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                value = grabNextValue(args, name, field);
3057850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
3067850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
3074cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com        setValue(field, arg, handler, value);
3087850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
3097850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3107850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    // Given boolean options a and b, and non-boolean option f, we want to allow:
3117850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    // -ab
3127850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    // -abf out.txt
3137850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    // -abfout.txt
3147850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    // (But not -abf=out.txt --- POSIX doesn't mention that either way, but GNU expressly forbids it.)
3157850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    private void parseGroupedShortOptions(String arg, Iterator<String> args) {
3167850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        for (int i = 1; i < arg.length(); ++i) {
3177850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            final String name = "-" + arg.charAt(i);
3187850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            final Field field = fieldForArg(name);
3197850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            final Handler handler = getHandler(field.getGenericType());
3207850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            String value;
3217850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            if (handler.isBoolean()) {
3227850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                value = "true";
3237850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            } else {
3247850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                // We need a value. If there's anything left, we take the rest of this "short option".
3257850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                if (i + 1 < arg.length()) {
3267850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                    value = arg.substring(i + 1);
3277850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                    i = arg.length() - 1;
3287850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                } else {
3297850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                    value = grabNextValue(args, name, field);
3307850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                }
3317850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
3324cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com            setValue(field, arg, handler, value);
3337850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
3347850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
3357850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3367850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    @SuppressWarnings("unchecked")
3374cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com    private void setValue(Field field, String arg, Handler handler, String valueText) {
3387850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3397850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        Object value = handler.translate(valueText);
3407850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        if (value == null) {
3417850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            final String type = field.getType().getSimpleName().toLowerCase();
3427850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            throw new RuntimeException("couldn't convert '" + valueText + "' to a " + type + " for option '" + arg + "'");
3437850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
3447850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        try {
3457850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            field.setAccessible(true);
3464cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com            // record the original value of the field so it can be reset
3474cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com            if (!defaultOptionMap.containsKey(field)) {
3484cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com                defaultOptionMap.put(field, field.get(optionSource));
3494cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com            }
3507850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            if (Collection.class.isAssignableFrom(field.getType())) {
3514cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com                Collection collection = (Collection) field.get(optionSource);
3527850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                collection.add(value);
3537850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            } else {
3544cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com                field.set(optionSource, value);
3557850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
3567850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        } catch (IllegalAccessException ex) {
3577850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            throw new RuntimeException("internal error", ex);
3587850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
3597850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
3607850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3614cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com    /**
3624cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com     * Resets optionSource's fields to their defaults
3634cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com     */
3644cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com    public void reset() {
3654cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com        for (Map.Entry<Field, Object> entry : defaultOptionMap.entrySet()) {
3664cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com            try {
3674cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com                entry.getKey().set(optionSource, entry.getValue());
3684cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com            } catch (IllegalAccessException e) {
3694cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com                throw new RuntimeException(e);
3704cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com            }
3714cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com        }
3724cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com    }
3734cb68042e7513b3f9444a17d91eb0d92480a74b4jsharpe@google.com
3747850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    // Returns the next element of 'args' if there is one. Uses 'name' and 'field' to construct a helpful error message.
3757850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    private String grabNextValue(Iterator<String> args, String name, Field field) {
3767850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        if (!args.hasNext()) {
3777850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            final String type = field.getType().getSimpleName().toLowerCase();
3787850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            throw new RuntimeException("option '" + name + "' requires a " + type + " argument");
3797850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
3807850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        return args.next();
3817850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
3827850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
3837850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    // Cache the available options and report any problems with the options themselves right away.
3847850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    private HashMap<String, Field> makeOptionMap() {
3857850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        final HashMap<String, Field> optionMap = new HashMap<String, Field>();
3867850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        final Class<?> optionClass = optionSource.getClass();
3877850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        for (Field field : optionClass.getDeclaredFields()) {
3887850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            if (field.isAnnotationPresent(Option.class)) {
3897850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                final Option option = field.getAnnotation(Option.class);
3907850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                final String[] names = option.names();
3917850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                if (names.length == 0) {
3927850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                    throw new RuntimeException("found an @Option with no name!");
3937850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                }
3947850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                for (String name : names) {
3957850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                    if (optionMap.put(name, field) != null) {
3967850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                        throw new RuntimeException("found multiple @Options sharing the name '" + name + "'");
3977850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                    }
3987850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                }
3997850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                if (getHandler(field.getGenericType()) == null) {
4007850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                    throw new RuntimeException("unsupported @Option field type '" + field.getType() + "'");
4017850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                }
4027850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
4037850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
4047850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        return optionMap;
4057850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
4067850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
4077850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    static abstract class Handler {
4087850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        // Only BooleanHandler should ever override this.
4097850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        boolean isBoolean() {
4107850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            return false;
4117850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
4127850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
4137850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        /**
4147850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com         * Returns an object of appropriate type for the given Handle, corresponding to 'valueText'.
4157850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com         * Returns null on failure.
4167850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com         */
4177850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        abstract Object translate(String valueText);
4187850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
4197850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
4207850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    static class BooleanHandler extends Handler {
4217850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        @Override boolean isBoolean() {
4227850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            return true;
4237850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
4247850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
4257850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        Object translate(String valueText) {
4267850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            if (valueText.equalsIgnoreCase("true") || valueText.equalsIgnoreCase("yes")) {
4277850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return Boolean.TRUE;
4287850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            } else if (valueText.equalsIgnoreCase("false") || valueText.equalsIgnoreCase("no")) {
4297850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return Boolean.FALSE;
4307850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
4317850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            return null;
4327850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
4337850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
4347850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
4357850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    static class ByteHandler extends Handler {
4367850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        Object translate(String valueText) {
4377850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            try {
4387850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return Byte.parseByte(valueText);
4397850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            } catch (NumberFormatException ex) {
4407850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return null;
4417850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
4427850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
4437850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
4447850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
4457850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    static class ShortHandler extends Handler {
4467850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        Object translate(String valueText) {
4477850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            try {
4487850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return Short.parseShort(valueText);
4497850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            } catch (NumberFormatException ex) {
4507850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return null;
4517850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
4527850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
4537850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
4547850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
4557850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    static class IntegerHandler extends Handler {
4567850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        Object translate(String valueText) {
4577850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            try {
4587850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return Integer.parseInt(valueText);
4597850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            } catch (NumberFormatException ex) {
4607850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return null;
4617850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
4627850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
4637850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
4647850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
4657850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    static class LongHandler extends Handler {
4667850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        Object translate(String valueText) {
4677850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            try {
4687850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return Long.parseLong(valueText);
4697850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            } catch (NumberFormatException ex) {
4707850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return null;
4717850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
4727850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
4737850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
4747850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
4757850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    static class FloatHandler extends Handler {
4767850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        Object translate(String valueText) {
4777850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            try {
4787850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return Float.parseFloat(valueText);
4797850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            } catch (NumberFormatException ex) {
4807850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return null;
4817850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
4827850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
4837850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
4847850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
4857850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    static class DoubleHandler extends Handler {
4867850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        Object translate(String valueText) {
4877850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            try {
4887850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return Double.parseDouble(valueText);
4897850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            } catch (NumberFormatException ex) {
4907850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com                return null;
4917850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            }
4927850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
4937850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
4947850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
4957850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    static class StringHandler extends Handler {
4967850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        Object translate(String valueText) {
4977850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com            return valueText;
4987850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
4997850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
5007850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
501d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com    @SuppressWarnings("unchecked") // creating an instance with a non-enum type is an error!
502d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com    static class EnumHandler extends Handler {
503d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com        private final Class<?> enumType;
504d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com
505d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com        public EnumHandler(Class<?> enumType) {
506d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com            this.enumType = enumType;
507d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com        }
508d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com
509d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com        Object translate(String valueText) {
51031acbb7dcb87286cdd93b2327b3a7def569ad389bdc@google.com            try {
51131acbb7dcb87286cdd93b2327b3a7def569ad389bdc@google.com                return Enum.valueOf((Class) enumType, valueText.toUpperCase());
51231acbb7dcb87286cdd93b2327b3a7def569ad389bdc@google.com            } catch (IllegalArgumentException e) {
51331acbb7dcb87286cdd93b2327b3a7def569ad389bdc@google.com                return null;
51431acbb7dcb87286cdd93b2327b3a7def569ad389bdc@google.com            }
515d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com        }
516d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com    }
517d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com
5187850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    static class FileHandler extends Handler {
5197850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        Object translate(String valueText) {
52080c3573dc1084dc6c81c3dc7bf537c1f0893b288jessewilson@google.com            return new File(valueText).getAbsoluteFile();
5217850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com        }
5227850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com    }
5237850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com}
524