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 https://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></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 /** FileInputStream class */
93 public static final Class<FileInputStream> EXISTING_FILE_VALUE = FileInputStream.class;
94
95 /// can we do this one??
96 // is meant to check that the file exists, else it errors.
97 // ie) it's for reading not writing.
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 .get();
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).get();
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
230 /**
231 * Deprecated, only provides static methods.
232 *
233 * @deprecated Will be private or class will be final.
234 */
235 @Deprecated
236 public PatternOptionBuilder() {
237 // empty
238 }
239 }