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