/* * Arguments.java * * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard * * This file is part of BEAST. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership and licensing. * * BEAST is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * BEAST is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with BEAST; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ package dr.app.util; import java.util.StringTokenizer; public class Arguments { public static final String ARGUMENT_CHARACTER = "-"; public static class ArgumentException extends Exception { /** * */ private static final long serialVersionUID = -3229759954341228233L; public ArgumentException() { super(); } public ArgumentException(String message) { super(message); } } public static class Option { public Option(String label, String description) { this.label = label; this.description = description; } String label; String description; boolean isAvailable = false; } public static class StringOption extends Option { /** * @param label Option name: * @param tag Descriptive name of option argument. * Example - tag "file-name" will show '-save <file-name>' in the usage. * @param description */ public StringOption(String label, String tag, String description) { super(label, description); this.tag = tag; } public StringOption(String label, String[] options, boolean caseSensitive, String description) { super(label, description); this.options = options; this.caseSensitive = caseSensitive; } String[] options = null; String tag = null; boolean caseSensitive = false; String value = null; } public static class IntegerOption extends Option { public IntegerOption(String label, String description) { super(label, description); } public IntegerOption(String label, int minValue, int maxValue, String description) { super(label, description); this.minValue = minValue; this.maxValue = maxValue; } int minValue = Integer.MIN_VALUE; int maxValue = Integer.MAX_VALUE; int value = 0; } public static class IntegerArrayOption extends IntegerOption { public IntegerArrayOption(String label, String description) { this(label, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, description); } public IntegerArrayOption(String label, int count, String description) { this(label, count, Integer.MIN_VALUE, Integer.MAX_VALUE, description); } public IntegerArrayOption(String label, int minValue, int maxValue, String description) { this(label, 0, minValue, maxValue, description); } public IntegerArrayOption(String label, int count, int minValue, int maxValue, String description) { super(label, minValue, maxValue, description); this.count = count; } int count; int[] values = null; } public static class LongOption extends Option { public LongOption(String label, String description) { super(label, description); } public LongOption(String label, long minValue, long maxValue, String description) { super(label, description); this.minValue = minValue; this.maxValue = maxValue; } long minValue = Long.MIN_VALUE; long maxValue = Long.MAX_VALUE; long value = 0; } public static class RealOption extends Option { public RealOption(String label, String description) { super(label, description); } public RealOption(String label, double minValue, double maxValue, String description) { super(label, description); this.minValue = minValue; this.maxValue = maxValue; } double minValue = Double.NEGATIVE_INFINITY; double maxValue = Double.POSITIVE_INFINITY; double value = 0; } public static class RealArrayOption extends RealOption { // public RealArrayOption(String label, String description) { // this(label, 0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, description); // } // A count of -1 means any length public RealArrayOption(String label, int count, String description) { this(label, count, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, description); } // public RealArrayOption(String label, double minValue, double maxValue, String description) { // this(label, 0, minValue, maxValue, description); // } public RealArrayOption(String label, int count, double minValue, double maxValue, String description) { super(label, minValue, maxValue, description); this.count = count; } private int count; double[] values = null; } /** * Parse a list of arguments ready for accessing */ public Arguments(Option[] options) { this.options = options; } public Arguments(Option[] options, boolean caseSensitive) { this.options = options; this.caseSensitive = caseSensitive; } /** * Parse a list of arguments ready for accessing */ public int parseArguments(String[] arguments) throws ArgumentException { int[] optionIndex = new int[arguments.length]; for (int i = 0; i < optionIndex.length; i++) { optionIndex[i] = -1; } for (int i = 0; i < options.length; i++) { Option option = options[i]; int index = findArgument(arguments, option.label); if (index != -1) { if (optionIndex[index] != -1) { throw new ArgumentException("Argument, " + arguments[index] + " overlaps with another argument"); } // the first value may be appended to the option label (e.g., '-t1.0'): String arg = arguments[index].substring(option.label.length() + 1); optionIndex[index] = i; option.isAvailable = true; if (option instanceof IntegerArrayOption) { IntegerArrayOption o = (IntegerArrayOption) option; o.values = new int[o.count]; int k = index; int j = 0; while (j < o.count) { if (arg.length() > 0) { StringTokenizer tokenizer = new StringTokenizer(arg, ",\t "); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.length() > 0) { try { o.values[j] = Integer.parseInt(token); } catch (NumberFormatException nfe) { throw new ArgumentException("Argument, " + arguments[index] + " has a bad integer value: " + token); } if (o.values[j] > o.maxValue || o.values[j] < o.minValue) { throw new ArgumentException("Argument, " + arguments[index] + " has a bad integer value: " + token); } j++; } } } k++; if (j < o.count) { if (k >= arguments.length) { throw new ArgumentException("Argument, " + arguments[index] + " is missing one or more values: expecting " + o.count + " integers"); } if (optionIndex[k] != -1) { throw new ArgumentException("Argument, " + arguments[index] + " overlaps with another argument"); } arg = arguments[k]; optionIndex[k] = i; } } } else if (option instanceof IntegerOption) { IntegerOption o = (IntegerOption) option; if (arg.length() == 0) { int k = index + 1; if (k >= arguments.length) { throw new ArgumentException("Argument, " + arguments[index] + " is missing its value: expecting an integer"); } if (optionIndex[k] != -1) { throw new ArgumentException("Argument, " + arguments[index] + " overlaps with another argument"); } arg = arguments[k]; optionIndex[k] = i; } try { o.value = Integer.parseInt(arg); } catch (NumberFormatException nfe) { throw new ArgumentException("Argument, " + arguments[index] + " has a bad integer value: " + arg); } if (o.value > o.maxValue || o.value < o.minValue) { throw new ArgumentException("Argument, " + arguments[index] + " has a bad integer value: " + arg); } } else if (option instanceof LongOption) { LongOption o = (LongOption) option; if (arg.length() == 0) { int k = index + 1; if (k >= arguments.length) { throw new ArgumentException("Argument, " + arguments[index] + " is missing its value: expecting a long integer"); } if (optionIndex[k] != -1) { throw new ArgumentException("Argument, " + arguments[index] + " overlaps with another argument"); } arg = arguments[k]; optionIndex[k] = i; } try { o.value = Long.parseLong(arg); } catch (NumberFormatException nfe) { throw new ArgumentException("Argument, " + arguments[index] + " has a bad integer value: " + arg); } if (o.value > o.maxValue || o.value < o.minValue) { throw new ArgumentException("Argument, " + arguments[index] + " has a bad long integer value: " + arg); } } else if (option instanceof RealArrayOption) { // I fixed only the real case to handle a variable sized array // I don't have the time to figure out the right way, so I duplicated some code so // that I do not break code by mistake RealArrayOption o = (RealArrayOption) option; if (o.count >= 0) { final int count = o.count; o.values = new double[count]; int k = index; int j = 0; while (j < count) { if (arg.length() > 0) { StringTokenizer tokenizer = new StringTokenizer(arg, ",\t "); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.length() > 0) { try { o.values[j] = Double.parseDouble(token); } catch (NumberFormatException nfe) { throw new ArgumentException("Argument, " + arguments[index] + " has a bad real value: " + token); } if (o.values[j] > o.maxValue || o.values[j] < o.minValue) { throw new ArgumentException("Argument, " + arguments[index] + " has a bad real value: " + token); } j++; } } } k++; if (j < count) { if (k >= arguments.length) { throw new ArgumentException("Argument, " + arguments[index] + " is missing one or more values: expecting " + count + " integers"); } if (optionIndex[k] != -1) { throw new ArgumentException("Argument, " + arguments[index] + " overlaps with another argument"); } arg = arguments[k]; optionIndex[k] = i; } } } else { double[] values = new double[100]; index += 1; arg = arguments[index]; optionIndex[index] = i; int j = 0; if (arg.length() > 0) { StringTokenizer tokenizer = new StringTokenizer(arg, ",\t "); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.length() > 0) { try { values[j] = Double.parseDouble(token); } catch (NumberFormatException nfe) { throw new ArgumentException("Argument, " + arguments[index] + " has a bad real value: " + token); } if (values[j] > o.maxValue || values[j] < o.minValue) { throw new ArgumentException("Argument, " + arguments[index] + " has a bad real value: " + token); } j++; } } } o.values = new double[j]; System.arraycopy(values, 0, o.values, 0, j); } } else if (option instanceof RealOption) { RealOption o = (RealOption) option; if (arg.length() == 0) { int k = index + 1; if (k >= arguments.length) { throw new ArgumentException("Argument, " + arguments[index] + " is missing its value: expecting a real number"); } if (optionIndex[k] != -1) { throw new ArgumentException("Argument, " + arguments[index] + " overlaps with another argument"); } arg = arguments[k]; optionIndex[k] = i; } try { o.value = Double.parseDouble(arg); } catch (NumberFormatException nfe) { throw new ArgumentException("Argument, " + arguments[index] + " has a bad real value: " + arg); } if (o.value > o.maxValue || o.value < o.minValue) { throw new ArgumentException("Argument, " + arguments[index] + " has a bad real value: " + arg); } } else if (option instanceof StringOption) { StringOption o = (StringOption) option; if (arg.length() == 0) { int k = index + 1; if (k >= arguments.length) { throw new ArgumentException("Argument, " + arguments[index] + " is missing its value: expecting a string"); } if (optionIndex[k] != -1) { throw new ArgumentException("Argument, " + arguments[index] + " overlaps with another argument"); } arg = arguments[k]; optionIndex[k] = i; } o.value = arg; if (o.options != null) { boolean found = false; for (String option1 : o.options) { if ((!caseSensitive && option1.equalsIgnoreCase(o.value)) || option1.equals(o.value)) { found = true; break; } } if (!found) { throw new ArgumentException("Argument, " + arguments[index] + " has a bad string value: " + arg); } } } else { // is simply an Option - nothing to do... } } } int n = 0; int i = arguments.length - 1; while (i >= 0 && optionIndex[i] == -1 && !arguments[i].startsWith(ARGUMENT_CHARACTER)) { n++; i--; } leftoverArguments = new String[n]; for (i = 0; i < n; i++) { leftoverArguments[i] = arguments[arguments.length - n + i]; } for (i = 0; i < arguments.length - n; i++) { if (optionIndex[i] == -1) { throw new ArgumentException("Unrecognized argument: " + arguments[i]); } } return n; } private int findArgument(String[] arguments, String label) { for (int i = 0; i < arguments.length; i++) { if (arguments[i].length() - 1 >= label.length()) { if (arguments[i].startsWith(ARGUMENT_CHARACTER)) { // String l = arguments[i].substring(1, label.length() + 1); // String l = arguments[i]; String l = arguments[i].substring(1, arguments[i].length()); if ((!caseSensitive && label.equalsIgnoreCase(l)) || label.equals(l)) { return i; } } } } return -1; } /** * Does an argument with label exist? */ public boolean hasOption(String label) { int n = findOption(label); if (n == -1) { return false; } return options[n].isAvailable; } /** * Return the value of an integer option */ public int getIntegerOption(String label) { IntegerOption o = (IntegerOption) options[findOption(label)]; return o.value; } /** * Return the value of an integer array option */ public int[] getIntegerArrayOption(String label) { IntegerArrayOption o = (IntegerArrayOption) options[findOption(label)]; return o.values; } /** * Return the value of an integer option */ public long getLongOption(String label) { LongOption o = (LongOption) options[findOption(label)]; return o.value; } /** * Return the value of an real number option */ public double getRealOption(String label) { RealOption o = (RealOption) options[findOption(label)]; return o.value; } /** * Return the value of an real array option */ public double[] getRealArrayOption(String label) { RealArrayOption o = (RealArrayOption) options[findOption(label)]; return o.values; } /** * Return the value of an string option */ public String getStringOption(String label) { StringOption o = (StringOption) options[findOption(label)]; return o.value; } /** * Return any arguments leftover after the options */ public String[] getLeftoverArguments() { return leftoverArguments; } public void printUsage(String name, String commandLine) { System.out.print(" Usage: " + name); for (Option option : options) { System.out.print(" [-" + option.label); if (option instanceof IntegerArrayOption) { IntegerArrayOption o = (IntegerArrayOption) option; for (int j = 1; j <= o.count; j++) { System.out.print(" <i" + j + ">"); } System.out.print("]"); } else if (option instanceof IntegerOption) { System.out.print(" <i>]"); } else if (option instanceof RealArrayOption) { RealArrayOption o = (RealArrayOption) option; for (int j = 1; j <= o.count; j++) { System.out.print(" <r" + j + ">"); } System.out.print("]"); } else if (option instanceof RealOption) { System.out.print(" <r>]"); } else if (option instanceof StringOption) { StringOption o = (StringOption) option; if (o.options != null) { System.out.print(" <" + o.options[0]); for (int j = 1; j < o.options.length; j++) { System.out.print("|" + o.options[j]); } System.out.print(">]"); } else { System.out.print(" <" + o.tag + ">]"); } } else { System.out.print("]"); } } System.out.println(" " + commandLine); for (Option option : options) { System.out.println(" -" + option.label + " " + option.description); } } private int findOption(String label) { for (int i = 0; i < options.length; i++) { String l = options[i].label; if ((!caseSensitive && label.equalsIgnoreCase(l)) || label.equals(l)) { return i; } } return -1; } private Option[] options = null; private String[] leftoverArguments = null; private boolean caseSensitive = false; }