    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}