1
2
3
4
5
6
7
8
9
10
11
12
13
14 package eu.fbk.rdfpro.util;
15
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21
22 import javax.annotation.Nullable;
23
24 public final class Options {
25
26 private final List<String> positionalArgs;
27
28 private final List<String> options;
29
30 private final Map<String, List<String>> optionArgs;
31
32 public static Options parse(final String spec, final String... args) {
33
34 final String[][] argsToOptions = new String[args.length][];
35 final int[] argsToMinCard = new int[args.length];
36 final int[] argsToMaxCard = new int[args.length];
37 final List<String> mandatoryOptions = new ArrayList<String>();
38 int minPositionalCard = 0;
39 int maxPositionalCard = 0;
40
41 for (final String tokenUntrimmed : spec.split("\\|")) {
42 final String token = tokenUntrimmed.trim();
43 final int len = token.length();
44 if (len == 0) {
45 continue;
46 }
47 final char last = token.charAt(len - 1);
48 final int minCard = last == '!' || last == '+' ? 1 : 0;
49 final int maxCard = last == '?' || last == '!' ? 1
50 : last == '+' || last == '*' ? Integer.MAX_VALUE : 0;
51 final boolean mandatory = minCard >= 1 && len >= 2 && token.charAt(len - 2) == last;
52 final String tokenOptions = token.substring(0, maxCard == 0 ? len
53 : mandatory ? len - 2 : len - 1);
54 if (tokenOptions.length() == 0) {
55 minPositionalCard = minCard;
56 maxPositionalCard = maxCard;
57 } else {
58 final String[] options = tokenOptions.split(",");
59 for (int i = 0; i < options.length; ++i) {
60 options[i] = options[i].trim();
61 if (mandatory) {
62 mandatoryOptions.add(options[i]);
63 }
64 }
65 for (int i = 0; i < options.length; ++i) {
66 final String optionExt = (options[i].length() == 1 ? "-" : "--") + options[i];
67 for (int j = 0; j < args.length; ++j) {
68 if (args[j].equals(optionExt)) {
69 argsToOptions[j] = options;
70 argsToMinCard[j] = minCard;
71 argsToMaxCard[j] = maxCard;
72 }
73 }
74 }
75 }
76 }
77
78 final List<String> positionalArgs = new ArrayList<String>();
79 final List<String> optionList = new ArrayList<String>();
80 final Map<String, List<String>> optionArgs = new HashMap<String, List<String>>();
81 int j = 0;
82 while (j < args.length) {
83 final String[] options = argsToOptions[j];
84 if (options != null) {
85 final String optionWithDashes = args[j];
86 final int minCard = argsToMinCard[j];
87 final int maxCard = argsToMaxCard[j];
88 ++j;
89 List<String> values = optionArgs.get(options[0]);
90 if (values == null) {
91 values = new ArrayList<String>();
92 optionList.add(options[0]);
93 for (int i = 0; i < options.length; ++i) {
94 optionArgs.put(options[i], values);
95 }
96 }
97 for (int k = 0; k < maxCard; ++k) {
98 if (j < args.length && argsToOptions[j] == null) {
99 values.add(args[j++]);
100 } else if (k < minCard) {
101 throw new IllegalArgumentException("Expected at least " + minCard
102 + " arguments for option '" + optionWithDashes + "'");
103 } else {
104 break;
105 }
106 }
107 } else if (args[j].startsWith("-") && !args[j].contains(" ")) {
108 throw new IllegalArgumentException("Unrecognized option '" + args[j] + "'");
109 } else {
110 positionalArgs.add(args[j++]);
111 }
112 }
113
114 if (positionalArgs.size() < minPositionalCard) {
115 throw new IllegalArgumentException("Expected at least " + minPositionalCard
116 + " positional arguments");
117 } else if (positionalArgs.size() > maxPositionalCard) {
118 throw new IllegalArgumentException("Expected at most " + maxPositionalCard
119 + " positional arguments");
120 }
121
122 for (final String option : mandatoryOptions) {
123 if (!optionArgs.containsKey(option)) {
124 throw new IllegalArgumentException("Missing mandatory option '" + option + "'");
125 }
126 }
127
128 return new Options(positionalArgs, optionList, optionArgs);
129 }
130
131 private Options(final List<String> args, final List<String> options,
132 final Map<String, List<String>> optionValues) {
133
134 this.positionalArgs = args;
135 this.options = new ArrayList<String>(options);
136 this.optionArgs = optionValues;
137
138 Collections.sort(this.options);
139 }
140
141 public <T> List<T> getPositionalArgs(final Class<T> type) {
142 return convert(this.positionalArgs, type);
143 }
144
145 public <T> T getPositionalArg(final int index, final Class<T> type) {
146 return convert(this.positionalArgs.get(index), type);
147 }
148
149 public <T> T getPositionalArg(final int index, final Class<T> type, final T defaultValue) {
150 try {
151 return convert(this.positionalArgs.get(index), type);
152 } catch (final Throwable ex) {
153 return defaultValue;
154 }
155 }
156
157 public int getPositionalArgCount() {
158 return this.positionalArgs.size();
159 }
160
161 public List<String> getOptions() {
162 return this.options;
163 }
164
165 public boolean hasOption(final String optionName) {
166 return this.optionArgs.containsKey(optionName);
167 }
168
169 public <T> List<T> getOptionArgs(final String optionName, final Class<T> type) {
170 final List<String> strings = this.optionArgs.get(optionName);
171 if (strings != null) {
172 return convert(strings, type);
173 }
174 return Collections.emptyList();
175 }
176
177 @Nullable
178 public <T> T getOptionArg(final String optionName, final Class<T> type) {
179 final List<String> strings = this.optionArgs.get(optionName);
180 if (strings == null || strings.isEmpty()) {
181 return null;
182 }
183 if (strings.size() > 1) {
184 throw new IllegalArgumentException("Multiple args for option '" + optionName + "': "
185 + String.join(", ", strings));
186 }
187 try {
188 return convert(strings.get(0), type);
189 } catch (final Throwable ex) {
190 throw new IllegalArgumentException("'" + strings.get(0) + "' is not a valid "
191 + type.getSimpleName(), ex);
192 }
193 }
194
195 @Nullable
196 public <T> T getOptionArg(final String optionName, final Class<T> type,
197 @Nullable final T defaultValue) {
198 final List<String> strings = this.optionArgs.get(optionName);
199 if (strings == null || strings.isEmpty() || strings.size() > 1) {
200 return defaultValue;
201 }
202 try {
203 return convert(strings.get(0), type);
204 } catch (final Throwable ex) {
205 return defaultValue;
206 }
207 }
208
209 public int getOptionCount() {
210 return this.options.size();
211 }
212
213 @Override
214 public boolean equals(final Object object) {
215 if (object == this) {
216 return true;
217 }
218 if (!(object instanceof Options)) {
219 return false;
220 }
221 final Options other = (Options) object;
222 return this.positionalArgs.equals(other.positionalArgs)
223 && this.optionArgs.equals(other.optionArgs);
224 }
225
226 @Override
227 public int hashCode() {
228 return this.positionalArgs.hashCode() * 37 + this.optionArgs.hashCode();
229 }
230
231 @Override
232 public String toString() {
233 final StringBuilder builder = new StringBuilder();
234 for (final String option : this.options) {
235 builder.append(builder.length() != 0 ? " " : "").append(option);
236 final List<String> args = this.optionArgs.get(option);
237 if (args != null) {
238 for (final String arg : args) {
239 builder.append(' ').append(arg);
240 }
241 }
242 }
243 for (final String arg : this.positionalArgs) {
244 builder.append(builder.length() != 0 ? " " : "").append(arg);
245 }
246 return builder.toString();
247 }
248
249 private static <T> T convert(final String string, final Class<T> type) {
250 try {
251 return Statements.convert(string, type);
252 } catch (final Throwable ex) {
253 throw new IllegalArgumentException("'" + string + "' is not a valid "
254 + type.getSimpleName(), ex);
255 }
256 }
257
258 @SuppressWarnings("unchecked")
259 private static <T> List<T> convert(final List<String> strings, final Class<T> type) {
260 if (type == String.class) {
261 return (List<T>) strings;
262 }
263 final List<T> list = new ArrayList<T>();
264 for (final String string : strings) {
265 list.add(convert(string, type));
266 }
267 return Collections.unmodifiableList(list);
268 }
269
270 }