001/*
002  Licensed to the Apache Software Foundation (ASF) under one or more
003  contributor license agreements.  See the NOTICE file distributed with
004  this work for additional information regarding copyright ownership.
005  The ASF licenses this file to You under the Apache License, Version 2.0
006  (the "License"); you may not use this file except in compliance with
007  the License.  You may obtain a copy of the License at
008
009      http://www.apache.org/licenses/LICENSE-2.0
010
011  Unless required by applicable law or agreed to in writing, software
012  distributed under the License is distributed on an "AS IS" BASIS,
013  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014  See the License for the specific language governing permissions and
015  limitations under the License.
016 */
017package Apache.CLI;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.HashSet;
024import java.util.LinkedHashMap;
025import java.util.List;
026import java.util.Map;
027
028/**
029 * Main entry-point into the library.
030 *
031 * <BR /><BR />Options represents a collection of {@link Option} objects, which describe the
032 * possible options for a command-line.
033 *
034 * <BR /><BR />It may flexibly parse long and short options, with or without values. Additionally,
035 * it may parse only a portion of a commandline, allowing for flexible multi-stage parsing.
036 *
037 * @see Apache.CLI.CommandLine
038 */
039@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="LICENSE")
040public class Options implements Serializable
041{
042    // The serial version UID.
043    private static final long serialVersionUID = 1L;
044
045    // A map of the options with the character key
046    private final Map<String, Option> shortOpts = new LinkedHashMap<>();
047
048    // A map of the options with the long key
049    private final Map<String, Option> longOpts = new LinkedHashMap<>();
050
051    // A map of the required options
052    // N.B. This can contain either a String (addOption) or an OptionGroup (addOptionGroup)
053    // TODO this seems wrong
054
055    private final List<Object> requiredOpts = new ArrayList<>();
056
057    // A map of the option groups
058    private final Map<String, OptionGroup> optionGroups = new LinkedHashMap<>();
059
060    /**
061     * Adds an option instance
062     * @param opt the option that is to be added
063     * @return the resulting Options instance
064     */
065    public Options addOption(final Option opt)
066    {
067        final String key = opt.getKey();
068
069        // add it to the long option list
070        if (opt.hasLongOpt()) longOpts.put(opt.getLongOpt(), opt);
071
072        // if the option is required add it to the required list
073        if (opt.isRequired())
074        {
075            if (requiredOpts.contains(key)) requiredOpts.remove(requiredOpts.indexOf(key));
076            requiredOpts.add(key);
077        }
078
079        shortOpts.put(key, opt);
080
081        return this;
082    }
083
084    /**
085     * Add an option that only contains a short-name.
086     *
087     * <BR /><BR />It may be specified as requiring an argument.
088     *
089     * @param opt Short single-character name of the option.
090     * @param hasArg flag signalling if an argument is required after this option
091     * @param description Self-documenting description
092     * @return the resulting Options instance
093     */
094    public Options addOption(final String opt, final boolean hasArg, final String description)
095    {
096        addOption(opt, null, hasArg, description);
097        return this;
098    }
099
100    /**
101     * Add an option that only contains a short name.
102     *
103     * <BR /><BR />The option does not take an argument.
104     *
105     * @param opt Short single-character name of the option.
106     * @param description Self-documenting description
107     * @return the resulting Options instance
108     */
109    public Options addOption(final String opt, final String description)
110    {
111        addOption(opt, null, false, description);
112        return this;
113    }
114
115    /**
116     * Add an option that contains a short-name and a long-name.
117     *
118     * <BR /><BR />It may be specified as requiring an argument.
119     *
120     * @param opt Short single-character name of the option.
121     * @param longOpt Long multi-character name of the option.
122     * @param hasArg flag signalling if an argument is required after this option
123     * @param description Self-documenting description
124     * @return the resulting Options instance
125     */
126    public Options addOption
127        (final String opt, final String longOpt, final boolean hasArg, final String description)
128    {
129        addOption(new Option(opt, longOpt, hasArg, description));
130        return this;
131    }
132
133    /**
134     * Add the specified option group.
135     * @param group the OptionGroup that is to be added
136     * @return the resulting Options instance
137     */
138    public Options addOptionGroup(final OptionGroup group)
139    {
140        if (group.isRequired()) requiredOpts.add(group);
141
142        for (final Option option : group.getOptions())
143        {
144            // an Option cannot be required if it is in an OptionGroup, either the group is
145            // required or nothing is required
146
147            option.setRequired(false);
148            addOption(option);
149
150            optionGroups.put(option.getKey(), group);
151        }
152
153        return this;
154    }
155
156    /**
157     * Add an option that contains a short-name and a long-name.
158     *
159     * <BR /><BR />The added option is set as required. It may be specified as requiring an
160     * argument. This method is a shortcut for:
161     *
162     * <DIV CLASS=EXAMPLE>{@code
163     * Options option = new Option(opt, longOpt, hasArg, description);
164     * option.setRequired(true);
165     * options.add(option);
166     * }</DIV>
167     *
168     * @param opt Short single-character name of the option.
169     * @param longOpt Long multi-character name of the option.
170     * @param hasArg flag signalling if an argument is required after this option
171     * @param description Self-documenting description
172     * @return the resulting Options instance
173     */
174    public Options addRequiredOption(
175            final String opt,
176            final String longOpt,
177            final boolean hasArg,
178            final String description
179        )
180    {
181        final Option option = new Option(opt, longOpt, hasArg, description);
182        option.setRequired(true);
183        addOption(option);
184        return this;
185    }
186
187    /**
188     * Gets the options with a long name starting with the name specified.
189     * @param opt the partial name of the option
190     * @return the options matching the partial name specified, or an empty list if none matches
191     */
192    public List<String> getMatchingOptions(String opt)
193    {
194        opt = Util.stripLeadingHyphens(opt);
195
196        final List<String> matchingOpts = new ArrayList<>();
197
198        // for a perfect match return the single option only
199        if (longOpts.containsKey(opt)) return Collections.singletonList(opt);
200
201        for (final String longOpt : longOpts.keySet())
202            if (longOpt.startsWith(opt))
203                matchingOpts.add(longOpt);
204
205        return matchingOpts;
206    }
207
208    /**
209     * Gets the {@link Option} matching the long or short name specified.
210     *
211     * <BR /><BR />The leading hyphens in the name are ignored (up to 2).
212     *
213     * @param opt short or long name of the {@link Option}
214     * @return the option represented by opt
215     */
216    public Option getOption(String opt)
217    {
218        opt = Util.stripLeadingHyphens(opt);
219
220        final Option option = shortOpts.get(opt);
221
222        return option != null ? option : longOpts.get(opt);
223    }
224
225    /**
226     * Gets the OptionGroup the {@code opt} belongs to.
227     * @param opt the option whose OptionGroup is being queried.
228     * @return the OptionGroup if {@code opt} is part of an OptionGroup, otherwise return null
229     */
230    public OptionGroup getOptionGroup(final Option opt)
231    { return optionGroups.get(opt.getKey()); }
232
233    /**
234     * Gets the OptionGroups that are members of this Options instance.
235     * @return a Collection of OptionGroup instances.
236     */
237    Collection<OptionGroup> getOptionGroups()
238    { return new HashSet<>(optionGroups.values()); }
239
240    /**
241     * Gets a read-only list of options in this set
242     * @return read-only Collection of {@link Option} objects in this descriptor
243     */
244    public Collection<Option> getOptions()
245    { return Collections.unmodifiableCollection(helpOptions()); }
246
247    /**
248     * Gets the required options.
249     * @return read-only List of required options
250     */
251    @SuppressWarnings("rawtypes")
252    public List getRequiredOptions()
253    { return Collections.unmodifiableList(requiredOpts); }
254
255    /**
256     * Returns whether the named {@link Option} is a member of this {@link Options}.
257     * @param opt long name of the {@link Option}
258     * @return true if the named {@link Option} is a member of this {@link Options}
259     */
260    public boolean hasLongOption(String opt)
261    {
262        opt = Util.stripLeadingHyphens(opt);
263        return longOpts.containsKey(opt);
264    }
265
266    /**
267     * Returns whether the named {@link Option} is a member of this {@link Options}.
268     * @param opt short or long name of the {@link Option}
269     * @return true if the named {@link Option} is a member of this {@link Options}
270     */
271    public boolean hasOption(String opt)
272    {
273        opt = Util.stripLeadingHyphens(opt);
274        return shortOpts.containsKey(opt) || longOpts.containsKey(opt);
275    }
276
277    /**
278     * Returns whether the named {@link Option} is a member of this {@link Options}.
279     * @param opt short name of the {@link Option}
280     * @return true if the named {@link Option} is a member of this {@link Options}
281     */
282    public boolean hasShortOption(String opt)
283    {
284        opt = Util.stripLeadingHyphens(opt);
285        return shortOpts.containsKey(opt);
286    }
287
288    /**
289     * Returns the Options for use by the HelpFormatter.
290     * @return the List of Options
291     */
292    List<Option> helpOptions()
293    { return new ArrayList<>(shortOpts.values()); }
294
295    /**
296     * Dump state, suitable for debugging.
297     * @return Stringified form of this object
298     */
299    @Override
300    public String toString()
301    {
302        final StringBuilder buf = new StringBuilder();
303
304        buf.append("[ Options: [ short ");
305        buf.append(shortOpts.toString());
306        buf.append(" ] [ long ");
307        buf.append(longOpts);
308        buf.append(" ]");
309
310        return buf.toString();
311    }
312}