    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      https://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 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}