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.unittesting;
13  
14  import java.lang.reflect.InvocationHandler;
15  import java.lang.reflect.Proxy;
16  
17  import org.hyphenType.datastructure.Options;
18  import org.hyphenType.exceptions.InvalidOptionsInterfaceException;
19  import org.hyphenType.exit.StatusCode;
20  import org.hyphenType.lexerparser.OptionValues;
21  import org.hyphenType.optionprocessors.ArgumentsProcessorEngine;
22  import org.hyphenType.optionsextractor.OptionsExtractor;
23  
24  /**
25   * An {@link OptionsExtractor} that is ready for unit tests. This kind of
26   * {@link OptionsExtractor} will replace the actual routines to terminate the
27   * JVM ({@link Options#exit(Enum)} and {@link Options#exit(int)}) with mock
28   * implementations. Calls for exit methods will be logged and can be read by
29   * unit tests after the code under test was executed. To analyze the usage of
30   * exit methods, the following methods are available:<br>
31   * <ul>
32   * <li> {@link UnitTestingOptionExtractor#getStatusCodeEnum(Options)}</li>
33   * <li> {@link UnitTestingOptionExtractor#exitEnumCalled(Options)}</li>
34   * <li> {@link UnitTestingOptionExtractor#getStatusCodeInt(Options)}</li>
35   * <li> {@link UnitTestingOptionExtractor#exitIntCalled(Options)}</li>
36   * </ul>
37   * This class can also be utilized to test option interface processors (see
38   * {@link ArgumentsProcessorEngine}).
39   * 
40   * @author Aurelio Akira M. Matsui
41   * @param <T>
42   * @see ArgumentsProcessorEngine
43   * @see OptionsExtractor
44   */
45  public class UnitTestingOptionExtractor<T extends Options<?>> extends OptionsExtractor<T> {
46  
47      private final boolean throwsNotExceptionalExit;
48  
49      /**
50       * Creates a new {@link UnitTestingOptionExtractor} object that will work as
51       * a factory for objects whose options interface's class is the class given
52       * as argument. This constructor will configure
53       * {@link UnitTestingOptionExtractor} to factor option objects that throw
54       * {@link NonExceptionalExit} exceptions. If you want to chose whether or
55       * not the options objects will throw {@link NonExceptionalExit}, use the
56       * constructor
57       * {@link UnitTestingOptionExtractor#UnitTestingOptionExtractor(Class, boolean)}
58       * instead.
59       * 
60       * @param clazz
61       * @throws InvalidOptionsInterfaceException
62       * @throws InvalidOptionException
63       */
64      public UnitTestingOptionExtractor(Class<T> clazz) throws InvalidOptionsInterfaceException {
65          this(clazz, true);
66      }
67  
68      /**
69       * Creates a new {@link UnitTestingOptionExtractor} object in a way that
70       * allows the caller to chose whether or not calls to
71       * {@link Options#exit(Enum)} or {@link Options#exit(int)} will result into
72       * throwing a {@link NonExceptionalExit} exception.
73       * 
74       * @param clazz
75       *            The option interface class.
76       * @param throwsNotExceptionalExit
77       *            A flag to configure whether option objects will throw
78       *            {@link NonExceptionalExit} when one calls
79       *            {@link Options#exit(Enum)} or {@link Options#exit(int)}.
80       * @throws InvalidOptionsInterfaceException
81       * @throws InvalidOptionException
82       * @see NonExceptionalExit
83       */
84      public UnitTestingOptionExtractor(Class<T> clazz, boolean throwsNotExceptionalExit) throws InvalidOptionsInterfaceException {
85          super(clazz);
86          this.throwsNotExceptionalExit = throwsNotExceptionalExit;
87      }
88  
89      @Override
90      protected InvocationHandler buildInvocationHandler(OptionValues<T> values, Class<? extends Options<?>> optionsInterface, Class<? extends StatusCode> exitCodeEnumClass, String[] rawArguments) {
91          return new MockArgumentsInvocationHandler<T>(values, optionsInterface, exitCodeEnumClass, rawArguments, throwsNotExceptionalExit);
92      }
93  
94      /**
95       * Returns whether or not the method {@link Options#exit(Enum)} was called
96       * on the given options.
97       * 
98       * @param options
99       * @return
100      */
101     public boolean exitEnumCalled(T options) {
102         return retrieveInvocationHandler(options).exitEnumCalled();
103     }
104 
105     /**
106      * Retrieves the status code of a certain options object. The status code is
107      * set when the method {@link Options#exit(Enum)} is called. If
108      * {@link Options#exit(Enum)} was never called on the given options object,
109      * this method will throw a {@link RuntimeException}. You can call
110      * {@link UnitTestingOptionExtractor#exitEnumCalled(Options)} to avoid
111      * having to catch the exception.
112      * 
113      * @param options
114      *            The options object that we will extract the status code
115      *            enumeration from.
116      * @return The status code, if the method {@link Options#exit(Enum)} was
117      *         already called.
118      * @throws RuntimeException
119      *             If the method {@link Options#exit(Enum)} was never called.
120      */
121     public Enum<?> getStatusCodeEnum(T options) {
122         return retrieveInvocationHandler(options).getStatusCodeEnum();
123     }
124 
125     /**
126      * Returns whether or not the method {@link Options#exit(int)} was called on
127      * the given options.
128      * 
129      * @param options
130      * @return
131      */
132     public boolean exitIntCalled(T options) {
133         return retrieveInvocationHandler(options).exitIntCalled();
134     }
135 
136     /**
137      * Retrieves the status code (int) of a certain options object. The status
138      * code (int) is set when the method {@link Options#exit(int)} is called. If
139      * {@link Options#exit(int)} was never called on the given options object,
140      * this method will throw a {@link RuntimeException}. You can call
141      * {@link UnitTestingOptionExtractor#exitEnumCalled(int)} to avoid having to
142      * catch the exception.
143      * 
144      * @param options
145      *            The options object that we will extract the status code
146      *            integer from.
147      * @return The status code, if the method {@link Options#exit(int)} was
148      *         already called.
149      * @throws RuntimeException
150      *             If the method {@link Options#exit(int)} was never called.
151      */
152     public int getStatusCodeInt(T options) {
153         return retrieveInvocationHandler(options).getStatusCodeInt();
154     }
155 
156     /**
157      * Retrieves a {@link ValidatorsInvocationHandler} object from the argument
158      * object.
159      * 
160      * @param options
161      *            The {@link Options} object based on which the
162      *            {@link ValidatorsInvocationHandler} object will be retrieved.
163      * @return The {@link ValidatorsInvocationHandler} object.
164      * @throws RuntimeException
165      *             If the argument cannot be cast to
166      *             {@link ValidatorsInvocationHandler}.
167      */
168     @SuppressWarnings("unchecked")
169     private MockArgumentsInvocationHandler<T> retrieveInvocationHandler(T options) {
170         Object obj = Proxy.getInvocationHandler(options);
171         if (obj instanceof MockArgumentsInvocationHandler) {
172             MockArgumentsInvocationHandler<T> mih = (MockArgumentsInvocationHandler<T>) obj;
173             return mih;
174         } else {
175             throw new RuntimeException("Argument is not a " + MockArgumentsInvocationHandler.class.getName() + " object, which probably means it was not created by " + UnitTestingOptionExtractor.class.getName() + ".");
176         }
177     }
178 }