    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
    077    /** String class */
    078    public static final Class<String> STRING_VALUE = String.class;
    079
    080    /** Object class */
    081    public static final Class<Object> OBJECT_VALUE = Object.class;
    082
    083    /** Number class */
    084    public static final Class<Number> NUMBER_VALUE = Number.class;
    085
    086    /** Date class */
    087    public static final Class<Date> DATE_VALUE = Date.class;
    088
    089    /** Class class */
    090    public static final Class<?> CLASS_VALUE = Class.class;
    091
    092    /// can we do this one??
    093    // is meant to check that the file exists, else it errors.
    094    // ie) it's for reading not writing.
    095
    096    /** FileInputStream class */
    097    public static final Class<FileInputStream> EXISTING_FILE_VALUE = FileInputStream.class;
    098
    099    /** File class */
    100    public static final Class<File> FILE_VALUE = File.class;
    101
    102    /** File array class */
    103    public static final Class<File[]> FILES_VALUE = File[].class;
    104
    105    /** URL class */
    106    public static final Class<URL> URL_VALUE = URL.class;
    107
    108    /** The converter to use for Unimplemented data types */
    109    private static final Converter<?, UnsupportedOperationException> UNSUPPORTED = s -> {
    110        throw new UnsupportedOperationException("Not yet implemented");
    111    };
    112
    113    /**
    114     * Retrieve the class that {@code ch} represents.
    115     *
    116     * @param ch the specified character
    117     * @return The class that {@code ch} represents
    118     * @deprecated use {@link #getValueType(char)}
    119     */
    120    @Deprecated // since="1.7.0"
    121    public static Object getValueClass(final char ch) {
    122        return getValueType(ch);
    123    }
    124
    125    /**
    126     * Retrieve the class that {@code ch} represents.
    127     *
    128     * @param ch the specified character
    129     * @return The class that {@code ch} represents
    130     * @since 1.7.0
    131     */
    132    public static Class<?> getValueType(final char ch) {
    133        switch (ch) {
    134        case '@':
    135            return OBJECT_VALUE;
    136        case ':':
    137            return STRING_VALUE;
    138        case '%':
    139            return NUMBER_VALUE;
    140        case '+':
    141            return CLASS_VALUE;
    142        case '#':
    143            return DATE_VALUE;
    144        case '<':
    145            return EXISTING_FILE_VALUE;
    146        case '>':
    147            return FILE_VALUE;
    148        case '*':
    149            return FILES_VALUE;
    150        case '/':
    151            return URL_VALUE;
    152        }
    153
    154        return null;
    155    }
    156
    157    /**
    158     * Returns whether {@code ch} is a value code, i.e. whether it represents a class in a pattern.
    159     *
    160     * @param ch the specified character
    161     * @return true if {@code ch} is a value code, otherwise false.
    162     */
    163    public static boolean isValueCode(final char ch) {
    164        return ch == '@' || ch == ':' || ch == '%' || ch == '+' || ch == '#' || ch == '<' || ch == '>' || ch == '*' || ch == '/' || ch == '!';
    165    }
    166
    167    /**
    168     * Returns the {@link Options} instance represented by {@code pattern}.
    169     *
    170     * @param pattern the pattern string
    171     * @return The {@link Options} instance
    172     */
    173    public static Options parsePattern(final String pattern) {
    174        char opt = Char.SP;
    175        boolean required = false;
    176        Class<?> type = null;
    177        Converter<?, ?> converter = Converter.DEFAULT;
    178
    179        final Options options = new Options();
    180
    181        for (int i = 0; i < pattern.length(); i++) {
    182            final char ch = pattern.charAt(i);
    183
    184            // a value code comes after an option and specifies
    185            // details about it
    186            if (!isValueCode(ch)) {
    187                if (opt != Char.SP) {
    188                    // @formatter:off
    189                    final Option option = Option.builder(String.valueOf(opt))
    190                            .hasArg(type != null)
    191                            .required(required)
    192                            .type(type)
    193                            .converter(converter)
    194                            .build();
    195                    // @formatter:on
    196                    // we have a previous one to deal with
    197                    options.addOption(option);
    198                    required = false;
    199                    type = null;
    200                    converter = Converter.DEFAULT;
    201                }
    202
    203                opt = ch;
    204            } else if (ch == '!') {
    205                required = true;
    206            } else {
    207                type = getValueType(ch);
    208                final Map<Class<?>, Converter<?, ? extends Throwable>> map = TypeHandler.createDefaultMap();
    209                // Backward compatibility (probably).
    210                map.put(FILES_VALUE, unsupported());
    211                converter = new TypeHandler(map).getConverter(getValueType(ch));
    212            }
    213        }
    214
    215        if (opt != Char.SP) {
    216            final Option option = Option.builder(String.valueOf(opt)).hasArg(type != null).required(required).type(type).build();
    217
    218            // we have a final one to deal with
    219            options.addOption(option);
    220        }
    221
    222        return options;
    223    }
    224
    225    @SuppressWarnings("unchecked")
    226    static <T> T unsupported() {
    227        return (T) UNSUPPORTED;
    228    }
    229}