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