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 }