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         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  
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 with no 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 Throwable 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 }