View Javadoc
1   /*
2     Licensed to the Apache Software Foundation (ASF) under one or more
3     contributor license agreements.  See the NOTICE file distributed with
4     this work for additional information regarding copyright ownership.
5     The ASF licenses this file to You under the Apache License, Version 2.0
6     (the "License"); you may not use this file except in compliance with
7     the License.  You may obtain a copy of the License at
8   
9         http://www.apache.org/licenses/LICENSE-2.0
10  
11    Unless required by applicable law or agreed to in writing, software
12    distributed under the License is distributed on an "AS IS" BASIS,
13    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14    See the License for the specific language governing permissions and
15    limitations under the License.
16   */
17  
18  package org.apache.commons.cli;
19  
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.net.URL;
23  import java.util.Date;
24  import java.util.Map;
25  
26  /**
27   * Allows Options to be created from a single String. The pattern contains various single character flags and via an
28   * optional punctuation character, their expected type.
29   *
30   * <table border="1">
31   * <caption>Overview of PatternOptionBuilder patterns</caption>
32   * <tr>
33   * <td>a</td>
34   * <td>-a flag</td>
35   * </tr>
36   * <tr>
37   * <td>b@</td>
38   * <td>-b [class name]</td>
39   * </tr>
40   * <tr>
41   * <td>c&gt;</td>
42   * <td>-c [file name]</td>
43   * </tr>
44   * <tr>
45   * <td>d+</td>
46   * <td>-d [class name] (creates object via empty constructor)</td>
47   * </tr>
48   * <tr>
49   * <td>e%</td>
50   * <td>-e [number] (creates Double/Long instance depending on existing of a '.')</td>
51   * </tr>
52   * <tr>
53   * <td>f/</td>
54   * <td>-f [URL]</td>
55   * </tr>
56   * <tr>
57   * <td>g:</td>
58   * <td>-g [string]</td>
59   * </tr>
60   * </table>
61   *
62   * <p>
63   * For example, the following allows command line flags of '-v -p string-value -f /dir/file'. The exclamation mark
64   * precede a mandatory option.
65   * </p>
66   *
67   * <pre>
68   * Options options = PatternOptionBuilder.parsePattern("vp:!f/");
69   * </pre>
70   *
71   * <p>
72   * TODO These need to break out to OptionType and also to be pluggable.
73   * </p>
74   */
75  public class PatternOptionBuilder {
76  
77      /** String class */
78      public static final Class<String> STRING_VALUE = String.class;
79  
80      /** Object class */
81      public static final Class<Object> OBJECT_VALUE = Object.class;
82  
83      /** Number class */
84      public static final Class<Number> NUMBER_VALUE = Number.class;
85  
86      /** Date class */
87      public static final Class<Date> DATE_VALUE = Date.class;
88  
89      /** Class class */
90      public static final Class<?> CLASS_VALUE = Class.class;
91  
92      /// can we do this one??
93      // is meant to check that the file exists, else it errors.
94      // ie) it's for reading not writing.
95  
96      /** FileInputStream class */
97      public static final Class<FileInputStream> EXISTING_FILE_VALUE = FileInputStream.class;
98  
99      /** 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 }