/**
* Copyright 2005-2012 Akiban Technologies, Inc.
*
* Licensed 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 com.persistit.util;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* A simple command line argument parser that provides primitive type conversion
* and value checking. An application passes a template and the command line
* argument array to the constructor of this class. The constructor validates
* the argument list. Subsequently the application can access specific fields
* from the command line using the <code>ArgParser</code>'s accessor methods.
* </p>
* <p>
* If the command line includes the argument <code>-?</code> then
* <code>ArgParser</code> displays a summary of permissible arguments and sets
* its UsageOnly property to true. The calling application should simply exit if
* <code>isUsageonly</code> is true for the constructed ArgParser.
* </p>
*
* @author peter
* @version 1.0
*/
public class ArgParser {
private final String _progName;
private final String[] _template;
private final String _flags;
private final String[] _strArgs;
private final long[] _longArgs;
private final boolean[] _specified;
private boolean _usageOnly;
private final List<String> _unparsed = new ArrayList<String>();
/**
* <p>
* Construct an instance that parses an array of command line arguments
* according to specifications provided in a template. The supplied template
* is an array of specification elements.
* </p>
* <p>
* Command line arguments are specified by name, not by position. Each
* argument must either be a flag in the form <code>-<i>X</i></code> (where
* <i>X</i> is letter) or a name-value pair in the form
* <code><i>argname</i>=</i>value</i></code>. The permissible flags and
* argument names are specified by the array of template strings, each of
* which must have the form:
*
* <blockquote>
*
* <pre>
* <code> _flag|<i>flchar</i>|<i>description</i></code>
* or
* <code> <i>argname</i>|<i>argtype</i>|<i>description</i></code>
* </pre>
*
* </blockquote>
* </p>
* <p>
* where
* <dl>
* <dt><code><i>flchar</i></code></dt>
* <dd>is a single letter that can be used as a flag on the command line.
* For example, to allow a the flag "-x", use a template string of the form
* <code>_flags|x|Enable the x option</code>.</dd>
* <dt><code><i>argname</i></code></dt>
* <dd>Parameter name.</dd>
* </dd>
* <dt><code><i>argtype</i></code></dt>
* <dd>
* One of:
*
* <pre>
* <code>int:<i>defaultvalue</i>:<i>lowbound</i>:<i>highbound</i></code>
* <code>long:<i>defaultvalue</i>:<i>lowbound</i>:<i>highbound</i></code>
* <code>String:<i>default</i></code>
* </pre>
*
* </dd>
* </dl>
* </p>
*/
public ArgParser(final String progName, final String[] args, final String[] template) {
_progName = progName;
_template = template;
_strArgs = new String[template.length];
_longArgs = new long[template.length];
_specified = new boolean[template.length];
final StringBuilder flags = new StringBuilder();
String flagsTemplate = "";
for (int i = 0; i < template.length; i++) {
if (template[i].startsWith("_flag|")) {
flagsTemplate += piece(template[i], '|', 1);
} else {
doField(null, i);
}
}
for (int i = 0; i < args.length; i++) {
final String arg = args[i];
if (arg.startsWith("-")) {
for (int j = 1; j < arg.length(); j++) {
final char ch = arg.charAt(j);
if (ch == '?') {
usage();
} else if (flagsTemplate.indexOf(ch) >= 0) {
flags.append(ch);
} else {
_unparsed.add(arg);
}
}
} else {
final String fieldName = piece(args[i], '=', 0);
final int position = lookupName(fieldName);
if (position < 0) {
_unparsed.add(args[i]);
} else {
final String argValue = arg.substring(fieldName.length() + 1);
_specified[position] = true;
doField(argValue, position);
}
}
}
_flags = flags.toString();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < _template.length; i++) {
final String t = _template[i];
if (t.startsWith("_flag|")) {
sb.append(" flag -");
sb.append(piece(t, '|', 1));
tab(sb, 24);
sb.append(piece(t, '|', 2));
} else {
sb.append(" ");
sb.append(piece(t, '|', 0));
tab(sb, 24);
sb.append(piece(t, '|', 2));
}
sb.append(Util.NEW_LINE);
}
return sb.toString();
}
public ArgParser strict() {
if (!_unparsed.isEmpty()) {
throw new IllegalArgumentException("Unrecognized arguments: " + _unparsed);
}
return this;
}
/**
* Array of arguments that were not parsed by this template. This array may
* be passed into another ArgParser for further processing.
*
* @return array of argument strings
*/
public String[] getUnparsedArray() {
return _unparsed.toArray(new String[_unparsed.size()]);
}
/**
* List of the arguments that were not parsed by this template. The list is
* modifiable.
*
* @return List of argument strings
*/
public List<String> getUnparsedList() {
return _unparsed;
}
/**
* Display a description of the permissible argument values to
* {@link java.lang.System#out}.
*/
public void usage() {
_usageOnly = true;
System.out.println();
System.out.println("Usage: java " + _progName + " arguments");
System.out.println(toString());
}
/**
* @return <i>true</i> if the command line arguments contain a help flag,
* <code>-?</code>.
*/
public boolean isUsageOnly() {
return _usageOnly;
}
/**
* @param ch
* The flag character
* @return <i>true</i> if the command line arguments contain the specified
* flag.
*/
public boolean isFlag(final int ch) {
return _flags.indexOf(ch) >= 0;
}
/**
* @return A String containing all the flag characters specified in the
* command line arguments. For example, if the command line
* specifies value flags <code>-a -b -c</code> then this method
* returns <code>"abc"</code>.
*/
public String getFlags() {
return _flags;
}
/**
* Return the boolean value of argument specified by its index in the
* template
*
* @param index
* @return the boolean value for the specified template item
*/
public boolean booleanValue(final int index) {
final String t = _template[index];
if (t.startsWith("_flag|")) {
return isFlag(t.charAt(6));
} else {
return false;
}
}
/**
* @param fieldName
* Argument name of a String value specification in the template
* array.
* @return The corresponding command line argument, or <i>null</i> if the
* command line does not name this item.
*/
public String getStringValue(final String fieldName) {
return _strArgs[lookupName(fieldName)];
}
/**
* @param fieldName
* Argument name of an int value specification in the template
* array.
* @return The corresponding command line argument, or <i>null</i> if the
* command line does not name this item.
*/
public int getIntValue(final String fieldName) {
return (int) _longArgs[lookupName(fieldName)];
}
/**
* Return the integer value of argument specified by its index in the
* template
*
* @param index
* @return the int value for the specified template item
*/
public int intValue(final int index) {
return (int) _longArgs[index];
}
/**
* @param fieldName
* Argument name of a long value specification in the template
* array.
* @return The corresponding command line argument, or <i>null</i> if the
* command line does not name this item.
*/
public long getLongValue(final String fieldName) {
return _longArgs[lookupName(fieldName)];
}
/**
* Return the long value of argument specified by its index in the template
*
* @param index
* @return the long value for the specified template item
*/
public long longValue(final int index) {
return _longArgs[index];
}
/**
* Return the String value of argument specified by its index in the
* template
*
* @param index
* @return the String value for the specified template item
*/
public String stringValue(final int index) {
return _strArgs[index];
}
/**
* Indicate whether the value returned for the specified field is the
* default value.
*
* @param fieldName
* @return <code>true</code> if the field contains its default value
*/
public boolean isSpecified(final String fieldName) {
return _specified[lookupName(fieldName)];
}
private int lookupName(final String name) {
final String fieldName1 = name + '|';
for (int i = 0; i < _template.length; i++) {
if (_template[i].startsWith(fieldName1))
return i;
}
return -1;
}
private void doField(String arg, final int position) {
final String type = piece(_template[position], '|', 1);
final String t = piece(type, ':', 0);
if (arg == null) {
arg = piece(type, ':', 1);
}
if ("int".equals(t)) {
final long lo = longVal(piece(type, ':', 2), 0);
final long hi = longVal(piece(type, ':', 3), Integer.MAX_VALUE);
final long argInt = longVal(arg, Long.MIN_VALUE);
if (_specified[position]
&& (argInt == Long.MIN_VALUE || argInt < lo || argInt > hi || argInt > Integer.MAX_VALUE)) {
throw new IllegalArgumentException("Invalid argument " + piece(_template[position], '|', 0) + "=" + arg);
}
_longArgs[position] = argInt;
} else if ("long".equals(t)) {
final long lo = longVal(piece(type, ':', 2), 0);
final long hi = longVal(piece(type, ':', 3), Long.MAX_VALUE);
final long argInt = longVal(arg, Long.MIN_VALUE);
if (_specified[position] && (argInt == Long.MIN_VALUE || argInt < lo || argInt > hi)) {
throw new IllegalArgumentException("Invalid argument " + piece(_template[position], '|', 0) + "=" + arg);
}
_longArgs[position] = argInt;
} else {
_strArgs[position] = arg;
}
}
private long longVal(final String s, final long dflt) {
if (s.length() == 0)
return dflt;
try {
return Long.parseLong(s.replace(",", ""));
} catch (final NumberFormatException e) {
throw new IllegalArgumentException(s + " is not a number");
}
}
private void tab(final StringBuilder sb, final int count) {
final int last = sb.lastIndexOf(Util.NEW_LINE);
while (sb.length() - last + 1 < count) {
sb.append(' ');
}
}
private String piece(final String str, final char delimiter, final int count) {
int p = -1;
int q = -1;
for (int i = 0; i <= count; i++) {
if (p == str.length())
return "";
q = p;
p = str.indexOf(delimiter, p + 1);
if (p == -1)
p = str.length();
}
return str.substring(q + 1, p);
}
}