/* * 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; import java.util.BitSet; /** * This class parses a command line that uses multi-character options, * such as <code>-verbose</code> or <code>-help</code>. * <p> * To use <code>MultiCommandLine</code>, create a <code>MultiCommandLine</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>MultiCommandLine</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>MultiCommandLine.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> * No options * can be consumed after <code>getOperands</code> is invoked. Each * option 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>MultiCommandLine</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 MultiCommandLine extends CommandLine { /** The args provided. */ private String[] args; /** which ones have been used? */ private BitSet used; /** Have all the options been consumed? */ private boolean allUsed; /** 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 String opt; // which String was found /** * Create a new <code>MultiCommandLine</code> object that will * return specified options, arguments, and operands. */ public MultiCommandLine(String[] args) { this(null, args); } /** * Create a new <code>MultiCommandLine</code> object that will * return specified options, arguments, and operands. The * <code>prog</code> parameter is the program name. */ public MultiCommandLine(String prog, String[] args) { this.prog = prog; this.args = args; used = new BitSet(args.length); options = new ArrayList(); } /** * Used to store known option types so we can generate a usage message. */ private static class Opt { /** The option. */ String opt; /** The argument type. */ String argType; /** Option can be specified more than once. */ boolean multi; Opt(String 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(String opt) { addOpt(opt, null); 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(String opt) throws BadInvocationException { 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(String 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(String 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(String 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(String 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(String 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(String 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(String 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(String 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 option is not specified, return <code>defaultValue</code>. * * @see CommandLine#parseOutputStream(java.lang.String,java.io.OutputStream) */ public synchronized OutputStream getOutputStream(String 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(String 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(String 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(String 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(String 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(String opt, String path, String mode) throws IOException, BadInvocationException { addOpt(opt, "file"); return parseRandomAccessFile(getArgument(opt), path, mode); } /** * 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(String opt) { if (allUsed) return false; boolean seenUnused = false; for (int i = 0; i < args.length; i++) { if (used.get(i)) // already consumed continue; if (!args[i].startsWith("-")) // not an option continue; if (args[i].equals("--")) // "--" ends the list break; seenUnused = true; if (args[i].length() - 1 == opt.length() && args[i].regionMatches(1, opt, 0, opt.length())) { str = i; this.opt = opt; used.set(i); return 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 (str >= args.length) throw new BadInvocationException(opt); used.set(str + 1); return args[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("?") || getBoolean("help")) { usage(); throw new HelpOnlyException(); } StringBuffer unknown = new StringBuffer(); int a; for (a = 0; a < args.length; a++) { if (used.get(a)) // skip used parameters continue; if (!args[a].startsWith("-")) // first non-option argument break; if (args[a].equals("--")) { // "--" ends things a++; // skip the "--" break; } unknown.append(' ').append(args[a]); } if (unknown.length() != 0) { String ustr = unknown.toString(); throw new BadInvocationException("unknown option" + (ustr.indexOf(' ') > 0 ? "s" : "") + ":" + ustr); } String[] remains = new String[args.length - a]; System.arraycopy(args, a, remains, 0, remains.length); usedUp = true; return remains; } /** * Add the given option of the given type to the list of known options. * "-?" and "-help" are handled separately in <code>getOperands</code>. * * @see #getOperands * @see #usage */ private void addOpt(String 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.equals(opt)) { o.multi = true; return; // already known } } if (!isHelp(opt)) options.add(new Opt(opt, optType)); } private static boolean isHelp(String opt) { return (opt.equals("?") || opt.equals("help")); } /** * 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 options take arguments Iterator it = options.iterator(); while (it.hasNext()) { Opt o = (Opt) it.next(); System.out.print(" [-"); System.out.print(o.opt); if (o.argType != null) { System.out.print(' '); System.out.print(o.argType); } System.out.print("]"); if (o.multi) System.out.print("..."); } // assume other arguments System.out.println(" ..."); } }