Skip to content

Commit 9256a4d

Browse files
authored
Add and use a Converter interface and implementations without using BeanUtils (apache#216)
* Fixes for CLI-321 Adds BeanUtils dependency to POM and implements TypeHandler using the BeanUtils classes. Handles all methods that were in the original TypeHandler as well as other default classes provided by BeanUtils. Test updated to show proper parsing of types that previously were not implemented. Includes new cli.converters package that may be better handled by BeanUtils as it moves forward to support FunctionalInterfaces. * Fixed issues with date parsing * Switched to ConvertUtilsBean2 for conversions. Added getParsedOptionValues methods with default values. Fixed som javadoc, * Implementation with tests * fixed checkstyle issues * fixed spotbugs error & some javadoc * Updated spotbugs to 4.8.2.0 * added javadocs and tests * fixed breaking changes and javadoc * added since annotation to Converter and Verifier * Added an Enum Validator implementation * fixed formatting issues * fixed checkstyle error * Converted Verifier to Predicate<String> Verifier interface became static class to hold common Predicate<String> instances for verification. Moved EnumVerifier into Verifier as a static method. All references to Verifier as a data type chagned to Predicate<String>. * Moved Converter, Verifier and their respective tests to base cli package. * Removed verifier management and rebased to master * moved param <T> tags * updated numberic tests * Updated documentation, added Supplier<String> as a default provider for getOptionValue * fixed checkstyle issues * Added Path to TypeHandler and updated documentation * removed verifier * removed verifier * updated javadoc and site documentation
1 parent 7ead3d0 commit 9256a4d

15 files changed

Lines changed: 837 additions & 182 deletions

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@
192192
<artifactId>junit-vintage-engine</artifactId>
193193
<scope>test</scope>
194194
</dependency>
195+
<dependency>
196+
<groupId>org.junit.jupiter</groupId>
197+
<artifactId>junit-jupiter-params</artifactId>
198+
<scope>test</scope>
199+
</dependency>
195200
</dependencies>
196201

197202
<properties>
@@ -246,6 +251,7 @@
246251
<configuration>
247252
<excludeFilterFile>${basedir}/src/conf/spotbugs-exclude-filter.xml</excludeFilterFile>
248253
</configuration>
254+
<version>4.8.2.0</version>
249255
</plugin>
250256
<plugin>
251257
<groupId>org.apache.maven.plugins</groupId>

src/main/java/org/apache/commons/cli/CommandLine.java

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more
2525
import java.util.LinkedList;
2626
import java.util.List;
2727
import java.util.Properties;
28+
import java.util.function.Supplier;
2829

2930
/**
3031
* Represents list of arguments parsed against a {@link Options} descriptor.
@@ -254,6 +255,18 @@ public String getOptionValue(final char opt) {
254255
* @return Value of the argument if option is set, and has an argument, otherwise {@code defaultValue}.
255256
*/
256257
public String getOptionValue(final char opt, final String defaultValue) {
258+
return getOptionValue(String.valueOf(opt), () -> defaultValue);
259+
}
260+
261+
/**
262+
* Gets the argument, if any, of an option.
263+
*
264+
* @param opt character name of the option
265+
* @param defaultValue is a supplier for the default value to be returned if the option is not specified.
266+
* @return Value of the argument if option is set, and has an argument, otherwise {@code defaultValue}.
267+
* @since 1.7.0
268+
*/
269+
public String getOptionValue(final char opt, final Supplier<String> defaultValue) {
257270
return getOptionValue(String.valueOf(opt), defaultValue);
258271
}
259272

@@ -281,10 +294,22 @@ public String getOptionValue(final Option option) {
281294
* @since 1.5.0
282295
*/
283296
public String getOptionValue(final Option option, final String defaultValue) {
284-
final String answer = getOptionValue(option);
285-
return answer != null ? answer : defaultValue;
297+
return getOptionValue(option, () -> defaultValue);
286298
}
287299

300+
/**
301+
* Gets the first argument, if any, of an option.
302+
*
303+
* @param option name of the option.
304+
* @param defaultValue is a supplier for the default value to be returned if the option is not specified.
305+
* @return Value of the argument if option is set, and has an argument, otherwise {@code defaultValue}.
306+
* @since 1.7.0
307+
*/
308+
public String getOptionValue(final Option option, final Supplier<String> defaultValue) {
309+
final String answer = getOptionValue(option);
310+
return answer != null ? answer : defaultValue.get();
311+
}
312+
288313
/**
289314
* Gets the first argument, if any, of this option.
290315
*
@@ -303,9 +328,22 @@ public String getOptionValue(final String opt) {
303328
* @return Value of the argument if option is set, and has an argument, otherwise {@code defaultValue}.
304329
*/
305330
public String getOptionValue(final String opt, final String defaultValue) {
331+
return getOptionValue(resolveOption(opt), () -> defaultValue);
332+
}
333+
334+
/**
335+
* Gets the first argument, if any, of an option.
336+
*
337+
* @param opt name of the option.
338+
* @param defaultValue is a supplier for the default value to be returned if the option is not specified.
339+
* @return Value of the argument if option is set, and has an argument, otherwise {@code defaultValue}.
340+
* @since 1.7.0
341+
*/
342+
public String getOptionValue(final String opt, final Supplier<String> defaultValue) {
306343
return getOptionValue(resolveOption(opt), defaultValue);
307344
}
308345

346+
309347
/**
310348
* Gets the array of values, if any, of an option.
311349
*
@@ -349,46 +387,97 @@ public String[] getOptionValues(final String opt) {
349387
* Gets a version of this {@code Option} converted to a particular type.
350388
*
351389
* @param opt the name of the option.
390+
* @param <T> The return type for the method.
352391
* @return the value parsed into a particular object.
353392
* @throws ParseException if there are problems turning the option value into the desired type
354393
* @see PatternOptionBuilder
355394
* @since 1.5.0
356395
*/
357-
public Object getParsedOptionValue(final char opt) throws ParseException {
396+
public <T> T getParsedOptionValue(final char opt) throws ParseException {
358397
return getParsedOptionValue(String.valueOf(opt));
359398
}
360399

361400
/**
362401
* Gets a version of this {@code Option} converted to a particular type.
363402
*
364403
* @param option the name of the option.
404+
* @param <T> The return type for the method.
365405
* @return the value parsed into a particular object.
366406
* @throws ParseException if there are problems turning the option value into the desired type
367407
* @see PatternOptionBuilder
368408
* @since 1.5.0
369409
*/
370-
public Object getParsedOptionValue(final Option option) throws ParseException {
410+
public <T> T getParsedOptionValue(final Option option) throws ParseException {
411+
return getParsedOptionValue(option, null);
412+
}
413+
414+
/**
415+
* Gets a version of this {@code Option} converted to a particular type.
416+
*
417+
* @param opt the name of the option.
418+
* @param <T> The return type for the method.
419+
* @return the value parsed into a particular object.
420+
* @throws ParseException if there are problems turning the option value into the desired type
421+
* @see PatternOptionBuilder
422+
* @since 1.2
423+
*/
424+
public <T> T getParsedOptionValue(final String opt) throws ParseException {
425+
return getParsedOptionValue(resolveOption(opt));
426+
}
427+
428+
/**
429+
* Gets a version of this {@code Option} converted to a particular type.
430+
*
431+
* @param opt the name of the option.
432+
* @param defaultValue the default value to return if opt is not set.
433+
* @param <T> The return type for the method.
434+
* @return the value parsed into a particular object.
435+
* @throws ParseException if there are problems turning the option value into the desired type
436+
* @see PatternOptionBuilder
437+
* @since 1.7.0
438+
*/
439+
public <T> T getParsedOptionValue(final char opt, final T defaultValue) throws ParseException {
440+
return getParsedOptionValue(String.valueOf(opt), defaultValue);
441+
}
442+
443+
/**
444+
* Gets a version of this {@code Option} converted to a particular type.
445+
*
446+
* @param option the name of the option.
447+
* @param defaultValue the default value to return if opt is not set.
448+
* @param <T> The return type for the method.
449+
* @return the value parsed into a particular object.
450+
* @throws ParseException if there are problems turning the option value into the desired type
451+
* @see PatternOptionBuilder
452+
* @since 1.7.0
453+
*/
454+
@SuppressWarnings("unchecked")
455+
public <T> T getParsedOptionValue(final Option option, final T defaultValue) throws ParseException {
371456
if (option == null) {
372457
return null;
373458
}
374459
final String res = getOptionValue(option);
375-
if (res == null) {
376-
return null;
460+
461+
try {
462+
return res == null ? defaultValue : (T) option.getConverter().apply(res);
463+
} catch (Exception e) {
464+
throw ParseException.wrap(e);
377465
}
378-
return TypeHandler.createValue(res, option.getType());
379466
}
380467

381468
/**
382469
* Gets a version of this {@code Option} converted to a particular type.
383470
*
384471
* @param opt the name of the option.
472+
* @param defaultValue the default value to return if opt is not set.
473+
* @param <T> The return type for the method.
385474
* @return the value parsed into a particular object.
386475
* @throws ParseException if there are problems turning the option value into the desired type
387476
* @see PatternOptionBuilder
388-
* @since 1.2
477+
* @since 1.7.0
389478
*/
390-
public Object getParsedOptionValue(final String opt) throws ParseException {
391-
return getParsedOptionValue(resolveOption(opt));
479+
public <T> T getParsedOptionValue(final String opt, final T defaultValue) throws ParseException {
480+
return getParsedOptionValue(resolveOption(opt), defaultValue);
392481
}
393482

394483
/**
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
http://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+
package org.apache.commons.cli;
18+
19+
import java.io.File;
20+
import java.net.URL;
21+
import java.nio.file.Path;
22+
import java.text.SimpleDateFormat;
23+
import java.util.Date;
24+
25+
/**
26+
* The definition of the functional interface to call when doing a conversion.
27+
* Like {@code Function<String,T>} but can throw an Exception.
28+
*
29+
* @param <T> The return type for the function.
30+
* @since 1.7.0
31+
*/
32+
@FunctionalInterface
33+
public interface Converter<T> {
34+
35+
/** The default converter. Does nothing. */
36+
Converter<?> DEFAULT = s -> s;
37+
38+
/** Class name converter. Calls {@code Class.forName}. */
39+
Converter<Class<?>> CLASS = s -> Class.forName(s);
40+
41+
/** File name converter. Calls @{code new File(s)} */
42+
Converter<File> FILE = s -> new File(s);
43+
44+
/** Path converter. Calls @{code new Path(s)} */
45+
Converter<Path> PATH = s -> new File(s).toPath();
46+
47+
/**
48+
* Number converter. Converts to a Double if a decimal point ('.') is in the
49+
* string or a Long otherwise.
50+
*/
51+
Converter<Number> NUMBER = s -> {
52+
if (s.indexOf('.') != -1) {
53+
return Double.valueOf(s);
54+
}
55+
return Long.valueOf(s);
56+
};
57+
58+
/**
59+
* Converts a class name to an instance of the class. Uses the Class converter
60+
* to find the class and then call the default constructor.
61+
* @see #CLASS
62+
*/
63+
Converter<Object> OBJECT = s -> CLASS.apply(s).getConstructor().newInstance();
64+
65+
/** Creates a URL. Calls {@code new URL(s)}. */
66+
Converter<URL> URL = s -> new URL(s);
67+
68+
/** Converts to a date using the format string Form "EEE MMM dd HH:mm:ss zzz yyyy". */
69+
Converter<Date> DATE = s -> new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy").parse(s);
70+
71+
/**
72+
* Applies the conversion function to the String argument.
73+
* @param str the String to convert
74+
* @return the Object from the conversion.
75+
* @throws Exception on error.
76+
*/
77+
T apply(String str) throws Exception;
78+
}

src/main/java/org/apache/commons/cli/Option.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ public static final class Builder {
8282

8383
/** The character that is the value separator */
8484
private char valueSeparator;
85+
86+
/** The converter to convert to type **/
87+
private Converter<?> converter;
8588

8689
/**
8790
* Constructs a new {@code Builder} with the minimum required parameters for an {@code Option} instance.
@@ -270,6 +273,19 @@ public Builder valueSeparator(final char valueSeparator) {
270273
this.valueSeparator = valueSeparator;
271274
return this;
272275
}
276+
277+
/**
278+
* Sets the converter for the option.
279+
* <p>Note: see {@link TypeHandler} for serialization discussion.</p>
280+
* @param converter the Converter to use.
281+
* @return this builder, to allow method chaining.
282+
* @since 1.7.0
283+
*/
284+
public Builder converter(final Converter<?> converter) {
285+
this.converter = converter;
286+
return this;
287+
}
288+
273289
}
274290

275291
/** Specifies the number of argument values has not been specified */
@@ -335,6 +351,9 @@ public static Builder builder(final String option) {
335351

336352
/** The character that is the value separator. */
337353
private char valuesep;
354+
355+
/** The explicit converter for this option. May be null */
356+
private transient Converter<?> converter;
338357

339358
/**
340359
* Private constructor used by the nested Builder class.
@@ -351,6 +370,7 @@ private Option(final Builder builder) {
351370
this.required = builder.required;
352371
this.type = builder.type;
353372
this.valuesep = builder.valueSeparator;
373+
this.converter = builder.converter;
354374
}
355375

356376
/**
@@ -423,7 +443,6 @@ private void add(final String value) {
423443
if (!acceptsArg()) {
424444
throw new IllegalArgumentException("Cannot add value, list full.");
425445
}
426-
427446
// store value
428447
values.add(value);
429448
}
@@ -695,6 +714,8 @@ private boolean hasNoValues() {
695714
}
696715

697716
/**
717+
* Returns whether this Option can have an optional argument.
718+
*
698719
* @return whether this Option can have an optional argument
699720
*/
700721
public boolean hasOptionalArg() {
@@ -865,6 +886,24 @@ public void setType(final Object type) {
865886
public void setValueSeparator(final char sep) {
866887
this.valuesep = sep;
867888
}
889+
890+
/**
891+
* Gets the value to type converter.
892+
* @return the value to type converter
893+
* @since 1.7.0
894+
*/
895+
public Converter<?> getConverter() {
896+
return converter == null ? TypeHandler.getConverter(type) : converter;
897+
}
898+
899+
/**
900+
* Sets the value to type converter.
901+
* @param converter The converter to convert the string value to the type.
902+
* @since 1.7.0
903+
*/
904+
public void setConverter(final Converter<?> converter) {
905+
this.converter = converter;
906+
}
868907

869908
/**
870909
* Dump state, suitable for debugging.

src/main/java/org/apache/commons/cli/OptionBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more
3131
@Deprecated
3232
public final class OptionBuilder {
3333

34+
3435
/** Long option */
3536
private static String longOption;
3637

@@ -108,6 +109,7 @@ public static Option create(final String opt) throws IllegalArgumentException {
108109
option.setOptionalArg(optionalArg);
109110
option.setArgs(argCount);
110111
option.setType(type);
112+
option.setConverter(TypeHandler.getConverter(type));
111113
option.setValueSeparator(valueSeparator);
112114
option.setArgName(argName);
113115
} finally {

0 commit comments

Comments
 (0)