/* Java Command Line Parser Library * Copyright (C) 2007 Lane O.B. Schwartz * * This library 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.1 of the License, or (at your option) any later version. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package joshua.util; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; /** * Java Command Line Parser * <p> * The current version supports string and integer options. * <p> * Support is not included for options which take a list of values. * * @author Lane O.B. Schwartz * @version $LastChangedDate: 2009-05-22 23:31:12 -0500 (Fri, 22 May 2009) $ */ @SuppressWarnings("unchecked") public class CommandLineParser { private Map<Character,Option<Integer>> intShortForms; private Map<String,Option<Integer>> intLongForms; private Map<Character,Option<String>> stringShortForms; private Map<String,Option<String>> stringLongForms; private Map<Character,Option<Boolean>> booleanShortForms; private Map<String,Option<Boolean>> booleanLongForms; private List<Option> allOptions; private final Set<String> localizedTrueStrings = new HashSet<String>(); private final Set<String> localizedFalseStrings = new HashSet<String>(); public CommandLineParser() { intShortForms = new HashMap<Character,Option<Integer>>(); intLongForms = new HashMap<String,Option<Integer>>(); stringShortForms = new HashMap<Character,Option<String>>(); stringLongForms = new HashMap<String,Option<String>>(); booleanShortForms = new HashMap<Character,Option<Boolean>>(); booleanLongForms = new HashMap<String,Option<Boolean>>(); allOptions = new LinkedList<Option>(); localizedTrueStrings.add("true"); localizedTrueStrings.add("yes"); localizedFalseStrings.add("false"); localizedFalseStrings.add("no"); } public CommandLineParser(Set<String> localizedTrueStrings, Set<String> localizedFalseStrings) { this(); this.localizedTrueStrings.clear(); this.localizedFalseStrings.clear(); this.localizedTrueStrings.addAll(localizedTrueStrings); this.localizedFalseStrings.addAll(localizedFalseStrings); } public Option<Integer> addIntegerOption(char shortForm, String longForm, String valueVariable, Integer defaultValue, Set<Integer> legalValues, String comment) { if (shortForm!=Option.MISSING_SHORT_FORM && (intShortForms.containsKey(shortForm)) || (!longForm.equals(Option.MISSING_LONG_FORM) && intLongForms.containsKey(longForm))) throw new DuplicateOptionException("Duplicate options are not allowed"); Option<Integer> o = new Option<Integer>(shortForm, longForm, valueVariable, defaultValue, legalValues, comment); intShortForms.put(shortForm, o); intLongForms.put(longForm, o); allOptions.add(o); return o; } public Option<Integer> addIntegerOption(char shortForm, String longForm, String valueVariable, Set<Integer> legalValues, String comment) { return addIntegerOption(shortForm, longForm, valueVariable, null, legalValues, comment); } public Option<Integer> addIntegerOption(char shortForm, String longForm, String valueVariable, String comment) { return addIntegerOption(shortForm, longForm, valueVariable, null, new UniversalSet<Integer>(), comment); } public Option<Integer> addIntegerOption(char shortForm, String longForm, String comment) { return addIntegerOption(shortForm, longForm, null, null, new UniversalSet<Integer>(), comment); } public Option<Integer> addIntegerOption(char shortForm, String longForm, String valueVariable, Integer defaultValue, String comment) { return addIntegerOption(shortForm, longForm, valueVariable, defaultValue, new UniversalSet<Integer>(), comment); } public Option<Integer> addIntegerOption(String longForm, String valueVariable, Integer defaultValue, String comment) { return addIntegerOption(Option.MISSING_SHORT_FORM,longForm, valueVariable, defaultValue, new UniversalSet<Integer>(), comment); } public Option<Integer> addIntegerOption(char shortForm, String longForm) { return addIntegerOption(shortForm, longForm, null, null, new UniversalSet<Integer>(), ""); } public Option<Integer> addIntegerOption(char shortForm) { return addIntegerOption(shortForm,Option.MISSING_LONG_FORM); } public Option<Integer> addIntegerOption(String longForm) { return addIntegerOption(Option.MISSING_SHORT_FORM,longForm); } public Option<Integer> addIntegerOption(String longForm, String comment) { return addIntegerOption(Option.MISSING_SHORT_FORM, longForm, comment); } // String options public Option<String> addStringOption(char shortForm, String longForm, String valueVariable, String defaultValue, Set<String> legalValues, String comment) { if (shortForm!=Option.MISSING_SHORT_FORM && (intShortForms.containsKey(shortForm)) || (!longForm.equals(Option.MISSING_LONG_FORM) && intLongForms.containsKey(longForm))) throw new DuplicateOptionException("Duplicate options are not allowed"); Option<String> o = new Option<String>(shortForm, longForm, valueVariable, defaultValue, legalValues, comment); stringShortForms.put(shortForm, o); stringLongForms.put(longForm, o); allOptions.add(o); return o; } public Option<String> addStringOption(char shortForm, String longForm, String valueVariable, Set<String> legalValues, String comment) { return addStringOption(shortForm, longForm, valueVariable, null, legalValues, comment); } public Option<String> addStringOption(char shortForm, String longForm, String valueVariable, String comment) { return addStringOption(shortForm, longForm, valueVariable, null, new UniversalSet<String>(), comment); } public Option<String> addStringOption(String longForm, String valueVariable, String comment) { return addStringOption(Option.MISSING_SHORT_FORM, longForm, valueVariable, null, new UniversalSet<String>(), comment); } public Option<String> addStringOption(char shortForm, String longForm, String comment) { return addStringOption(shortForm, longForm, null, null, new UniversalSet<String>(), comment); } public Option<String> addStringOption(char shortForm, String longForm, String valueVariable, String defaultValue, String comment) { return addStringOption(shortForm, longForm, valueVariable, defaultValue, new UniversalSet<String>(), comment); } public Option<String> addStringOption(String longForm, String valueVariable, String defaultValue, String comment) { return addStringOption(Option.MISSING_SHORT_FORM,longForm, valueVariable, defaultValue, new UniversalSet<String>(), comment); } public Option<String> addStringOption(char shortForm, String longForm) { return addStringOption(shortForm, longForm, null, null, new UniversalSet<String>(), ""); } public Option<String> addStringOption(char shortForm) { return addStringOption(shortForm,Option.MISSING_LONG_FORM); } public Option<String> addStringOption(String longForm) { return addStringOption(Option.MISSING_SHORT_FORM,longForm); } public Option<String> addStringOption(String longForm, String comment) { return addStringOption(Option.MISSING_SHORT_FORM, longForm, comment); } // boolean options public Option<Boolean> addBooleanOption(char shortForm, String longForm, String valueVariable, Boolean defaultValue, String comment) { if (shortForm!=Option.MISSING_SHORT_FORM && (booleanShortForms.containsKey(shortForm)) || (!longForm.equals(Option.MISSING_LONG_FORM) && booleanLongForms.containsKey(longForm))) throw new DuplicateOptionException("Duplicate options are not allowed"); Set<Boolean> legalBooleanValues = new HashSet<Boolean>(); legalBooleanValues.add(true); legalBooleanValues.add(false); Option<Boolean> o = new Option<Boolean>(shortForm, longForm, valueVariable, defaultValue, legalBooleanValues, comment); booleanShortForms.put(shortForm, o); booleanLongForms.put(longForm, o); allOptions.add(o); return o; } public Option<Boolean> addBooleanOption(char shortForm, String longForm, String valueVariable, String comment) { return addBooleanOption(shortForm, longForm, valueVariable, null, comment); } public Option<Boolean> addBooleanOption(char shortForm, String longForm, String comment) { return addBooleanOption(shortForm, longForm, null, null, comment); } public Option<Boolean> addBooleanOption(String longForm, Boolean defaultValue, String comment) { return addBooleanOption(Option.MISSING_SHORT_FORM,longForm, null, defaultValue, comment); } public Option<Boolean> addBooleanOption(String longForm, String valueVariable, Boolean defaultValue, String comment) { return addBooleanOption(Option.MISSING_SHORT_FORM,longForm, valueVariable, defaultValue, comment); } public Option<Boolean> addBooleanOption(char shortForm, String longForm) { return addBooleanOption(shortForm, longForm, null, null, ""); } public Option<Boolean> addBooleanOption(char shortForm) { return addBooleanOption(shortForm,Option.MISSING_LONG_FORM); } public Option<Boolean> addBooleanOption(String longForm) { return addBooleanOption(Option.MISSING_SHORT_FORM,longForm); } public Option<Boolean> addBooleanOption(String longForm, String comment) { return addBooleanOption(Option.MISSING_SHORT_FORM, longForm, comment); } // float options /// /* public Option<Integer> addIntegerOption(char shortForm, String longForm) { if (intShortForms.containsKey(shortForm) || intLongForms.containsKey(longForm)) throw new DuplicateOptionException("Duplicate options are not allowed"); Option<Integer> o = new Option<Integer>(shortForm, longForm); intShortForms.put(shortForm, o); intLongForms.put(longForm, o); allOptions.add(o); return o; } public Option<Integer> addIntegerOption(char shortForm, String longForm, String valueVariable, int defaultValue, Set<Integer> legalValues, String comment) { if (intShortForms.containsKey(shortForm) || intLongForms.containsKey(longForm)) throw new DuplicateOptionException("Duplicate options are not allowed"); Option<Integer> o = new Option<Integer>(shortForm, longForm, valueVariable, defaultValue, comment); intShortForms.put(shortForm, o); intLongForms.put(longForm, o); allOptions.add(o); return o; } public Option<Integer> addIntegerOption(char shortForm, String longForm, String valueVariable, int defaultValue, String comment) { if (intShortForms.containsKey(shortForm) || intLongForms.containsKey(longForm)) throw new DuplicateOptionException("Duplicate options are not allowed"); Option<Integer> o = new Option<Integer>(shortForm, longForm, valueVariable, defaultValue, comment); intShortForms.put(shortForm, o); intLongForms.put(longForm, o); allOptions.add(o); return o; } public Option<Integer> addIntegerOption(char shortForm, String longForm, String valueVariable, String comment) { if (intShortForms.containsKey(shortForm) || intLongForms.containsKey(longForm)) throw new DuplicateOptionException("Duplicate options are not allowed"); Option<Integer> o = new Option<Integer>(shortForm, longForm, valueVariable, comment); intShortForms.put(shortForm, o); intLongForms.put(longForm, o); allOptions.add(o); return o; } */ /* public Option<String> addStringOption(char shortForm, String longForm) { if (stringShortForms.containsKey(shortForm) || stringLongForms.containsKey(longForm)) throw new DuplicateOptionException("Duplicate options are not allowed"); Option<String> o = new Option<String>(shortForm, longForm); stringShortForms.put(shortForm, o); stringLongForms.put(longForm, o); allOptions.add(o); return o; } */ public void parse(String[] argv) { Collection<Option> parsedOptions = new HashSet<Option>(); int index = 0; while (index < argv.length) { if (argv[index].startsWith("--")) { int splitPoint = argv[index].indexOf('='); if (splitPoint==2) { throw new CommandLineParserException("Invalid option: --"); } else if (splitPoint>=0) { String option = argv[index].substring(2, splitPoint); String value = argv[index].substring(splitPoint+1); parsedOptions.add(parseLongForm(option,value)); } else if (index+1 < argv.length) { String option = argv[index].substring(2); String value = argv[index+1]; if (value.startsWith("-") && !value.equals("-") && !value.equals("--")) { parsedOptions.add(parseLongForm(option)); } else { parsedOptions.add(parseLongForm(option,value)); index++; } } else { // Must be a boolean option String option = argv[index].substring(2); parsedOptions.add(parseLongForm(option)); //throw new CommandLineParserException("No value provided for option " + argv[index].substring(2)); } } else if (argv[index].startsWith("-")) { String option = argv[index].substring(1); if (option.length()==1) { if (index+1 < argv.length) { String value = argv[index+1]; if (value.startsWith("-") && !value.equals("-") && !value.equals("--")) { // Must be a boolean option parsedOptions.add(parseShortForm(option.charAt(0))); } else { parsedOptions.add(parseShortForm(option.charAt(0),value)); index++; } } else { // Must be a boolean option parsedOptions.add(parseShortForm(option.charAt(0))); } } else { throw new CommandLineParserException(argv[index] + " is not a valid option"); } } index++; } for (Option o : allOptions) { if (o.isRequired() && !parsedOptions.contains(o)) { die("A required option was not provided:\n " + o + "\n"); } } } public void printUsage() { System.err.println("Usage:"); for (Option o : allOptions) { System.err.println(o); } } private void die(String error) { System.err.println(error); printUsage(); System.exit(1); } public Option parseLongForm(String key, String value) { if (intLongForms.containsKey(key)) { try { Option<Integer> o = intLongForms.get(key); o.setValue(Integer.valueOf(value)); return o; } catch (NumberFormatException e) { die("Option " + key + " requires an integer value."); return null; } } else if (stringLongForms.containsKey(key)) { Option<String> o = stringLongForms.get(key); o.setValue(value); return o; } else if (booleanLongForms.containsKey(key)) { Option<Boolean> o = booleanLongForms.get(key); if (localizedTrueStrings.contains(value.toLowerCase())) { o.setValue(true); } else if (localizedFalseStrings.contains(value.toLowerCase())) { o.setValue(false); } else { throw new CommandLineParserException("Invalid value \"" + value+ "\" for boolean option " + key); } return o; } else { throw new Error("Bug in command line parser - unexpected option type encountered for option " + key); } } public Option parseLongForm(String key) { if (booleanLongForms.containsKey(key)) { Option<Boolean> o = booleanLongForms.get(key); o.setValue(true); return o; } else { throw new CommandLineParserException("No such boolean option exists: --" + key); } } public Option parseShortForm(Character key) { if (booleanShortForms.containsKey(key)) { Option<Boolean> o = booleanShortForms.get(key); o.setValue(true); return o; } else { throw new CommandLineParserException("No such boolean option exists: -" + key); } } public Option parseShortForm(Character key, String value) { if (intShortForms.containsKey(key)) { try { Option<Integer> o = intShortForms.get(key); o.setValue(Integer.valueOf(value)); return o; } catch (NumberFormatException e) { die("Option " + key + " requires an integer value."); return null; } } else if (stringShortForms.containsKey(key)) { Option<String> o = stringShortForms.get(key); o.setValue(value); return o; } else if (booleanShortForms.containsKey(key)) { Option<Boolean> o = booleanShortForms.get(key); if (localizedTrueStrings.contains(value.toLowerCase())) { o.setValue(true); } else if (localizedFalseStrings.contains(value.toLowerCase())) { o.setValue(false); } else { throw new CommandLineParserException("Invalid value \"" + value+ "\" for boolean option " + key); } return o; } else { throw new Error("Bug in command line parser - unexpected option type encountered"); } } /* public int intValue(Option o) { if (intOptions.containsKey(o)) return intOptions.get(o); else throw new RuntimeException("No such integer option"); } public String stringValue(Option o) { if (stringOptions.containsKey(o)) return stringOptions.get(o); else throw new RuntimeException("No such string option"); } */ public <OptionType> OptionType getValue(Option<OptionType> option) { return option.getValue(); } public boolean hasValue(Option<?> option) { return option.hasValue(); } public static void main(String[] args) { CommandLineParser parser = new CommandLineParser(); Option<Integer> n = parser.addIntegerOption('n', "number","NUMBER", "a number to be supplied"); parser.parse(args); //parser.printUsage(); System.out.println(parser.getValue(n)); } public static class CommandLineParserException extends RuntimeException { public CommandLineParserException(String message) { super(message); } } public static class DuplicateOptionException extends RuntimeException { public DuplicateOptionException(String message) { super(message); } } public class Option<OptionType> { private final char shortForm; private final String longForm; private final String comment; private final OptionType defaultValue; private final String valueVariable; private final Set legalValues; public static final char MISSING_SHORT_FORM = '\u0000'; public static final String MISSING_LONG_FORM = "\u0000"; private OptionType optionValue; public Option(char shortForm, String longForm, String valueVariable, OptionType defaultValue, Set<OptionType> legalValues, String comment) { if (longForm==null) throw new NullPointerException("longForm must not be null"); if (comment==null) throw new NullPointerException("comment must not be null"); this.shortForm = shortForm; this.longForm = longForm; this.comment = comment; this.valueVariable = valueVariable; this.defaultValue = defaultValue; this.legalValues = legalValues; this.optionValue = null; } public Option(char shortForm, String longForm, String valueVariable, Set<OptionType> legalValues, String comment) { this(shortForm, longForm, valueVariable, null, legalValues, comment); } public Option(char shortForm, String longForm, String valueVariable, String comment) { this(shortForm, longForm, valueVariable, null, new UniversalSet<OptionType>(), comment); } public Option(char shortForm, String longForm, String comment) { this(shortForm, longForm, null, null, new UniversalSet<OptionType>(), comment); } public Option(char shortForm, String longForm, String valueVariable, OptionType defaultValue, String comment) { this(shortForm, longForm, valueVariable, defaultValue, new UniversalSet<OptionType>(), comment); } public Option(String longForm, String valueVariable, OptionType defaultValue, String comment) { this(MISSING_SHORT_FORM, longForm, valueVariable, defaultValue, new UniversalSet<OptionType>(), comment); } public Option(char shortForm, String longForm) { this(shortForm, longForm, null, null, new UniversalSet<OptionType>(), ""); } public Option(char shortForm) { this(shortForm,MISSING_LONG_FORM); } public Option(String longForm) { this(MISSING_SHORT_FORM,longForm); } public Option(String longForm, String comment) { this(MISSING_SHORT_FORM, longForm, comment); } public boolean isOptional() { return (null != defaultValue); } public boolean isRequired() { return (null == defaultValue); } public char getShortForm() { return shortForm; } public String getLongForm() { return longForm; } public String getComment() { return comment; } void setValue(OptionType value) { this.optionValue = value; } OptionType getValue() { if (optionValue != null) { return optionValue; } else if (defaultValue != null) { return defaultValue; } else { throw new CommandLineParserException("Unable to get value because option has not been initialized and does not have a default value: " + this.toString()); } } boolean hasValue() { return ! (null == optionValue && null == defaultValue); } public String toString() { String formattedShortForm; if (shortForm == Option.MISSING_SHORT_FORM) { formattedShortForm = ""; } else { formattedShortForm = "-" + shortForm; } String formattedLongForm; if (longForm.equals(Option.MISSING_LONG_FORM)) { formattedLongForm = ""; } else { formattedLongForm = "--" + longForm; } if (shortForm != Option.MISSING_SHORT_FORM && !longForm.equals(Option.MISSING_LONG_FORM)) { formattedShortForm += ","; } if (valueVariable!=null && valueVariable.length()>=1) { formattedLongForm += "=" + valueVariable; } String string = String.format(" %1$3s %2$-21s", formattedShortForm, formattedLongForm); if (null != comment) { string += " " + comment; } if (! (legalValues instanceof UniversalSet) ) { string += " " + legalValues; } return string; } public boolean equals(Object o) { if (o instanceof Option) { return (shortForm==((Option) o).shortForm && longForm==((Option) o).longForm); } else { return false; } } public int hashCode() { return (shortForm + longForm).hashCode(); } } static class UniversalSet<E> implements Set<E> { public boolean add(Object o) { throw new UnsupportedOperationException(); } public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } public void clear() { throw new UnsupportedOperationException(); } public boolean contains(Object o) { return true; } public boolean containsAll(Collection c) { return true; } public boolean isEmpty() { return false; } public Iterator<E> iterator() { return null; } public boolean remove(Object o) { throw new UnsupportedOperationException(); } public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } public int size() { return Integer.MAX_VALUE; } public Object[] toArray() { return null; } public <T>T[] toArray(T[] a) { return null; } } }