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}