/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package openbook.tools; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Processes options. * <br> * User can register a set of command options. Then this processor will parse a set of Strings to * store the values for each of the registered options as well as optionally any unrecognized * option values. * * @author Pinaki Poddar * */ public class CommandProcessor { private final Map<Option, String> registeredOptions = new HashMap<Option,String>(); private final Set<Option> unregisteredOptions = new HashSet<Option>(); private boolean allowsUnregisteredOption = true; /** * Set the option values from the given arguments. * All elements of the given array is <em>not</em> consumed, * only till the index that appears to be a valid option. * * @see #lastIndex(String[]) * * @param args an array of arguments. * * @return the array elements that are not consumed. */ public String[] setFrom(String[] args) { return setFrom(args, 0, args != null ? lastIndex(args) : 0); } /** * Set the option values from the given arguments between the given indices. * * @see #lastIndex(String[]) * * @param args an array of arguments. * * @return the array elements that are not consumed. */ public String[] setFrom(String[] args, int from, int to) { if (args == null) return null; if (args.length == 0) return new String[0]; assertValidIndex(from, args, "Initial index " + from + " is an invalid index to " + Arrays.toString(args)); assertValidIndex(to, args, "Last index " + to + " is an invalid index to " + Arrays.toString(args)); int i = from; for (; i < to; i++) { String c = args[i]; Option command = findCommand(c); if (command == null) { throw new IllegalArgumentException(c + " is not a recongined option"); } if (command.requiresInput()) { i++; } if (i > to) { throw new IllegalArgumentException("Command " + c + " requires a value, but no value is specified"); } registeredOptions.put(command, args[i]); } String[] remaining = new String[args.length-to]; System.arraycopy(args, i, remaining, 0, remaining.length); return remaining; } /** * Gets the last index in the given array that can be processed as an option. * The array elements are sequentially tested if they are a valid option name * (i.e. starts with - character) and if valid then the next element is consumed * as value, if the option requires a value. The search ends when either * the array is exhausted or encounters elements that are not options. * * @param args an array of arguments * @return the last index that will/can be consumed by this processor. */ public int lastIndex(String[] args) { int i = 0; for (; i < args.length; i++) { if (Option.isValidName(args[i])) { Option cmd = findCommand(args[i]); if (cmd != null) { if (cmd.requiresInput()) { i++; } continue; } } break; } return i; } /** * Register the given aliases as a command option. * * @param requiresValue if true then the option must be specified with a value. * @param aliases strings to recognize this option. Each must begin with a dash character. * * @return the command that is registered */ public Option register(boolean requiresValue, String...aliases) { Option option = new Option(requiresValue, aliases); registeredOptions.put(option, null); return option; } /** * Finds a command with the given name. * If no command has been registered with the given name, but this processor * allows unrecognized options, then as a result of this call, the * unknown name is registered as an option. * * @param option a command alias. * * @return null if the given String is not a valid command option name. * */ public Option findCommand(String option) { if (!Option.isValidName(option)) return null; for (Option registeredOption : registeredOptions.keySet()) { if (registeredOption.match(option)) return registeredOption; } for (Option unregisteredOption : unregisteredOptions) { if (unregisteredOption.match(option)) return unregisteredOption; } if (allowsUnregisteredOption) { Option cmd = new Option(option); unregisteredOptions.add(cmd); return cmd; } else { return null; } } /** * Gets all the unrecognized command options. * * @return empty set if no commands are unrecognized. */ public Set<Option> getUnregisteredCommands() { return Collections.unmodifiableSet(unregisteredOptions); } <T> void assertValidIndex(int i, T[] a, String message) { if (i <0 || (a != null && i >= a.length)) throw new ArrayIndexOutOfBoundsException(message); } /** * Gets value of the option matching the given alias. * * @param alias an alias. * * @return value of the given option. */ public String getValue(String alias) { Option cmd = findCommand(alias); return getValue(cmd); } /** * Gets value of the given option. * * @param opt an option. * * @return value of the given option. */ String getValue(Option opt) { String val = registeredOptions.get(opt); if (val == null) val = opt.getDefaultValue(); return val; } /** * @return the allowsUnregisteredOption */ public boolean getAllowsUnregisteredOption() { return allowsUnregisteredOption; } /** * @param allowsUnregisteredOption the allowsUnregisteredOption to set */ public void setAllowsUnregisteredOption(boolean allowsUnregisteredOption) { this.allowsUnregisteredOption = allowsUnregisteredOption; } /** * A simple immutable object represents meta-data about a command option. * * @author Pinaki Poddar * */ public static class Option { private static final String DASH = "-"; /** * Affirms if the given string can be a valid option name. * An option name always starts with dash and must be followed by at least one character. */ public static boolean isValidName(String s) { return s != null && s.startsWith(DASH) && s.length() > 1; } /** * Possible names of this command option. * All aliases must start with a dash (<code>-</code>). */ private String[] aliases; /** * Does the option require a value? */ private boolean requiresInput; /** * A default value for this option. */ private String defValue; /** * A description String. */ private String _description = ""; /** * Create a command with given aliases. This option requires a value. * * @param aliases strings each must start with a dash (<code>-</code>). */ public Option(String... aliases) { this(true, aliases); } /** * Create a option with given aliases. * * @param requiresInput does it require a value? * @param aliases strings each must start with a dash (<code>-</code>). */ public Option(boolean requiresInput, String...aliases) { super(); if (aliases == null || aliases.length == 0) throw new IllegalArgumentException("Can not create command with null or empty aliases"); for (String alias : aliases) { if (!isValidName(alias)) { throw new IllegalArgumentException("Invalid alias [" + alias + "]. " + "Aliases must start with - followded by at least one character"); } } this.aliases = aliases; this.requiresInput = requiresInput; } /** * Gets the first alias as the name. */ public String getName() { return aliases[0]; } /** * Sets the default value for this option. * * @param v a default value. * * @return this command itself. * * @exception IllegalStateException if this option does not require a value. */ public Option setDefault(String v) { if (!requiresInput) throw new IllegalStateException(this + " does not require a value. Can not set default value [" + v + "]"); defValue = v; return this; } public Option setDescription(String desc) { if (desc != null) { _description = desc; } return this; } public String getDescription() { return _description; } /** * Affirms if the given name any of the aliases. */ public boolean match(String name) { for (String alias : aliases) { if (name.equals(alias)) return true; } return false; } /** * Affirms if this option requires a value. */ public boolean requiresInput() { return requiresInput; } /** * Gets the default value of this option. * * @return the default value. null if no default value has been set. */ public String getDefaultValue() { return defValue; } public String toString() { return getName(); } } }