View Javadoc
1   /*
2     Licensed to the Apache Software Foundation (ASF) under one or more
3     contributor license agreements.  See the NOTICE file distributed with
4     this work for additional information regarding copyright ownership.
5     The ASF licenses this file to You under the Apache License, Version 2.0
6     (the "License"); you may not use this file except in compliance with
7     the License.  You may obtain a copy of the License at
8   
9         https://www.apache.org/licenses/LICENSE-2.0
10  
11    Unless required by applicable law or agreed to in writing, software
12    distributed under the License is distributed on an "AS IS" BASIS,
13    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14    See the License for the specific language governing permissions and
15    limitations under the License.
16   */
17  
18  package org.apache.commons.cli;
19  
20  import java.io.Serializable;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashSet;
25  import java.util.LinkedHashMap;
26  import java.util.List;
27  import java.util.Map;
28  
29  /**
30   * Main entry-point into the library.
31   * <p>
32   * Options represents a collection of {@link Option} objects, which describe the possible options for a command-line.
33   * </p>
34   * <p>
35   * It may flexibly parse long and short options, with or without values. Additionally, it may parse only a portion of a
36   * command-line, allowing for flexible multi-stage parsing.
37   * </p>
38   *
39   * @see org.apache.commons.cli.CommandLine
40   */
41  public class Options implements Serializable {
42  
43      /** The serial version UID. */
44      private static final long serialVersionUID = 1L;
45  
46      /** A map of the options with the character key */
47      private final Map<String, Option> shortOpts = new LinkedHashMap<>();
48  
49      /** A map of the options with the long key */
50      private final Map<String, Option> longOpts = new LinkedHashMap<>();
51  
52      /** A map of the required options */
53      // This can contain either a String (addOption) or an OptionGroup (addOptionGroup)
54      // TODO this seems wrong
55      private final List<Object> requiredOpts = new ArrayList<>();
56  
57      /** A map of the option groups */
58      private final Map<String, OptionGroup> optionGroups = new LinkedHashMap<>();
59  
60      /**
61       * Constructs new instance.
62       */
63      public Options() {
64          // empty
65      }
66  
67      /**
68       * Adds an option instance.
69       *
70       * @param opt the option that is to be added.
71       * @return the resulting Options instance.
72       */
73      public Options addOption(final Option opt) {
74          final String key = opt.getKey();
75          // add it to the long option list
76          if (opt.hasLongOpt()) {
77              longOpts.put(opt.getLongOpt(), opt);
78          }
79          // if the option is required add it to the required list
80          if (opt.isRequired()) {
81              if (requiredOpts.contains(key)) {
82                  requiredOpts.remove(requiredOpts.indexOf(key));
83              }
84              requiredOpts.add(key);
85          }
86          shortOpts.put(key, opt);
87          return this;
88      }
89  
90      /**
91       * Adds an option that only contains a short-name.
92       * <p>
93       * It may be specified as requiring an argument.
94       * </p>
95       *
96       * @param opt Short single-character name of the option.
97       * @param hasArg flag signaling if an argument is required after this option.
98       * @param description Self-documenting description.
99       * @return the resulting Options instance.
100      */
101     public Options addOption(final String opt, final boolean hasArg, final String description) {
102         addOption(opt, null, hasArg, description);
103         return this;
104     }
105 
106     /**
107      * Adds an option that only contains a short name.
108      * <p>
109      * The option does not take an argument.
110      * </p>
111      *
112      * @param opt Short single-character name of the option.
113      * @param description Self-documenting description.
114      * @return the resulting Options instance.
115      * @since 1.3
116      */
117     public Options addOption(final String opt, final String description) {
118         addOption(opt, null, false, description);
119         return this;
120     }
121 
122     /**
123      * Adds an option that contains a short-name and a long-name.
124      * <p>
125      * It may be specified as requiring an argument.
126      * </p>
127      *
128      * @param opt Short single-character name of the option.
129      * @param longOpt Long multi-character name of the option.
130      * @param hasArg flag signaling if an argument is required after this option.
131      * @param description Self-documenting description.
132      * @return the resulting Options instance.
133      */
134     public Options addOption(final String opt, final String longOpt, final boolean hasArg, final String description) {
135         addOption(new Option(opt, longOpt, hasArg, description));
136         return this;
137     }
138 
139     /**
140      * Adds the specified option group.
141      * <p>
142      * An Option cannot be required if it is in an {@link OptionGroup}, either the group is required or nothing is required. This means that {@link Option} in
143      * the given group are set to optional.
144      * </p>
145      *
146      * @param optionGroup the OptionGroup that is to be added.
147      * @return the resulting Options instance.
148      */
149     public Options addOptionGroup(final OptionGroup optionGroup) {
150         if (optionGroup.isRequired()) {
151             requiredOpts.add(optionGroup);
152         }
153         for (final Option option : optionGroup.getOptions()) {
154             // an Option cannot be required if it is in an
155             // OptionGroup, either the group is required or
156             // nothing is required
157             option.setRequired(false);
158             final String key = option.getKey();
159             requiredOpts.remove(key);
160             addOption(option);
161             optionGroups.put(key, optionGroup);
162         }
163         return this;
164     }
165 
166     /**
167      * Adds options to this option.  If any Option in {@code options} already exists
168      * in this Options an IllegalArgumentException is thrown.
169      *
170      * @param options the options to add.
171      * @return The resulting Options instance.
172      * @since 1.7.0
173      */
174     public Options addOptions(final Options options) {
175         for (final Option opt : options.getOptions()) {
176             if (hasOption(opt.getKey())) {
177                 throw new IllegalArgumentException("Duplicate key: " + opt.getKey());
178             }
179             addOption(opt);
180         }
181         options.getOptionGroups().forEach(this::addOptionGroup);
182         return this;
183     }
184 
185     /**
186      * Adds an option that contains a short-name and a long-name.
187      * <p>
188      * The added option is set as required. It may be specified as requiring an argument. This method is a shortcut for:
189      * </p>
190      * <pre>
191      * <code>
192      * Options option = new Option(opt, longOpt, hasArg, description);
193      * option.setRequired(true);
194      * options.add(option);
195      * </code>
196      * </pre>
197      *
198      * @param opt Short single-character name of the option.
199      * @param longOpt Long multi-character name of the option.
200      * @param hasArg flag signaling if an argument is required after this option.
201      * @param description Self-documenting description.
202      * @return the resulting Options instance.
203      * @since 1.4
204      */
205     public Options addRequiredOption(final String opt, final String longOpt, final boolean hasArg, final String description) {
206         final Option option = new Option(opt, longOpt, hasArg, description);
207         option.setRequired(true);
208         addOption(option);
209         return this;
210     }
211 
212     /**
213      * Gets the options with a long name starting with the name specified.
214      *
215      * @param opt the partial name of the option.
216      * @return the options matching the partial name specified, or an empty list if none matches.
217      * @since 1.3
218      */
219     public List<String> getMatchingOptions(final String opt) {
220         final String clean = Util.stripLeadingHyphens(opt);
221         final List<String> matchingOpts = new ArrayList<>();
222         // for a perfect match return the single option only
223         if (longOpts.containsKey(clean)) {
224             return Collections.singletonList(clean);
225         }
226         for (final String longOpt : longOpts.keySet()) {
227             if (longOpt.startsWith(clean)) {
228                 matchingOpts.add(longOpt);
229             }
230         }
231         return matchingOpts;
232     }
233 
234     /**
235      * Gets the {@link Option} matching the long or short name specified.
236      * <p>
237      * The leading hyphens in the name are ignored (up to 2).
238      * </p>
239      *
240      * @param opt short or long name of the {@link Option}.
241      * @return the option represented by opt.
242      */
243     public Option getOption(final String opt) {
244         final String clean = Util.stripLeadingHyphens(opt);
245         final Option option = shortOpts.get(clean);
246         return option != null ? option : longOpts.get(clean);
247     }
248 
249     /**
250      * Gets the OptionGroup the {@code opt} belongs to.
251      *
252      * @param option the option whose OptionGroup is being queried.
253      * @return the OptionGroup if {@code opt} is part of an OptionGroup, otherwise return null.
254      */
255     public OptionGroup getOptionGroup(final Option option) {
256         return optionGroups.get(option.getKey());
257     }
258 
259     /**
260      * Gets the OptionGroups that are members of this Options instance.
261      *
262      * @return a Collection of OptionGroup instances.
263      */
264     Collection<OptionGroup> getOptionGroups() {
265 
266         // The optionGroups map will have duplicates in the values() results. We
267         // use the HashSet to filter out duplicates and return a collection of
268         // OpitonGroup. The decision to return a Collection rather than a set
269         // was probably to keep symmetry with the getOptions() method.
270         return new HashSet<>(optionGroups.values());
271     }
272 
273     /**
274      * Gets a read-only list of options in this set.
275      *
276      * @return read-only Collection of {@link Option} objects in this descriptor.
277      */
278     public Collection<Option> getOptions() {
279         return Collections.unmodifiableCollection(helpOptions());
280     }
281 
282     /**
283      * Gets the required options.
284      *
285      * @return read-only List of required options.
286      */
287     public List<?> getRequiredOptions() {
288         return Collections.unmodifiableList(requiredOpts);
289     }
290 
291     /**
292      * Tests whether the named {@link Option} is a member of this {@link Options}.
293      *
294      * @param opt long name of the {@link Option}.
295      * @return true if the named {@link Option} is a member of this {@link Options}.
296      * @since 1.3
297      */
298     public boolean hasLongOption(final String opt) {
299         return longOpts.containsKey(Util.stripLeadingHyphens(opt));
300     }
301 
302     /**
303      * Tests whether the named {@link Option} is a member of this {@link Options}.
304      *
305      * @param opt short or long name of the {@link Option}.
306      * @return true if the named {@link Option} is a member of this {@link Options}.
307      */
308     public boolean hasOption(final String opt) {
309         final String clean = Util.stripLeadingHyphens(opt);
310         return shortOpts.containsKey(clean) || longOpts.containsKey(clean);
311     }
312 
313     /**
314      * Tests whether the named {@link Option} is a member of this {@link Options}.
315      *
316      * @param opt short name of the {@link Option}.
317      * @return true if the named {@link Option} is a member of this {@link Options}.
318      * @since 1.3
319      */
320     public boolean hasShortOption(final String opt) {
321         final String clean = Util.stripLeadingHyphens(opt);
322         return shortOpts.containsKey(clean);
323     }
324 
325     /**
326      * Returns the Options for use by the HelpFormatter.
327      *
328      * @return the List of Options.
329      */
330     List<Option> helpOptions() {
331         return new ArrayList<>(shortOpts.values());
332     }
333 
334     /**
335      * Dump state, suitable for debugging.
336      *
337      * @return Stringified form of this object.
338      */
339     @Override
340     public String toString() {
341         final StringBuilder buf = new StringBuilder();
342         buf.append("[ Options: [ short ");
343         buf.append(shortOpts.toString());
344         buf.append(" ] [ long ");
345         buf.append(longOpts);
346         buf.append(" ]");
347         return buf.toString();
348     }
349 }