/*
* 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 com.sun.jini.system;
import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
/**
* This class parses a command line using POSIX 1003.2-1992 rules.
* These are:
* <ul>
* <li>Each option name is a single alphanumeric character.
* <li>All options should be preceded by the "<code>-</code>" character
* <li>Options that take no arguments can be grouped together behind
* a single <code>-</code> (e.g., <code>-nek</code>)
* <li>Each option that takes an argument should be specified separately.
* <li>Arguments to options are not optional; an option either always
* takes an argument or it doesn't.
* <li>If multiple arguments are given to the same option, they should
* be separated by commas (e.g., <code>-f path1,path2,path3</code>).
* <li>All options should precede operands on the command line. ("Operands"
* are the arguments that are not options; in <code>cat -u x y</code>,
* <code>x</code> and <code>y</code> are operands.)
* <li>The argument <code>--</code> signals the end of options. All
* remaining words on the command line are operands
* <li>The order of options relative to one another should not matter.
* <li>For utilities that use operands to specify files to be opened,
* the "<code>-</code>" should mean standard input or output.
* </ul>
* <code>POSIXCommandLine</code> does not enforce the alphanumeric property
* of option characters, nor that options with arguments must be alone
* on a line (<code>-nekfpath</code> <em>vs.</em> <code>-nek
* -fpath</code>). <code>POSIXCommandLine</code> also recognizes the common
* style of using a <code>-?</code> option to get command usage;
* nothing prevents you from adding your own option for this purpose.
* <code>POSIXCommandLine</code> does not aid in splitting up multiple
* arguments to one option; <code>java.util.StringTokenizer</code> does
* this quite well enough.
* <p>
* To use <code>POSIXCommandLine</code>, create a <code>POSIXCommandLine</code>
* object with the array of strings you wish to parse (typically the
* array passed to the utility's <code>main</code> method), and then
* consume options from it, providing default values in case the option
* is not specified by the user. When you have consumed all the
* options, you invoke the <code>POSIXCommandLine</code> object's
* <code>getOperands</code> method to return the remaining operands on
* the command line. If <code>--</code> is specified it is neither an
* option nor an operand, just a separator between the two lists. The
* <code>CommandLine.BadInvocationException</code> is used to signal
* errors in the construction of the strings, that is, a user error,
* such as specifying a option that takes an argument but forgetting to
* provide that argument. See the documentation for
* <code>POSIXCommandLine.main</code> for an example.
* <p>
* You must call <code>getOperands</code> for proper behavior, even if
* you do not use any operands in your command. <code>getOperands</code>
* checks for several user errors, including unknown options. If you
* do not expect to use operands, you should check the return value of
* <code>getOperands</code> and complain if any are specified.
* <p>
* You must consume (check for) all options that take arguments before
* you consume any boolean (no-argument) option. Further, no options
* can be consumed after <code>getOperands</code> is invoked. Each
* option character may be used only once. Failure to follow these
* rule is a programmer error that will result in a
* <code>CommandLine.ProgrammingException</code>.
* <p>
* <code>POSIXCommandLine</code> provides you several methods to get input
* streams from the command line. If these do not suffice for your
* particular needs, you can get the argument as a <code>String</code>
* and do your own processing.
*
* @author Sun Microsystems, Inc.
*
* @see java.util.StringTokenizer
*/
public class POSIXCommandLine extends CommandLine {
/** The original words provided. */
private String[] orig;
/** The words blown up into an array of array of characters. */
private char[][] args;
/** Have all the options been consumed? */
private boolean allUsed;
/** Have we been asked for any single-character (boolean) options? */
private boolean singles;
/** Has the whole command line been eaten (via <code>getOperands</code>)? */
private boolean usedUp;
/** The list of known options for the usage message. */
private ArrayList options;
/** The program name (if specified). */
private String prog;
// I wouldn't do this stateful stuff if I could return more than one
// value from a method -- it didn't seem worth creating a new object
// to hold the necessary values on each call to findOpt(). So I've
// ensured that only one parsing method can be executing at a time
// and "returned" values via this side effect. YUCK!!!!
private int str; // found in which string
private int pos; // found at what position
private char opt; // which char was found
/** Marks a character as being used up in the options. */
private static final char USED = '\u0000';
/**
* Create a new <code>CommandLine</code> object that will return
* specified options, arguments, and operands.
*/
public POSIXCommandLine(String[] args) {
this(null, args);
}
/**
* Create a new <code>CommandLine</code> object that will return
* specified options, arguments, and operands. The <code>prog</code>
* parameter is the program name.
*/
public POSIXCommandLine(String prog, String[] args) {
orig = args;
this.args = new char[args.length][];
this.prog = prog;
for (int i = 0; i < args.length; i++)
this.args[i] = args[i].toCharArray();
options = new ArrayList();
}
/**
* Used to store known option types so we can generate a usage message.
*/
private static class Opt {
/** The option. */
char opt;
/** The argument type. */
String argType;
/** Can be specified multiple times. */
boolean multi;
Opt(char opt, String argType) {
this.opt = opt;
this.argType = argType;
}
}
/**
* Return <code>true</code> if the given option is specified on the
* command line.
*/
public synchronized boolean getBoolean(char opt) {
addOpt(opt, null);
singles = true;
boolean retval = false;
while (findOpt(opt))
retval = true;
return retval;
}
/**
* Return the argument for the given option. This is a workhorse
* routine shared by all the methods that get options with arguments.
*/
private String getArgument(char opt)
throws BadInvocationException
{
assertNoSingles();
if (findOpt(opt))
return optArg();
return null;
}
/**
* Return the argument of the given string option from the command
* line. If the option is not specified, return
* <code>defaultValue</code>.
*/
public synchronized String getString(char opt, String defaultValue)
throws BadInvocationException
{
addOpt(opt, "str");
return parseString(getArgument(opt), defaultValue);
}
/**
* Return the argument of the given <code>int</code> option from
* the command line. If the option is not specified, return
* <code>defaultValue</code>.
*
* @see CommandLine#parseInt
*/
public synchronized int getInt(char opt, int defaultValue)
throws BadInvocationException, NumberFormatException
{
addOpt(opt, "int");
return parseInt(getArgument(opt), defaultValue);
}
/**
* Return the argument of the given <code>long</code> option from
* the command line. If the option is not specified, return
* <code>defaultValue</code>.
*
* @see CommandLine#parseLong
*/
public synchronized long getLong(char opt, long defaultValue)
throws BadInvocationException, NumberFormatException
{
addOpt(opt, "long");
return parseLong(getArgument(opt), defaultValue);
}
/**
* Return the value of the given <code>double</code> from the command line.
* If the option is not specified, return <code>defaultValue</code>.
*
* @see CommandLine#parseDouble
*/
public synchronized double getDouble(char opt, double defaultValue)
throws BadInvocationException, NumberFormatException
{
addOpt(opt, "val");
return parseDouble(getArgument(opt), defaultValue);
}
/**
* Return a <code>Writer</code> that is the result of creating a new
* <code>FileWriter</code> object for the file named by the given
* option.
* If the option is not specified, return <code>defaultValue</code>.
*
* @see CommandLine#parseWriter(java.lang.String,java.io.Writer)
*/
public synchronized Writer getWriter(char opt, Writer defaultValue)
throws IOException, BadInvocationException
{
addOpt(opt, "file");
return parseWriter(getArgument(opt), defaultValue);
}
/**
* Return a <code>Writer</code> that is the result of creating a new
* <code>FileWriter</code> object for the file named by the given
* option.
* If the option is not specified, the string <code>path</code> is used
* as the file name.
*
* @see CommandLine#parseWriter(java.lang.String,java.lang.String)
*/
public synchronized Writer getWriter(char opt, String path)
throws IOException, BadInvocationException
{
addOpt(opt, "file");
return parseWriter(getArgument(opt), path);
}
/**
* Return a <code>Reader</code> that is the result of creating a new
* <code>FileReader</code> object for the file named by the given
* option.
* If the option is not specified, return <code>defaultValue</code>.
*
* @see CommandLine#parseReader(java.lang.String,java.io.Reader)
*/
public synchronized Reader getReader(char opt, Reader defaultValue)
throws IOException, BadInvocationException
{
addOpt(opt, "file");
return parseReader(getArgument(opt), defaultValue);
}
/**
* Return a <code>Reader</code> that is the result of creating a new
* <code>FileReader</code> object for the file named by the given
* option.
* If the option is not specified, the string <code>path</code> is used
* as the file name.
*
* @see CommandLine#parseReader(java.lang.String,java.lang.String)
*/
public synchronized Reader getReader(char opt, String path)
throws IOException, BadInvocationException
{
addOpt(opt, "file");
return parseReader(getArgument(opt), path);
}
/**
* Return a <code>OutputStream</code> that is the result of creating a new
* <code>FileOutputStream</code> object for the file named by the given
* option. If the argument is <code>-</code> then
* <code>System.out</code> is returned.
* If the option is not specified, return <code>defaultValue</code>.
*
* @see CommandLine#parseOutputStream(java.lang.String,java.io.OutputStream)
*/
public synchronized OutputStream
getOutputStream(char opt, OutputStream defaultValue)
throws IOException, BadInvocationException
{
addOpt(opt, "file");
return parseOutputStream(getArgument(opt), defaultValue);
}
/**
* Return a <code>InputStream</code> that is the result of creating a new
* <code>FileInputStream</code> object for the file named by the given
* option.
* If the option is not specified, the string <code>path</code> is used
* as the file name.
*
* @see CommandLine#parseOutputStream(java.lang.String,java.lang.String)
*/
public synchronized OutputStream getOutputStream(char opt, String path)
throws IOException, BadInvocationException
{
addOpt(opt, "file");
return parseOutputStream(getArgument(opt), path);
}
/**
* Return a <code>InputStream</code> that is the result of creating a new
* <code>FileInputStream</code> object for the file named by the given
* option.
* If the option is not specified, return <code>defaultValue</code>.
*
* @see CommandLine#parseInputStream(java.lang.String,java.io.InputStream)
*/
public synchronized InputStream
getInputStream(char opt, InputStream defaultValue)
throws IOException, BadInvocationException
{
addOpt(opt, "file");
return parseInputStream(getArgument(opt), defaultValue);
}
/**
* Return a <code>InputStream</code> that is the result of creating a new
* <code>FileInputStream</code> object for the file named by the given
* option.
* If the option is not specified, the string <code>path</code> is used
* as the file name.
*
* @see CommandLine#parseInputStream(java.lang.String,java.lang.String)
*/
public synchronized InputStream getInputStream(char opt, String path)
throws IOException, BadInvocationException
{
addOpt(opt, "file");
return parseInputStream(getArgument(opt), path);
}
/**
* Return a <code>RandomAccessFile</code> that is the result of
* creating a new <code>RandomAccessFile</code> object for the file
* named by the given option, using the given <code>mode</code>.
* If the option is not specified, return <code>defaultValue</code>.
*
* @see CommandLine#parseRandomAccessFile(java.lang.String,java.io.RandomAccessFile,java.lang.String)
*/
public synchronized RandomAccessFile
getRandomAccessFile(char opt, RandomAccessFile defaultValue,
String mode)
throws IOException, BadInvocationException
{
addOpt(opt, "file");
return parseRandomAccessFile(getArgument(opt), defaultValue, mode);
}
/**
* Return a <code>RandomAccessFile</code> that is the result of
* creating a new <code>RandomAccessFile</code> object for the file
* named by the given option, using the given <code>mode</code>.
* If the option is not specified, the string <code>path</code> is used
* as the file name.
*
* @see CommandLine#parseRandomAccessFile(java.lang.String,java.lang.String,java.lang.String)
*/
public synchronized RandomAccessFile
getRandomAccessFile(char opt, String path, String mode)
throws IOException, BadInvocationException
{
addOpt(opt, "file");
return parseRandomAccessFile(getArgument(opt), path, mode);
}
/**
* Assert that no boolean options have yet been consumed.
*/
private void assertNoSingles() {
assertNotUsedUp();
if (singles)
throw new ProgrammingException("opts with args must come first");
}
/**
* Assert that the entire command line hasn't been consumed, that is,
* that <code>getOperands</code> hasn't yet been called.
*/
private void assertNotUsedUp() {
if (usedUp)
throw new ProgrammingException("Command line used up");
}
/**
* Find the given option somewhere in the command line. If the
* option is not found, return <code>false</code>. Otherwise set
* <code>str</code>, <code>pos</code>, and <code>opt</code> fields,
* mark the option character as <code>USED</code>, and then return
<code>true</code>.
*/
private boolean findOpt(char opt) {
if (allUsed)
return false;
boolean seenUnused = false;
for (int i = 0; i < args.length; i++) {
if (args[i][0] != '-')
continue;
if (args[i][1] == '-') // "--" ends the list
break;
for (int j = 1; j < args[i].length; j++) {
if (args[i][j] == opt) {
str = i;
pos = j;
this.opt = opt;
args[i][j] = USED;
return true;
} else if (args[i][j] != USED) {
seenUnused = true;
}
}
}
if (!seenUnused)
allUsed = true;
return false;
}
/**
* Return the current option's argument, marking its characters
* as <code>USED</code>.
*
* @exception BadInvocationException No argument is given.
*/
private String optArg() throws BadInvocationException {
if (pos + 1 < args[str].length) {
for (int i = pos + 1; i < args[str].length; i++)
args[str][i] = USED;
return orig[str].substring(pos + 1);
} else {
if (str >= orig.length)
throw new BadInvocationException(new Character(opt));
for (int i = 0; i < args[str + 1].length; i++)
args[str + 1][i] = USED;
return orig[str + 1];
}
}
/**
* Return the command line operands that come after the options.
* This checks to make sure that all specified options have been
* consumed -- any options remaining at this point are assumed to
* be unknown options. If no operands remain, an empty array is
* returned.
* <p>
* This is also where <code>-?</code> is handled. If the user
* specifies <code>-?</code> then the method <code>usage</code> is
* invoked and <code>HelpOnlyException</code> is thrown. The
* program is expected to catch this exception and simply exit
* successfully.
*
* @see #usage
*/
public String[] getOperands()
throws BadInvocationException, HelpOnlyException
{
if (getBoolean('?')) {
usage();
throw new HelpOnlyException();
}
StringBuffer unknown = new StringBuffer();
int a;
for (a = 0; a < args.length; a++) {
if (args[a][0] == USED) // skip used parameters
continue;
if (args[a][0] != '-') // first non-option argument
break;
if (args[a][1] == '-') { // "--" ends things
a++; // skip the "--"
break;
}
for (int j = 1; j < args[a].length; j++) {
if (args[a][j] != USED)
unknown.append(args[a][j]);
}
}
if (unknown.length() != 0) {
throw new BadInvocationException("unknown option" +
(unknown.length() > 1 ? "s" : "") + ": " + unknown);
}
String[] remains = new String[args.length - a];
for (int i = a; i < args.length; i++)
remains[i - a] = orig[i];
usedUp = true;
return remains;
}
/**
* Add the given option of the given type to the list of known options.
* '?' is handled separately in <code>getOperands</code>.
*
* @see #getOperands
* @see #usage
*/
private void addOpt(char opt, String optType) {
// ensure this is a new, not a redundant, option.
Iterator it = options.iterator();
while (it.hasNext()) {
Opt o = (Opt) it.next();
if (o.opt == opt) {
o.multi = true;
return; // already known
}
}
if (opt != '?')
options.add(new Opt(opt, optType));
}
/**
* Print out a summary of the commands usage, inferred from the
* requested options. You can override this to provide a more
* specific summary. This implementation is only valid after all
* known options have been requested and <code>getOperands</code>
* has been (or is being) called. Adds <code>...</code> for
* operands.
*
* @see #getOperands
*/
public void usage() {
if (prog != null) {
System.out.print(prog);
System.out.print(' ');
}
System.out.print("[-?]");
// print out boolean options
boolean seenBool = false;
Iterator it = options.iterator();
while (it.hasNext()) {
Opt o = (Opt) it.next();
if (o.argType == null) {
if (!seenBool)
System.out.print(" [-");
System.out.print(o.opt);
seenBool = true;
}
}
if (seenBool)
System.out.print(']');
// print out options that take arguments
it = options.iterator();
while (it.hasNext()) {
Opt o = (Opt) it.next();
if (o.argType != null) {
System.out.print(" [-");
System.out.print(o.opt);
System.out.print(' ');
System.out.print(o.argType);
System.out.print("]");
if (o.multi)
System.out.print("...");
}
}
// assume other arguments
System.out.println(" ...");
}
}