    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.net.URL;
    023import java.util.Date;
    024import java.util.Map;
    025
    026/**
    027 * Allows Options to be created from a single String. The pattern contains various single character flags and via an
    028 * optional punctuation character, their expected type.
    029 *
    030 * <table border="1">
    031 * <caption>Overview of PatternOptionBuilder patterns</caption>
    032 * <tr>
    033 * <td>a</td>
    034 * <td>-a flag</td>
    035 * </tr>
    036 * <tr>
    037 * <td>b@</td>
    038 * <td>-b [class name]</td>
    039 * </tr>
    040 * <tr>
    041 * <td>c&gt;</td>
    042 * <td>-c [file name]</td>
    043 * </tr>
    044 * <tr>
    045 * <td>d+</td>
    046 * <td>-d [class name] (creates object via empty constructor)</td>
    047 * </tr>
    048 * <tr>
    049 * <td>e%</td>
    050 * <td>-e [number] (creates Double/Long instance depending on existing of a '.')</td>
    051 * </tr>
    052 * <tr>
    053 * <td>f/</td>
    054 * <td>-f [URL]</td>
    055 * </tr>
    056 * <tr>
    057 * <td>g:</td>
    058 * <td>-g [string]</td>
    059 * </tr>
    060 * </table>
    061 *
    062 * <p>
    063 * For example, the following allows command line flags of '-v -p string-value -f /dir/file'. The exclamation mark
    064 * precede a mandatory option.
    065 * </p>
    066 *
    067 * <pre>
    068 * Options options = PatternOptionBuilder.parsePattern("vp:!f/");
    069 * </pre>
    070 *
    071 * <p>
    072 * TODO These need to break out to OptionType and also to be pluggable.
    073 * </p>
    074 */
    075public class PatternOptionBuilder {
    076    /** String class */
    077    public static final Class<String> STRING_VALUE = String.class;
    078
    079    /** Object class */
    080    public static final Class<Object> OBJECT_VALUE = Object.class;
    081
    082    /** Number class */
    083    public static final Class<Number> NUMBER_VALUE = Number.class;
    084
    085    /** Date class */
    086    public static final Class<Date> DATE_VALUE = Date.class;
    087
    088    /** Class class */
    089    public static final Class<?> CLASS_VALUE = Class.class;
    090
    091    /// can we do this one??
    092    // is meant to check that the file exists, else it errors.
    093    // ie) it's for reading not writing.
    094
    095    /** FileInputStream class */
    096    public static final Class<FileInputStream> EXISTING_FILE_VALUE = FileInputStream.class;
    097
    098    /** File class */
    099    public static final Class<File> FILE_VALUE = File.class;
    100
    101    /** File array class */
    102    public static final Class<File[]> FILES_VALUE = File[].class;
    103
    104    /** URL class */
    105    public static final Class<URL> URL_VALUE = URL.class;
    106
    107    /** The converter to use for Unimplemented data types */
    108    private static final Converter<?, UnsupportedOperationException> UNSUPPORTED = s -> {
    109        throw new UnsupportedOperationException("Not yet implemented");
    110    };
    111
    112    /**
    113     * Retrieve the class that {@code ch} represents.
    114     *
    115     * @param ch the specified character
    116     * @return The class that {@code ch} represents
    117     * @deprecated use {@link #getValueType(char)}
    118     */
    119    @Deprecated // since="1.7.0"
    120    public static Object getValueClass(final char ch) {
    121        return getValueType(ch);
    122    }
    123
    124    /**
    125     * Retrieve the class that {@code ch} represents.
    126     *
    127     * @param ch the specified character
    128     * @return The class that {@code ch} represents
    129     * @since 1.7.0
    130     */
    131    public static Class<?> getValueType(final char ch) {
    132        switch (ch) {
    133        case '@':
    134            return PatternOptionBuilder.OBJECT_VALUE;
    135        case ':':
    136            return PatternOptionBuilder.STRING_VALUE;
    137        case '%':
    138            return PatternOptionBuilder.NUMBER_VALUE;
    139        case '+':
    140            return PatternOptionBuilder.CLASS_VALUE;
    141        case '#':
    142            return PatternOptionBuilder.DATE_VALUE;
    143        case '<':
    144            return PatternOptionBuilder.EXISTING_FILE_VALUE;
    145        case '>':
    146            return PatternOptionBuilder.FILE_VALUE;
    147        case '*':
    148            return PatternOptionBuilder.FILES_VALUE;
    149        case '/':
    150            return PatternOptionBuilder.URL_VALUE;
    151        }
    152
    153        return null;
    154    }
    155
    156    /**
    157     * Returns whether {@code ch} is a value code, i.e. whether it represents a class in a pattern.
    158     *
    159     * @param ch the specified character
    160     * @return true if {@code ch} is a value code, otherwise false.
    161     */
    162    public static boolean isValueCode(final char ch) {
    163        return ch == '@' || ch == ':' || ch == '%' || ch == '+' || ch == '#' || ch == '<' || ch == '>' || ch == '*' || ch == '/' || ch == '!';
    164    }
    165
    166    /**
    167     * Returns the {@link Options} instance represented by {@code pattern}.
    168     *
    169     * @param pattern the pattern string
    170     * @return The {@link Options} instance
    171     */
    172    public static Options parsePattern(final String pattern) {
    173        char opt = Char.SP;
    174        boolean required = false;
    175        Class<?> type = null;
    176        Converter<?, ?> converter = Converter.DEFAULT;
    177
    178        final Options options = new Options();
    179
    180        for (int i = 0; i < pattern.length(); i++) {
    181            final char ch = pattern.charAt(i);
    182
    183            // a value code comes after an option and specifies
    184            // details about it
    185            if (!isValueCode(ch)) {
    186                if (opt != Char.SP) {
    187                    // @formatter:off
    188                    final Option option = Option.builder(String.valueOf(opt))
    189                            .hasArg(type != null)
    190                            .required(required)
    191                            .type(type)
    192                            .converter(converter)
    193                            .build();
    194                    // @formatter:on
    195                    // we have a previous one to deal with
    196                    options.addOption(option);
    197                    required = false;
    198                    type = null;
    199                    converter = Converter.DEFAULT;
    200                }
    201
    202                opt = ch;
    203            } else if (ch == '!') {
    204                required = true;
    205            } else {
    206                type = getValueType(ch);
    207                final Map<Class<?>, Converter<?, ? extends Throwable>> map = TypeHandler.createDefaultMap();
    208                // Backward compatibility (probably).
    209                map.put(FILES_VALUE, unsupported());
    210                converter = new TypeHandler(map).getConverter(getValueType(ch));
    211            }
    212        }
    213
    214        if (opt != Char.SP) {
    215            final Option option = Option.builder(String.valueOf(opt)).hasArg(type != null).required(required).type(type).build();
    216
    217            // we have a final one to deal with
    218            options.addOption(option);
    219        }
    220
    221        return options;
    222    }
    223
    224    @SuppressWarnings("unchecked")
    225    static <T> T unsupported() {
    226        return (T) UNSUPPORTED;
    227    }
    228}