CmdLnOption.java Source Code

  • CmdLnOption Javadoc
    /*
     * Copyright (C) 2007-2010 Stephen Ostermiller
     * http://ostermiller.org/contact.pl?regarding=Java+Utilities
     *
     * This program is free software; you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation; either version 2 of the License, or
     * (at your option) any later version.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * See LICENSE.txt for details.
     */
    package com.Ostermiller.util;
    
    import java.util.*;
    
    /**
     * A command line option used by the CommandLineOptions parser.
     *
     * More information about this class and code samples for suggested use are
     * available from <a target="_top" href=
     * "http://ostermiller.org/utils/CmdLn.html">ostermiller.org</a>.
     *
     * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities
     * @since ostermillerutils 1.07.00
     */
    public final class CmdLnOption {
    
    	/**
    	 * The list of long names that specify this option.
    	 * May be null, but here must be at least one long
    	 * or short name.
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	private List<String> longNames;
    
    	/**
    	 * The list of short names that specify this option.
    	 * May be null, but here must be at least one long
    	 * or short name.
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	private List<Character> shortNames;
    
    	/**
    	 * The minimum number of arguments that this
    	 * option must have.
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	private int minArguments = 0;
    
    	/**
    	 * The maximum number of arguments that this
    	 * option may have.
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	private int maxArguments = 0;
    
    	/**
    	 * Whether public setters can be called on this option.
    	 * This option becomes immutable once it is used
    	 * by a command line.
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	private boolean mutable = true;
    
    	/**
    	 * The description of this object for use in the help
    	 * message (may be null).
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	private String description = null;
    
    	/**
    	 * Listener associated with this object that should
    	 * be called when a result is found (may be null).
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	private CmdLnListener callback;
    
    	/**
    	 * @param longNames list long names for this option
    	 * @throws IllegalArgumentException if the the list does not contain at least one long name
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption(String[] longNames){
    		this(longNames, null);
    	}
    
    	/**
    	 * @param shortNames list short names for this option
    	 * @throws IllegalArgumentException if the the list does not contain at least one short name
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption(char[] shortNames){
    		this(null, shortNames);
    	}
    
    	/**
    	 * @param longName the long name for this option
    	 * @throws IllegalArgumentException if the name is null
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption(String longName){
    		this(longName, null);
    	}
    
    	/**
    	 * @param shortName the short name for this option
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption(Character shortName){
    		this(null, shortName);
    	}
    
    	/**
    	 * @param longNames list long names for this option
    	 * @param shortNames list short names for this option
    	 * @throws IllegalArgumentException if the the lists do not contain at least one name
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption(String[] longNames, char[] shortNames){
    
    		if ((longNames == null || longNames.length == 0) && (shortNames == null || shortNames.length == 0)){
    			throw new IllegalArgumentException("At least one long name or short name must be specified");
    		}
    
    		if (longNames == null){
    			this.longNames = new ArrayList<String>(0);
    		} else {
    			this.longNames = new ArrayList<String>(longNames.length);
    			addLongNames(longNames);
    		}
    
    		if (shortNames == null){
    			this.shortNames = new ArrayList<Character>(0);
    		} else {
    			this.shortNames = new ArrayList<Character>(shortNames.length);
    			addShortNames(shortNames);
    		}
    	}
    
    	/**
    	 * @param longName the long name for this option
    	 * @param shortName the short name for this option
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption(String longName, Character shortName){
    
    		if (longName == null  && shortName == null){
    			throw new IllegalArgumentException("A long name or short name must be specified");
    		}
    
    
    		if (longName == null){
    			this.longNames = new ArrayList<String>(0);
    		} else {
    			this.longNames = new ArrayList<String>(1);
    			addLongName(longName);
    		}
    
    		if (shortName == null){
    			this.shortNames = new ArrayList<Character>(0);
    		} else {
    			this.shortNames = new ArrayList<Character>(1);
    			addShortName(shortName);
    		}
    	}
    
    	/**
    	 * Called by the command line options parser
    	 * to set this option to not modifiable.
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	void setImmutable(){
    		if (!mutable){
    			throw new IllegalStateException("Command line argument already immutable (Used by more than one CommandLineOption?)");
    		}
    		mutable = false;
    	}
    
    	/**
    	 * Throw an exception if no longer mutable
    	 *
    	 * @throws IllegalStateException if not mutable
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	private void checkState(){
    		if (!mutable){
    			throw new IllegalStateException("option no longer modifiable");
    		}
    	}
    
    	/**
    	 * Sets the argument bounds to require no arguments
    	 * (zero arguments minimum, zero arguments maximum).
    	 * This is the default state for a new command line option.
    	 *
    	 * @throws IllegalStateException if this argument has already been used in parsing CommandLineOptions
    	 * @return this command line option for method chaining
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption setNoArguments(){
    		setArgumentBounds(0, 0);
    		return this;
    	}
    
    	/**
    	 * Sets the argument bounds for a single optional argument
    	 * (zero arguments minimum, one argument maximum).
    	 *
    	 * @throws IllegalStateException if this argument has already been used in parsing CommandLineOptions
    	 * @return this command line option for method chaining
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption setOptionalArgument(){
    		setArgumentBounds(0, 1);
    		return this;
    	}
    
    	/**
    	 * Sets the argument bounds for a single required argument
    	 * (one argument minimum, one argument maximum).
    	 *
    	 * @throws IllegalStateException if this argument has already been used in parsing CommandLineOptions
    	 * @return this command line option for method chaining
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption setRequiredArgument(){
    		setArgumentBounds(1, 1);
    		return this;
    	}
    
    	/**
    	 * Sets the argument bounds for unlimited (but optional) arguments
    	 * (zero arguments minimum, Integer.MAX_VALUE arguments maximum).
    	 *
    	 * @throws IllegalStateException if this argument has already been used in parsing CommandLineOptions
    	 * @return this command line option for method chaining
    	 */
    	public CmdLnOption setUnlimitedArguments(){
    		setArgumentBounds(0, Integer.MAX_VALUE);
    		return this;
    	}
    
    	/**
    	 * Sets the bounds for command line arguments.
    	 *
    	 * @param minArguments the minimum number of arguments this command line option should expect
    	 * @param maxArguments the maximum number of arguments this command line option will accept
    	 * @throws IllegalArgumentException if minimum arguments is negative
    	 * @throws IllegalArgumentException if maximum arguments is less than minimum arguments
    	 * @throws IllegalStateException if this argument has already been used in parsing CommandLineOptions
    	 * @return this command line option for method chaining
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption setArgumentBounds(int minArguments, int maxArguments){
    		checkState();
    		if (minArguments < 0) throw new IllegalArgumentException("min arguments cannot be negative");
    		if (maxArguments < minArguments) throw new IllegalArgumentException("max arguments cannot be less than min arguments");
    		this.minArguments = minArguments;
    		this.maxArguments = maxArguments;
    		return this;
    	}
    
    	/**
    	 * @param longNames long names to be added
    	 * @return this for method chaining
    	 * @throws IllegalArgumentException if the name is null or blank
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption addLongNames(Collection<String> longNames){
    		checkState();
    		for(String name: longNames){
    			addLongName(name);
    		}
    		return this;
    	}
    
    	/**
    	 * @param longNames long names to be added
    	 * @return this for method chaining
    	 * @throws IllegalArgumentException if the name is null or blank
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption addLongNames(String[] longNames){
    		checkState();
    		for(String name: longNames){
    			addLongName(name);
    		}
    		return this;
    	}
    
    	/**
    	 * @param name long name to be added
    	 * @return this for method chaining
    	 * @throws IllegalArgumentException if the name is null or blank
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption addLongName(String name){
    		checkState();
    		if (name == null){
    			throw new IllegalArgumentException("long name cannot be null");
    		}
    		if ("".equals(name)){
    			throw new IllegalArgumentException("long name cannot be blank");
    		}
    		longNames.add(name);
    		return this;
    	}
    
    	/**
    	 * @param shortNames short names to be added
    	 * @return this for method chaining
    	 * @throws IllegalArgumentException if the name is null or blank
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption addShortNames(Collection<Character> shortNames){
    		checkState();
    		for(Character name: shortNames){
    			addShortName(name);
    		}
    		return this;
    	}
    
    	/**
    	 * @param shortNames short names to be added
    	 * @return this for method chaining
    	 * @throws IllegalArgumentException if the name is null or blank
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption addShortNames(char[] shortNames){
    		checkState();
    		for(char name: shortNames){
    			addShortName(name);
    		}
    		return this;
    	}
    
    	/**
    	 * @param shortNames short names to be added
    	 * @return this for method chaining
    	 * @throws IllegalArgumentException if the name is null or blank
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption addShortNames(Character[] shortNames){
    		checkState();
    		for(char name: shortNames){
    			addShortName(name);
    		}
    		return this;
    	}
    
    	/**
    	 * @param name short name to be added
    	 * @return this for method chaining
    	 * @throws IllegalArgumentException if the name is null or blank
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption addShortName(Character name){
    		checkState();
    		if (name == null){
    			throw new IllegalArgumentException("short name cannot be null");
    		}
    		if ("".equals(name)){
    			throw new IllegalArgumentException("short name cannot be blank");
    		}
    		shortNames.add(name);
    		return this;
    	}
    
    	/**
    	 * Get the first long name or null if no long names
    	 *
    	 * @return long name
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	String getLongName(){
    		if (longNames.size() > 1) return null;
    		return longNames.get(0);
    	}
    
    	/**
    	 * Get the entire list of long names
    	 * @return unmodifiable list of long names
    	 */
    	List<String> getLongNames(){
    		return Collections.unmodifiableList(longNames);
    	}
    
    	/**
    	 * Get the first short name or null if no short names
    	 *
    	 * @return short name
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	Character getShortName(){
    		if (shortNames.size() > 1) return null;
    		return shortNames.get(0);
    	}
    
    	/**
    	 * Get the entire list of short names
    	 *
    	 * @return unmodifiable list of short names
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	List<Character> getShortNames(){
    		return Collections.unmodifiableList(shortNames);
    	}
    
    	/**
    	 * @return the minimum number of arguments allowed
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	int getMinArguments(){
    		return minArguments;
    	}
    
    	/**
    	 * @return the maximum number of arguments allowed
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	int getMaxArguments(){
    		return maxArguments;
    	}
    
    	/**
    	 * Get the length of the argument specification portion of
    	 * the help message in characters.  Does not include any space
    	 * between the specification and the description.
    	 *
    	 * @param longStart What long options start with (typically "--")
    	 * @param shortStart What short options start with (typically "-")
    	 * @return number of characters in the argument specification
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	int getHelpArgumentsLength(String longStart, String shortStart){
    		if (description == null){
    			return 0;
    		}
    		int length = 1;
    		if (shortStart != null && shortNames.size()>0){
    			length += 1;
    			length += shortStart.length();
    			length += 1;
    		}
    		if (longStart != null && longNames.size()>0){
    			length += 1;
    			length += longStart.length();
    			length += longNames.get(0).length();
    		}
    		if (getMaxArguments() > 0){
    			length += 4;
    		}
    		length += 3;
    		return length;
    	}
    
    	/**
    	 * Get the help message for this option appropriate for inclusion in
    	 * "print help".
    	 * <p>
    	 * It will be formatted like this:
    	 * <pre>  --option -o <?>  description</pre>
    	 * Two spaces at the beginning, and at least two spaces after the option
    	 * specification before the description.  If the indent is large,
    	 * there may be more spaces before the description.
    	 * <p>
    	 * If it is longer that the line width and must wrap, the description will
    	 * continue on the next line which will start with eight spaces.
    	 *
    	 * @param longStart What long options start with (typically "--")
    	 * @param shortStart What short options start with (typically "-")
    	 * @param indent Minimum character count at which to start the description after specifying the option
    	 * @param lineWidth Character count at which to wrap (if possible)
    	 * @return help message
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	String getHelp(String longStart, String shortStart, int indent, int lineWidth){
    		if (description == null){
    			return null;
    		}
    		int expectedLength = 32;
    		expectedLength += description.length();
    		StringBuffer sb = new StringBuffer(expectedLength);
    		sb.append(" ");
    		if (shortStart != null && shortNames.size()>0){
    			sb.append(" ").append(shortStart).append(shortNames.get(0));
    		}
    		if (longStart != null && longNames.size()>0){
    			sb.append(" ").append(longStart).append(longNames.get(0));
    		}
    		if (getMaxArguments() > 0){
    			sb.append(" ").append("<?>");
    		}
    		while(indent > sb.length() + 2){
    			sb.append(" ");
    		}
    		sb.append("  ");
    		int descriptionIndex = 0;
    		int charactersLeft = lineWidth - sb.length();
    		for (int lineNumber=1; descriptionIndex < description.length(); lineNumber++){
    			int endIndex = descriptionIndex + charactersLeft;
    			if (endIndex > description.length()){
    				endIndex = description.length();
    			} else {
    				if (description.charAt(endIndex) == ' '){
    					// Space right at the break
    				} else if (description.lastIndexOf(' ', endIndex) > descriptionIndex){
    					// Space sometime before the break
    					endIndex = description.lastIndexOf(' ', endIndex);
    				} else if (description.indexOf(' ', endIndex) != -1){
    					// Space sometime after the break
    					endIndex = description.lastIndexOf(' ', endIndex);
    				} else {
    					// No remaining spaces
    					endIndex = description.length();
    				}
    			}
    			if (lineNumber != 1){
    				sb.append("\n        ");
    			}
    			sb.append(description.substring(descriptionIndex,endIndex).trim());
    			descriptionIndex = endIndex + 1;
    			charactersLeft = lineWidth - 8;
    		}
    
    		return sb.toString();
    	}
    
    	/**
    	 * Get a short string description this option.
    	 * It will be either the long name (if it has one)
    	 * or the short name if it does not have a long name
    	 *
    	 * @return string representation
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	@Override public String toString(){
    		if (longNames.size() > 0){
    			return longNames.get(0);
    		}
    		return shortNames.get(0).toString();
    	}
    
    	/**
    	 * Get the call back object
    	 *
    	 * @return the call back object
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	CmdLnListener getListener(){
    		return callback;
    	}
    
    	/**
    	 * Set the call back object
    	 *
    	 * @param callback the call back object
    	 * @return this for method chaining
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption setListener(CmdLnListener callback){
    		this.callback = callback;
    		return this;
    	}
    
    	/**
    	 * An object that may be set by the user.
    	 * Suggested use: set the user object to an enum
    	 * value that can be used in a switch statement.
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	private Object userObject;
    
    	/**
    	 * An object that may be set by the user.
    	 * Suggested use: set the user object to an enum
    	 * value that can be used in a switch statement.
    	 *
    	 * @since ostermillerutils 1.07.00
    	 *
    	 * @return the userObject
    	 */
    	public Object getUserObject() {
    		return userObject;
    	}
    
    	/**
    	 * An object that may be set by the user.
    	 * Suggested use: set the user object to an enum
    	 * value that can be used in a switch statement.
    	 *
    	 * @param userObject the userObject to set
    	 * @return this for method chaining
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption setUserObject(Object userObject) {
    		this.userObject = userObject;
    		return this;
    	}
    
    	/**
    	 * @return the description used in the help message or null if
    	 * no description has been set.
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public String getDescription() {
    		return description;
    	}
    
    	/**
    	 * @param description the description used in the help message
    	 * @return this for method chaining
    	 *
    	 * @since ostermillerutils 1.07.00
    	 */
    	public CmdLnOption setDescription(String description) {
    		this.description = description;
    		return this;
    	}
    }