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.dynamicproxy;
13  
14  import java.io.ByteArrayOutputStream;
15  import java.io.PrintStream;
16  import java.io.PrintWriter;
17  import java.lang.annotation.Annotation;
18  import java.lang.reflect.Array;
19  import java.lang.reflect.Field;
20  import java.lang.reflect.InvocationHandler;
21  import java.lang.reflect.Method;
22  import java.text.MessageFormat;
23  
24  import org.hyphenType.datastructure.Options;
25  import org.hyphenType.documentation.DocumentationFormatterEngine;
26  import org.hyphenType.exit.ExitStatusConstant;
27  import org.hyphenType.exit.ExitStatusHelper;
28  import org.hyphenType.exit.StatusCode;
29  import org.hyphenType.lexerparser.OptionValues;
30  import org.hyphenType.unittesting.NonExceptionalExit;
31  
32  /**
33   * @author Aurelio Akira M. Matsui
34   * @param <T> The option interface type.
35   */
36  public abstract class AbstractArgumentsInvocationHandler<T extends Options<?>> implements InvocationHandler {
37  
38      /**
39       * TODO Comment.
40       */
41      private final OptionValues<T> optionValues;
42      
43      /**
44       * TODO Comment.
45       */
46      private final Class<? extends Options<?>> optionsInterface;
47      
48      /**
49       * TODO Comment.
50       */
51      private final Class<? extends StatusCode> exitCodeEnumClass;
52      
53      /**
54       * TODO Comment.
55       */
56      private final String[] rawArguments;
57  
58      @SuppressWarnings("unchecked")
59      private DocumentationFormatterEngine defaultFormatter = null;
60      
61      /**
62       * TODO Comment.
63       * 
64       * @param optionValues TODO Comment.
65       * @param formatter TODO Comment.
66       * @param exitCodeEnumClass TODO Comment.
67       * @param rawArguments TODO Comment.
68       */
69      public AbstractArgumentsInvocationHandler(final OptionValues<T> optionValues, final Class<? extends Options<?>> optionsInterface, final Class<? extends StatusCode> exitCodeEnumClass, final String[] rawArguments) {
70          this.optionValues = optionValues;
71          this.optionsInterface = optionsInterface;
72          this.exitCodeEnumClass = exitCodeEnumClass;
73          this.rawArguments = rawArguments;
74      }
75      
76      /**
77       * Initializes the formatter. This is a lazy
78       * initialization to avoid building a formatter
79       * when the user does not need one.
80       */
81      @SuppressWarnings("unchecked")
82      private DocumentationFormatterEngine defaultFormatter() {
83          if(defaultFormatter == null) {
84              defaultFormatter = DocumentationFormatterEngine.preferredFormatter(optionsInterface);
85          }
86          return defaultFormatter;
87      }
88  
89      @SuppressWarnings("unchecked")
90      @Override
91      public final Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
92          
93          if (method.equals(Object.class.getMethod("equals", Object.class))) {
94              return this.equals(args[0]);
95          }
96          
97          if (method.equals(Object.class.getMethod("hashCode"))) {
98              return this.hashCode();
99          }
100         
101         if (method.equals(Object.class.getMethod("toString"))) {
102             return this.toString();
103         }
104         
105         /*
106          * Methods from the {@link Options} interface.
107          */
108         
109         if (method.equals(Options.class.getMethod("printDocumentation"))) {
110             printDocumentation();
111             return null;
112         }
113         
114         if (method.equals(Options.class.getMethod("printDocumentation", PrintStream.class))) {
115             printDocumentation((PrintStream) args[0]);
116             return null;
117         }
118         
119         if (method.equals(Options.class.getMethod("printDocumentation", Class.class))) {
120             printDocumentation((Class) args[0]);
121             return null;
122         }
123         
124         if (method.equals(Options.class.getMethod("printDocumentation", Class.class, PrintStream.class))) {
125             printDocumentation((Class) args[0], (PrintStream) args[1]);
126             return null;
127         }
128         
129         if (method.equals(Options.class.getMethod("unparsedArguments"))) {
130             return optionValues.unusedArguments();
131         }
132         
133         if (method.equals(Options.class.getMethod("exit", Enum.class, Object[].class))) {
134             Object obj = args[0];
135             if (StatusCode.class.isAssignableFrom(obj.getClass())) {
136                 StatusCode statusCode = (StatusCode) obj;
137                 Object[] arguments = (Object[])args[1];
138                 statusCode.beforeExit(new ExitStatusHelper(optionsInterface, (Enum<? extends StatusCode>) statusCode, null, arguments));
139             }
140             exit((Enum<?>) obj);
141             return null;
142         }
143         
144         if (method.equals(Options.class.getMethod("exit", int.class, Object[].class))) {
145             int code = (Integer) args[0];
146             
147             Enum<?>[] consts = (Enum[]) exitCodeEnumClass.getMethod("values").invoke(null);
148             if (code > -1 && code < consts.length) {
149                 StatusCode statusCode = (StatusCode) consts[code];
150                 Object[] arguments = (Object[])args[1];
151                 statusCode.beforeExit(new ExitStatusHelper(optionsInterface, (Enum<? extends StatusCode>) statusCode, null, arguments));
152             }
153             exit(code);
154         }
155         
156         if (method.equals(Options.class.getMethod("exit", Throwable.class))) {
157             
158             Throwable t = (Throwable) args[0];
159             Class tClass = t.getClass();
160             if(tClass.equals(NonExceptionalExit.class)) {
161                 /*
162                  * Adding an exception! We simply ignore the NonExceptionalExit
163                  * exceptions since they have a special meaning and are thrown
164                  * to interrupt execution flow when something calls exit.
165                  * It is part of the contract of the ExitStatusConstant.catches()
166                  * that no exit status constant can catch the NonExceptionalExit
167                  */
168                 return false;
169             }
170             do {
171                 for(Field f : exitCodeEnumClass.getFields()) {
172                     if(f.isAnnotationPresent(ExitStatusConstant.class)) {
173                         ExitStatusConstant annotation = f.getAnnotation(ExitStatusConstant.class);
174                         for(Class<? extends Throwable> ec: annotation.catches()) {
175                             if(ec.equals(tClass)) {
176                                 
177                                 // Retrieving the localized message
178                                 String localizedMessage = t.getLocalizedMessage();
179                                 if(localizedMessage == null) {
180                                     localizedMessage = "";
181                                 }
182                                 
183                                 // Retrieving the stack trace
184                                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
185                                 PrintWriter pw = new PrintWriter(baos);
186                                 t.printStackTrace(pw);
187                                 pw.flush();
188                                 String stackTrace = new String(baos.toByteArray());
189                                 
190                                 StatusCode s = StatusCode.class.cast(f.get(null));
191                                 // {0}=localizedMessage, {1}=stackTrace
192                                 s.beforeExit(new ExitStatusHelper(optionsInterface, (Enum<? extends StatusCode>) s, t, localizedMessage, stackTrace));
193                                 exit((Enum<? extends StatusCode>) s);
194                                 return true;
195                             }
196                         }
197                     }
198                 }
199                 tClass = tClass.getSuperclass();
200             } while(!tClass.equals(Object.class)); // Repeats until the Throwable class.
201             return false;
202         }
203         
204         if (method.equals(Options.class.getMethod("rawArguments"))) {
205             return rawArguments;
206         }
207         
208         if (method.equals(Options.class.getMethod("localeMessage", String.class))) {
209             return defaultFormatter().getMessage((String)args[0], null);
210         }
211         
212         if (method.equals(Options.class.getMethod("localeMessage", String.class, String.class))) {
213             return defaultFormatter().getMessage((String)args[0], (String)args[1]);
214         }
215         
216         if (method.equals(Options.class.getMethod("formattedLocaleMessage", String.class, Object[].class))) {
217             String pattern = defaultFormatter().getMessage((String)args[0], null);
218             return MessageFormat.format(pattern, (Object[])args[1]);
219         }
220         
221         if (method.equals(Options.class.getMethod("formattedLocaleMessageDefault", String.class, String.class, Object[].class))) {
222             String pattern = defaultFormatter().getMessage((String)args[0], (String)args[1]);
223             return MessageFormat.format(pattern, (Object[])args[2]);
224         }
225         
226         if (method.getReturnType().isArray() && optionValues.getValue(method) == null) {
227             return Array.newInstance(method.getReturnType().getComponentType(), 0);
228         }
229         
230         return optionValues.getValue(method);
231     }
232     
233     /**
234      * TODO Comment.
235      */
236     protected final void printDocumentation() {
237         defaultFormatter().printDocumentation();
238     }
239 
240     /**
241      * TODO Comment.
242      * 
243      * @param printStream TODO Comment.
244      */
245     protected final void printDocumentation(final PrintStream printStream) {
246         defaultFormatter().printDocumentation(printStream);
247     }
248 
249     /**
250      * TODO Comment.
251      */
252     protected final void printDocumentation(Class<? extends Annotation> formatterAnnotationClass) {
253         DocumentationFormatterEngine.buildFormatter(optionsInterface, formatterAnnotationClass).printDocumentation();
254     }
255 
256     /**
257      * TODO Comment.
258      * 
259      * @param printStream TODO Comment.
260      */
261     protected final void printDocumentation(Class<? extends Annotation> formatterAnnotationClass, final PrintStream printStream) {
262         DocumentationFormatterEngine.buildFormatter(optionsInterface, formatterAnnotationClass).printDocumentation(printStream);
263     }
264 
265     /**
266      * TODO Comment.
267      * 
268      * @param e TODO Comment.
269      */
270     protected abstract void exit(final Enum<?> e);
271 
272     /**
273      * TODO Comment.
274      * 
275      * @param code TODO Comment.
276      */
277     protected abstract void exit(final int code);
278 
279     @Override
280     public final boolean equals(final Object obj) {
281         if (!(obj instanceof AbstractArgumentsInvocationHandler<?>)) {
282             return false;
283         }
284         AbstractArgumentsInvocationHandler<?> other = (AbstractArgumentsInvocationHandler<?>) obj;
285         return optionValues.equals(other.optionValues) && exitCodeEnumClass.equals(other.exitCodeEnumClass);
286     }
287 
288     @Override
289     public final int hashCode() {
290         return optionValues.hashCode();
291     }
292 
293     @Override
294     public final String toString() {
295         return optionValues.toString();
296     }
297 }