OptionParser.java revision 31acbb7dcb87286cdd93b2327b3a7def569ad389
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 2108918b5cafd482363a48e0bc9ae0114028cda7e79jsharpe@google.com public 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(); 39100d4ce596c71916d975f6c8d967af53ab0a8f453jsharpe@google.com final boolean savedInTag = option.savedInTag(); 3927850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com if (names.length == 0) { 3937850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com throw new RuntimeException("found an @Option with no name!"); 3947850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 3957850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com for (String name : names) { 3967850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com if (optionMap.put(name, field) != null) { 3977850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com throw new RuntimeException("found multiple @Options sharing the name '" + name + "'"); 3987850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 39900d4ce596c71916d975f6c8d967af53ab0a8f453jsharpe@google.com if (!savedInTag) { 40000d4ce596c71916d975f6c8d967af53ab0a8f453jsharpe@google.com int numArgs = getHandler(field.getGenericType()).isBoolean() ? 0 : 1; 40100d4ce596c71916d975f6c8d967af53ab0a8f453jsharpe@google.com Tag.addUnsavedOption(name, numArgs); 40200d4ce596c71916d975f6c8d967af53ab0a8f453jsharpe@google.com } 4037850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4047850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com if (getHandler(field.getGenericType()) == null) { 4057850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com throw new RuntimeException("unsupported @Option field type '" + field.getType() + "'"); 4067850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4077850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4087850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4097850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return optionMap; 4107850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4117850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 4127850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com static abstract class Handler { 4137850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com // Only BooleanHandler should ever override this. 4147850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com boolean isBoolean() { 4157850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return false; 4167850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4177850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 4187850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com /** 4197850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Returns an object of appropriate type for the given Handle, corresponding to 'valueText'. 4207850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Returns null on failure. 4217850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */ 4227850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com abstract Object translate(String valueText); 4237850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4247850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 4257850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com static class BooleanHandler extends Handler { 4267850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com @Override boolean isBoolean() { 4277850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return true; 4287850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4297850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 4307850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com Object translate(String valueText) { 4317850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com if (valueText.equalsIgnoreCase("true") || valueText.equalsIgnoreCase("yes")) { 4327850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return Boolean.TRUE; 4337850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } else if (valueText.equalsIgnoreCase("false") || valueText.equalsIgnoreCase("no")) { 4347850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return Boolean.FALSE; 4357850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4367850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return null; 4377850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4387850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4397850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 4407850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com static class ByteHandler extends Handler { 4417850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com Object translate(String valueText) { 4427850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com try { 4437850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return Byte.parseByte(valueText); 4447850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } catch (NumberFormatException ex) { 4457850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return null; 4467850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4477850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4487850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4497850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 4507850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com static class ShortHandler extends Handler { 4517850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com Object translate(String valueText) { 4527850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com try { 4537850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return Short.parseShort(valueText); 4547850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } catch (NumberFormatException ex) { 4557850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return null; 4567850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4577850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4587850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4597850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 4607850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com static class IntegerHandler extends Handler { 4617850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com Object translate(String valueText) { 4627850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com try { 4637850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return Integer.parseInt(valueText); 4647850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } catch (NumberFormatException ex) { 4657850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return null; 4667850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4677850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4687850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4697850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 4707850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com static class LongHandler extends Handler { 4717850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com Object translate(String valueText) { 4727850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com try { 4737850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return Long.parseLong(valueText); 4747850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } catch (NumberFormatException ex) { 4757850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return null; 4767850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4777850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4787850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4797850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 4807850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com static class FloatHandler extends Handler { 4817850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com Object translate(String valueText) { 4827850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com try { 4837850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return Float.parseFloat(valueText); 4847850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } catch (NumberFormatException ex) { 4857850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return null; 4867850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4877850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4887850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4897850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 4907850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com static class DoubleHandler extends Handler { 4917850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com Object translate(String valueText) { 4927850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com try { 4937850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return Double.parseDouble(valueText); 4947850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } catch (NumberFormatException ex) { 4957850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return null; 4967850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4977850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4987850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 4997850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 5007850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com static class StringHandler extends Handler { 5017850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com Object translate(String valueText) { 5027850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com return valueText; 5037850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 5047850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 5057850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 506d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com @SuppressWarnings("unchecked") // creating an instance with a non-enum type is an error! 507d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com static class EnumHandler extends Handler { 508d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com private final Class<?> enumType; 509d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com 510d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com public EnumHandler(Class<?> enumType) { 511d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com this.enumType = enumType; 512d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com } 513d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com 514d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com Object translate(String valueText) { 51531acbb7dcb87286cdd93b2327b3a7def569ad389bdc@google.com try { 51631acbb7dcb87286cdd93b2327b3a7def569ad389bdc@google.com return Enum.valueOf((Class) enumType, valueText.toUpperCase()); 51731acbb7dcb87286cdd93b2327b3a7def569ad389bdc@google.com } catch (IllegalArgumentException e) { 51831acbb7dcb87286cdd93b2327b3a7def569ad389bdc@google.com return null; 51931acbb7dcb87286cdd93b2327b3a7def569ad389bdc@google.com } 520d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com } 521d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com } 522d0944e3ecda89a97ac35537e280b2776b53c25ecjessewilson@google.com 5237850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com static class FileHandler extends Handler { 5247850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com Object translate(String valueText) { 52580c3573dc1084dc6c81c3dc7bf537c1f0893b288jessewilson@google.com return new File(valueText).getAbsoluteFile(); 5267850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 5277850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com } 5287850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com} 529