1
2
3
4
5
6
7
8
9
10
11
12 package org.hyphenType.lexerparser;
13
14 import java.lang.annotation.Annotation;
15 import java.lang.reflect.Method;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.regex.Pattern;
22
23 import org.hyphenType.datastructure.Options;
24 import org.hyphenType.datastructure.annotations.ArgumentsObject;
25 import org.hyphenType.datastructure.annotations.InputChannel;
26 import org.hyphenType.datastructure.annotations.Option;
27 import org.hyphenType.datastructure.annotations.OptionArgument;
28 import org.hyphenType.datastructure.annotations.OptionMapValue;
29 import org.hyphenType.datastructure.annotations.OptionValue;
30 import org.hyphenType.datastructure.annotations.SimpleArgument;
31 import org.hyphenType.datastructure.lexer.LexToken;
32 import org.hyphenType.datastructure.lexer.option.LexOption;
33 import org.hyphenType.datastructure.lexer.option.LexOptionMapValue;
34 import org.hyphenType.datastructure.lexer.option.LexOptionValue;
35 import org.hyphenType.datastructure.lexer.simple.LexArgument;
36 import org.hyphenType.datastructure.parser.StructureElement;
37 import org.hyphenType.datastructure.parser.option.StructureOption;
38 import org.hyphenType.datastructure.parser.option.StructureOptionArgument;
39 import org.hyphenType.datastructure.parser.option.StructureOptionMapValue;
40 import org.hyphenType.datastructure.parser.option.StructureOptionValue;
41 import org.hyphenType.datastructure.parser.simple.StructureSimpleArgument;
42 import org.hyphenType.exceptions.InvalidOptionsInterfaceException;
43 import org.hyphenType.util.DefaultAnnotation;
44 import org.hyphenType.util.I18NResourceBundle;
45
46
47
48
49
50
51
52
53
54
55
56
57 public class LexerParser<T extends org.hyphenType.datastructure.Options<?>> {
58
59
60
61
62 private final ArgumentsObject argumentsObject;
63
64
65
66 private final Class<T> optionsInterface;
67
68
69
70 private final List<StructureOption> parsedOptions;
71
72
73
74 private final List<StructureSimpleArgument> simpleArguments;
75
76
77
78
79
80 public LexerParser(final Class<T> optionsInterfaceClass) throws InvalidOptionsInterfaceException {
81 this(optionsInterfaceClass, true);
82 }
83
84
85
86
87
88
89 @SuppressWarnings("unchecked")
90 public LexerParser(final Class<T> optionsInterfaceClass, boolean loadFromResourceBundle) throws InvalidOptionsInterfaceException {
91
92
93
94 optionsInterface = optionsInterfaceClass;
95
96 if (!optionsInterfaceClass.isInterface()) {
97 throw new InvalidOptionsInterfaceException(optionsInterfaceClass.getName() + " is not an interface.");
98 }
99
100 argumentsObject = DefaultAnnotation.getAnnotation(optionsInterfaceClass, ArgumentsObject.class);
101
102 I18NResourceBundle i18nrb = null;
103 if(loadFromResourceBundle) {
104 i18nrb = new I18NResourceBundle(optionsInterfaceClass);
105 DefaultAnnotation.fillWithResourceBundle(argumentsObject, i18nrb);
106 }
107
108 boolean foundOptionsInterface = false;
109 for (Class c : optionsInterfaceClass.getInterfaces()) {
110 if (c.equals(Options.class)) {
111 foundOptionsInterface = true;
112 }
113 }
114 if (!foundOptionsInterface) {
115 throw new InvalidOptionsInterfaceException("Interface " + optionsInterfaceClass.getName() + " should extend the interface " + Options.class.getName() + ".");
116 }
117
118 if (!argumentsObject.statusCodeEnum().isEnum()) {
119 throw new InvalidOptionsInterfaceException("The status code class " + argumentsObject.statusCodeEnum().getName() + " is not an enumeration.");
120 }
121
122
123
124 ArrayList<StructureOption> tempOptions = new ArrayList<StructureOption>();
125 ArrayList<StructureSimpleArgument> tempSimpleArguments = new ArrayList<StructureSimpleArgument>();
126
127 Method[] methods = optionsInterfaceClass.getMethods();
128
129
130 a: for (Method m : methods) {
131 for (Method m2 : Options.class.getMethods()) {
132 if (m.equals(m2)) {
133 continue a;
134 }
135 }
136 int annotations = 0;
137 if (m.isAnnotationPresent(Option.class)) {
138 annotations++;
139 }
140 if (m.isAnnotationPresent(OptionArgument.class)) {
141 annotations++;
142 }
143 if (m.isAnnotationPresent(OptionMapValue.class)) {
144 annotations++;
145 }
146 if (m.isAnnotationPresent(OptionValue.class)) {
147 annotations++;
148 }
149 if (m.isAnnotationPresent(SimpleArgument.class)) {
150 annotations++;
151 }
152 if (annotations == 0) {
153 throw new InvalidOptionsInterfaceException("Method " + m + " has no annotation.");
154 }
155 if (annotations > 1) {
156 throw new InvalidOptionsInterfaceException("Method " + m + " has more than one annotation.");
157 }
158 }
159
160
161 for (Method m : methods) {
162
163 if (m.isAnnotationPresent((Class<? extends Annotation>) Option.class)) {
164
165 if (m.getParameterTypes().length > 0) {
166 throw new InvalidOptionsInterfaceException("Method " + m + " should have no parameters.");
167 }
168
169 if (!m.getReturnType().equals(boolean.class) && !m.getReturnType().equals(Boolean.class) && !m.getReturnType().equals(int.class) && !m.getReturnType().equals(Integer.class)) {
170 throw new InvalidOptionsInterfaceException("Method " + m + " should return boolean, " + Boolean.class.getName() + ", int, or " + Integer.class.getName() + ".");
171 }
172
173 ArrayList<String> alternatives = new ArrayList<String>();
174 Option opt = DefaultAnnotation.getAnnotation(m, Option.class);
175 if(loadFromResourceBundle) {
176 DefaultAnnotation.fillWithResourceBundle(opt, i18nrb);
177 }
178 if (opt.names().length == 1 && opt.names()[0].equals("")) {
179 alternatives.add(m.getName());
180 } else {
181 for (String alternative : opt.names()) {
182 alternatives.add(alternative);
183 }
184 }
185
186 StructureOptionValue value = null;
187 for (Method m2 : methods) {
188 if (m2.isAnnotationPresent((Class<? extends Annotation>) OptionValue.class)) {
189 OptionValue optionValue = DefaultAnnotation.getAnnotation(m2, OptionValue.class);
190 if(loadFromResourceBundle) {
191 DefaultAnnotation.fillWithResourceBundle(optionValue, i18nrb);
192 }
193 if (optionValue.option().equals(m.getName())) {
194 if (value == null) {
195 if (optionValue.name().equals("")) {
196 value = new StructureOptionValue(m2, m2.getName(), optionValue.mandatory(), optionValue.arraySeparator(), optionValue.arrayUseFileSeparator());
197 } else {
198 value = new StructureOptionValue(m2, optionValue.name(), optionValue.mandatory(), optionValue.arraySeparator(), optionValue.arrayUseFileSeparator());
199 }
200 } else {
201 throw new InvalidOptionsInterfaceException("The option " + optionValue.option() + " has more than one option value. Options should have only one option value.");
202 }
203 }
204 }
205 }
206
207 StructureOptionMapValue map = null;
208 for (Method m2 : methods) {
209 if (m2.isAnnotationPresent((Class<? extends Annotation>) OptionMapValue.class)) {
210 OptionMapValue optionMapValue = DefaultAnnotation.getAnnotation(m2, OptionMapValue.class);
211 if(loadFromResourceBundle) {
212 DefaultAnnotation.fillWithResourceBundle(optionMapValue, i18nrb);
213 }
214 if (!m2.getReturnType().equals(Map.class)) {
215 throw new InvalidOptionsInterfaceException("The method " + m2 + " is an option map, therefore its return type should be " + Map.class.getName() + ".");
216 }
217 if (optionMapValue.option().equals(m.getName())) {
218 if (map == null) {
219 map = new StructureOptionMapValue(m2, optionMapValue.keyName(), optionMapValue.valueName(), optionMapValue.valueType(), optionMapValue.mandatory());
220 } else {
221 throw new InvalidOptionsInterfaceException("The option " + optionMapValue.option() + " has more than one option map. Options should have only one option map.");
222 }
223 }
224 }
225 }
226
227 List<StructureOptionArgument> optionArguments = new ArrayList<StructureOptionArgument>();
228 boolean alreadyHasArrayArgument = false;
229 boolean somethingFound = false;
230 boolean alreadyHasOptional = false;
231 boolean alreadyHasOptionWithoutArgumentInputChannel = false;
232 do {
233 somethingFound = false;
234 for (Method m2 : methods) {
235 if (m2.isAnnotationPresent((Class<? extends Annotation>) OptionArgument.class)) {
236 OptionArgument optionArgument = DefaultAnnotation.getAnnotation(m2, OptionArgument.class);
237 if(loadFromResourceBundle) {
238 DefaultAnnotation.fillWithResourceBundle(optionArgument, i18nrb);
239 }
240 if (optionArgument.option().equals(m.getName()) && optionArgument.index() == optionArguments.size()) {
241
242 somethingFound = true;
243
244 if (optionArgument.mandatory()) {
245 if (alreadyHasOptional) {
246 throw new InvalidOptionsInterfaceException("LexUnknown " + m2 + " is mandatory after an optional argument. No mandatory argument can follow an optional argument.");
247 }
248 } else {
249 alreadyHasOptional = true;
250 }
251
252 if (alreadyHasArrayArgument) {
253 throw new InvalidOptionsInterfaceException("Arguments referring to option " + m + " can only have one array and the array argument should be the last one. To solve this problem you can remove agument " + m2);
254 }
255 if (m2.getReturnType().isArray()) {
256 alreadyHasArrayArgument = true;
257 }
258 String name = optionArgument.name();
259 if (name.equals("")) {
260 name = m2.getName();
261 }
262 if (optionArgument.channels().length == 0) {
263 throw new InvalidOptionsInterfaceException("Option argument " + m2 + " should have at least one input channel.");
264 }
265 if (Arrays.binarySearch(optionArgument.channels(), InputChannel.ARGUMENT, null) >= 0) {
266 if (alreadyHasOptionWithoutArgumentInputChannel) {
267 throw new InvalidOptionsInterfaceException("Option argument " + m2 + " should not have the " + InputChannel.ARGUMENT + " input channel. Arguments containing the " + InputChannel.ARGUMENT + " input channel should not succeed arguments without this input channel.");
268 }
269 } else {
270 alreadyHasOptionWithoutArgumentInputChannel = true;
271 }
272
273 if (!optionArgument.mandatory() && Arrays.binarySearch(optionArgument.channels(), InputChannel.GUI, null) >= 0) {
274 throw new InvalidOptionsInterfaceException("Option argument " + m2 + " is optional. So it should not have the " + InputChannel.GUI + " input channel.");
275 }
276 if (!optionArgument.mandatory() && Arrays.binarySearch(optionArgument.channels(), InputChannel.TEXT, null) >= 0) {
277 throw new InvalidOptionsInterfaceException("Option argument " + m2 + " is optional. So it should not have the " + InputChannel.TEXT + " input channel.");
278 }
279
280 optionArguments.add(new StructureOptionArgument(name, m2, optionArgument.mandatory(), optionArgument.index(), optionArgument.regex(), Arrays.asList(optionArgument.channels()), optionArgument.description()));
281 }
282 }
283 }
284 } while (somethingFound);
285
286 Collections.sort(optionArguments);
287 tempOptions.add(new StructureOption(m, opt.description(), alternatives, value, map, Collections.unmodifiableList(optionArguments)));
288 }
289 }
290
291
292 for (Method m : methods) {
293 if (m.isAnnotationPresent((Class<? extends Annotation>) SimpleArgument.class)) {
294
295 if (m.getParameterTypes().length > 0) {
296 throw new InvalidOptionsInterfaceException("Method " + m + " should have no parameters.");
297 }
298
299 SimpleArgument annotation = DefaultAnnotation.getAnnotation(m, SimpleArgument.class);
300 if(loadFromResourceBundle) {
301 DefaultAnnotation.fillWithResourceBundle(annotation, i18nrb);
302 }
303
304 String name;
305 if (annotation.name().equals("")) {
306 name = m.getName();
307 } else {
308 name = annotation.name();
309 }
310 tempSimpleArguments.add(new StructureSimpleArgument(m, name, annotation.mandatory(), annotation.index(), annotation.regex(), Arrays.asList(annotation.channels()), annotation.description()));
311 }
312 }
313
314 Collections.sort(tempSimpleArguments);
315
316 boolean alreadyHasOptional = false;
317 boolean alreadyHasArray = false;
318
319 for (StructureSimpleArgument a : tempSimpleArguments) {
320 if (alreadyHasArray) {
321 throw new InvalidOptionsInterfaceException("Cannot have any argument after an array argument. Array arguments will consume all remaining arguments.");
322 }
323 if (a.isMandatory()) {
324 if (alreadyHasOptional) {
325 throw new InvalidOptionsInterfaceException("The parsedOptions interface should not have a mandatory agument after an optional one.");
326 }
327 } else {
328 alreadyHasOptional = true;
329 }
330 if (a.method.getReturnType().isArray()) {
331 if (alreadyHasArray) {
332 throw new InvalidOptionsInterfaceException("Only the last argument can be an array argument.");
333 }
334 alreadyHasArray = true;
335 }
336 }
337
338
339
340
341
342
343
344 Collections.sort(tempOptions);
345 parsedOptions = Collections.unmodifiableList(tempOptions);
346 Collections.sort(tempSimpleArguments);
347 simpleArguments = Collections.unmodifiableList(tempSimpleArguments);
348
349 if (parsedOptions.isEmpty() && simpleArguments.isEmpty()) {
350 throw new InvalidOptionsInterfaceException("Empty parsedOptions interface: " + optionsInterfaceClass.getName());
351 }
352 }
353
354
355
356
357 public final ArgumentsObject getArgsObject() {
358 return argumentsObject;
359 }
360
361
362
363
364 public final Class<T> getOptionsInterface() {
365 return optionsInterface;
366 }
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381 public final List<LexToken> lexArguments(final String... arguments) {
382
383 ArrayList<LexToken> expandedArgs = new ArrayList<LexToken>();
384
385
386
387 final String hyphen = argumentsObject.singleHyphen();
388 final String hyphenHyphen = argumentsObject.doubleHyphen();
389 final String equals = argumentsObject.equals();
390
391
392 int simpleArgumentIndex = 0;
393
394 a: for (String argument : arguments) {
395
396 if (argument.equals(hyphen)) {
397 expandedArgs.add(new LexArgument(hyphen));
398 continue a;
399 }
400
401 if (argument.equals(hyphenHyphen)) {
402 expandedArgs.add(new LexArgument(hyphenHyphen));
403 continue a;
404 }
405
406 if (simpleArguments.size() > 0 && simpleArgumentIndex < simpleArguments.size()) {
407 StructureSimpleArgument simpleArgument = simpleArguments.get(simpleArgumentIndex);
408 String regex = simpleArgument.getRegex();
409 regex = regex.replace("\\h", argumentsObject.singleHyphen());
410 regex = regex.replace("\\H", argumentsObject.doubleHyphen());
411 if (Pattern.matches(regex, argument)) {
412 expandedArgs.add(new LexArgument(argument));
413 if (!simpleArgument.method.getReturnType().isArray()) {
414 simpleArgumentIndex++;
415 }
416 continue a;
417 }
418 }
419
420 if (argumentsObject.doubleHyphenInLongOptions()) {
421 if (argument.startsWith(hyphen) && !argument.startsWith(hyphenHyphen)) {
422 StructureOption option = searchOption(argument.substring(hyphen.length(), hyphen.length() + 1));
423 if (option == null || option.map == null) {
424 int i = hyphen.length();
425 while (i < argument.length() && argument.indexOf(equals) != i) {
426 expandedArgs.add(new LexOption(String.valueOf(argument.charAt(i))));
427 if (i + 1 < argument.length() && argument.indexOf(equals) == i + 1) {
428 expandedArgs.add(new LexOptionValue(argument.substring(i + 1 + equals.length())));
429 }
430 i++;
431 }
432 } else {
433 if (argument.contains(equals)) {
434 expandedArgs.add(new LexOption(String.valueOf(argument.charAt(hyphen.length()))));
435 expandedArgs.add(new LexOptionMapValue(argument.substring(hyphen.length() + equals.length())));
436 } else {
437 for (int i = 1; i < argument.length(); i++) {
438 expandedArgs.add(new LexOption(String.valueOf(argument.charAt(i))));
439 }
440 }
441 }
442 } else if (argument.startsWith(hyphenHyphen)) {
443 if (argument.contains(equals)) {
444 expandedArgs.add(new LexOption(argument.substring(hyphenHyphen.length(), argument.indexOf(equals))));
445 expandedArgs.add(new LexOptionValue(argument.substring(argument.indexOf(equals) + equals.length(), argument.length())));
446 } else {
447 expandedArgs.add(new LexOption(argument.substring(hyphenHyphen.length())));
448 }
449 } else {
450 expandedArgs.add(new LexArgument(argument));
451 }
452 } else {
453 if (argument.startsWith(hyphen)) {
454 if (argument.contains(equals)) {
455 String prefix = searchOptionPrefixing(argument);
456 if (prefix != null) {
457 expandedArgs.add(new LexOption(prefix));
458 if (argument.indexOf(equals) == prefix.length() + 1) {
459 expandedArgs.add(new LexOptionValue(argument.substring(prefix.length() + 1 + equals.length())));
460 } else {
461 expandedArgs.add(new LexOptionMapValue(argument.substring(prefix.length() + 1)));
462 }
463 } else {
464 expandedArgs.add(new LexOption(argument.substring(1, argument.indexOf(equals))));
465 expandedArgs.add(new LexOptionValue(argument.substring(argument.indexOf(equals) + 1, argument.length())));
466 }
467 } else {
468 expandedArgs.add(new LexOption(String.valueOf(argument.substring(hyphen.length()))));
469 }
470 } else {
471 expandedArgs.add(new LexArgument(argument));
472 }
473 }
474 }
475
476 return expandedArgs;
477 }
478
479
480
481
482 public final List<StructureOption> getParsedOptions() {
483 return Collections.unmodifiableList(parsedOptions);
484 }
485
486
487
488
489 public final List<StructureSimpleArgument> getSimpleArguments() {
490 return Collections.unmodifiableList(simpleArguments);
491 }
492
493
494
495
496
497
498 public final StructureOption searchOption(final String name) {
499 for (StructureOption parsedOption : parsedOptions) {
500 if (parsedOption.alternatives.contains(name)) {
501 return parsedOption;
502 }
503 }
504 return null;
505 }
506
507
508
509
510
511
512 public final String searchOptionPrefixing(final String name) {
513 String filteredName = name.substring(1);
514 for (StructureOption option : parsedOptions) {
515 for (String alternative : option.alternatives) {
516 if (filteredName.startsWith(alternative)) {
517 return alternative;
518 }
519 }
520 }
521 return null;
522 }
523
524
525
526
527
528
529 public final StructureSimpleArgument searchArgument(final String name) {
530 for (StructureSimpleArgument simpleArgument : simpleArguments) {
531 if (simpleArgument.getName().equals(name)) {
532 return simpleArgument;
533 }
534 }
535 return null;
536 }
537
538
539
540
541
542
543 public final StructureElement searchElement(final Method method) {
544 for (StructureOption option : parsedOptions) {
545 if (option.method.equals(method)) {
546 return option;
547 }
548 if (option.value != null && option.value.method.equals(method)) {
549 return option.value;
550 }
551 if (option.map != null && option.map.method.equals(method)) {
552 return option.map;
553 }
554 for (StructureOptionArgument optionArgument : option.arguments) {
555 if (optionArgument.method.equals(method)) {
556 return optionArgument;
557 }
558 }
559 }
560 for (StructureSimpleArgument simpleArgument : simpleArguments) {
561 if (simpleArgument.method.equals(method)) {
562 return simpleArgument;
563 }
564 }
565
566 return null;
567 }
568 }