View Javadoc

1   /*
2    * This file is part of hyphenType. hyphenType is free software: you can
3    * redistribute it and/or modify it under the terms of the GNU General Public
4    * License as published by the Free Software Foundation, either version 3 of the
5    * License, or (at your option) any later version. hyphenType is distributed in
6    * the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
8    * the GNU General Public License for more details. You should have received a
9    * copy of the GNU General Public License along with hyphenType. If not, see
10   * <http://www.gnu.org/licenses/>.
11   */
12  package org.hyphenType.lexerparser;
13  
14  import java.io.File;
15  import java.lang.reflect.Array;
16  import java.lang.reflect.Method;
17  import java.util.ArrayList;
18  import java.util.Collections;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.hyphenType.datastructure.Options;
24  import org.hyphenType.datastructure.annotations.InputChannel;
25  import org.hyphenType.datastructure.lexer.LexToken;
26  import org.hyphenType.datastructure.lexer.option.LexOption;
27  import org.hyphenType.datastructure.lexer.option.LexOptionMapValue;
28  import org.hyphenType.datastructure.lexer.option.LexOptionValue;
29  import org.hyphenType.datastructure.lexer.simple.LexArgument;
30  import org.hyphenType.datastructure.parser.StructureArgument;
31  import org.hyphenType.datastructure.parser.option.StructureOption;
32  import org.hyphenType.datastructure.parser.option.StructureOptionArgument;
33  import org.hyphenType.datastructure.parser.option.StructureOptionMapValue;
34  import org.hyphenType.datastructure.parser.option.StructureOptionValue;
35  import org.hyphenType.datastructure.parser.simple.StructureSimpleArgument;
36  import org.hyphenType.input.StandardUserInput;
37  import org.hyphenType.input.UserInput;
38  import org.hyphenType.input.UserInputException;
39  import org.hyphenType.lexerparser.exceptions.MandatoryMapValueNotFoundException;
40  import org.hyphenType.lexerparser.exceptions.MandatorySimpleArgumentNotFoundException;
41  import org.hyphenType.lexerparser.exceptions.MandatoryValueNotFoundException;
42  import org.hyphenType.lexerparser.exceptions.OptionValuesException;
43  import org.hyphenType.lexerparser.exceptions.RegexMismatchException;
44  import org.hyphenType.lexerparser.exceptions.StringParseException;
45  import org.hyphenType.lexerparser.exceptions.StringParsingErrorException;
46  import org.hyphenType.util.soc.StringObjectConversion;
47  import org.hyphenType.util.soc.StringParsingError;
48  
49  /**
50   * Stores the values related to a data structure holding the structure of an
51   * options interface.
52   * 
53   * @author Aurelio Akira M. Matsui
54   * @param <T>
55   *            TODO
56   */
57  public class OptionValues<T extends Options<?>> {
58  
59      /**
60       * A map that relates methods of the options interface with
61       * its values. Whenever a value is an array, we store it as
62       * a {@link List}.
63       */
64      private HashMap<Method, Object> map = new HashMap<Method, Object>();
65  
66      /**
67       * 
68       */
69      private List<LexToken> unusedTokens = new ArrayList<LexToken>();
70  
71      /**
72       * 
73       */
74      private LexerParser<T> lexPar;
75  
76      /**
77       * Although this constructor throws many exceptions, all exceptions extend
78       * {@link OptionValuesException}. So you can simply catch
79       * {@link OptionValuesException} if you do not want to tackle each
80       * exception in a special way.
81       * 
82       * @param tokens
83       *            TODO
84       * @param parser
85       *            TODO
86       * @throws MandatorySimpleArgumentNotFoundException 
87       * @throws MandatoryMapValueNotFoundException 
88       * @throws StringParseException 
89       * @throws RegexMismatchException 
90       * @throws MandatoryValueNotFoundException 
91       * @throws StringParsingErrorException 
92       * @throws InvalidOptionException
93       *             TODO
94       */
95      public OptionValues(final List<LexToken> tokens, final LexerParser<T> parser) throws StringParsingErrorException, MandatoryValueNotFoundException, RegexMismatchException, StringParseException, MandatoryMapValueNotFoundException, MandatorySimpleArgumentNotFoundException {
96          this(tokens, parser, new StandardUserInput());
97      }
98  
99      /**
100      * Although this constructor throws many exceptions, all exceptions extend
101      * {@link OptionValuesException}. So you can simply catch
102      * {@link OptionValuesException} if you do not want to tackle each
103      * exception in a special way. 
104      * 
105      * @param tokens
106      *            TODO
107      * @param parser
108      *            TODO
109      * @param userInput
110      *            TODO
111      * @throws StringParsingErrorException 
112      * @throws MandatoryValueNotFoundException 
113      * @throws StringParseException 
114      * @throws RegexMismatchException 
115      * @throws MandatoryMapValueNotFoundException 
116      * @throws MandatorySimpleArgumentNotFoundException 
117      * @throws InvalidOptionException
118      *             TODO
119      */
120     @SuppressWarnings("unchecked")
121     public OptionValues(final List<LexToken> tokens, final LexerParser<T> parser, final UserInput userInput) throws StringParsingErrorException, MandatoryValueNotFoundException, RegexMismatchException, StringParseException, MandatoryMapValueNotFoundException, MandatorySimpleArgumentNotFoundException {
122 
123         int simpleArgumentIndex = 0;
124 
125         this.lexPar = parser;
126 
127         LexTokenStream lts = new LexTokenStream(tokens);
128 
129         a: while (lts.hasFutureToken()) {
130 
131             LexToken token = lts.consume();
132 
133             if (token instanceof LexOption) {
134                 LexOption lexOption = (LexOption) token;
135                 StructureOption option = parser.searchOption(lexOption.value);
136 
137                 if (option == null) {
138                     unusedTokens.add(lexOption);
139                     continue a;
140                 }
141                 if (map.containsKey(option.method)) {
142                     int x = (Integer) map.get(option.method);
143                     x++;
144                     map.put(option.method, x);
145                 } else {
146                     map.put(option.method, new Integer(1));
147                 }
148 
149                 if (option.value != null) {
150                     if (lts.hasFutureToken() && lts.futureToken() != null && lts.futureToken() instanceof LexOptionValue) {
151                         String value = lts.consume().value;
152                         try {
153                             Object convertedValue;
154                             if(option.value.arrayUseFileSeparator) {
155                                 convertedValue = StringObjectConversion.fromString(option.value.method.getReturnType(), value, true, File.pathSeparator);
156                             } else {
157                                 convertedValue = StringObjectConversion.fromString(option.value.method.getReturnType(), value, true, option.value.arraySeparator);
158                             }
159                             /* If the return type is an array, we need to get the return type
160                              * and convert it into a list.
161                              */
162                             if(option.value.method.getReturnType().isArray()) {
163                                 List list = new ArrayList();
164                                 for(int i=0; i<Array.getLength(convertedValue); i++) {
165                                     list.add(Array.get(convertedValue, i));
166                                 }
167                                 convertedValue = list;
168                             }
169                             map.put(option.value.method, convertedValue);
170                         } catch (StringParsingError e) {
171                             throw new StringParsingErrorException(e, lexPar.getOptionsInterface(), value, option.value.method.getReturnType());
172                         }
173                     } else if (option.value.mandatory) {
174                         throw new MandatoryValueNotFoundException(lexPar.getOptionsInterface(), option.value.name, token.value);
175                     }
176                 }
177 
178                 if (option.map != null) {
179                     if (lts.futureToken() != null && lts.futureToken() instanceof LexOptionMapValue) {
180 
181                         String value = lts.consume().value;
182                         String k = value.substring(0, value.indexOf(lexPar.getArgsObject().equals()));
183                         Object v;
184                         try {
185                             v = StringObjectConversion.fromString(option.map.valueType, value.substring(value.indexOf(parser.getArgsObject().equals()) + 1, value.length()));
186                         } catch (StringParsingError e) {
187                             throw new StringParsingErrorException(e, lexPar.getOptionsInterface(), value, option.map.valueType);
188                         }
189 
190                         if (map.containsKey(option.map.method)) {
191                             Map m = (Map) map.get(option.map.method);
192                             m.put(k, v);
193                         } else {
194                             Map m = new HashMap();
195                             m.put(k, v);
196                             map.put(option.map.method, m);
197                         }
198                     } else if (option.map.mandatory) {
199                         throw new MandatoryMapValueNotFoundException(lexPar.getOptionsInterface(), option.map.keyName, parser.getArgsObject().equals(), option.map.valueName, token.value);
200                     }
201                 }
202 
203                 for (StructureOptionArgument optionArgument : option.arguments) {
204                     if (lts.hasFutureToken() && lts.futureToken() instanceof LexArgument) {
205 
206                         if (optionArgument.method.getReturnType().isArray()) {
207                             Object v = checkConvert(optionArgument.method.getReturnType().getComponentType(), optionArgument.getName(), lts.consume().value, optionArgument.getRegex(), lexPar.getArgsObject().singleHyphen(), lexPar.getArgsObject().doubleHyphen());
208                             registerOptionArgument(optionArgument, v);
209                             while (lts.hasFutureToken() && lts.futureToken() instanceof LexArgument && check(lts.futureToken().value, optionArgument.getRegex(), lexPar.getArgsObject().singleHyphen(), lexPar.getArgsObject().doubleHyphen())) {
210                                 v = checkConvert(optionArgument.method.getReturnType().getComponentType(), optionArgument.getName(), lts.consume().value, optionArgument.getRegex(), lexPar.getArgsObject().singleHyphen(), lexPar.getArgsObject().doubleHyphen());
211                                 registerOptionArgument(optionArgument, v);
212                             }
213                         } else {
214                             Object v = checkConvert(optionArgument.method.getReturnType(), optionArgument.getName(), lts.consume().value, optionArgument.getRegex(), lexPar.getArgsObject().singleHyphen(), lexPar.getArgsObject().doubleHyphen());
215                             registerOptionArgument(optionArgument, v);
216                         }
217                     } else if (optionArgument.isMandatory()) {
218 
219                         try {
220                             if (optionArgument.method.getReturnType().isArray()) {
221                                 Object[] inputs = userInput.readArray(optionArgument, lexPar.getArgsObject().singleHyphen(), lexPar.getArgsObject().doubleHyphen());
222                                 for (Object input : inputs) {
223                                     registerOptionArgument(optionArgument, input);
224                                 }
225                             } else {
226                                 Object input = userInput.readString(optionArgument, lexPar.getArgsObject().singleHyphen(), lexPar.getArgsObject().doubleHyphen());
227                                 registerOptionArgument(optionArgument, input);
228                             }
229                         } catch (UserInputException e) {
230                             throw new MandatoryValueNotFoundException(lexPar.getOptionsInterface(), optionArgument.getName(), option.alternatives.toString());
231                         }
232                     }
233                 }
234 
235             } else if (token instanceof LexArgument) {
236 
237                 LexArgument lexArgument = (LexArgument) token;
238 
239                 if (simpleArgumentIndex == parser.getSimpleArguments().size()) {
240                     unusedTokens.add(token);
241                 } else {
242 
243                     StructureSimpleArgument simpleArgument = parser.getSimpleArguments().get(simpleArgumentIndex);
244 
245                     if (!simpleArgument.getChannels().contains(InputChannel.ARGUMENT)) {
246                         unusedTokens.add(token);
247                         continue a;
248                     }
249 
250                     if (simpleArgument.method.getReturnType().isArray()) {
251                         List list;
252 
253                         if (!map.containsKey(simpleArgument.method)) {
254                             list = new ArrayList();
255                         } else {
256                             list = (List) map.get(simpleArgument.method);
257                         }
258 
259                         Object v = checkConvert(simpleArgument.method.getReturnType().getComponentType(), simpleArgument.getName(), lexArgument.value, simpleArgument.getRegex(), lexPar.getArgsObject().singleHyphen(), lexPar.getArgsObject().doubleHyphen());
260                         list.add(v);
261                         while (lts.hasFutureToken() && lts.futureToken() instanceof LexArgument && check(lts.futureToken().value, simpleArgument.getRegex(), lexPar.getArgsObject().singleHyphen(), lexPar.getArgsObject().doubleHyphen())) {
262                             v = checkConvert(simpleArgument.method.getReturnType().getComponentType(), simpleArgument.getName(), lts.consume().value, simpleArgument.getRegex(), lexPar.getArgsObject().singleHyphen(), lexPar.getArgsObject().doubleHyphen());
263                             list.add(v);
264                         }
265 
266                         if (!map.containsKey(simpleArgument.method)) {
267                             map.put(simpleArgument.method, list);
268                         }
269                     } else {
270                         Object v = checkConvert(simpleArgument.method.getReturnType(), simpleArgument.getName(), lexArgument.value, simpleArgument.getRegex(), lexPar.getArgsObject().singleHyphen(), lexPar.getArgsObject().doubleHyphen());
271                         map.put(simpleArgument.method, v);
272                     }
273                     simpleArgumentIndex++;
274                 }
275             } else {
276                 unusedTokens.add(token);
277             }
278         }
279         
280         /*
281          * Checking whether we collected all the mandatory simple arguments or
282          * not.
283          */
284         
285         if (simpleArgumentIndex != parser.getSimpleArguments().size()) {
286             for (; simpleArgumentIndex < parser.getSimpleArguments().size(); simpleArgumentIndex++) {
287                 StructureSimpleArgument simpleArgument = parser.getSimpleArguments().get(simpleArgumentIndex);
288                 if (simpleArgument.isMandatory()) {
289                     
290                     /*
291                      * If we did not collect this argument, let's give a second
292                      * chance and try to get it from GUI or text, if available
293                      * for this simple argument.
294                      */
295                     
296                     try {
297                         if (simpleArgument.method.getReturnType().isArray()) {
298                             Object[] inputs = userInput.readArray(simpleArgument, lexPar.getArgsObject().singleHyphen(), lexPar.getArgsObject().doubleHyphen());
299                             for (Object input : inputs) {
300                                 registerOptionArgument(simpleArgument, input);
301                             }
302                         } else {
303                             Object input = userInput.readString(simpleArgument, lexPar.getArgsObject().singleHyphen(), lexPar.getArgsObject().doubleHyphen());
304                             registerOptionArgument(simpleArgument, input);
305                         }
306                         continue;
307                     } catch (UserInputException e) {
308                         throw new MandatorySimpleArgumentNotFoundException(e, lexPar.getOptionsInterface(), simpleArgument.getName());
309                     }
310                 }
311             }
312         }
313 
314         /*
315          * At this point, all array arguments are stored as List objects.
316          * Meaning, we need to convert them to actual arrays.
317          */
318 
319         for (Method method : map.keySet()) {
320             if (method.getReturnType().isArray()) {
321                 List list = (List) map.get(method);
322                 
323                 Object array = Array.newInstance(method.getReturnType().getComponentType(), list.size());
324                 for (int i = 0; i < list.size(); i++) {
325                     if (method.getReturnType().getComponentType().isPrimitive()) {
326                         Array.set(array, i, StringObjectConversion.toPrimitive(method.getReturnType().getComponentType(), list.get(i)));
327                     } else {
328                         Array.set(array, i, method.getReturnType().getComponentType().cast(list.get(i)));
329                     }
330                 }
331                 
332                 map.put(method, array);
333             }
334         }
335     }
336     
337     /**
338      * @param optionArgument
339      *            TODO
340      * @param value
341      *            TODO
342      * @throws InvalidOptionException
343      *             TODO
344      */
345     @SuppressWarnings("unchecked")
346     private void registerOptionArgument(final StructureArgument optionArgument, final Object value) {
347         
348         if (optionArgument.method.getReturnType().isArray()) {
349             if (map.containsKey(optionArgument.method)) {
350                 List list = (List) map.get(optionArgument.method);
351                 list.add(value);
352             } else {
353                 List list = new ArrayList();
354                 list.add(value);
355                 map.put(optionArgument.method, list);
356             }
357         } else {
358             map.put(optionArgument.method, value);
359         }
360     }
361 
362     /**
363      * Checks if the value matches the regex. In the regular expression, \h
364      * is replaced by the single hyphen, and \H is replaced by the double
365      * hyphen.
366      * 
367      * @param value
368      *            The value to be checked.
369      * @param regex
370      *            The regular expression that the value should match with.
371      * @param singleHyphen
372      *            The string that represents the single hyphen.
373      * @param doubleHyphen
374      *            The string that represents the double hyphen.
375      * @return TODO
376      */
377     private boolean check(final String value, final String regex, final String singleHyphen, final String doubleHyphen) {
378         String transformedRegex = regex.replace("\\h", singleHyphen);
379         transformedRegex = transformedRegex.replace("\\H", doubleHyphen);
380         return value.matches(transformedRegex);
381     }
382 
383     /**
384      * @param <Z>
385      *            TODO
386      * @param clazz
387      *            TODO
388      * @param name
389      *            TODO
390      * @param value
391      *            TODO
392      * @param regex
393      *            TODO
394      * @param singleHyphen
395      *            TODO
396      * @param doubleHyphen
397      *            TODO
398      * @return TODO
399      * @throws RegexMismatchException 
400      * @throws StringParseException 
401      * @throws InvalidOptionException
402      *             TODO
403      */
404     private <Z> Z checkConvert(final Class<Z> clazz, final String name, final String value, final String regex, final String singleHyphen, final String doubleHyphen) throws RegexMismatchException, StringParseException {
405         if (!check(value, regex, singleHyphen, doubleHyphen)) {
406             throw new RegexMismatchException(lexPar.getOptionsInterface(), value, name, regex);
407         }
408         try {
409             return StringObjectConversion.fromString(clazz, value);
410         } catch (StringParsingError e) {
411             throw new StringParseException(e, lexPar.getOptionsInterface(), value);
412         }
413     }
414 
415     /**
416      * @param option
417      *            TODO
418      * @return TODO
419      */
420     public final int getParsedOptionValue(final StructureOption option) {
421         if (option == null) {
422             System.out.println("Ugh!");
423         }
424         if (map.containsKey(option.method)) {
425             return (Integer) map.get(option.method);
426         } else {
427             return 0;
428         }
429     }
430 
431     /**
432      * @param optionMapValue
433      *            TODO
434      * @return TODO
435      */
436     @SuppressWarnings("unchecked")
437     public final Map getOptionMapValue(final StructureOptionMapValue optionMapValue) {
438         return (Map) map.get(optionMapValue.method);
439     }
440 
441     /**
442      * @param optionValue
443      *            TODO
444      * @return TODO
445      */
446     public final Object getOptionValue(final StructureOptionValue optionValue) {
447         return map.get(optionValue.method);
448     }
449 
450     /**
451      * @param argument
452      *            TODO
453      * @return TODO
454      */
455     public final Object getSimpleArgumentValue(final StructureSimpleArgument argument) {
456         if (argument == null) {
457             System.out.println("Ugh!");
458         }
459         if (map.containsKey(argument.method)) {
460             return map.get(argument.method);
461         } else {
462             if(argument.method.getReturnType().isArray()) {
463                 return Array.newInstance(argument.method.getReturnType().getComponentType(), 0);
464             }
465             if(argument.method.getReturnType().equals(boolean.class) && argument.method.getReturnType().equals(Boolean.class)) {
466                 return false;
467             }
468             if(argument.method.getReturnType().equals(int.class) && argument.method.getReturnType().equals(Integer.class)) {
469                 return 0;
470             }
471             // TODO IMPORTANT: We should return something better than null for other types.
472             return null;
473         }
474     }
475 
476     /**
477      * @return TODO
478      */
479     public final List<LexToken> unusedArguments() {
480         return Collections.unmodifiableList(unusedTokens);
481     }
482 
483     /**
484      * @param method
485      *            TODO
486      * @return TODO
487      */
488     public final Object getValue(final Method method) {
489         if (lexPar.searchElement(method) instanceof StructureOption) {
490             Object obj = map.get(method);
491             int n;
492             if (obj == null) {
493                 n = 0;
494             } else {
495                 n = (Integer) map.get(method);
496             }
497             if (method.getReturnType().equals(boolean.class) || method.getReturnType().equals(Boolean.class)) {
498                 return n > 0;
499             } else if (method.getReturnType().equals(int.class) || method.getReturnType().equals(Integer.class)) {
500                 return n;
501             }
502         }
503 
504         return map.get(method);
505     }
506 
507     @Override
508     public final int hashCode() {
509         return toString().hashCode();
510     }
511 
512     @Override
513     public final String toString() {
514         String result = "[";
515         for (Method method : map.keySet()) {
516             if (method.getReturnType().isArray()) {
517                 result += String.format("%s=[", method.getName());
518                 Object array = map.get(method);
519                 for (int i = 0; i < Array.getLength(array); i++) {
520                     result += String.format("%s, ", method.getName(), Array.get(array, i));
521                 }
522                 if (Array.getLength(array) > 0) {
523                     result = result.substring(0, result.length() - 2);
524                 }
525                 result += "], ";
526             } else {
527                 result += String.format("%s=%s, ", method.getName(), map.get(method));
528             }
529         }
530         result += "unusedTokens=" + unusedTokens.toString() + "]";
531         return result;
532     }
533 }