001/* 002 Licensed to the Apache Software Foundation (ASF) under one or more 003 contributor license agreements. See the NOTICE file distributed with 004 this work for additional information regarding copyright ownership. 005 The ASF licenses this file to You under the Apache License, Version 2.0 006 (the "License"); you may not use this file except in compliance with 007 the License. You may obtain a copy of the License at 008 009 http://www.apache.org/licenses/LICENSE-2.0 010 011 Unless required by applicable law or agreed to in writing, software 012 distributed under the License is distributed on an "AS IS" BASIS, 013 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 See the License for the specific language governing permissions and 015 limitations under the License. 016 */ 017 018package org.apache.commons.cli; 019 020import java.io.File; 021import java.io.FileInputStream; 022import java.math.BigDecimal; 023import java.math.BigInteger; 024import java.net.URL; 025import java.nio.file.Path; 026import java.util.Date; 027import java.util.HashMap; 028import java.util.Map; 029import java.util.Objects; 030 031/** 032 * TypeHandler will handle the pluggable conversion and verification of Option types. It handles the mapping of classes to bot converters and verifiers. It 033 * provides the default conversion and verification methods when converters and verifiers are not explicitly set. 034 * <p> 035 * If Options are serialized and deserialized their converters and verifiers will revert to the defaults defined in this class. To correctly de-serialize 036 * Options with custom converters and/or verifiers, using the default serialization methods, this class should be properly configured with the custom converters 037 * and verifiers for the specific class. 038 * </p> 039 */ 040public class TypeHandler { 041 042 /** 043 * The default TypeHandler. 044 */ 045 private static final TypeHandler DEFAULT = new TypeHandler(); 046 047 /** Value of hex conversion of strings */ 048 private static final int HEX_RADIX = 16; 049 050 /** 051 * Returns the class whose name is {@code className}. 052 * 053 * @param className the class name 054 * @return The class if it is found 055 * @throws ParseException if the class could not be found 056 */ 057 public static Class<?> createClass(final String className) throws ParseException { 058 return createValue(className, Class.class); 059 } 060 061 /** 062 * Returns the date represented by {@code string}. 063 * <p> 064 * This method is not yet implemented and always throws an {@link UnsupportedOperationException}. 065 * </p> 066 * 067 * @param string the date string 068 * @return The date if {@code string} is a valid date string, otherwise return null. 069 */ 070 public static Date createDate(final String string) { 071 return createValueUnchecked(string, Date.class); 072 } 073 074 /** 075 * Creates a default converter map. 076 * 077 * @return a default converter map. 078 * @since 1.7.0 079 */ 080 public static Map<Class<?>, Converter<?, ? extends Throwable>> createDefaultMap() { 081 return putDefaultMap(new HashMap<>()); 082 } 083 084 /** 085 * Returns the File represented by {@code string}. 086 * 087 * @param string the File location 088 * @return The file represented by {@code string}. 089 */ 090 public static File createFile(final String string) { 091 return createValueUnchecked(string, File.class); 092 } 093 094 /** 095 * Creates the File[] represented by {@code string}. 096 * 097 * <p> 098 * This method is not yet implemented and always throws an {@link UnsupportedOperationException}. 099 * </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}