1/*
2 * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26/*
27 * This file is available under and governed by the GNU General Public
28 * License version 2 only, as published by the Free Software Foundation.
29 * However, the following notice accompanied the original version of this
30 * file:
31 *
32 * The MIT License
33 *
34 * Copyright (c) 2004-2014 Paul R. Holser, Jr.
35 *
36 * Permission is hereby granted, free of charge, to any person obtaining
37 * a copy of this software and associated documentation files (the
38 * "Software"), to deal in the Software without restriction, including
39 * without limitation the rights to use, copy, modify, merge, publish,
40 * distribute, sublicense, and/or sell copies of the Software, and to
41 * permit persons to whom the Software is furnished to do so, subject to
42 * the following conditions:
43 *
44 * The above copyright notice and this permission notice shall be
45 * included in all copies or substantial portions of the Software.
46 *
47 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
48 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
49 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
50 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
51 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
52 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
53 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
54 */
55
56package jdk.internal.joptsimple;
57
58import java.util.*;
59
60import static java.util.Collections.*;
61
62import static jdk.internal.joptsimple.internal.Objects.*;
63
64/**
65 * Representation of a group of detected command line options, their arguments, and non-option arguments.
66 *
67 * @author <a href="mailto:pholser@alumni.rice.edu">Paul Holser</a>
68 */
69public class OptionSet {
70    private final List<OptionSpec<?>> detectedSpecs;
71    private final Map<String, AbstractOptionSpec<?>> detectedOptions;
72    private final Map<AbstractOptionSpec<?>, List<String>> optionsToArguments;
73    private final Map<String, AbstractOptionSpec<?>> recognizedSpecs;
74    private final Map<String, List<?>> defaultValues;
75
76    /*
77     * Package-private because clients don't create these.
78     */
79    OptionSet( Map<String, AbstractOptionSpec<?>> recognizedSpecs ) {
80        detectedSpecs = new ArrayList<OptionSpec<?>>();
81        detectedOptions = new HashMap<String, AbstractOptionSpec<?>>();
82        optionsToArguments = new IdentityHashMap<AbstractOptionSpec<?>, List<String>>();
83        defaultValues = defaultValues( recognizedSpecs );
84        this.recognizedSpecs = recognizedSpecs;
85    }
86
87    /**
88     * Tells whether any options were detected.
89     *
90     * @return {@code true} if any options were detected
91     */
92    public boolean hasOptions() {
93        return !detectedOptions.isEmpty();
94    }
95
96    /**
97     * Tells whether the given option was detected.
98     *
99     * @param option the option to search for
100     * @return {@code true} if the option was detected
101     * @see #has(OptionSpec)
102     */
103    public boolean has( String option ) {
104        return detectedOptions.containsKey( option );
105    }
106
107    /**
108     * Tells whether the given option was detected.
109     *
110     * <p>This method recognizes only instances of options returned from the fluent interface methods.</p>
111     *
112     * <p>Specifying a {@linkplain ArgumentAcceptingOptionSpec#defaultsTo(Object, Object[])} default argument value}
113     * for an option does not cause this method to return {@code true} if the option was not detected on the command
114     * line.</p>
115     *
116     * @param option the option to search for
117     * @return {@code true} if the option was detected
118     * @see #has(String)
119     */
120    public boolean has( OptionSpec<?> option ) {
121        return optionsToArguments.containsKey( option );
122    }
123
124    /**
125     * Tells whether there are any arguments associated with the given option.
126     *
127     * @param option the option to search for
128     * @return {@code true} if the option was detected and at least one argument was detected for the option
129     * @see #hasArgument(OptionSpec)
130     */
131    public boolean hasArgument( String option ) {
132        AbstractOptionSpec<?> spec = detectedOptions.get( option );
133        return spec != null && hasArgument( spec );
134    }
135
136    /**
137     * Tells whether there are any arguments associated with the given option.
138     *
139     * <p>This method recognizes only instances of options returned from the fluent interface methods.</p>
140     *
141     * <p>Specifying a {@linkplain ArgumentAcceptingOptionSpec#defaultsTo(Object, Object[]) default argument value}
142     * for an option does not cause this method to return {@code true} if the option was not detected on the command
143     * line, or if the option can take an optional argument but did not have one on the command line.</p>
144     *
145     * @param option the option to search for
146     * @return {@code true} if the option was detected and at least one argument was detected for the option
147     * @throws NullPointerException if {@code option} is {@code null}
148     * @see #hasArgument(String)
149     */
150    public boolean hasArgument( OptionSpec<?> option ) {
151        ensureNotNull( option );
152
153        List<String> values = optionsToArguments.get( option );
154        return values != null && !values.isEmpty();
155    }
156
157    /**
158     * Gives the argument associated with the given option.  If the option was given an argument type, the argument
159     * will take on that type; otherwise, it will be a {@link String}.
160     *
161     * <p>Specifying a {@linkplain ArgumentAcceptingOptionSpec#defaultsTo(Object, Object[]) default argument value}
162     * for an option will cause this method to return that default value even if the option was not detected on the
163     * command line, or if the option can take an optional argument but did not have one on the command line.</p>
164     *
165     * @param option the option to search for
166     * @return the argument of the given option; {@code null} if no argument is present, or that option was not
167     * detected
168     * @throws NullPointerException if {@code option} is {@code null}
169     * @throws OptionException if more than one argument was detected for the option
170     */
171    public Object valueOf( String option ) {
172        ensureNotNull( option );
173
174        AbstractOptionSpec<?> spec = detectedOptions.get( option );
175        if ( spec == null ) {
176            List<?> defaults = defaultValuesFor( option );
177            return defaults.isEmpty() ? null : defaults.get( 0 );
178        }
179
180        return valueOf( spec );
181    }
182
183    /**
184     * Gives the argument associated with the given option.
185     *
186     * <p>This method recognizes only instances of options returned from the fluent interface methods.</p>
187     *
188     * @param <V> represents the type of the arguments the given option accepts
189     * @param option the option to search for
190     * @return the argument of the given option; {@code null} if no argument is present, or that option was not
191     * detected
192     * @throws OptionException if more than one argument was detected for the option
193     * @throws NullPointerException if {@code option} is {@code null}
194     * @throws ClassCastException if the arguments of this option are not of the expected type
195     */
196    public <V> V valueOf( OptionSpec<V> option ) {
197        ensureNotNull( option );
198
199        List<V> values = valuesOf( option );
200        switch ( values.size() ) {
201            case 0:
202                return null;
203            case 1:
204                return values.get( 0 );
205            default:
206                throw new MultipleArgumentsForOptionException( option.options() );
207        }
208    }
209
210    /**
211     * <p>Gives any arguments associated with the given option.  If the option was given an argument type, the
212     * arguments will take on that type; otherwise, they will be {@link String}s.</p>
213     *
214     * @param option the option to search for
215     * @return the arguments associated with the option, as a list of objects of the type given to the arguments; an
216     * empty list if no such arguments are present, or if the option was not detected
217     * @throws NullPointerException if {@code option} is {@code null}
218     */
219    public List<?> valuesOf( String option ) {
220        ensureNotNull( option );
221
222        AbstractOptionSpec<?> spec = detectedOptions.get( option );
223        return spec == null ? defaultValuesFor( option ) : valuesOf( spec );
224    }
225
226    /**
227     * <p>Gives any arguments associated with the given option.  If the option was given an argument type, the
228     * arguments will take on that type; otherwise, they will be {@link String}s.</p>
229     *
230     * <p>This method recognizes only instances of options returned from the fluent interface methods.</p>
231     *
232     * @param <V> represents the type of the arguments the given option accepts
233     * @param option the option to search for
234     * @return the arguments associated with the option; an empty list if no such arguments are present, or if the
235     * option was not detected
236     * @throws NullPointerException if {@code option} is {@code null}
237     * @throws OptionException if there is a problem converting the option's arguments to the desired type; for
238     * example, if the type does not implement a correct conversion constructor or method
239     */
240    public <V> List<V> valuesOf( OptionSpec<V> option ) {
241        ensureNotNull( option );
242
243        List<String> values = optionsToArguments.get( option );
244        if ( values == null || values.isEmpty() )
245            return defaultValueFor( option );
246
247        AbstractOptionSpec<V> spec = (AbstractOptionSpec<V>) option;
248        List<V> convertedValues = new ArrayList<V>();
249        for ( String each : values )
250            convertedValues.add( spec.convert( each ) );
251
252        return unmodifiableList( convertedValues );
253    }
254
255    /**
256     * Gives the set of options that were detected, in the form of {@linkplain OptionSpec}s, in the order in which the
257     * options were found on the command line.
258     *
259     * @return the set of detected command line options
260     */
261    public List<OptionSpec<?>> specs() {
262        List<OptionSpec<?>> specs = detectedSpecs;
263        specs.remove( detectedOptions.get( NonOptionArgumentSpec.NAME ) );
264
265        return unmodifiableList( specs );
266    }
267
268    /**
269     * Gives all declared options as a map of string to {@linkplain OptionSpec}.
270     *
271     * @return the declared options as a map
272     */
273    public Map<OptionSpec<?>, List<?>> asMap() {
274        Map<OptionSpec<?>, List<?>> map = new HashMap<OptionSpec<?>, List<?>>();
275        for ( AbstractOptionSpec<?> spec : recognizedSpecs.values() )
276            if ( !spec.representsNonOptions() )
277                map.put( spec, valuesOf( spec ) );
278        return unmodifiableMap( map );
279    }
280
281    /**
282     * @return the detected non-option arguments
283     */
284    public List<?> nonOptionArguments() {
285        return unmodifiableList( valuesOf( detectedOptions.get( NonOptionArgumentSpec.NAME ) ) );
286    }
287
288    void add( AbstractOptionSpec<?> spec ) {
289        addWithArgument( spec, null );
290    }
291
292    void addWithArgument( AbstractOptionSpec<?> spec, String argument ) {
293        detectedSpecs.add( spec );
294
295        for ( String each : spec.options() )
296            detectedOptions.put( each, spec );
297
298        List<String> optionArguments = optionsToArguments.get( spec );
299
300        if ( optionArguments == null ) {
301            optionArguments = new ArrayList<String>();
302            optionsToArguments.put( spec, optionArguments );
303        }
304
305        if ( argument != null )
306            optionArguments.add( argument );
307    }
308
309    @Override
310    public boolean equals( Object that ) {
311        if ( this == that )
312            return true;
313
314        if ( that == null || !getClass().equals( that.getClass() ) )
315            return false;
316
317        OptionSet other = (OptionSet) that;
318        Map<AbstractOptionSpec<?>, List<String>> thisOptionsToArguments =
319            new HashMap<AbstractOptionSpec<?>, List<String>>( optionsToArguments );
320        Map<AbstractOptionSpec<?>, List<String>> otherOptionsToArguments =
321            new HashMap<AbstractOptionSpec<?>, List<String>>( other.optionsToArguments );
322        return detectedOptions.equals( other.detectedOptions )
323            && thisOptionsToArguments.equals( otherOptionsToArguments );
324    }
325
326    @Override
327    public int hashCode() {
328        Map<AbstractOptionSpec<?>, List<String>> thisOptionsToArguments =
329            new HashMap<AbstractOptionSpec<?>, List<String>>( optionsToArguments );
330        return detectedOptions.hashCode() ^ thisOptionsToArguments.hashCode();
331    }
332
333    @SuppressWarnings( "unchecked" )
334    private <V> List<V> defaultValuesFor( String option ) {
335        if ( defaultValues.containsKey( option ) )
336            return (List<V>) defaultValues.get( option );
337
338        return emptyList();
339    }
340
341    private <V> List<V> defaultValueFor( OptionSpec<V> option ) {
342        return defaultValuesFor( option.options().iterator().next() );
343    }
344
345    private static Map<String, List<?>> defaultValues( Map<String, AbstractOptionSpec<?>> recognizedSpecs ) {
346        Map<String, List<?>> defaults = new HashMap<String, List<?>>();
347        for ( Map.Entry<String, AbstractOptionSpec<?>> each : recognizedSpecs.entrySet() )
348            defaults.put( each.getKey(), each.getValue().defaultValues() );
349        return defaults;
350    }
351}
352