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}