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