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.optionsextractor;
13  
14  import java.lang.annotation.Annotation;
15  import java.lang.reflect.Constructor;
16  import java.lang.reflect.InvocationHandler;
17  import java.lang.reflect.InvocationTargetException;
18  import java.lang.reflect.Proxy;
19  
20  import org.hyphenType.datastructure.Options;
21  import org.hyphenType.dynamicproxy.ConcreteArgumentsInvocationHandler;
22  import org.hyphenType.exceptions.InvalidOptionsInterfaceException;
23  import org.hyphenType.exit.StatusCode;
24  import org.hyphenType.input.StandardUserInput;
25  import org.hyphenType.input.UserInput;
26  import org.hyphenType.lexerparser.LexerParser;
27  import org.hyphenType.lexerparser.OptionValues;
28  import org.hyphenType.lexerparser.exceptions.MandatoryMapValueNotFoundException;
29  import org.hyphenType.lexerparser.exceptions.MandatorySimpleArgumentNotFoundException;
30  import org.hyphenType.lexerparser.exceptions.MandatoryValueNotFoundException;
31  import org.hyphenType.lexerparser.exceptions.RegexMismatchException;
32  import org.hyphenType.lexerparser.exceptions.StringParseException;
33  import org.hyphenType.lexerparser.exceptions.StringParsingErrorException;
34  import org.hyphenType.optionprocessors.ArgumentsProcessorEngine;
35  import org.hyphenType.util.DefaultAnnotation;
36  import org.hyphenType.util.I18NResourceBundle;
37  import org.hyphenType.wrapper.StandAloneAppWrapper;
38  
39  /**
40   * A factory for option objects, and the easiest way to get started with this
41   * tool. This class provides the easiest (although not the most flexible, nor
42   * the most automated) way to obtain an instance of an option object
43   * programmatically. For a non-programmatical and automated method to obtain an
44   * option object (i.e. if you do not want to call any API), please utilize the
45   * {@link StandAloneAppWrapper} class instead.<br>
46   * Here is (possibly the simplest and least invasive) example on how to use this
47   * class: <code>
48   * <pre>
49   * public static void main(String[] arguments) {
50   *     OptionsExtractor<MyOptionInterface> soe = new OptionsExtractor<MyOptionInterface>(MyOptionInterface.class);
51   *     MyOptionInterface options = soe.options(arguments);
52   * }
53   * </pre>
54   * </code> In the source code above, the OptionsExtractor constructor will
55   * perform a deep analysis on the options interface
56   * <code>MyOptionInterface</code> All arguments received by the
57   * <code>main</code> method will be parsed according to the rules declared in
58   * the options interface <code>MyOptionInterface</code> .
59   * 
60   * @author Aurelio Akira M. Matsui
61   * @param <T>
62   *            The options interface type.
63   */
64  public class OptionsExtractor<T extends Options<?>> {
65  
66      private final Class<T> optionInterfaceClass;
67      private final LexerParser<T> lexPar;
68  
69      /**
70       * Creates an {@link OptionsExtractor} object based on an options interface
71       * class. Exceptions thrown by this constructor means that there is a
72       * problem with the structure of the options interface. If you are sure your
73       * options interface does not have any inconsistency (which can be tested
74       * simply by calling this method before wrapping your application), you do
75       * not have any reason to expect any exception being thrown from this
76       * method.<br/>
77       * <br/>
78       * The user input option allows the caller to chose which interactive user
79       * input to utilize. If you do not want to bother about interactive user
80       * inputs (either because your application does not use interactive user
81       * input or because you are satisfied with the standard user input), simply
82       * call the {@link OptionsExtractor#OptionsExtractor(Class)} constructor
83       * instead.
84       * 
85       * @param optionInterfaceClass
86       * @param userInput
87       *            The user input to utilize.
88       * @throws InvalidOptionsInterfaceException
89       * @throws InvalidOptionException
90       */
91      public OptionsExtractor(final Class<T> optionInterfaceClass) throws InvalidOptionsInterfaceException {
92  
93          this.optionInterfaceClass = optionInterfaceClass;
94          this.lexPar = new LexerParser<T>(optionInterfaceClass);
95      }
96  
97      public T options(final String... arguments) throws StringParsingErrorException, MandatoryValueNotFoundException, RegexMismatchException, StringParseException, MandatoryMapValueNotFoundException, MandatorySimpleArgumentNotFoundException, OptionsExtractorException {
98          return options(new StandardUserInput(), arguments);
99      }
100 
101     /**
102      * Factory method that creates an instance of the options interface specified in the
103      * constructor {@link OptionsExtractor#OptionsExtractor(Class)}.
104      * 
105      * @param userInput
106      *            The input from the user.
107      * @param arguments
108      *            The arguments to be parsed.
109      * @return A new instance of the option interface, containing all arguments
110      *         parsed.
111      * @throws OptionsExtractorException 
112      * @throws MandatorySimpleArgumentNotFoundException 
113      * @throws MandatoryMapValueNotFoundException 
114      * @throws StringParseException 
115      * @throws RegexMismatchException 
116      * @throws MandatoryValueNotFoundException 
117      * @throws StringParsingErrorException 
118      * @throws InvalidOptionException
119      */
120     @SuppressWarnings("unchecked")
121     public T options(final UserInput userInput, final String... arguments) throws OptionsExtractorException, StringParsingErrorException, MandatoryValueNotFoundException, RegexMismatchException, StringParseException, MandatoryMapValueNotFoundException, MandatorySimpleArgumentNotFoundException {
122         OptionValues<T> values = new OptionValues<T>(lexPar.lexArguments(arguments), lexPar, userInput);
123         InvocationHandler handler = buildInvocationHandler(values, lexPar.getOptionsInterface(), lexPar.getArgsObject().statusCodeEnum(), arguments);
124         Class proxyClass = Proxy.getProxyClass(OptionsExtractor.class.getClassLoader(), new Class[] {optionInterfaceClass});
125         T f;
126         try {
127             f = (T) proxyClass.getConstructor(new Class[] {InvocationHandler.class}).newInstance(new Object[] {handler});
128 
129         } catch (IllegalArgumentException e) {
130             e.printStackTrace();
131             return null;
132         } catch (SecurityException e) {
133             e.printStackTrace();
134             return null;
135         } catch (InstantiationException e) {
136             e.printStackTrace();
137             return null;
138         } catch (IllegalAccessException e) {
139             e.printStackTrace();
140             return null;
141         } catch (InvocationTargetException e) {
142             e.printStackTrace();
143             return null;
144         } catch (NoSuchMethodException e) {
145             e.printStackTrace();
146             return null;
147         }
148 
149         /* Fills all annotations with data from the resource bundles and
150          * applies all argument processors.
151          */
152 
153         for (Annotation annotation : DefaultAnnotation.getAnnotations(optionInterfaceClass)) {
154 
155             DefaultAnnotation.fillWithResourceBundle(annotation, new I18NResourceBundle(optionInterfaceClass));
156 
157             if (annotation.annotationType().getEnclosingClass() != null && ArgumentsProcessorEngine.class.isAssignableFrom(annotation.annotationType().getEnclosingClass())) {
158 
159                 Class<? extends ArgumentsProcessorEngine<?>> processorClass = (Class<? extends ArgumentsProcessorEngine<?>>) annotation.annotationType().getEnclosingClass();
160                 Constructor<?> processorConstructor;
161                 try {
162                     processorConstructor = processorClass.getConstructor();
163                 } catch (SecurityException e) {
164                     throw new OptionsExtractorException("Could not access default constructor in argument processor.");
165                 } catch (NoSuchMethodException e) {
166                     throw new OptionsExtractorException("Argument processor constructor not found.");
167                 }
168                 ArgumentsProcessorEngine processor;
169                 try {
170                     processor = (ArgumentsProcessorEngine) processorConstructor.newInstance();
171                 } catch (IllegalArgumentException e) {
172                     throw new OptionsExtractorException("Illegal argument while trying to create a new processor.", e);
173                 } catch (InstantiationException e) {
174                     throw new OptionsExtractorException("Could not instantiate processor.", e);
175                 } catch (IllegalAccessException e) {
176                     throw new OptionsExtractorException("Illegal access to constructor of argument processor.", e);
177                 } catch (InvocationTargetException e) {
178                     throw new OptionsExtractorException("Exception thrown by something inside the constructor of argument processor.", e);
179                 }
180                 processor.process(optionInterfaceClass, f, annotation);
181             }
182         }
183 
184         return f;
185     }
186 
187     /**
188      * Prepared to be replaced by a subclass. Extending {@link OptionsExtractor}
189      * is useful to replace the composition of objects that will process the
190      * arguments.
191      * 
192      * @param values
193      * @param formatter
194      * @param exitCodeEnumClass
195      * @param rawArguments
196      * @return
197      */
198     protected InvocationHandler buildInvocationHandler(final OptionValues<T> values, final Class<? extends Options<?>> optionsInterface, final Class<? extends StatusCode> exitCodeEnumClass, final String[] rawArguments) {
199 
200         return new ConcreteArgumentsInvocationHandler<T>(values, optionsInterface, exitCodeEnumClass, rawArguments);
201     }
202 }