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