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.File;
21  import java.io.FileInputStream;
22  import java.math.BigDecimal;
23  import java.math.BigInteger;
24  import java.net.URL;
25  import java.nio.file.Path;
26  import java.util.Date;
27  import java.util.HashMap;
28  import java.util.Map;
29  import java.util.Objects;
30  
31  /**
32   * TypeHandler will handle the pluggable conversion and verification of Option types. It handles the mapping of classes to bot converters and verifiers. It
33   * provides the default conversion and verification methods when converters and verifiers are not explicitly set.
34   * <p>
35   * If Options are serialized and deserialized their converters and verifiers will revert to the defaults defined in this class. To correctly de-serialize
36   * Options with custom converters and/or verifiers, using the default serialization methods, this class should be properly configured with the custom converters
37   * and verifiers for the specific class.
38   * </p>
39   */
40  public class TypeHandler {
41  
42      /**
43       * The default TypeHandler.
44       */
45      private static final TypeHandler DEFAULT = new TypeHandler();
46  
47      /** Value of hex conversion of strings. */
48      private static final int HEX_RADIX = 16;
49  
50      /**
51       * Returns the class whose name is {@code className}.
52       *
53       * @param className the class name.
54       * @return The class if it is found.
55       * @throws ParseException if the class could not be found.
56       */
57      public static Class<?> createClass(final String className) throws ParseException {
58          return createValue(className, Class.class);
59      }
60  
61      /**
62       * Returns the date represented by {@code string}.
63       * <p>
64       * This method is not yet implemented and always throws an {@link UnsupportedOperationException}.
65       * </p>
66       *
67       * @param string the date string.
68       * @return The date if {@code string} is a valid date string, otherwise return null.
69       */
70      public static Date createDate(final String string) {
71          return createValueUnchecked(string, Date.class);
72      }
73  
74      /**
75       * Creates a default converter map.
76       *
77       * @return a default converter map.
78       * @since 1.7.0
79       */
80      public static Map<Class<?>, Converter<?, ? extends Throwable>> createDefaultMap() {
81          return putDefaultMap(new HashMap<>());
82      }
83  
84      /**
85       * Returns the File represented by {@code string}.
86       *
87       * @param string the File location.
88       * @return The file represented by {@code string}.
89       */
90      public static File createFile(final String string) {
91          return createValueUnchecked(string, File.class);
92      }
93  
94      /**
95       * Creates the File[] represented by {@code string}.
96       *
97       * <p>
98       * This method is not yet implemented and always throws an {@link UnsupportedOperationException}.
99       * </p>
100      *
101      * @param string the paths to the files.
102      * @return The File[] represented by {@code string}.
103      * @throws UnsupportedOperationException always.
104      * @deprecated Without replacement.
105      */
106     @Deprecated // since 1.7.0
107     public static File[] createFiles(final String string) {
108         // to implement/port:
109         // return FileW.findFiles(string);
110         throw new UnsupportedOperationException("Not yet implemented");
111     }
112 
113     /**
114      * Creates a number from a String. If a '.' is present, it creates a Double, otherwise a Long.
115      *
116      * @param string the value.
117      * @return the number represented by {@code string}.
118      * @throws ParseException if {@code string} is not a number.
119      */
120     @Deprecated // since 1.7.0
121     public static Number createNumber(final String string) throws ParseException {
122         return createValue(string, Number.class);
123     }
124 
125     /**
126      * Creates an Object from the class name and empty constructor.
127      *
128      * @param className the argument value.
129      * @return the initialized object.
130      * @throws ParseException if the class could not be found or the object could not be created.
131      * @deprecated Use {@link #createValue(String, Class)}.
132      */
133     @Deprecated // since 1.7.0
134     public static Object createObject(final String className) throws ParseException {
135         return createValue(className, Object.class);
136     }
137 
138     /**
139      * Creates the URL represented by {@code string}.
140      *
141      * @param string the URL string.
142      * @return The URL in {@code string} is well-formed.
143      * @throws ParseException if the URL in {@code string} is not well-formed.
144      */
145     public static URL createURL(final String string) throws ParseException {
146         return createValue(string, URL.class);
147     }
148 
149     /**
150      * Creates the @code Object} of type {@code clazz} with the value of {@code string}.
151      *
152      * @param string the command line value.
153      * @param clazz  the class representing the type of argument.
154      * @param <T>    type of argument.
155      * @return The instance of {@code clazz} initialized with the value of {@code string}.
156      * @throws ParseException if the value creation for the given class threw an exception.
157      */
158     public static <T> T createValue(final String string, final Class<T> clazz) throws ParseException {
159         try {
160             return getDefault().getConverter(clazz).apply(string);
161         } catch (final Exception e) {
162             throw ParseException.wrap(e);
163         }
164     }
165 
166     /**
167      * Creates the {@code Object} of type {@code obj} with the value of {@code string}.
168      *
169      * @param string the command line value.
170      * @param obj    the type of argument.
171      * @return The instance of {@code obj} initialized with the value of {@code string}.
172      * @throws ParseException if the value creation for the given object type failed.
173      * @deprecated Use {@link #createValue(String, Class)}.
174      */
175     @Deprecated // since 1.7.0
176     public static Object createValue(final String string, final Object obj) throws ParseException {
177         return createValue(string, (Class<?>) obj);
178     }
179 
180     /**
181      * Delegates to {@link #createValue(String, Class)} throwing IllegalArgumentException instead of ParseException.
182      *
183      * @param string the command line value.
184      * @param clazz  the class representing the type of argument.
185      * @param <T>    type of argument.
186      * @return The instance of {@code clazz} initialized with the value of {@code string}.
187      * @throws IllegalArgumentException if the value creation for the given class threw an exception.
188      */
189     private static <T> T createValueUnchecked(final String string, final Class<T> clazz) {
190         try {
191             return createValue(string, clazz);
192         } catch (final ParseException e) {
193             throw new IllegalArgumentException(e);
194         }
195     }
196 
197     /**
198      * Gets the default TypeHandler.
199      *
200      * @return the default TypeHandler.
201      * @since 1.7.0
202      */
203     public static TypeHandler getDefault() {
204         return DEFAULT;
205     }
206 
207     /**
208      * Returns the opened FileInputStream represented by {@code string}.
209      *
210      * @param string the file location.
211      * @return The file input stream represented by {@code string}.
212      * @throws ParseException if the file is not exist or not readable.
213      * @deprecated Use {@link #createValue(String, Class)}.
214      */
215     @Deprecated // since 1.7.0
216     public static FileInputStream openFile(final String string) throws ParseException {
217         return createValue(string, FileInputStream.class);
218     }
219 
220     private static Map<Class<?>, Converter<?, ? extends Throwable>> putDefaultMap(final Map<Class<?>, Converter<?, ? extends Throwable>> map) {
221         map.put(Object.class, Converter.OBJECT);
222         map.put(Class.class, Converter.CLASS);
223         map.put(Date.class, Converter.DATE);
224         map.put(File.class, Converter.FILE);
225         map.put(Path.class, Converter.PATH);
226         map.put(Number.class, Converter.NUMBER);
227         map.put(URL.class, Converter.URL);
228         map.put(FileInputStream.class, FileInputStream::new);
229         map.put(Long.class, Long::parseLong);
230         map.put(Integer.class, Integer::parseInt);
231         map.put(Short.class, Short::parseShort);
232         map.put(Byte.class, Byte::parseByte);
233         map.put(Character.class, s -> s.startsWith("\\u") ? Character.toChars(Integer.parseInt(s.substring(2), HEX_RADIX))[0] : s.charAt(0));
234         map.put(Double.class, Double::parseDouble);
235         map.put(Float.class, Float::parseFloat);
236         map.put(BigInteger.class, BigInteger::new);
237         map.put(BigDecimal.class, BigDecimal::new);
238         return map;
239     }
240 
241     /**
242      * Map of Class to Converter.
243      * <p>
244      * For each entry, that Class' type must match the Converter's first type.
245      * </p>
246      */
247     private final Map<Class<?>, Converter<?, ? extends Throwable>> converterMap;
248 
249     /**
250      * Constructs a default initialized instance.
251      */
252     public TypeHandler() {
253         this(createDefaultMap());
254     }
255 
256     /**
257      * Constructs a default initialized instance.
258      * <p>
259      * For each entry, that Class' type must match the Converter's first type.
260      * </p>
261      *
262      * @param converterMap The converter map, not null.
263      * @since 1.7.0
264      */
265     public TypeHandler(final Map<Class<?>, Converter<?, ? extends Throwable>> converterMap) {
266         this.converterMap = Objects.requireNonNull(converterMap, "converterMap");
267     }
268 
269     /**
270      * Gets the registered converter for the the Class, or {@link Converter#DEFAULT} if absent.
271      *
272      * @param <T>   The Class parameter type.
273      * @param clazz The Class to get the Converter for.
274      * @return the registered converter if any, {@link Converter#DEFAULT} otherwise.
275      * @since 1.7.0
276      */
277     @SuppressWarnings("unchecked") // returned value will have type T because it is fixed by clazz
278     public <T> Converter<T, ?> getConverter(final Class<T> clazz) {
279         return (Converter<T, ?>) converterMap.getOrDefault(clazz, Converter.DEFAULT);
280     }
281 
282 }