View Javadoc

1   /*
2    * $Id: CommandLineOption.java 178 2010-10-31 18:01:20Z roland $
3    * Copyright (C) 2007 Roland Krueger
4    * Created on 08.05.2009
5    *
6    * Author: Roland Krueger (www.rolandkrueger.info)
7    *
8    * This file is part of RoKlib.
9    *
10   * This library is free software; you can redistribute it and/or
11   * modify it under the terms of the GNU Lesser General Public License
12   * as published by the Free Software Foundation; either version 2.1 of
13   * the License, or (at your option) any later version.
14   *
15   * This library is distributed in the hope that it will be useful, but
16   * WITHOUT ANY WARRANTY; without even the implied warranty of
17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18   * Lesser General Public License for more details.
19   *
20   * You should have received a copy of the GNU Lesser General Public
21   * License along with this library; if not, write to the Free Software
22   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
23   * USA
24   */
25  package info.rolandkrueger.roklib.cli;
26  
27  import java.util.HashSet;
28  import java.util.LinkedList;
29  import java.util.List;
30  import java.util.Set;
31  
32  /**
33   * This class constitutes a single command line option that is managed by a
34   * {@link CommandLineArgumentEvaluator}. An option has several properties that
35   * can be configured through this class. An option can have the following
36   * properties:<br>
37   * <ul>
38   * <li><b>Long option name and short option name:</b> There are two ways for the
39   * user to provide an option: either as a long option or as a short option. Both
40   * variants are equal. An example for that is <code>--server</code> for the long
41   * option and <code>-s</code> for the short option. Both variants are configured
42   * through the constructor of class {@link CommandLineOption}. It is possible to
43   * omit one of these variants if, for instance, only a long option should be
44   * available.
45   * <li><b>Option type:</b> Each option can be of one of the following types
46   * <i>flag option</i>, <i>list option</i>, or <i>singular option</i>. For more
47   * information about that see {@link CommandLineOptionType}.
48   * <li><b>Mandatory vs. optional options:</b> Some options can be marked as
49   * mandatory. In that case, the user is compelled to provide a value for that
50   * option. An example would be the target parameter for a copy command. If the
51   * user forgets to provide a value for a mandatory option, the
52   * {@link CommandLineArgumentEvaluator} will detect this. Note that it makes no
53   * sense to mark flag options as mandatory. Therefore, trying to do so will
54   * raise an unchecked exception.
55   * <li><b>Default values:</b> Non-mandatory options can be provided with default
56   * values. These values are set for the respective option if the user omits them
57   * on the command line. To set an option's default values, you can use one of
58   * the methods {@link CommandLineOption#addDefaultValue(String)} and
59   * {@link CommandLineOption#addDefaultValues(String[])}.
60   * </ul>
61   * <h4>Creating {@link CommandLineOption} objects</h4> Since class
62   * {@link CommandLineOption} is a public inner class, a special syntax has to be
63   * applied in order to create new instances of this class. To do so, you need an
64   * object of {@link CommandLineOption}'s enclosing class
65   * {@link CommandLineArgumentEvaluator}. Say such an object is named
66   * <code>evaluator</code>. A new Option can then be created with <blockquote>
67   * 
68   * <pre>
69   *     Option option = evaluator.new Option (...);
70   * </pre>
71   * 
72   * </blockquote> <h4>Querying an {@link CommandLineOption}'s status, i.e. the
73   * user input</h4> After invoking method
74   * {@link CommandLineArgumentEvaluator#evaluate()}, the status of an option can
75   * be queried. That is, you can check whether the user's input is valid, which
76   * values she has provided for list and singular options, and which flag options
77   * she has used. To check if a flag option has been used, you can call method
78   * {@link CommandLineOption#isSet()}. To obtain the list of values that have
79   * been provided for a list or a singular option, method
80   * {@link CommandLineOption#getInputList()} can be used. This method will return
81   * a one-element list for singular options. A simple idiom for querying the
82   * user-provided value of a singular option is the following: <blockquote>
83   * 
84   * <pre>
85   * Option singularOption;
86   * CommandLineArgumentEvaluator evaluator;
87   * // ...
88   * evaluator.evaluate ();
89   * // ...
90   * String value = singularOption.getInputList ()[0];
91   * </pre>
92   * 
93   * </blockquote>
94   * 
95   * @see CommandLineOptionType
96   * @author Roland Krueger
97   */
98  public class CommandLineOption
99  {
100   private String mShortOptionName;
101   private String mLongOptionName;
102   private String mDescription;
103   private CommandLineOptionType mType;
104   private boolean mIsSet = false;
105   private boolean mMandatory = false;
106   private boolean mDefaultValueSet = false;
107   private List<String> mValues;
108   private boolean mRepetionsAllowed = false;
109   private int mRepetitionCount = 0;
110 
111   /**
112    * List of other options which must not be provided together with this option.
113    */
114   private Set<CommandLineOption> mMutuallyExclusiveOptions;
115 
116   /**
117    * Set of other option objects which must be provided in combination with this
118    * option.
119    */
120   private Set<CommandLineOption> mAssociateOptions;
121 
122   private ICommandLineOptionCallback mCallback;
123 
124   /**
125    * Constructor.
126    * 
127    * @param shortOption
128    *          short version of the option name. For example, <code>-s</code>
129    * @param longOption
130    *          long version of the option name. For example,
131    *          <code>--server_address</code>
132    * @param description
133    *          short description for the option. This description will be used
134    *          for assembling a help message by method
135    *          {@link CommandLineArgumentEvaluator#getOptionDescription()}.
136    * @param type
137    *          type of the option. An option can be a flag option, a singular
138    *          option, or a list option.
139    * @param mandatory
140    *          <code>true</code> if the option is mandatory. Is only allowed for
141    *          list and singular options
142    * @throws IllegalArgumentException
143    *           either if an option was both configured to be a flag option and
144    *           mandatory or if both short and long option name were left blank
145    */
146   public CommandLineOption (Character shortOption, String longOption, String description,
147       CommandLineOptionType type, boolean mandatory, ICommandLineOptionCallback callback)
148   {
149     this ();
150     setCallback (callback);
151     if (type == CommandLineOptionType.FLAG && mandatory == true)
152       throw new IllegalArgumentException ("A flag option must not be mandatory.");
153     if (shortOption == null)
154       mShortOptionName = "";
155     else
156       mShortOptionName = String.valueOf (shortOption);
157     if (longOption == null) longOption = "";
158     if (mShortOptionName.equals ("") && longOption.equals (""))
159       throw new IllegalArgumentException ("Short and long option must not be both empty");
160     mLongOptionName = longOption;
161     mType = type;
162     mMandatory = mandatory;
163     if (description == null) description = "";
164     mDescription = description;
165   }
166 
167   public CommandLineOption (Character shortOption, String longOption, String description,
168       CommandLineOptionType type)
169   {
170     this (shortOption, longOption, description, type, false);
171   }
172 
173   public CommandLineOption (Character shortOption, String longOption, String description,
174       CommandLineOptionType type, boolean mandatory)
175   {
176     this (shortOption, longOption, description, type, mandatory, new EmptyCallback ());
177   }
178 
179   /**
180    * Private default constructor.
181    */
182   private CommandLineOption ()
183   {
184     mCallback = new EmptyCallback ();
185     mValues = new LinkedList<String> ();
186     mShortOptionName = "";
187     mLongOptionName = "";
188   }
189 
190   /**
191    * Constructor for creating a {@link CommandLineOption} without a description.
192    */
193   public CommandLineOption (Character shortOption, String longOption, CommandLineOptionType type,
194       boolean mandatory)
195   {
196     this (shortOption, longOption, "", type, mandatory);
197   }
198 
199   public void setCallback (ICommandLineOptionCallback callback)
200   {
201     if (callback == null) throw new NullPointerException ("Callback is null");
202     mCallback = callback;
203   }
204 
205   public void allowOptionRepetition (boolean yesNo)
206   {
207     if (yesNo == true && mType == CommandLineOptionType.SINGULAR)
208       throw new IllegalStateException ("Cannot allow option repetition for single value options.");
209 
210     mRepetionsAllowed = yesNo;
211   }
212 
213   public void setMandatory (boolean mandatory)
214   {
215     if (isFlag ()) throw new IllegalStateException ("A flag option must not be mandatory.");
216     mMandatory = mandatory;
217   }
218 
219   public boolean isOptionRepetitionAllowed ()
220   {
221     return mRepetionsAllowed;
222   }
223 
224   /**
225    * Checks whether this option is a flag option.
226    * 
227    * @return <code>true</code> if this option was configured as a flag option
228    */
229   public boolean isFlag ()
230   {
231     return mType == CommandLineOptionType.FLAG;
232   }
233 
234   protected void invokeCallback (CommandLineArgumentEvaluator source)
235   {
236     mCallback.optionIsSet (source, mValues);
237   }
238 
239   /**
240    * Checks whether the option was used on the command line. This is useful for
241    * both flag options and non-mandatory options. Mandatory options that haven't
242    * been provided by the user can be queried with
243    * {@link CommandLineArgumentEvaluator#getMissingOptions()}.
244    * 
245    * @return <code>true</code> if the option was used by the user on the command
246    *         line.
247    */
248   public boolean isSet ()
249   {
250     return mIsSet;
251   }
252 
253   public void addAssociateOptions (CommandLineOption... associates)
254   {
255     // first check if both options which shall go together havn't earlier been
256     // defined as
257     // mutually exclusive
258     for (CommandLineOption option : associates)
259       if (isMutuallyExclusiveWith (option))
260         throw new IllegalArgumentException (String.format (
261             "Option %s is already defined to be mutually " + "exclusive to this option.", option));
262 
263     if (mAssociateOptions == null) mAssociateOptions = new HashSet<CommandLineOption> ();
264 
265     for (CommandLineOption option : associates)
266       mAssociateOptions.add (option);
267   }
268 
269   public void addMutuallyExclusiveOptions (CommandLineOption... otherOptions)
270   {
271     // first check if both options which shall be mutually exclusive havn't
272     // earlier been defined
273     // as associate options
274 
275     if (mMutuallyExclusiveOptions == null)
276       mMutuallyExclusiveOptions = new HashSet<CommandLineOption> ();
277 
278     for (CommandLineOption option : otherOptions)
279     {
280       if (isAssociateTo (option))
281         throw new IllegalArgumentException (String.format (
282             "Option %s is already defined as an associate option " + "to this option.", option));
283 
284       // check if both options are mandatory. If so, they cannot be mutually
285       // exclusive
286       if (isMandatory () || option.isMandatory ())
287         throw new IllegalArgumentException (String.format (
288             "Both this option and option %s which shall be " + "mutually exclusive are mandatory.",
289             option));
290 
291       // check if one of the options involved is already set (for example with a
292       // default value). If so
293       // adding them to a mutually exclusive relationship is disallowed
294       if (isSet () || option.isSet ())
295         throw new IllegalArgumentException (String.format (
296             "Cannot add to mutually exclusive relationship. Either "
297                 + "this option, both options or option %s are already set.", option));
298 
299       mMutuallyExclusiveOptions.add (option);
300       if (! option.isMutuallyExclusiveWith (this)) option.addMutuallyExclusiveOptions (this);
301     }
302   }
303 
304   public boolean isMutuallyExclusiveWith (CommandLineOption otherOption)
305   {
306     if (mMutuallyExclusiveOptions == null) return false;
307     return (mMutuallyExclusiveOptions.contains (otherOption));
308   }
309 
310   public boolean isAssociateTo (CommandLineOption otherOption)
311   {
312     if (mAssociateOptions == null) return false;
313     return (mAssociateOptions.contains (otherOption));
314   }
315 
316   /**
317    * Returns the list of values that have been provided by the user for an
318    * option. If this {@link CommandLineOption} object is a singular option, the
319    * returned list has exactly one element.
320    * 
321    * @return the list of values that have been provided by the user for an
322    *         option or <code>null</code> if there aren't any values for this
323    *         option
324    */
325   public String[] getInputList ()
326   {
327     if (mValues.size () == 0) return new String[] {};
328     String[] result = new String[mValues.size ()];
329     mValues.toArray (result);
330     return result;
331   }
332 
333   public String getSingleParameterValue ()
334   {
335     if (mType == CommandLineOptionType.FLAG)
336       throw new IllegalStateException (
337           "This command line option is of type FLAG. It cannot provide a value.");
338     if (! isSet ()) throw new IllegalStateException ("This option has not been set.");
339     assert mValues.size () != 0;
340     return mValues.get (0);
341   }
342 
343   /**
344    * Mark this option as set.
345    */
346   protected void set ()
347   {
348     mIsSet = true;
349   }
350 
351   /**
352    * Returns whether this option is mandatory or optional.
353    * 
354    * @return <code>true</code> if this option is marked as mandatory
355    */
356   public boolean isMandatory ()
357   {
358     return mMandatory;
359   }
360 
361   protected boolean addUserInput (String input)
362   {
363     // check if default values have benn provided. If so, remove them since user
364     // input is available
365     if (mDefaultValueSet)
366     {
367       mValues.clear ();
368       mDefaultValueSet = false;
369     }
370     set ();
371     // has a value already been given for this option if it is singular?
372     if (mType == CommandLineOptionType.SINGULAR && mValues.size () == 1) return false;
373     // if this option is a flag, don't accept input
374     if (mType == CommandLineOptionType.FLAG) return false;
375 
376     // otherwise accept input
377     mValues.add (input);
378     return true;
379   }
380 
381   @Override
382   public boolean equals (Object obj)
383   {
384     if (obj == null) return false;
385     if (obj == this) return true;
386     if (obj instanceof CommandLineOption)
387     {
388       CommandLineOption other = (CommandLineOption) obj;
389       if (! mLongOptionName.equals ("") && mLongOptionName.equals (other.mLongOptionName))
390         return true;
391       if (! mShortOptionName.equals ("") && mShortOptionName.equals (other.mShortOptionName))
392         return true;
393     }
394     return false;
395   }
396 
397   @Override
398   public int hashCode ()
399   {
400     return super.hashCode ();
401   }
402 
403   /**
404    * Returns the description of this option as provided through the constructor.
405    * 
406    * @return the description of this option.
407    */
408   public String getDescription ()
409   {
410     return mDescription;
411   }
412 
413   /**
414    * Adds a default value for this option. Every non-mandatory
415    * {@link CommandLineOption} object maintains a list of default values. If the
416    * user has omitted an option on the command line, querying the option with
417    * {@link CommandLineOption#getInputList()} will yield its default values.
418    * Additionally, each option with default values will return <code>true</code>
419    * wenn invoking its {@link CommandLineOption#isSet()} method.<br>
420    * <br>
421    * Repeated calls to {@link CommandLineOption#addDefaultValue(String)} will
422    * add each provided value to the list of default values for list options.
423    * This is not the case for singular options. Here, only the value provided
424    * with the last call to {@link CommandLineOption#addDefaultValue(String)}
425    * will be used as the only default value.<br>
426    * <br>
427    * Note that default values should usually be set before evaluating the
428    * command line arguments with {@link CommandLineArgumentEvaluator#evaluate()}
429    * . Otherwise the actual data provided by the user will be overwritten for
430    * singular options or the default values will be added to the user's data for
431    * list options, respectively.
432    * 
433    * @param defaultValue
434    *          the default value to be added
435    * @throws IllegalStateException
436    *           if this option is either a flag option (which aren't meant to be
437    *           provided with a value by definition) or a mandatory option (where
438    *           the user is forced to provide a value himself).
439    */
440   public void addDefaultValue (String defaultValue)
441   {
442     if (mType == CommandLineOptionType.FLAG)
443       throw new IllegalStateException ("Cannot set default value for a flag option.");
444     if (mMandatory)
445       throw new IllegalStateException (
446           "Setting a default value of a mandatory option is not allowed.");
447     if (mMutuallyExclusiveOptions != null && mMutuallyExclusiveOptions.size () > 0)
448       throw new IllegalStateException (
449           "This option is already part of a mutually exclusive relationship.");
450 
451     if (mType == CommandLineOptionType.SINGULAR)
452     {
453       mValues = new LinkedList<String> ();
454     }
455     mValues.add (defaultValue);
456     mDefaultValueSet = true;
457     set ();
458   }
459 
460   /**
461    * Adds a list of values to the default values of this option. For singular
462    * options, this list must not contain more than one element.
463    * 
464    * @see #addDefaultValue(String)
465    * @param values
466    *          list of default values
467    * @throws UnsupportedOperationException
468    *           if this option is a flag option or if the passed
469    *           <code>values</code> list has more than one element for singular
470    *           options
471    */
472   public void addDefaultValues (String... values)
473   {
474     if (mType == CommandLineOptionType.FLAG)
475       throw new UnsupportedOperationException ("Cannot set a default value for a flag option.");
476     if (values.length > 1 && mType == CommandLineOptionType.SINGULAR)
477       throw new UnsupportedOperationException ("Cannot set more than one default value "
478           + "for a singular option. Use setDefaultValue(String) instead.");
479     for (String value : values)
480     {
481       addDefaultValue (value);
482     }
483   }
484 
485   /**
486    * Returns the option name's long version.
487    * 
488    * @return the option name's long version
489    */
490   public String getLongOption ()
491   {
492     return mLongOptionName;
493   }
494 
495   /**
496    * Returns the option name's short version.
497    * 
498    * @return the option name's short version
499    */
500   public String getShortOption ()
501   {
502     return mShortOptionName;
503   }
504 
505   public CommandLineOptionType getType ()
506   {
507     return mType;
508   }
509 
510   public void increaseRepetitions ()
511   {
512     mRepetitionCount++;
513   }
514 
515   public int getRepetitionCount ()
516   {
517     return mRepetitionCount;
518   }
519 
520   @Override
521   public String toString ()
522   {
523     StringBuilder buf = new StringBuilder ();
524     buf.append ("['").append (mShortOptionName).append ("', '");
525     buf.append (mLongOptionName).append ("', ").append ("'");
526     buf.append (mDescription).append ("', ");
527     buf.append (mType.toString ());
528     buf.append (", mandatory=").append (mMandatory);
529     buf.append ("]");
530     return buf.toString ();
531   }
532 
533   private static class EmptyCallback implements ICommandLineOptionCallback
534   {
535     public void optionIsSet (CommandLineArgumentEvaluator source, List<String> paramValues)
536     {
537     };
538   }
539 }