0001: /**
0002: * $Id: CLIPParser.java,v 1.2 2003/12/19 22:46:34 jtb Exp $
0003: * Copyright 2003 Sun Microsystems, Inc. All
0004: * rights reserved. Use of this product is subject
0005: * to license terms. Federal Acquisitions:
0006: * Commercial Software -- Government Users
0007: * Subject to Standard License Terms and
0008: * Conditions.
0009: *
0010: * Sun, Sun Microsystems, the Sun logo, and Sun ONE
0011: * are trademarks or registered trademarks of Sun Microsystems,
0012: * Inc. in the United States and other countries.
0013: */package com.sun.portal.wsrp.consumer.cli;
0014:
0015: import java.util.List;
0016: import java.util.ArrayList;
0017: import java.util.Map;
0018: import java.util.HashMap;
0019: import java.util.StringTokenizer;
0020: import java.util.Set;
0021: import java.util.HashSet;
0022: import java.util.Iterator;
0023: import java.util.Collections;
0024:
0025: /**
0026: * The CLIPParser implements a Command Line User Interface parser following
0027: * the Sun ONE UI Style Project Guide 2.1.
0028: * <P>
0029: * It parses the command line arguments according to the <B>CLIP full</B> specification.
0030: * <P>
0031: * Refer to the <A HREF="http://bones.red.iplanet.com/ftp/hie/projects/DesktopUIStyle/Version2-1Final/Desktop_UI_Style_Guide/A_La_Carte/CLUI/BareNakedGuidelines/Home.htm#CLIPSpec">CLIP specification</A> for more details.
0032: * <P>
0033: * CLIPParser instances are thread-safe.
0034: * <P>
0035: * CLIPParser example with sub commands:
0036: * <P>
0037: * <PRE>
0038: *
0039: * import com.iplanet.common.util. CLIPParser;
0040: * import com.iplanet.common.util. CLIPException;
0041: *
0042: * public class CLIPParserTest {
0043: *
0044: * public static void main(String[] args) throws CLIPException {
0045: *
0046: * CLIPParser.Option[] options0 = new CLIPParser.Option[2];
0047: * options0[0] = new CLIPParser.Option("user","u",CLIPParser.REGULAR,null,"user name");
0048: * options0[1] = new CLIPParser.Option("password","p",CLIPParser.REGULAR,null,"user password");
0049: *
0050: * CLIPParser.Option[] options1 = new CLIPParser.Option[4];
0051: * options1[0] = new CLIPParser.Option("user","u",CLIPParser.REGULAR,null,"user name");
0052: * options1[1] = new CLIPParser.Option("password","p",CLIPParser.REGULAR,null,"user password");
0053: * options1[3] = new CLIPParser.Option("owner","o",CLIPParser.REGULAR,"user","elements owner");
0054: * options1[2] = new CLIPParser.Option("verbose","v",CLIPParser.BOOLEAN,"false","echoes actions");
0055: *
0056: * CLIPParser.Option[] options2 = new CLIPParser.Option[3];
0057: * options2[0] = new CLIPParser.Option("user","u",CLIPParser.REGULAR,null,"user name");
0058: * options2[1] = new CLIPParser.Option("password","p",CLIPParser.REGULAR,null,"user password");
0059: * options2[2] = new CLIPParser.Option("verbose","v",CLIPParser.BOOLEAN,"false","echos actions");
0060: *
0061: * CLIPParser.SubCommand[] subCommands = new CLIPParser.SubCommand[3];
0062: *
0063: * subCommands[0] = new CLIPParser.SubCommand("list",options0,0,0,"Lists current elements","");
0064: * subCommands[1] = new CLIPParser.SubCommand("add",options1,1,10000,"Adds new elements","elements to add");
0065: * subCommands[2] = new CLIPParser.SubCommand("remove",options2,1,1000,"Removes existing elements","elemenst to remove");
0066: *
0067: * CLIPParser clip = new CLIPParser(subCommands,"Super command (w/ sub commands)");
0068: *
0069: * clip.verifyArguments(args);
0070: *
0071: * if (clip.needsHelp(args)) {
0072: * System.out.println(clip.getHelp(args));
0073: * }
0074: * else {
0075: * System.out.println(clip.toString(args));
0076: * }
0077: * }
0078: *
0079: * }
0080: *
0081: * </PRE>
0082: *
0083: * @author <A HREF="mailto:alejandro.abdelnur@sun.com">Alejandro Abdelnur</A>
0084: *
0085: */
0086: public class CLIPParser {
0087:
0088: /**
0089: * Prefix for all types of options.
0090: *
0091: */
0092: private static final String OPTION_PREFIX = "-";
0093:
0094: /**
0095: * The switch for short option arguments.
0096: *
0097: */
0098: private static final String SHORT_OPTION = OPTION_PREFIX;
0099:
0100: /**
0101: * The switch for long option arguments.
0102: *
0103: */
0104: private static final String LONG_OPTION = OPTION_PREFIX + "-";
0105:
0106: /**
0107: * The switch for negating a boolean option argument.
0108: *
0109: */
0110: private static final String NEGATE_OPTION = OPTION_PREFIX + "-no-";
0111:
0112: /**
0113: * Indicates that no more options occur.
0114: *
0115: */
0116: private static final String NO_MORE_OPTIONS = OPTION_PREFIX + "-";
0117:
0118: /**
0119: * Indicates addition to collection option.
0120: *
0121: */
0122: private static final String ADD_TO_COLLECTION_OPTION = OPTION_PREFIX
0123: + "-add-";
0124:
0125: /**
0126: * Indicates removal from collection option.
0127: *
0128: */
0129: private static final String REMOVE_FROM_COLLECTION_OPTION = OPTION_PREFIX
0130: + "-remove-";
0131:
0132: /**
0133: * Type for boolean options.
0134: *
0135: */
0136: public static final int BOOLEAN = 0;
0137:
0138: /**
0139: * Type for regular options.
0140: *
0141: */
0142: public static final int REGULAR = 1;
0143:
0144: /**
0145: * Type for collection options.
0146: *
0147: */
0148: public static final int COLLECTION = 2;
0149:
0150: /**
0151: * The Option class defines a CLIP option.
0152: * <P>
0153: * It defines the long and short (if exists) names, if the option is mandatory, its type and default value (if any).
0154: * <P>
0155: *
0156: * @author <A HREF="mailto:alejandro.abdelnur@sun.com">Alejandro Abdelnur</A>
0157: *
0158: */
0159: public static class Option {
0160: private String _longName;
0161: private String _shortName;
0162: private boolean _mandatory;
0163: private int _type;
0164: private String _argName = null;
0165: private String[] _defaultValues;
0166: private String _helpMessage;
0167:
0168: /**
0169: * Creates a CLIPParser Option.
0170: * <P>
0171: *
0172: * @param longName option's long name, it can not be NULL and it has to be at least 2 characters.
0173: *
0174: * @param shortName option's short name, it can be undefined (NULL) or it has to be exactly one character.
0175: *
0176: * @param type indicates the type of the option, it can be BOOLEAN, REGULAR or COLLECTION.
0177: *
0178: * @param defaultValues specifies the option default value if any, NULL indicates no default value.
0179: * If the option has not default value the option is mandatory.
0180: * If the option is a collection the elements have to be separated with commas or white spaces,
0181: * if a white space is present then the commas are consider part of a single value.
0182: * For boolean options the default value can be 'true' or 'false'.
0183: *
0184: * @param argName option argument name
0185: *
0186: * @param helpMessage help message for the option.
0187: *
0188: * @throws IllegalArgumentException if the information passed is invalid.
0189: *
0190: */
0191: public Option(String longName, String shortName, int type,
0192: String defaultValues, String argName, String helpMessage)
0193: throws IllegalArgumentException {
0194: verifyName(longName,
0195: "CLIPParser.Option - Option's long name");
0196: _longName = longName;
0197:
0198: if (shortName != null && shortName.length() > 1) {
0199: throw new IllegalArgumentException(
0200: "CLIPParser.Option - Option's short name has to be undefined (NULL) or one character long");
0201: }
0202: if (shortName != null && shortName.equals("-")) {
0203: throw new IllegalArgumentException(
0204: "CLIPParser.Option - Option's short name can not be '-'");
0205: }
0206: _shortName = shortName;
0207:
0208: _mandatory = defaultValues == null;
0209:
0210: _argName = argName;
0211:
0212: if (helpMessage == null) {
0213: throw new IllegalArgumentException(
0214: "CLIPParser.Option - Option's help can not be NULL");
0215: }
0216: _helpMessage = helpMessage;
0217:
0218: _type = type;
0219: switch (type) {
0220: case BOOLEAN:
0221: if (defaultValues != null) {
0222: if (!defaultValues.equals("true")
0223: && !defaultValues.equals("false")) {
0224: throw new IllegalArgumentException(
0225: "CLIPParser.Option - Boolean default value has to be 'true' or 'false'");
0226: }
0227: _defaultValues = new String[1];
0228: _defaultValues[0] = defaultValues;
0229: }
0230: break;
0231: case REGULAR:
0232: if (defaultValues != null) {
0233: _defaultValues = new String[1];
0234: _defaultValues[0] = defaultValues;
0235: }
0236: break;
0237: case COLLECTION:
0238: if (shortName != null) {
0239: throw new IllegalArgumentException(
0240: "CLIPParser.Option - Collection options can not have short names");
0241: }
0242: if (defaultValues != null) {
0243: List l = new ArrayList();
0244: String tokenSeparator = (defaultValues.indexOf(" ") > -1) ? " "
0245: : ",";
0246: StringTokenizer tokens = new StringTokenizer(
0247: defaultValues, tokenSeparator);
0248: while (tokens.hasMoreTokens()) {
0249: String token = tokens.nextToken();
0250: l.add(token);
0251: }
0252: _defaultValues = new String[l.size()];
0253: l.toArray(_defaultValues);
0254: }
0255: break;
0256: default:
0257: throw new IllegalArgumentException(
0258: "CLIPParser.Option - Invalid type");
0259: }
0260: }
0261:
0262: public Option(String longName, String shortName, int type,
0263: String defaultValues, String helpMessage)
0264: throws IllegalArgumentException {
0265: this (longName, shortName, type, defaultValues, null,
0266: helpMessage);
0267: }
0268: }
0269:
0270: /**
0271: * The SubCommand class defines a CLIP sub command.
0272: * <P>
0273: * A sub command has a set of options, a mininum and maximum number of operands
0274: * and help messages.
0275: * <P>
0276: *
0277: * @author <A HREF="mailto:alejandro.abdelnur@sun.com">Alejandro Abdelnur</A>
0278: *
0279: */
0280: public static class SubCommand {
0281: private String _subCommandName;
0282: private List _orderedOptions;
0283: private Map _validOptions;
0284: private Map _defaultOptionValues;
0285: private int _minOperands;
0286: private int _maxOperands;
0287: private String _subCommandHelp;
0288: private String _operandsHelp;
0289:
0290: /**
0291: * Creates a CLIPParser Option.
0292: * <P>
0293: *
0294: * @param name name of the sub command or NULL if no sub command are expected for this CLIPParser.
0295: *
0296: * @param options array with all the valid options for the sub command, or NULL if the sub command expects no options.
0297: *
0298: * @param minOperands minimum number of operands the sub command expects.
0299: *
0300: * @param maxOperands maximum number of operands the sub command expects.
0301: *
0302: * @param subCommandHelp help message for the sub command.
0303: *
0304: * @param operandsHelp help message for the operands.
0305: *
0306: * @throws IllegalArgumentException if invalid information is passed.
0307: *
0308: */
0309: public SubCommand(String name, Option[] options,
0310: int minOperands, int maxOperands,
0311: String subCommandHelp, String operandsHelp)
0312: throws IllegalArgumentException {
0313: if (name != null) {
0314: verifyName(name, "CLIPParser - Sub command");
0315: }
0316: _subCommandName = name;
0317:
0318: _validOptions = new HashMap();
0319: _orderedOptions = new ArrayList();
0320:
0321: if (options != null) {
0322: for (int i = 0; i < options.length; i++) {
0323: if (options[i] == null) {
0324: throw new IllegalArgumentException(
0325: "CLIPParser.SubCommand - Sub command '"
0326: + name
0327: + "', option instances can not be NULL");
0328: }
0329: if (_validOptions.containsKey(options[i]._longName)) {
0330: throw new IllegalArgumentException(
0331: "CLIPParser.SubCommand - Sub command '"
0332: + name
0333: + "', option long names can not be duplicated within a CLIPParser instance");
0334: }
0335: _validOptions.put(options[i]._longName, options[i]);
0336: _orderedOptions.add(options[i]);
0337: if (options[i]._shortName != null) {
0338: if (_validOptions
0339: .containsKey(options[i]._shortName)) {
0340: throw new IllegalArgumentException(
0341: "CLIPParser.SubCommand - Sub command '"
0342: + name
0343: + "', option short names can not be duplicated within a CLIPParser instance");
0344: }
0345: _validOptions.put(options[i]._shortName,
0346: options[i]);
0347: }
0348: }
0349: }
0350:
0351: _defaultOptionValues = new HashMap();
0352: if (options != null) {
0353: for (int i = 0; i < options.length; i++) {
0354: String[] defaultValues = options[i]._defaultValues;
0355: if (defaultValues != null) {
0356: List l = new ArrayList();
0357: for (int j = 0; j < defaultValues.length; j++) {
0358: l.add(defaultValues[j]);
0359: }
0360: _defaultOptionValues.put(options[i]._longName,
0361: l);
0362: }
0363: }
0364: }
0365:
0366: if (minOperands < 0) {
0367: throw new IllegalArgumentException(
0368: "CLIPParser.SubCommand - Sub command '"
0369: + name
0370: + "', minimum number of operands can not be less than zero");
0371: }
0372: _minOperands = minOperands;
0373:
0374: if (minOperands > maxOperands) {
0375: throw new IllegalArgumentException(
0376: "CLIPParser.SubCommand - Sub command '"
0377: + name
0378: + "', maximum number of operands can not be less than minimum number of operands");
0379: }
0380: _maxOperands = maxOperands;
0381:
0382: if (subCommandHelp == null) {
0383: throw new IllegalArgumentException(
0384: "CLIPParser.SubCommand - Sub command '" + name
0385: + "', sub command help can not be NULL");
0386: }
0387: _subCommandHelp = subCommandHelp;
0388:
0389: if (operandsHelp == null) {
0390: throw new IllegalArgumentException(
0391: "CLIPParser.SubCommand - Sub command '" + name
0392: + "', operands help can not be NULL");
0393: }
0394: _operandsHelp = operandsHelp;
0395: }
0396: }
0397:
0398: private String _commandName;
0399: private String _commandHelpMessage;
0400: private Map _subCommands;
0401: private List _orderedSubCommands;
0402:
0403: /**
0404: * Verifies a name (sub command or long option name ) is well formed.
0405: *
0406: */
0407: private static void verifyName(String name,
0408: String exceptionMsgPrefix) {
0409: if (name == null || name.length() < 2) {
0410: throw new IllegalArgumentException(exceptionMsgPrefix
0411: + " has to be at least 2 chars long");
0412: }
0413: if (name.equals("--")) {
0414: throw new IllegalArgumentException(exceptionMsgPrefix
0415: + " can not be '--'");
0416: }
0417: if (name.startsWith("-") || name.endsWith("-")) {
0418: throw new IllegalArgumentException(exceptionMsgPrefix
0419: + " can not start or end with '-'");
0420: }
0421: if (!name.equals(name.toLowerCase())) {
0422: throw new IllegalArgumentException(exceptionMsgPrefix
0423: + " has to be in lower case");
0424: }
0425: }
0426:
0427: /**
0428: * Creates a CLIP parser instance.
0429: * <P>
0430: *
0431: */
0432: private CLIPParser() {
0433: _subCommands = new HashMap();
0434: _orderedSubCommands = new ArrayList();
0435: }
0436:
0437: /**
0438: * Creates a CLIP parser instance with a set of sub commands.
0439: * <P>
0440: *
0441: * @param commandName name of the command
0442: *
0443: * @param subCommand array of sub commands for the CLIPParser.
0444: *
0445: * @param help message for the command.
0446: *
0447: * @throws IllegalArgumentException if invalid information is passed.
0448: *
0449: */
0450: public CLIPParser(String commandName, SubCommand[] subCommands,
0451: String commandHelpMessage) throws IllegalArgumentException {
0452: this ();
0453: _commandName = commandName;
0454: _commandHelpMessage = commandHelpMessage;
0455: if (subCommands == null || subCommands.length == 0) {
0456: throw new IllegalArgumentException(
0457: "CLIPParser - sub command array can not be null or empty");
0458: }
0459: for (int i = 0; i < subCommands.length; i++) {
0460: if (subCommands[i]._subCommandName == null) {
0461: throw new IllegalArgumentException(
0462: "CLIPParser - Sub command can not be null");
0463: }
0464: addSubCommand(subCommands[i]);
0465: }
0466: }
0467:
0468: /**
0469: * Creates a CLIP parser instance with no sub commands.
0470: * <P>
0471: *
0472: * @param options array with all the valid options for the sub command, or NULL if the sub command expects no options.
0473: *
0474: * @param minOperands minimum number of operands the sub command expects.
0475: *
0476: * @param maxOperands maximum number of operands the sub command expects.
0477: *
0478: * @param commandName name of the command
0479: *
0480: * @param commandHelp help message for the command.
0481: *
0482: * @param operandsHelp help message for the operands.
0483: *
0484: * @throws IllegalArgumentException if invalid information is passed.
0485: *
0486: */
0487: public CLIPParser(String commandName, Option[] options,
0488: int minOperands, int maxOperands,
0489: String commandHelpMessage, String operandsHelp)
0490: throws IllegalArgumentException {
0491: this ();
0492: _commandName = commandName;
0493: _commandHelpMessage = commandHelpMessage;
0494: SubCommand subCommand = new SubCommand(null, options,
0495: minOperands, maxOperands, _commandHelpMessage,
0496: operandsHelp);
0497: addSubCommand(subCommand);
0498: }
0499:
0500: /**
0501: * Adds a sub command and its options to the parser.
0502: *
0503: * <P>
0504: *
0505: * @param name name of the sub command or NULL if no sub command are expected for this CLIPParser.
0506: *
0507: * @param options array with all the valid options for the sub command, or NULL if the sub command expects no options.
0508: *
0509: * @param minOperands minimum number of operands the sub command expects.
0510: *
0511: * @param maxOperands maximum number of operands the sub command expects.
0512: *
0513: * @param subCommandHelp help message for the sub command.
0514: *
0515: * @param operandsHelp help message for the operands.
0516: *
0517: * @throws IllegalArgumentException if invalid information is passed.
0518: *
0519: */
0520: private void addSubCommand(SubCommand subCommand)
0521: throws IllegalArgumentException {
0522: if (subCommand._subCommandName != null) {
0523: verifyName(subCommand._subCommandName,
0524: "CLIPParser - Sub command");
0525: }
0526: if (_subCommands.containsKey(subCommand._subCommandName)) {
0527: throw new IllegalArgumentException(
0528: "CLIPParser - Sub command '"
0529: + subCommand._subCommandName
0530: + "', sub command already defined");
0531: }
0532:
0533: _subCommands.put(subCommand._subCommandName, subCommand);
0534: _orderedSubCommands.add(subCommand);
0535: }
0536:
0537: /**
0538: * Verifies the argument array is valid for the CLIP parser definition.
0539: * <P>
0540: *
0541: * @param args argument array to verify.
0542: *
0543: * @throws CLIPException thrown if the argument array could not be processed successfuly.
0544: *
0545: */
0546: public void verifyArguments(String[] args) throws CLIPException {
0547: boolean skip = args.length == 1
0548: && (args[0].equals("--help") || (args[0].equals("-?")));
0549: if (!skip) {
0550: getSubCommand(args);
0551: skip = args.length == 2
0552: && (args[1].equals("--help") || (args[0]
0553: .equals("-?")));
0554: if (!skip) {
0555: getOptions(args);
0556: getOperands(args);
0557: }
0558: }
0559: }
0560:
0561: /**
0562: * Returns the sub command from the argument array.
0563: * <P>
0564: *
0565: * @param args argument array to parse for sub command.
0566: *
0567: * @return the entered sub command or NULL if no sub command was found.
0568: *
0569: * @throws CLIPException thrown if the sub command is invalid.
0570: *
0571: */
0572: public String getSubCommand(String[] args) throws CLIPException {
0573: boolean hasSubCommands = !_subCommands.containsKey(null);
0574: String firstArgument = (args.length > 0 && hasSubCommands) ? args[0]
0575: : null;
0576: SubCommand subCommand = (SubCommand) _subCommands
0577: .get(firstArgument);
0578: if (subCommand == null) {
0579: throw new CLIPException("Invalid sub command: "
0580: + firstArgument, 0);
0581: }
0582: return firstArgument;
0583: }
0584:
0585: /**
0586: * Returns a Map with all the (key,value) for all the options.
0587: * <P>
0588: * The key is the option long name.
0589: * <P>
0590: * The value is always a String array, it's length is always one except if it's a collection option, then the
0591: * length is the number of collection values in the option.
0592: * <P>
0593: * If the option is a boolean option (not option value or it a negated option "--no-<OPTION>") the value will be
0594: * <B>true</B> or <B>false</B>.
0595: * <P>
0596: *
0597: * @param args argument array to parse for options.
0598: *
0599: * @return a Map instance with all the options of the argument array.
0600: *
0601: * @throws CLIPException thrown if there are invalid or incorrect options in the argument array or if mandatory options
0602: * are missing from the argument array.
0603: *
0604: */
0605: public Map getOptions(String[] args) throws CLIPException {
0606: SubCommand subCommand = (SubCommand) _subCommands
0607: .get(getSubCommand(args));
0608:
0609: Map map = new HashMap();
0610: Iterator i = subCommand._defaultOptionValues.keySet()
0611: .iterator();
0612: while (i.hasNext()) {
0613: String optionName = (String) i.next();
0614: List optionValues = (List) subCommand._defaultOptionValues
0615: .get(optionName);
0616: map.put(optionName, ((ArrayList) optionValues).clone());
0617: }
0618:
0619: int argumentIndex = processOptions(args, map);
0620: i = subCommand._validOptions.keySet().iterator();
0621: while (i.hasNext()) {
0622: String optionName = (String) i.next();
0623: Option option = (Option) subCommand._validOptions
0624: .get(optionName);
0625: if (option._defaultValues == null
0626: && !map.containsKey(option._longName)) {
0627: throw new CLIPException("Missing mandatory option: "
0628: + option._longName, argumentIndex);
0629: }
0630: }
0631: return massageOptions(map);
0632: }
0633:
0634: /**
0635: * Returns a String array with all the operands of the argument array.
0636: * <P>
0637: * All the operands after the options end or after the operand demarcator "--".
0638: * <P>
0639: *
0640: * @param args argument array to parse for operands.
0641: *
0642: * @return all the operands from the argument array, if the argument array has no operands it returns an empty array.
0643: *
0644: * @throws CLIPException thrown if the operands can not be process because errors in the options or if the number
0645: * of operands in the argument array is outside of the minimum and maximum operand boundaries.
0646: *
0647: */
0648: public String[] getOperands(String[] args) throws CLIPException {
0649: SubCommand subCommand = (SubCommand) _subCommands
0650: .get(getSubCommand(args));
0651:
0652: int i = processOptions(args, new HashMap());
0653:
0654: int numberOfOperands = args.length - i;
0655: if (subCommand._minOperands > numberOfOperands
0656: || numberOfOperands > subCommand._maxOperands) {
0657: throw new CLIPException("Invalid number of operands", i);
0658: }
0659:
0660: for (int j = i; j < args.length; j++) {
0661: if (args[j].equals("--")) {
0662: throw new CLIPException("'--' can not be an operand", j);
0663: }
0664: }
0665:
0666: String[] operands = new String[numberOfOperands];
0667: if (args.length > 0) {
0668: System.arraycopy(args, i, operands, 0, numberOfOperands);
0669: }
0670: return operands;
0671: }
0672:
0673: /**
0674: * Process options and returns index where operands start.
0675: * <P>
0676: * Used by getOptions and getOperands.
0677: *
0678: */
0679: private int processOptions(String[] args, Map map)
0680: throws CLIPException {
0681: SubCommand subCommand = (SubCommand) _subCommands
0682: .get(getSubCommand(args));
0683:
0684: boolean allOptionsProcessed = false;
0685: int i = (subCommand._subCommandName != null) ? 1 : 0;
0686: while (!allOptionsProcessed && i < args.length) {
0687:
0688: if (args[i].startsWith(ADD_TO_COLLECTION_OPTION)) {
0689: String optionName = args[i]
0690: .substring(ADD_TO_COLLECTION_OPTION.length());
0691: Option option = (Option) subCommand._validOptions
0692: .get(optionName);
0693: if (optionName.length() < 2 || option == null) {
0694: throw new CLIPException("Invalid option: "
0695: + optionName, i);
0696: }
0697: if (option._type != COLLECTION) {
0698: throw new CLIPException("Not a collection option: "
0699: + optionName, i);
0700: }
0701: List values = getOptionValues(subCommand, map,
0702: optionName);
0703: if ((i + 1) == args.length) {
0704: throw new CLIPException(
0705: "Missing collection values to add to option: "
0706: + optionName, i);
0707: } else {
0708: i++;
0709: String optionValue = args[i];
0710: StringTokenizer tokens = new StringTokenizer(
0711: optionValue,
0712: (optionValue.indexOf(" ") > -1) ? " " : ",");
0713: while (tokens.hasMoreTokens()) {
0714: String token = tokens.nextToken();
0715: values.add(token);
0716: }
0717: }
0718: } else if (args[i]
0719: .startsWith(REMOVE_FROM_COLLECTION_OPTION)) {
0720: String optionName = args[i]
0721: .substring(REMOVE_FROM_COLLECTION_OPTION
0722: .length());
0723: Option option = (Option) subCommand._validOptions
0724: .get(optionName);
0725: if (optionName.length() < 2 || option == null) {
0726: throw new CLIPException("Invalid option: "
0727: + optionName, i);
0728: }
0729: if (option._type != COLLECTION) {
0730: throw new CLIPException("Not a collection option: "
0731: + optionName, i);
0732: }
0733: if ((i + 1) == args.length) {
0734: throw new CLIPException(
0735: "Missing collection values to delete from option: "
0736: + optionName, i);
0737: } else {
0738: i++;
0739: List values = getOptionValues(subCommand, map,
0740: optionName);
0741: String optionValue = args[i];
0742: StringTokenizer tokens = new StringTokenizer(
0743: optionValue,
0744: (optionValue.indexOf(" ") > -1) ? " " : ",");
0745: while (tokens.hasMoreTokens()) {
0746: String token = tokens.nextToken();
0747: if (values.contains(token)) {
0748: values.remove(token);
0749: } else { // if the option value is name/value pair, removes if name is specified
0750: for (int j = 0; j < values.size(); j++) {
0751: String v = (String) values.get(j);
0752: if (v.startsWith(token + "=")) {
0753: values.remove(j);
0754: }
0755: }
0756: }
0757: }
0758: }
0759: } else if (args[i].equals(NO_MORE_OPTIONS)) { // only operands follow, stop processing options
0760: i++;
0761: allOptionsProcessed = true;
0762: } else if (args[i].startsWith(NEGATE_OPTION)) {
0763: String optionName = args[i].substring(NEGATE_OPTION
0764: .length());
0765: Option option = (Option) subCommand._validOptions
0766: .get(optionName);
0767: if (option == null) {
0768: throw new CLIPException("Invalid option: "
0769: + optionName, i);
0770: }
0771: if (option._type != BOOLEAN) {
0772: throw new CLIPException("Not a boolean option: "
0773: + optionName, i);
0774: }
0775: String optionValue = "false";
0776: List values = getOptionValues(subCommand, map,
0777: optionName);
0778: values.clear();
0779: values.add(optionValue);
0780: } else if (args[i].startsWith(LONG_OPTION)) {
0781: String optionName = args[i].substring(LONG_OPTION
0782: .length());
0783: Option option = (Option) subCommand._validOptions
0784: .get(optionName);
0785: if (option == null) {
0786: throw new CLIPException("Invalid option: "
0787: + optionName, i);
0788: }
0789:
0790: if (option._type == BOOLEAN) {
0791: String optionValue = "true";
0792: List values = getOptionValues(subCommand, map,
0793: optionName);
0794: values.clear();
0795: values.add(optionValue);
0796: } else if (option._type == REGULAR) {
0797: if ((i + 1) == args.length) {
0798: throw new CLIPException(
0799: "Missing value for regular option: "
0800: + optionName, i);
0801: } else {
0802: i++;
0803: String optionValue = args[i];
0804: List values = getOptionValues(subCommand, map,
0805: optionName);
0806: values.clear();
0807: values.add(optionValue);
0808: }
0809: } else if (option._type == COLLECTION) {
0810: if ((i + 1) == args.length) {
0811: throw new CLIPException(
0812: "Missing value for collection option: "
0813: + optionName, i);
0814: } else {
0815: i++;
0816: String optionValue = args[i];
0817: List values = getOptionValues(subCommand, map,
0818: optionName);
0819: values.clear();
0820: StringTokenizer tokens = new StringTokenizer(
0821: optionValue,
0822: (optionValue.indexOf(" ") > -1) ? " "
0823: : ",");
0824: while (tokens.hasMoreTokens()) {
0825: String token = tokens.nextToken();
0826: values.add(token);
0827: }
0828: }
0829: } else {
0830: throw new CLIPException(
0831: "CLIPParser - Internal error", i);
0832: }
0833: } else if (args[i].startsWith(SHORT_OPTION)) {
0834: String optionName = args[i].substring(SHORT_OPTION
0835: .length());
0836:
0837: // process a set of short options within a single -, all but the last have to be of boolean type
0838: for (int j = 0; j < optionName.length() - 1; j++) {
0839: String shortOptionName = "" + optionName.charAt(j);
0840: Option option = (Option) subCommand._validOptions
0841: .get(shortOptionName);
0842: if (option == null) {
0843: throw new CLIPException("Invalid option: "
0844: + shortOptionName, i);
0845: }
0846: if (option._type == BOOLEAN) {
0847: String optionValue = "true";
0848: List values = getOptionValues(subCommand, map,
0849: shortOptionName);
0850: values.clear();
0851: values.add(optionValue);
0852: } else {
0853: throw new CLIPException(
0854: "Not an boolean option: "
0855: + shortOptionName, i);
0856: }
0857: }
0858:
0859: optionName = ""
0860: + optionName.charAt(optionName.length() - 1);
0861: Option option = (Option) subCommand._validOptions
0862: .get(optionName);
0863: if (option == null) {
0864: throw new CLIPException("Invalid option: "
0865: + optionName, i);
0866: }
0867:
0868: if (option._type == BOOLEAN) {
0869: String optionValue = "true";
0870: List values = getOptionValues(subCommand, map,
0871: optionName);
0872: values.clear();
0873: values.add(optionValue);
0874: } else if (option._type == REGULAR) {
0875: if ((i + 1) == args.length) {
0876: throw new CLIPException(
0877: "Missing value for regular option: "
0878: + optionName, i);
0879: } else {
0880: i++;
0881: String optionValue = args[i];
0882: List values = getOptionValues(subCommand, map,
0883: optionName);
0884: values.clear();
0885: values.add(optionValue);
0886: }
0887: } else {
0888: throw new CLIPException(
0889: "CLIPParser - Internal error", i);
0890: }
0891: } else {
0892: allOptionsProcessed = true;
0893: }
0894:
0895: i++;
0896: }
0897:
0898: if (allOptionsProcessed) {
0899: i--;
0900: }
0901:
0902: return i;
0903: }
0904:
0905: /**
0906: * Maps from short to long options and creates option value list.
0907: *
0908: */
0909: private List getOptionValues(SubCommand subCommand, Map map,
0910: String optionName) {
0911: Option option = (Option) subCommand._validOptions
0912: .get(optionName);
0913: String longName = option._longName;
0914: List list = (List) map.get(longName);
0915: if (list == null) {
0916: list = new ArrayList();
0917: map.put(longName, list);
0918: }
0919: return list;
0920: }
0921:
0922: /**
0923: * Converts Map entries from List to String[].
0924: *
0925: */
0926: private Map massageOptions(Map map) {
0927: Iterator i = map.keySet().iterator();
0928: while (i.hasNext()) {
0929: String option = (String) i.next();
0930: List values = (List) map.get(option);
0931: String[] arrayValues = new String[values.size()];
0932: values.toArray(arrayValues);
0933: map.put(option, arrayValues);
0934: }
0935: return map;
0936: }
0937:
0938: /**
0939: * Returns the name part of a name/value pair element.
0940: * <P>
0941: * It uses '=' as the name/value separator.
0942: * <P>
0943: *
0944: * @param element the option or operand name/value pair element.
0945: *
0946: * @return the name portion of the name/value pair or the complete
0947: * element if there is no value.
0948: *
0949: */
0950: public static String getName(String element) {
0951: String name = element;
0952: int index = element.indexOf("=");
0953: if (index > -1) {
0954: name = element.substring(0, index);
0955: }
0956: return name;
0957: }
0958:
0959: /**
0960: * Returns the value part of a name/value pair element.
0961: * <P>
0962: * It uses '=' as the name/value separator.
0963: * <P>
0964: *
0965: * @param element the option or operand name/value pair element.
0966: *
0967: * @return the value portion of the name/value pair or <B>null</B>
0968: * if there is not value in the element.
0969: *
0970: */
0971: public static String getValue(String element) {
0972: String value = null;
0973: int index = element.indexOf("=");
0974: if (index > -1) {
0975: value = element.substring(index + 1);
0976: }
0977: return value;
0978: }
0979:
0980: /**
0981: * String representation of the processed command arguments.
0982: * <P>
0983: *
0984: * @param args argument array
0985: *
0986: * @return String representation.
0987: *
0988: * @throws CLIPException thrown if the argument array could not be processed successfuly.
0989: *
0990: */
0991: public String toString(String[] args) throws CLIPException {
0992: StringBuffer sb = new StringBuffer(1024);
0993: String subCmd = getSubCommand(args);
0994: Map options = getOptions(args);
0995: String[] operands = getOperands(args);
0996:
0997: sb.append("\n").append("Sub-command: ").append(subCmd).append(
0998: "\n").append("\n").append("Options: ").append("\n");
0999: Iterator i = options.keySet().iterator();
1000: while (i.hasNext()) {
1001: String option = (String) i.next();
1002: String[] values = (String[]) options.get(option);
1003: sb.append((" " + option + " ").substring(0,
1004: 12)
1005: + ": ");
1006: for (int j = 0; j < values.length; j++) {
1007: sb.append(values[j]).append(" ");
1008: }
1009: sb.append("\n");
1010: }
1011: sb.append("\n").append("Operands: ").append("\n");
1012: for (int j = 0; j < operands.length; j++) {
1013: sb.append(" ").append(operands[j]).append("\n");
1014: }
1015: sb.append("\n");
1016: return sb.toString();
1017: }
1018:
1019: /**
1020: * Returns if the command invocation needs help or not.
1021: * <P>
1022: * It may be an exlicit request for help, using the --help (-?) option,
1023: * or because an invalid argument set has been given.
1024: * <P>
1025: *
1026: * @param args the array argument.
1027: *
1028: * @return <B>true</B> if help is needed, <B>false</B> otherwise.
1029: *
1030: */
1031: public boolean needsHelp(String[] args) {
1032: boolean help = false;
1033: try {
1034: verifyArguments(args);
1035: Map options = getOptions(args);
1036: help = options.containsKey("help")
1037: && ((String[]) options.get("help"))[0]
1038: .equals("true");
1039: } catch (CLIPException ex) {
1040: help = true;
1041: }
1042: return help;
1043: }
1044:
1045: /**
1046: * Returns the Help for the command.
1047: * <P>
1048: * Using the current arguments determines if a full help or a sub command help
1049: * should be returned.
1050: * <P>
1051: *
1052: * @param args the current argument array, to determine the type of help to return.
1053: *
1054: * @return the help for the command.
1055: *
1056: */
1057: public String getHelp(String[] args) {
1058: boolean fullHelp = false;
1059: String firstArgument = (args.length > 0) ? args[0] : null;
1060: fullHelp = !_subCommands.containsKey(firstArgument);
1061: SubCommand subCommand;
1062:
1063: if (!fullHelp) {
1064: subCommand = (SubCommand) _subCommands.get(firstArgument);
1065: } else {
1066: subCommand = (SubCommand) _subCommands.get(null);
1067: fullHelp = subCommand == null
1068: || subCommand._subCommandName != null;
1069: }
1070:
1071: StringBuffer sb = new StringBuffer(2048);
1072:
1073: if (fullHelp) {
1074: sb.append("Usage: " + _commandName
1075: + " SUBCOMMAND ARGUMENTS\n\n");
1076: sb.append(_commandHelpMessage).append("\n\n");
1077: sb.append("The accepted values for SUBCOMMAND are:\n\n");
1078: int size = _orderedSubCommands.size();
1079: for (int i = 0; i < size; i++) {
1080: subCommand = (SubCommand) _orderedSubCommands.get(i);
1081: String name = subCommand._subCommandName;
1082: if (name == null) {
1083: name = "<NO SUB COMMAND> ";
1084: } else {
1085: name = name + "\t";
1086: }
1087: sb.append(name).append(subCommand._subCommandHelp)
1088: .append(".\n");
1089: }
1090: } else {
1091: sb.append("Usage: " + _commandName + " "
1092: + subCommand._subCommandName + " [OPTIONS...]\n\n");
1093: sb.append(subCommand._subCommandHelp).append(".\n\n");
1094: doOptionsHelp(sb, "", subCommand._orderedOptions);
1095: if (subCommand._maxOperands > 0) {
1096: if (subCommand._maxOperands == 1) {
1097: sb.append("\nOPERAND\n");
1098: } else {
1099: sb.append("\nOPERANDS\n");
1100: }
1101: sb.append(" ").append(subCommand._operandsHelp)
1102: .append("\n");
1103: }
1104: }
1105: return sb.toString();
1106: }
1107:
1108: /**
1109: * Returns if the command invocation needs version or not.
1110: * <P>
1111: * When version is detected anywhere in the options, no further parsing
1112: * is performed. A version option is considered to be one of the
1113: * followings: <code>"-V"</code>, <code>"--version"</code> or
1114: * <code>"--version=true"</code>.
1115: * <P>
1116: *
1117: * @param args the array argument.
1118: *
1119: * @return <B>true</B> if version is needed, <B>false</B> otherwise.
1120: *
1121: */
1122: public boolean needsVersion(String[] args) {
1123: for (int i = 0; i < args.length; i++) {
1124: if (args[i].equals("-V") || args[i].equals("--version")) {
1125: return true;
1126: }
1127: }
1128: return false;
1129: }
1130:
1131: /**
1132: * Renders the help for a seto of options, used by getHelp.
1133: *
1134: */
1135: private void doOptionsHelp(StringBuffer sb, String padding,
1136: List options) {
1137: int size = options.size();
1138: for (int i = 0; i < size; i++) {
1139: Option option = (Option) options.get(i);
1140: sb.append(padding);
1141: if (option._shortName != null) {
1142: sb.append('-').append(option._shortName);
1143: if (option._argName != null) {
1144: sb.append(" ").append(option._argName);
1145: }
1146: sb.append(", ");
1147: }
1148: sb.append("--").append(option._longName);
1149: if (option._argName != null) {
1150: sb.append("=").append(option._argName).append("\n\t");
1151: }
1152: sb.append("\t").append(option._helpMessage).append(" (");
1153: if (option._defaultValues == null) {
1154: sb.append("none");
1155: } else {
1156: for (int j = 0; j < option._defaultValues.length; j++) {
1157: if (j > 0) {
1158: sb.append(' ');
1159: }
1160: sb.append(option._defaultValues[j]);
1161: }
1162: }
1163: sb.append(")\n\n");
1164: }
1165: }
1166:
1167: }
|