1 /* 2 * BasicCmdLineHandler.java 3 * 4 * Classes: 5 * public BasicCmdLineHandler 6 * 7 * ***** BEGIN LICENSE BLOCK ***** 8 * Version: MPL 1.1 9 * 10 * The contents of this file are subject to the Mozilla Public License Version 11 * 1.1 (the "License"); you may not use this file except in compliance with 12 * the License. You may obtain a copy of the License at 13 * http://www.mozilla.org/MPL/ 14 * 15 * Software distributed under the License is distributed on an "AS IS" basis, 16 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 17 * for the specific language governing rights and limitations under the 18 * License. 19 * 20 * The Original Code is the Java jcmdline (command line management) package. 21 * 22 * The Initial Developer of the Original Code is Lynne Lawrence. 23 * 24 * Portions created by the Initial Developer are Copyright (C) 2002 25 * the Initial Developer. All Rights Reserved. 26 * 27 * Contributor(s): Lynne Lawrence <lgl@visuallink.com> 28 * 29 * ***** END LICENSE BLOCK ***** 30 */ 31 32 package jcmdline; 33 34 import java.util.ArrayList; 35 import java.util.Collection; 36 import java.util.HashMap; 37 import java.util.List; 38 39 /** 40 * Used to define, parse, and validate the parameters associated with an 41 * executable's command line. 42 * <P> 43 * Objects of this class use a {@link CmdLineParser} to do the actual parsing of 44 * the command line. By default, this class uses a {@link PosixCmdLineParser}. 45 * <p> 46 * The following semantics are used throughout the documentation for this class. 47 * A command line <i>parameter</i> refers to either a command line 48 * <i>option</i>, or a command line <i>argument</i>. 49 * <P> 50 * A command line <i>option</i> is identified by a <i>tag</i> and may or not 51 * have an associated value. For instance, in the following posix-type option, 52 * "outfile" is the <i>tag</i> and "/tmp/myoutfile" is the <i>value</i>: 53 * 54 * <pre> 55 * --outfile / tmp / myoutfile 56 * </pre> 57 * <P> 58 * Command line <i>arguments</i> are what are left on the command line after all 59 * options have been processed. For example, again using a posix style command 60 * line, "filename1" and "filename2" would be the <i>arguments</i>: 61 * 62 * <pre> 63 * -e 's/red/blue/' filename1 filename2 64 * </pre> 65 * <P> 66 * Parameters may be designated as <i>hidden</i>. <i>Hidden parameters</i> are 67 * those that can be specified, but whose documentation is not displayed to the 68 * user when a normal usage is printed. 69 * <P> 70 * This class is used as in the following example of a 'cat' facsimile in java: 71 * 72 * <pre> 73 * public class Concat { 74 * static FileParam outfile = new FileParam("out", 75 * "a file to receive the concatenated files (default is stdout)"); 76 * static BooleanParam delete = new BooleanParam("delete", 77 * "specifies that all of the original files are to be deleted"); 78 * static FileParam infiles = new FileParam("filename", 79 * "files to be concatenated", FileParam.IS_FILE 80 * & FileParam.IS_READABLE, FileParam.REQUIRED, 81 * FileParam.MULTI_VALUED); 82 * 83 * public static void main(String[] args) { 84 * outfile.setOptionLabel("outfile"); 85 * BasicCmdLineHandler clp = new BasicCmdLineHandler( 86 * "Concat", "concatenates the specified files", 87 * new Parameter[] { outfile, delete }, 88 * new Parameter[] { infiles }); 89 * clp.parse(args); 90 * 91 * if (outfile.isSet()) { 92 * .... 93 * } else { 94 * ... 95 * } 96 * for (Iterator itr = infiles.getFiles().iterator(); itr.hasNext(); ) { 97 * ... 98 * } 99 * if (delete.isTrue()) { 100 * ... 101 * } 102 * } 103 * } 104 * 105 * </pre> 106 * <P> 107 * This class implements no options on its own. It it typically used in 108 * conjunction with one or more of the {@link AbstractHandlerDecorator} classes 109 * that provide some useful options. 110 * <P> 111 * <b>Post Processing Command Line Parameters</b> 112 * <P> 113 * It may be the case that none of the supplied Parameter types fully 114 * accomodates a program's needs. For instance, a program may require a filename 115 * option that is an html filename, ending with '.html'. In this case, the 116 * programmer has the options of creating their own Parameter subclass, or 117 * post-processing the returned FileParam parameter and generating their own 118 * error message. The {@link #exitUsageError(String) exitUsageError()} method is 119 * provided so that programs that post-process parameters can take the same exit 120 * as would be taken for "normal" parameter processing failures. For instance, 121 * in the case just described, the following code could be used to exit the 122 * program if the specified file did not end with '.html' (<code>myfile</code> 123 * being a FileParam object, and <code>cl</code> being the BasicCmdLineHandler 124 * object): 125 * 126 * <pre> 127 * cl.parse(args); 128 * if (!myfile.getFile().getPath().endsWith(".html")) { 129 * cl.exitUsageError("Filename specified for '" + myfile.getTag() 130 * + "' must end with '.html'"); 131 * } 132 * </pre> 133 * 134 * @author Lynne Lawrence 135 * @version jcmdline Rel. @VERSION@ $Id: BasicCmdLineHandler.java,v 1.4 136 * 2005/02/06 13:19:19 lglawrence Exp $ 137 * @see Parameter 138 * @see CmdLineParser 139 */ 140 public class BasicCmdLineHandler implements CmdLineHandler { 141 142 /** 143 * the arguments (after the options have been processed) associated with the 144 * command 145 */ 146 private ArrayList<Parameter<?>> args = new ArrayList<Parameter<?>>(); 147 148 /** 149 * a short description of the command's purpose 150 */ 151 private String cmdDesc; 152 153 /** 154 * the name of the command whose command line is to be parsed 155 */ 156 private String cmdName; 157 158 /** 159 * indicates that the command usage should be displayed, and System.exit(1) 160 * called in the case of a parse error. 161 * 162 * @see #setDieOnParseError(boolean) setDieOnParseError() 163 */ 164 private boolean dieOnParseError = true; 165 166 /** 167 * the options associated with the command 168 */ 169 private HashMap<String, Parameter<?>> options = new HashMap<String, Parameter<?>>(); 170 171 /** 172 * the error message from the last call to parse() 173 * 174 * @see #setParseError(String) setParseError() 175 * @see #getParseError() 176 */ 177 private String parseError; 178 179 /** 180 * the parser to be used to parse the command line 181 * 182 * @see #setParser(CmdLineParser) setParser() 183 * @see #getParser() 184 */ 185 private CmdLineParser parser; 186 187 /** 188 * constructor - uses the PosixCmdLineParser to parse the command line 189 * 190 * @param cmdName 191 * the name of the command creating this BasicCmdLineHandler 192 * @param cmdDesc 193 * a short description of the command's purpose 194 * @param options 195 * a collection of Parameter objects, describing the command's 196 * command-line options 197 * @param args 198 * a collection of Parameter objects, describing the command's 199 * command-line arguments (what is left on the command line after 200 * all options and their parameters have been processed) 201 * @throws IllegalArgumentException 202 * if any of the parameters are not correctly specified. 203 * @see #setCmdName(String) setCmdName() 204 * @see #setCmdDesc(String) setCmdDesc() 205 * @see #setOptions(Parameter[]) setOptions() 206 * @see PosixCmdLineParser 207 */ BasicCmdLineHandler(String cmdName, String cmdDesc, Collection<Parameter<?>> options, Collection<Parameter<?>> args)208 public BasicCmdLineHandler(String cmdName, String cmdDesc, 209 Collection<Parameter<?>> options, Collection<Parameter<?>> args) { 210 this(cmdName, cmdDesc, (Parameter[]) options.toArray(new Parameter[0]), 211 (Parameter[]) args.toArray(new Parameter[0])); 212 } 213 214 /** 215 * constructor - uses the PosixCmdLineParser to parse the command line 216 * 217 * @param cmdName 218 * the name of the command 219 * @param cmdDesc 220 * a short description of the command 221 * @param options 222 * a collection of Parameter objects, describing the command's 223 * command-line options 224 * @param args 225 * a collection of Parameter objects, describing the command's 226 * command-line arguments (what is left on the command line after 227 * all options and their parameters have been processed) 228 * @throws IllegalArgumentException 229 * if any of the parameters are not correctly specified. 230 * @see #setCmdName(String) setCmdName() 231 * @see #setCmdDesc(String) setCmdDesc() 232 * @see #setOptions(Parameter[]) setOptions() 233 * @see #setArgs(Parameter[]) setArgs() 234 * @see PosixCmdLineParser 235 */ BasicCmdLineHandler(String cmdName, String cmdDesc, Parameter<?>[] options, Parameter<?>[] args)236 public BasicCmdLineHandler(String cmdName, String cmdDesc, 237 Parameter<?>[] options, Parameter<?>[] args) { 238 this(cmdName, cmdDesc, options, args, new PosixCmdLineParser()); 239 } 240 241 /** 242 * constructor 243 * 244 * @param cmdName 245 * the name of the command 246 * @param cmdDesc 247 * a short description of the command 248 * @param options 249 * a collection of Parameter objects, describing the command's 250 * command-line options 251 * @param args 252 * a collection of Parameter objects, describing the command's 253 * command-line arguments (what is left on the command line after 254 * all options and their parameters have been processed) 255 * @param parser 256 * a CmdLineParser to be used to parse the command line 257 * @throws IllegalArgumentException 258 * if any of the parameters are not correctly specified. 259 * @see #setCmdName(String) setCmdName() 260 * @see #setCmdDesc(String) setCmdDesc() 261 * @see #setOptions(Parameter[]) setOptions() 262 * @see #setArgs(Parameter[]) setArgs() 263 * @see #setParser(CmdLineParser) setParser() 264 */ BasicCmdLineHandler(String cmdName, String cmdDesc, Parameter<?>[] options, Parameter<?>[] args, CmdLineParser parser)265 public BasicCmdLineHandler(String cmdName, String cmdDesc, 266 Parameter<?>[] options, Parameter<?>[] args, CmdLineParser parser) { 267 268 setCmdName(cmdName); 269 setCmdDesc(cmdDesc); 270 setOptions(options); 271 setArgs(args); 272 setParser(parser); 273 } 274 275 /** 276 * Adds a command line arguement. Command line arguments must be added in 277 * the order that they will be specified on the command line. 278 * 279 * @param arg 280 * the new command line argument 281 * @throws IllegalArgumentException 282 * if: 283 * <ul> 284 * <li><code>arg</code> is null <li>the previously added 285 * argument was multi-valued (only the last argument can be 286 * multi-valued) <li><code>arg</code> is a required argument but 287 * the previous argument was optional 288 * </ul> 289 */ addArg(Parameter<?> arg)290 public void addArg(Parameter<?> arg) { 291 if (arg == null) { 292 throw new IllegalArgumentException(Strings 293 .get("BasicCmdLineHandler.nullArgNotAllowed")); 294 } 295 if (args.size() > 0) { 296 Parameter<?> lastArg = args.get(args.size() - 1); 297 if (lastArg.isMultiValued()) { 298 throw new IllegalArgumentException(Strings.get( 299 "BasicCmdLineHandler.multiValueArgNotLast", 300 new Object[] { lastArg.getTag() })); 301 } 302 if (!arg.isOptional() && lastArg.isOptional()) { 303 throw new IllegalArgumentException(Strings.get( 304 "BasicCmdLineHandler.requiredArgAfterOptArg", 305 new Object[] { arg.getTag(), lastArg.getTag() })); 306 } 307 } 308 args.add(arg); 309 } 310 311 /** 312 * Adds a command line option. 313 * 314 * @param opt 315 * the new command line option 316 * @throws NullPointerException 317 * if <code>opt</code> is null. 318 * @throws IllegalArgumentException 319 * if an option with the same tag has already been added. 320 */ addOption(Parameter<?> opt)321 public void addOption(Parameter<?> opt) { 322 if (opt == null) { 323 throw new NullPointerException(); 324 } 325 if (options.containsKey(opt.getTag())) { 326 throw new IllegalArgumentException(Strings.get( 327 "BasicCmdLineHandler.duplicateOption", new Object[] { opt 328 .getTag() })); 329 } 330 options.put(opt.getTag().toLowerCase(), opt); 331 } 332 333 /** 334 * Prints the usage, followed by the specified error message, to stderr and 335 * exits the program with exit status = 1. The error message will be 336 * prefaced with 'ERROR: '. This method never returns - it exits the program 337 * with exit status of 1. 338 * 339 * @param errMsg 340 * the error message 341 */ exitUsageError(String errMsg)342 public void exitUsageError(String errMsg) { 343 System.err.println(getUsage(false)); 344 System.err.println("\n" 345 + parser.getUsageFormatter().formatErrorMsg(errMsg)); 346 quitProgram(1); 347 } 348 349 /** 350 * gets the argument specified by <code>tag</code> 351 * 352 * @param tag 353 * identifies the argument to be returned 354 * @return The argument associated with <code>tag</code>. If no matching 355 * argument is found, null is returned. 356 */ getArg(String tag)357 public Parameter<?> getArg(String tag) { 358 for (Parameter<?> arg : args) { 359 if (arg.getTag().equals(tag)) { 360 return arg; 361 } 362 } 363 return null; 364 } 365 366 /** 367 * gets the value of the arguments (what is left on the command line after 368 * all options, and their parameters, have been processed) associated with 369 * the command 370 * 371 * @return the command's options 372 */ getArgs()373 public List<Parameter<?>> getArgs() { 374 return args; 375 } 376 377 /** 378 * gets a description of the command's purpose 379 * 380 * @return the command's description 381 */ getCmdDesc()382 public String getCmdDesc() { 383 return cmdDesc; 384 } 385 386 /** 387 * gets the value of the command name associated with this 388 * BasicCmdLineHandler 389 * 390 * @return the command name 391 */ getCmdName()392 public String getCmdName() { 393 return cmdName; 394 } 395 396 /** 397 * Gets a flag indicating that the program should exit in the case of a 398 * parse error (after displaying the usage and an error message). 399 * 400 * @return <code>true</code> (the default) if the <code> 401 * parse</code> 402 * method should call System.exit() in case of a parse error, 403 * <code>false</code> if <code>parse()</code> should return to the 404 * user for error processing. 405 * @see #parse(String[]) parse() 406 */ getDieOnParseError()407 public boolean getDieOnParseError() { 408 return dieOnParseError; 409 } 410 411 /** 412 * gets the option specified by <code>tag</code> 413 * 414 * @param tag 415 * identifies the option to be returned 416 * @return the option associated with <code>tag</code> 417 */ getOption(String tag)418 public Parameter<?> getOption(String tag) { 419 return options.get(tag); 420 } 421 422 /** 423 * gets the value of the options associated with the command 424 * 425 * @return the command's options 426 */ getOptions()427 public Collection<Parameter<?>> getOptions() { 428 return options.values(); 429 } 430 431 /** 432 * Gets the error message from the last call to parse(). 433 * 434 * @return the error message from the last call to parse() 435 * @see #setParseError(String) setParseError() 436 */ getParseError()437 public String getParseError() { 438 return parseError; 439 } 440 441 /** 442 * Gets the parser to be used to parse the command line. 443 * 444 * @return the parser to be used to parse the command line 445 * @see #setParser(CmdLineParser) setParser() 446 */ getParser()447 public CmdLineParser getParser() { 448 return parser; 449 } 450 451 /** 452 * Gets the usage statement associated with the command. 453 * 454 * @param hidden 455 * indicates whether hidden options are to be included in the 456 * usage. 457 * @return the usage statement associated with the command 458 */ getUsage(boolean hidden)459 public String getUsage(boolean hidden) { 460 return parser.getUsageFormatter().formatUsage(cmdName, cmdDesc, 461 options, args, hidden); 462 } 463 464 /** 465 * Parse the specified command line arguments. This method will fail if: 466 * <ul> 467 * <li>the CmdLineParser is unable to parse the command line parameters into 468 * the required options and arguments. <li>a required Parameter has not been 469 * set by the parser. 470 * </ul> 471 * 472 * @param clargs 473 * command line arguments passed to the main() method of 474 * BasicCmdLineHandler's creating class. 475 * @return If <code>dieOnParseError</code> is set to <code>false</code>, 476 * this method will return true if there are no parse errors. If 477 * there are parse errors, <code>false</code>is returned and an 478 * appropriate error message may be obtained by calling 479 * {@link #getParseError()}. 480 * <P> 481 * If <code>dieOnParseError</code> is set to <code>true</code> and 482 * the method fails, the program will exit with exit code 1 after 483 * printing the usage to stderr. 484 */ parse(String[] clargs)485 public boolean parse(String[] clargs) { 486 if (clargs == null) { 487 clargs = new String[] {}; 488 } 489 try { 490 parser.parse(clargs, options, args); 491 if (!canSkipRequiredCheck()) { 492 checkForRequired(); 493 } 494 } catch (CmdLineException e) { 495 if (dieOnParseError) { 496 exitUsageError(e.getMessage()); 497 } else { 498 parseError = e.getMessage(); 499 return false; 500 } 501 } 502 return true; 503 } 504 505 /** 506 * sets the value of the arguments (what is left on the command line after 507 * all options, and their parameters, have been processed) associated with 508 * the command 509 * 510 * @param args 511 * A Collection of {@link Parameter} objects. This may be null if 512 * the command accepts no command line arguments. 513 */ setArgs(Parameter<?>[] args)514 public void setArgs(Parameter<?>[] args) { 515 this.args.clear(); 516 if (args != null) { 517 for (int i = 0; i < args.length; i++) { 518 addArg(args[i]); 519 } 520 } 521 } 522 523 /** 524 * sets a description of the command's purpose 525 * 526 * @param cmdDesc 527 * a short description of the command's purpose 528 * @throws IllegalArgumentException 529 * if <code>cmdDesc 530 * </code> is null or of 0 length. 531 */ setCmdDesc(String cmdDesc)532 public void setCmdDesc(String cmdDesc) { 533 if (cmdDesc == null || cmdDesc.length() <= 0) { 534 throw new IllegalArgumentException(Strings.get( 535 "BasicCmdLineHandler.cmdDescTooShort", 536 new Object[] { cmdDesc })); 537 } 538 this.cmdDesc = cmdDesc; 539 } 540 541 /** 542 * sets the value of the command name associated with this 543 * BasicCmdLineHandler 544 * 545 * @param cmdName 546 * the name of the command associated with this 547 * BasicCmdLineHandler 548 * @throws IllegalArgumentException 549 * if cmdName is null, or of 0 length 550 */ setCmdName(String cmdName)551 public void setCmdName(String cmdName) { 552 if (cmdName == null || cmdName.length() <= 0) { 553 throw new IllegalArgumentException(Strings.get( 554 "BasicCmdLineHandler.cmdNameTooShort", 555 new Object[] { cmdName })); 556 } 557 this.cmdName = cmdName; 558 } 559 560 /** 561 * Sets a flag indicating that the program should exit in the case of a 562 * parse error (after displaying the usage and an error message) - defaults 563 * to <code>true</code>. 564 * 565 * @param val 566 * <code>true</code> (the default) if the <code> 567 * parse</code> 568 * method should call System.exit() in case of a parse error, 569 * <code>false</code> if <code>parse()</code> should return to 570 * the user for error processing. 571 * @see #parse(String[]) parse() 572 */ setDieOnParseError(boolean val)573 public void setDieOnParseError(boolean val) { 574 dieOnParseError = val; 575 } 576 577 /** 578 * Sets the value of the options associated with the command 579 * 580 * @param options 581 * A Collection of {@link Parameter} objects. This may be null if 582 * the command accepts no command line options. 583 */ setOptions(Parameter<?>[] options)584 public void setOptions(Parameter<?>[] options) { 585 this.options.clear(); 586 if (options != null) { 587 for (int i = 0; i < options.length; i++) { 588 addOption(options[i]); 589 } 590 } 591 } 592 593 /** 594 * Sets the error message from the last call to parse(). 595 * 596 * @param parseError 597 * the error message from the last call to parse() 598 * @see #getParseError() 599 */ setParseError(String parseError)600 public void setParseError(String parseError) { 601 this.parseError = parseError; 602 } 603 604 /** 605 * Sets the parser to be used to parse the command line. 606 * 607 * @param parser 608 * the parser to be used to parse the command line 609 * @see #getParser() 610 */ setParser(CmdLineParser parser)611 public void setParser(CmdLineParser parser) { 612 this.parser = parser; 613 } 614 615 /** 616 * Indicates whether the check for required Parameters can be skipped due to 617 * a Parameter with flag <code>ignoreRequired = true</code> having been set. 618 * 619 * @return true if the check for required parameters may be skipped 620 */ canSkipRequiredCheck()621 private boolean canSkipRequiredCheck() { 622 for (Parameter<?> p : options.values()) { 623 if (p.getIgnoreRequired() && p.isSet()) { 624 return true; 625 } 626 } 627 for (Parameter<?> p : args) { 628 if (p.getIgnoreRequired() && p.isSet()) { 629 return true; 630 } 631 } 632 return false; 633 } 634 635 /** 636 * Verifies that all required options and arguments have been specified. 637 * 638 * @throws CmdLineException 639 * if a required option or argument is not set. 640 */ checkForRequired()641 private void checkForRequired() throws CmdLineException { 642 for (Parameter<?> p : options.values()) { 643 if (!p.isOptional() && !p.isSet()) { 644 throw new CmdLineException(Strings.get( 645 "BasicCmdLineHandler.missingRequiredOpt", 646 new Object[] { p.getTag() })); 647 } 648 } 649 for (Parameter<?> p : args) { 650 if (!p.isOptional() && !p.isSet()) { 651 throw new CmdLineException(Strings.get( 652 "BasicCmdLineHandler.missingRequiredArg", 653 new Object[] { p.getTag() })); 654 } 655 } 656 } 657 658 /** 659 * Exits the program with the specified exit status. 660 * 661 * @param exitStatus 662 * the program's exit status 663 * @return this method never returns - the program is terminated 664 */ quitProgram(int exitStatus)665 private void quitProgram(int exitStatus) { 666 System.exit(exitStatus); 667 } 668 } 669