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 }