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