org.powermock.reflect.internal.WhiteboxImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of powermock-reflect Show documentation
Show all versions of powermock-reflect Show documentation
Various utilities for accessing internals of a class.
/*
* Copyright 2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.powermock.reflect.internal;
import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;
import org.objenesis.instantiator.ObjectInstantiator;
import org.powermock.reflect.exceptions.*;
import org.powermock.reflect.internal.matcherstrategies.*;
import org.powermock.reflect.internal.primitivesupport.BoxedWrapper;
import org.powermock.reflect.internal.primitivesupport.PrimitiveWrapper;
import org.powermock.reflect.matching.FieldMatchingStrategy;
import org.powermock.reflect.spi.ProxyFramework;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
/**
* Various utilities for accessing internals of a class. Basically a simplified
* reflection utility intended for tests.
*/
public class WhiteboxImpl {
/**
* The proxy framework.
*/
private static ProxyFramework proxyFramework = null;
/**
* Convenience method to get a method from a class type without having to
* catch the checked exceptions otherwise required. These exceptions are
* wrapped as runtime exceptions.
*
* The method will first try to look for a declared method in the same
* class. If the method is not declared in this class it will look for the
* method in the super class. This will continue throughout the whole class
* hierarchy. If the method is not found an {@link MethodNotFoundException}
* is thrown. Since the method name is not specified an
*
* @param type The type of the class where the method is located.
* @param parameterTypes All parameter types of the method (may be null
).
* @return A . {@link TooManyMethodsFoundException} is thrown if two or more
* methods matches the same parameter types in the same class.
*/
public static Method getMethod(Class> type, Class>... parameterTypes) {
Class> thisType = type;
if (parameterTypes == null) {
parameterTypes = new Class>[0];
}
List foundMethods = new LinkedList();
while (thisType != null) {
Method[] methodsToTraverse = null;
if (thisType.isInterface()) {
// Interfaces only contain public (and abstract) methods, no
// need to traverse the hierarchy.
methodsToTraverse = getAllPublicMethods(thisType);
} else {
methodsToTraverse = thisType.getDeclaredMethods();
}
for (Method method : methodsToTraverse) {
if (checkIfParameterTypesAreSame(method.isVarArgs(), parameterTypes, method.getParameterTypes())) {
foundMethods.add(method);
if (foundMethods.size() == 1) {
method.setAccessible(true);
}
}
}
if (foundMethods.size() == 1) {
return foundMethods.get(0);
} else if (foundMethods.size() > 1) {
break;
}
thisType = thisType.getSuperclass();
}
if (foundMethods.isEmpty()) {
throw new MethodNotFoundException("No method was found with parameter types: [ "
+ getArgumentTypesAsString((Object[]) parameterTypes) + " ] in class "
+ getUnmockedType(type).getName() + ".");
} else {
throwExceptionWhenMultipleMethodMatchesFound("method name",
foundMethods.toArray(new Method[foundMethods.size()]));
}
// Will never happen
return null;
}
/**
* Convenience method to get a method from a class type without having to
* catch the checked exceptions otherwise required. These exceptions are
* wrapped as runtime exceptions.
*
* The method will first try to look for a declared method in the same
* class. If the method is not declared in this class it will look for the
* method in the super class. This will continue throughout the whole class
* hierarchy. If the method is not found an {@link IllegalArgumentException}
* is thrown.
*
* @param type The type of the class where the method is located.
* @param methodName The method names.
* @param parameterTypes All parameter types of the method (may be null
).
* @return A .
*/
public static Method getMethod(Class> type, String methodName, Class>... parameterTypes) {
Class> thisType = type;
if (parameterTypes == null) {
parameterTypes = new Class>[0];
}
while (thisType != null) {
Method[] methodsToTraverse = null;
if (thisType.isInterface()) {
// Interfaces only contain public (and abstract) methods, no
// need to traverse the hierarchy.
methodsToTraverse = getAllPublicMethods(thisType);
} else {
methodsToTraverse = thisType.getDeclaredMethods();
}
for (Method method : methodsToTraverse) {
if (methodName.equals(method.getName())
&& checkIfParameterTypesAreSame(method.isVarArgs(), parameterTypes, method.getParameterTypes())) {
method.setAccessible(true);
return method;
}
}
thisType = thisType.getSuperclass();
}
throwExceptionIfMethodWasNotFound(type, methodName, null, new Object[]{parameterTypes});
return null;
}
/**
* Convenience method to get a field from a class type.
*
* The method will first try to look for a declared field in the same class.
* If the method is not declared in this class it will look for the field in
* the super class. This will continue throughout the whole class hierarchy.
* If the field is not found an {@link IllegalArgumentException} is thrown.
*
* @param type The type of the class where the method is located.
* @param fieldName The method names.
* @return A .
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static Field getField(Class> type, String fieldName) {
LinkedList> examine = new LinkedList>();
examine.add(type);
Set> done = new HashSet>();
while (examine.isEmpty() == false) {
Class> thisType = examine.removeFirst();
done.add(thisType);
final Field[] declaredField = thisType.getDeclaredFields();
for (Field field : declaredField) {
if (fieldName.equals(field.getName())) {
field.setAccessible(true);
return field;
}
}
Set> potential = new HashSet>();
final Class> clazz = thisType.getSuperclass();
if (clazz != null) {
potential.add(thisType.getSuperclass());
}
potential.addAll((Collection) Arrays.asList(thisType.getInterfaces()));
potential.removeAll(done);
examine.addAll(potential);
}
throwExceptionIfFieldWasNotFound(type, fieldName, null);
return null;
}
/**
* Create a new instance of a class without invoking its constructor.
*
* No byte-code manipulation is needed to perform this operation and thus
* it's not necessary use the PowerMockRunner
or
* PrepareForTest
annotation to use this functionality.
*
* @param The type of the instance to create.
* @param classToInstantiate The type of the instance to create.
* @return A new instance of type T, created without invoking the
* constructor.
*/
@SuppressWarnings("unchecked")
public static T newInstance(Class classToInstantiate) {
int modifiers = classToInstantiate.getModifiers();
final Object object;
if (Modifier.isInterface(modifiers)) {
object = Proxy.newProxyInstance(WhiteboxImpl.class.getClassLoader(), new Class>[]{classToInstantiate},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return TypeUtils.getDefaultValue(method.getReturnType());
}
});
} else if (classToInstantiate.isArray()) {
object = Array.newInstance(classToInstantiate.getComponentType(), 0);
} else if (Modifier.isAbstract(modifiers)) {
throw new IllegalArgumentException(
"Cannot instantiate an abstract class. Please use the ConcreteClassGenerator in PowerMock support to generate a concrete class first.");
} else {
Objenesis objenesis = new ObjenesisStd();
ObjectInstantiator thingyInstantiator = objenesis.getInstantiatorOf(classToInstantiate);
object = thingyInstantiator.newInstance();
}
return (T) object;
}
/**
* Convenience method to get a (declared) constructor from a class type
* without having to catch the checked exceptions otherwise required. These
* exceptions are wrapped as runtime exceptions. The constructor is also set
* to accessible.
*
* @param type The type of the class where the constructor is located.
* @param parameterTypes All parameter types of the constructor (may be
* null
).
* @return A .
*/
public static Constructor> getConstructor(Class> type, Class>... parameterTypes) {
Class> unmockedType = WhiteboxImpl.getUnmockedType(type);
try {
final Constructor> constructor = unmockedType.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);
return constructor;
} catch (RuntimeException e) {
throw (RuntimeException) e;
} catch (Error e) {
throw (Error) e;
} catch (Throwable e) {
throw new ConstructorNotFoundException(String.format(
"Failed to lookup constructor with parameter types [ %s ] in class %s.",
getArgumentTypesAsString((Object[]) parameterTypes), unmockedType.getName()), e);
}
}
/**
* Set the value of a field using reflection. This method will traverse the
* super class hierarchy until a field with name fieldName is
* found.
*
* @param object the object whose field to modify
* @param fieldName the name of the field
* @param value the new value of the field
*/
public static void setInternalState(Object object, String fieldName, Object value) {
Field foundField = findFieldInHierarchy(object, fieldName);
setField(object, value, foundField);
}
/**
* Set the value of a field using reflection. This method will traverse the
* super class hierarchy until a field with name fieldName is
* found.
*
* @param object the object to modify
* @param fieldName the name of the field
* @param value the new value of the field
*/
public static void setInternalState(Object object, String fieldName, Object[] value) {
setInternalState(object, fieldName, (Object) value);
}
/**
* Set the value of a field using reflection. This method will traverse the
* super class hierarchy until the first field of type fieldType is
* found. The value will then be assigned to this field.
*
* @param object the object to modify
* @param fieldType the type of the field
* @param value the new value of the field
*/
public static void setInternalState(Object object, Class> fieldType, Object value) {
setField(object, value, findFieldInHierarchy(object, new AssignableFromFieldTypeMatcherStrategy(fieldType)));
}
/**
* Set the value of a field using reflection. This method will traverse the
* super class hierarchy until the first field assignable to the
* value type is found. The value (or
* additionaValues if present) will then be assigned to this field.
*
* @param object the object to modify
* @param value the new value of the field
* @param additionalValues Additional values to set on the object
*/
public static void setInternalState(Object object, Object value, Object... additionalValues) {
setField(object, value,
findFieldInHierarchy(object, new AssignableFromFieldTypeMatcherStrategy(getType(value))));
if (additionalValues != null && additionalValues.length > 0) {
for (Object additionalValue : additionalValues) {
setField(
object,
additionalValue,
findFieldInHierarchy(object, new AssignableFromFieldTypeMatcherStrategy(
getType(additionalValue))));
}
}
}
/**
* Set the value of a field using reflection at at specific place in the
* class hierarchy (where). This first field assignable to
* object will then be set to value.
*
* @param object the object to modify
* @param value the new value of the field
* @param where the class in the hierarchy where the field is defined
*/
public static void setInternalState(Object object, Object value, Class> where) {
setField(object, value, findField(object, new AssignableFromFieldTypeMatcherStrategy(getType(value)), where));
}
/**
* Set the value of a field using reflection at a specific location (
* where) in the class hierarchy. The value will then be
* assigned to this field.
*
* @param object the object to modify
* @param fieldType the type of the field the should be set.
* @param value the new value of the field
* @param where which class in the hierarchy defining the field
*/
public static void setInternalState(Object object, Class> fieldType, Object value, Class> where) {
if (fieldType == null || where == null) {
throw new IllegalArgumentException("fieldType and where cannot be null");
}
setField(object, value, findFieldOrThrowException(fieldType, where));
}
/**
* Set the value of a field using reflection. Use this method when you need
* to specify in which class the field is declared. This is useful if you
* have two fields in a class hierarchy that has the same name but you like
* to modify the latter.
*
* @param object the object to modify
* @param fieldName the name of the field
* @param value the new value of the field
* @param where which class the field is defined
*/
public static void setInternalState(Object object, String fieldName, Object value, Class> where) {
if (object == null || fieldName == null || fieldName.equals("") || fieldName.startsWith(" ")) {
throw new IllegalArgumentException("object, field name, and \"where\" must not be empty or null.");
}
final Field field = getField(fieldName, where);
try {
field.set(object, value);
} catch (Exception e) {
throw new RuntimeException("Internal Error: Failed to set field in method setInternalState.", e);
}
}
/**
* Get the value of a field using reflection. This method will iterate
* through the entire class hierarchy and return the value of the first
* field named fieldName. If you want to get a specific field value
* at specific place in the class hierarchy please refer to
*
* @param the generic type
* @param object the object to modify
* @param fieldName the name of the field
* @return the internal state
* {@link #getInternalState(Object, String, Class)}.
*/
@SuppressWarnings("unchecked")
public static T getInternalState(Object object, String fieldName) {
Field foundField = findFieldInHierarchy(object, fieldName);
try {
return (T) foundField.get(object);
} catch (IllegalAccessException e) {
throw new RuntimeException("Internal error: Failed to get field in method getInternalState.", e);
}
}
/**
* Find field in hierarchy.
*
* @param object the object
* @param fieldName the field name
* @return the field
*/
private static Field findFieldInHierarchy(Object object, String fieldName) {
return findFieldInHierarchy(object, new FieldNameMatcherStrategy(fieldName));
}
/**
* Find field in hierarchy.
*
* @param object the object
* @param strategy the strategy
* @return the field
*/
private static Field findFieldInHierarchy(Object object, FieldMatcherStrategy strategy) {
assertObjectInGetInternalStateIsNotNull(object);
return findSingleFieldUsingStrategy(strategy, object, true, getType(object));
}
/**
* Find field.
*
* @param object the object
* @param strategy the strategy
* @param where the where
* @return the field
*/
private static Field findField(Object object, FieldMatcherStrategy strategy, Class> where) {
return findSingleFieldUsingStrategy(strategy, object, false, where);
}
/**
* Find single field using strategy.
*
* @param strategy the strategy
* @param object the object
* @param checkHierarchy the check hierarchy
* @param startClass the start class
* @return the field
*/
private static Field findSingleFieldUsingStrategy(FieldMatcherStrategy strategy, Object object,
boolean checkHierarchy, Class> startClass) {
assertObjectInGetInternalStateIsNotNull(object);
Field foundField = null;
final Class> originalStartClass = startClass;
while (startClass != null) {
final Field[] declaredFields = startClass.getDeclaredFields();
for (Field field : declaredFields) {
if (strategy.matches(field) && hasFieldProperModifier(object, field)) {
if (foundField != null) {
throw new TooManyFieldsFoundException("Two or more fields matching " + strategy + ".");
}
foundField = field;
}
}
if (foundField != null) {
break;
} else if (checkHierarchy == false) {
break;
}
startClass = startClass.getSuperclass();
}
if (foundField == null) {
strategy.notFound(originalStartClass, !isClass(object));
}
foundField.setAccessible(true);
return foundField;
}
/**
* Find all fields using strategy.
*
* @param strategy the strategy
* @param object the object
* @param checkHierarchy the check hierarchy
* @param startClass the start class
* @return the set
*/
private static Set findAllFieldsUsingStrategy(FieldMatcherStrategy strategy, Object object,
boolean checkHierarchy, Class> startClass) {
assertObjectInGetInternalStateIsNotNull(object);
final Set foundFields = new LinkedHashSet();
while (startClass != null) {
final Field[] declaredFields = startClass.getDeclaredFields();
for (Field field : declaredFields) {
if (strategy.matches(field) && hasFieldProperModifier(object, field)) {
field.setAccessible(true);
foundFields.add(field);
}
}
if (!checkHierarchy) {
break;
}
startClass = startClass.getSuperclass();
}
return Collections.unmodifiableSet(foundFields);
}
/**
* Checks for field proper modifier.
*
* @param object the object
* @param field the field
* @return true, if successful
*/
private static boolean hasFieldProperModifier(Object object, Field field) {
return ((object instanceof Class> && Modifier.isStatic(field.getModifiers())) || ((object instanceof Class> == false && Modifier
.isStatic(field.getModifiers()) == false)));
}
/**
* Get the value of a field using reflection. This method will traverse the
* super class hierarchy until the first field of type fieldType is
* found. The value of this field will be returned.
*
* @param the generic type
* @param object the object to modify
* @param fieldType the type of the field
* @return the internal state
*/
@SuppressWarnings("unchecked")
public static T getInternalState(Object object, Class fieldType) {
Field foundField = findFieldInHierarchy(object, new AssignableToFieldTypeMatcherStrategy(fieldType));
try {
return (T) foundField.get(object);
} catch (IllegalAccessException e) {
throw new RuntimeException("Internal error: Failed to get field in method getInternalState.", e);
}
}
/**
* Get the value of a field using reflection. Use this method when you need
* to specify in which class the field is declared. The first field matching
* the fieldType in where will is the field whose value
* will be returned.
*
* @param the expected type of the field
* @param object the object to modify
* @param fieldType the type of the field
* @param where which class the field is defined
* @return the internal state
*/
@SuppressWarnings("unchecked")
public static T getInternalState(Object object, Class fieldType, Class> where) {
if (object == null) {
throw new IllegalArgumentException("object and type are not allowed to be null");
}
try {
return (T) findFieldOrThrowException(fieldType, where).get(object);
} catch (IllegalAccessException e) {
throw new RuntimeException("Internal error: Failed to get field in method getInternalState.", e);
}
}
/**
* Get the value of a field using reflection. Use this method when you need
* to specify in which class the field is declared. This might be useful
* when you have mocked the instance you are trying to access. Use this
* method to avoid casting.
*
* @param the expected type of the field
* @param object the object to modify
* @param fieldName the name of the field
* @param where which class the field is defined
* @return the internal state
*/
@SuppressWarnings("unchecked")
public static T getInternalState(Object object, String fieldName, Class> where) {
if (object == null || fieldName == null || fieldName.equals("") || fieldName.startsWith(" ")) {
throw new IllegalArgumentException("object, field name, and \"where\" must not be empty or null.");
}
Field field = null;
try {
field = where.getDeclaredField(fieldName);
field.setAccessible(true);
return (T) field.get(object);
} catch (NoSuchFieldException e) {
throw new FieldNotFoundException("Field '" + fieldName + "' was not found in class " + where.getName()
+ ".");
} catch (Exception e) {
throw new RuntimeException("Internal error: Failed to get field in method getInternalState.", e);
}
}
/**
* Invoke a private or inner class method without the need to specify the
* method name. This is thus a more refactor friendly version of the
*
* @param the generic type
* @param tested the tested
* @param arguments the arguments
* @return the t
* @throws Exception the exception
* {@link #invokeMethod(Object, String, Object...)} method and
* is recommend over this method for that reason. This method
* might be useful to test private methods.
*/
@SuppressWarnings("unchecked")
public static synchronized T invokeMethod(Object tested, Object... arguments) throws Exception {
return (T) doInvokeMethod(tested, null, null, arguments);
}
/**
* Invoke a private or inner class method without the need to specify the
* method name. This is thus a more refactor friendly version of the
*
* @param the generic type
* @param tested the tested
* @param arguments the arguments
* @return the t
* @throws Exception the exception
* {@link #invokeMethod(Object, String, Object...)} method and
* is recommend over this method for that reason. This method
* might be useful to test private methods.
*/
@SuppressWarnings("unchecked")
public static synchronized T invokeMethod(Class> tested, Object... arguments) throws Exception {
return (T) doInvokeMethod(tested, null, null, arguments);
}
/**
* Invoke a private or inner class method. This might be useful to test
* private methods.
*
* @param the generic type
* @param tested the tested
* @param methodToExecute the method to execute
* @param arguments the arguments
* @return the t
* @throws Exception the exception
*/
@SuppressWarnings("unchecked")
public static synchronized T invokeMethod(Object tested, String methodToExecute, Object... arguments)
throws Exception {
return (T) doInvokeMethod(tested, null, methodToExecute, arguments);
}
/**
* Invoke a private or inner class method in cases where power mock cannot
* automatically determine the type of the parameters, for example when
* mixing primitive types and wrapper types in the same method. For most
* situations use {@link #invokeMethod(Class, String, Object...)} instead.
*
* @param the generic type
* @param tested the tested
* @param methodToExecute the method to execute
* @param argumentTypes the argument types
* @param arguments the arguments
* @return the t
* @throws Exception Exception that may occur when invoking this method.
*/
@SuppressWarnings("unchecked")
public static synchronized T invokeMethod(Object tested, String methodToExecute, Class>[] argumentTypes,
Object... arguments) throws Exception {
final Class> unmockedType = getType(tested);
Method method = getMethod(unmockedType, methodToExecute, argumentTypes);
if (method == null) {
throwExceptionIfMethodWasNotFound(unmockedType, methodToExecute, null, arguments);
}
return (T) performMethodInvocation(tested, method, arguments);
}
/**
* Invoke a private or inner class method in a subclass (defined by
* definedIn
) in cases where power mock cannot automatically
* determine the type of the parameters, for example when mixing primitive
* types and wrapper types in the same method. For most situations use
*
* @param the generic type
* @param tested the tested
* @param methodToExecute the method to execute
* @param definedIn the defined in
* @param argumentTypes the argument types
* @param arguments the arguments
* @return the t
* @throws Exception Exception that may occur when invoking this method.
* {@link #invokeMethod(Class, String, Object...)} instead.
*/
@SuppressWarnings("unchecked")
public static synchronized T invokeMethod(Object tested, String methodToExecute, Class> definedIn,
Class>[] argumentTypes, Object... arguments) throws Exception {
Method method = getMethod(definedIn, methodToExecute, argumentTypes);
if (method == null) {
throwExceptionIfMethodWasNotFound(definedIn, methodToExecute, null, arguments);
}
return (T) performMethodInvocation(tested, method, arguments);
}
/**
* Invoke a private or inner class method in that is located in a subclass
* of the tested instance. This might be useful to test private methods.
*
* @param the generic type
* @param tested the tested
* @param declaringClass the declaring class
* @param methodToExecute the method to execute
* @param arguments the arguments
* @return the t
* @throws Exception Exception that may occur when invoking this method.
*/
@SuppressWarnings("unchecked")
public static synchronized T invokeMethod(Object tested, Class> declaringClass, String methodToExecute,
Object... arguments) throws Exception {
return (T) doInvokeMethod(tested, declaringClass, methodToExecute, arguments);
}
/**
* Invoke a private method in that is located in a subclass of an instance.
* This might be useful to test overloaded private methods.
*
* Use this for overloaded methods only, if possible use
*
* @param the generic type
* @param object the object
* @param declaringClass the declaring class
* @param methodToExecute the method to execute
* @param parameterTypes the parameter types
* @param arguments the arguments
* @return the t
* @throws Exception Exception that may occur when invoking this method.
* {@link #invokeMethod(Object, Object...)} or
* {@link #invokeMethod(Object, String, Object...)} instead.
*/
@SuppressWarnings("unchecked")
public static synchronized T invokeMethod(Object object, Class> declaringClass, String methodToExecute,
Class>[] parameterTypes, Object... arguments) throws Exception {
if (object == null) {
throw new IllegalArgumentException("object cannot be null");
}
final Method methodToInvoke = getMethod(declaringClass, methodToExecute, parameterTypes);
// Invoke method
return (T) performMethodInvocation(object, methodToInvoke, arguments);
}
/**
* Invoke a private or inner class method. This might be useful to test
* private methods.
*
* @param the generic type
* @param clazz the clazz
* @param methodToExecute the method to execute
* @param arguments the arguments
* @return the t
* @throws Exception the exception
*/
@SuppressWarnings("unchecked")
public static synchronized T invokeMethod(Class> clazz, String methodToExecute, Object... arguments)
throws Exception {
return (T) doInvokeMethod(clazz, null, methodToExecute, arguments);
}
/**
* Do invoke method.
*
* @param the generic type
* @param tested the tested
* @param declaringClass the declaring class
* @param methodToExecute the method to execute
* @param arguments the arguments
* @return the t
* @throws Exception the exception
*/
@SuppressWarnings("unchecked")
private static T doInvokeMethod(Object tested, Class> declaringClass, String methodToExecute,
Object... arguments) throws Exception {
Method methodToInvoke = findMethodOrThrowException(tested, declaringClass, methodToExecute, arguments);
// Invoke test
return (T) performMethodInvocation(tested, methodToInvoke, arguments);
}
/**
* Finds and returns a certain method. If the method couldn't be found this
* method delegates to
*
* @param tested The instance or class containing the method.
* @param declaringClass The class where the method is supposed to be declared (may be
* null
).
* @param methodToExecute The method name. If null
then method will be
* looked up based on the argument types only.
* @param arguments The arguments of the methods.
* @return A single method.
* @throws {@link org.powermock.reflect.exceptions.MethodNotFoundException} if no method was found
* @throws {@link org.powermock.reflect.exceptions.TooManyMethodsFoundException} if too methods matched
*/
public static Method findMethodOrThrowException(Object tested, Class> declaringClass, String methodToExecute,
Object[] arguments) {
if (tested == null) {
throw new IllegalArgumentException("The object to perform the operation on cannot be null.");
}
/*
* Get methods from the type if it's not mocked or from the super type
* if the tested object is mocked.
*/
Class> testedType = null;
if (isClass(tested)) {
testedType = (Class>) tested;
} else {
testedType = tested.getClass();
}
Method[] methods = null;
if (declaringClass == null) {
methods = getAllMethods(testedType);
} else {
methods = declaringClass.getDeclaredMethods();
}
Method potentialMethodToInvoke = null;
for (Method method : methods) {
if (methodToExecute == null || method.getName().equals(methodToExecute)) {
Class>[] paramTypes = method.getParameterTypes();
if ((arguments != null && (paramTypes.length == arguments.length))) {
if (paramTypes.length == 0) {
potentialMethodToInvoke = method;
break;
}
boolean methodFound = checkArgumentTypesMatchParameterTypes(method.isVarArgs(), paramTypes, arguments);
if (methodFound) {
if (potentialMethodToInvoke == null) {
potentialMethodToInvoke = method;
} else if (potentialMethodToInvoke.getName().equals(method.getName())) {
if (areAllArgumentsOfSameType(arguments) && potentialMethodToInvoke.getDeclaringClass() != method.getDeclaringClass()) {
// We've already found the method which means that "potentialMethodToInvoke" overrides "method".
return potentialMethodToInvoke;
} else {
// We've found an overloaded method
return getBestMethodCandidate(getType(tested), method.getName(), getTypes(arguments), false);
}
} else {
// A special case to be backward compatible
Method bestCandidateMethod = getMethodWithMostSpecificParameterTypes(method, potentialMethodToInvoke);
if (bestCandidateMethod != null) {
potentialMethodToInvoke = bestCandidateMethod;
continue;
}
/*
* We've already found a method match before, this
* means that PowerMock cannot determine which
* method to expect since there are two methods with
* the same name and the same number of arguments
* but one is using wrapper types.
*/
throwExceptionWhenMultipleMethodMatchesFound("argument parameter types", new Method[]{
potentialMethodToInvoke, method});
}
}
} else if (isPotentialVarArgsMethod(method, arguments)) {
if (potentialMethodToInvoke == null) {
potentialMethodToInvoke = method;
} else {
/*
* We've already found a method match before, this means
* that PowerMock cannot determine which method to
* expect since there are two methods with the same name
* and the same number of arguments but one is using
* wrapper types.
*/
throwExceptionWhenMultipleMethodMatchesFound("argument parameter types", new Method[]{
potentialMethodToInvoke, method});
}
break;
} else if (arguments != null && (paramTypes.length != arguments.length)) {
continue;
} else if (arguments == null && paramTypes.length == 1 && !paramTypes[0].isPrimitive()) {
potentialMethodToInvoke = method;
}
}
}
WhiteboxImpl.throwExceptionIfMethodWasNotFound(getType(tested), methodToExecute, potentialMethodToInvoke,
arguments);
return potentialMethodToInvoke;
}
/**
* Find the method whose parameter types most closely matches the types
.
*
* @param firstMethodCandidate The first method candidate
* @param secondMethodCandidate The second method candidate
* @return The method that most closely matches the provided types or null
if no method match.
*/
private static Method getMethodWithMostSpecificParameterTypes(Method firstMethodCandidate, Method secondMethodCandidate) {
Class>[] firstMethodCandidateParameterTypes = firstMethodCandidate.getParameterTypes();
Class>[] secondMethodCandidateParameterTypes = secondMethodCandidate.getParameterTypes();
Method bestMatch = null;
for (int i = 0; i < firstMethodCandidateParameterTypes.length; i++) {
Class> candidateType1 = toBoxedIfPrimitive(firstMethodCandidateParameterTypes[i]);
Class> candidateType2 = toBoxedIfPrimitive(secondMethodCandidateParameterTypes[i]);
if (!candidateType1.equals(candidateType2)) {
Method potentialMatch = null;
if (candidateType1.isAssignableFrom(candidateType2)) {
potentialMatch = secondMethodCandidate;
} else if (candidateType2.isAssignableFrom(candidateType1)) {
potentialMatch = firstMethodCandidate;
}
if (potentialMatch != null) {
if (bestMatch != null && !potentialMatch.equals(bestMatch)) {
/*
* We cannot determine which method is the most specific because one parameter of the first candidate
* was more specific and another parameter of the second candidate was more specific.
*/
return null;
} else {
bestMatch = potentialMatch;
}
}
}
}
return bestMatch;
}
private static Class> toBoxedIfPrimitive(Class> type) {
return type.isPrimitive() ? BoxedWrapper.getBoxedFromPrimitiveType(type) : type;
}
/**
* Gets the types.
*
* @param arguments the arguments
* @return the types
*/
private static Class>[] getTypes(Object[] arguments) {
Class>[] classes = new Class>[arguments.length];
for (int i = 0; i < arguments.length; i++) {
classes[i] = getType(arguments[i]);
}
return classes;
}
/**
* Gets the best method candidate.
*
* @param cls the cls
* @param methodName the method name
* @param signature the signature
* @param exactParameterTypeMatch true
if the expectedTypes
must match
* the parameter types must match exactly, false
if
* the expectedTypes
are allowed to be converted
* into primitive types if they are of a wrapped type and still
* match.
* @return the best method candidate
*/
public static Method getBestMethodCandidate(Class> cls, String methodName, Class>[] signature,
boolean exactParameterTypeMatch) {
final Method foundMethod;
final Method[] methods = getMethods(cls, methodName, signature, exactParameterTypeMatch);
if (methods.length == 1) {
foundMethod = methods[0];
} else {
/*
* We've found overloaded methods, we need to find the best one to
* invoke.
*/
Arrays.sort(methods, new Comparator() {
public int compare(Method m1, Method m2) {
final Class>[] typesMethod1 = m1.getParameterTypes();
final Class>[] typesMethod2 = m2.getParameterTypes();
final int size = typesMethod1.length;
for (int i = 0; i < size; i++) {
Class> type1 = typesMethod1[i];
Class> type2 = typesMethod2[i];
if (!type1.equals(type2)) {
if (type1.isAssignableFrom(type2)) {
if (!type1.isArray() && type2.isArray()) {
return -1;
}
return 1;
} else {
if (type1.isArray() && !type2.isArray()) {
return 1;
}
return -1;
}
}
}
return 0;
}
});
foundMethod = methods[0];
}
return foundMethod;
}
/**
* Finds and returns the default constructor. If the constructor couldn't be
* found this method delegates to {@link #throwExceptionWhenMultipleConstructorMatchesFound(java.lang.reflect.Constructor[])}.
*
* @param type The type where the constructor should be located.
* @return The found constructor.
* @throws {@link org.powermock.reflect.exceptions.TooManyConstructorsFoundException} if too many constructors was found.
*/
public static Constructor> findDefaultConstructorOrThrowException(Class> type) {
if (type == null) {
throw new IllegalArgumentException("type cannot be null");
}
final Constructor> declaredConstructor;
try {
declaredConstructor = type.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new ConstructorNotFoundException(String.format("Couldn't find a default constructor in %s.", type.getName()));
}
return declaredConstructor;
}
/**
* Finds and returns any constructor. If the constructor couldn't be
* found this method delegates to {@link #throwExceptionWhenMultipleConstructorMatchesFound(java.lang.reflect.Constructor[])}.
*
* @param type The type where the constructor should be located.
* @return The found constructor.
* @throws {@link org.powermock.reflect.exceptions.TooManyConstructorsFoundException} if too many constructors was found.
*/
public static Constructor> findConstructorOrThrowException(Class> type) {
final Constructor>[] declaredConstructors = filterPowerMockConstructor(type.getDeclaredConstructors());
if (declaredConstructors.length > 1) {
throwExceptionWhenMultipleConstructorMatchesFound(declaredConstructors);
}
return declaredConstructors[0];
}
/**
* Filter power mock constructor.
*
* @param declaredConstructors the declared constructors
* @return the constructor[]
*/
private static Constructor>[] filterPowerMockConstructor(Constructor>[] declaredConstructors) {
Set> constructors = new HashSet>();
for (Constructor> constructor : declaredConstructors) {
final Class>[] parameterTypes = constructor.getParameterTypes();
if (parameterTypes.length >= 1
&& parameterTypes[parameterTypes.length - 1].getName().equals(
"org.powermock.core.IndicateReloadClass")) {
continue;
} else {
constructors.add(constructor);
}
}
return constructors.toArray(new Constructor>[constructors.size()]);
}
/**
* Finds and returns a certain constructor. If the constructor couldn't be
* found this method delegates to
*
* @param type The type where the constructor should be located.
* @param arguments The arguments passed to the constructor.
* @return The found constructor.
* @throws {@link org.powermock.reflect.exceptions.ConstructorNotFoundException} if no constructor was found
* @throws {@link org.powermock.reflect.exceptions.TooManyConstructorsFoundException} if too constructors matched
*/
public static Constructor> findUniqueConstructorOrThrowException(Class> type, Object... arguments) {
if (type == null) {
throw new IllegalArgumentException("Class type cannot be null.");
}
Class> unmockedType = getUnmockedType(type);
if ((unmockedType.isLocalClass() || unmockedType.isAnonymousClass() || unmockedType.isMemberClass())
&& !Modifier.isStatic(unmockedType.getModifiers()) && arguments != null) {
Object[] argumentsForLocalClass = new Object[arguments.length + 1];
argumentsForLocalClass[0] = unmockedType.getEnclosingClass();
System.arraycopy(arguments, 0, argumentsForLocalClass, 1, arguments.length);
arguments = argumentsForLocalClass;
}
Constructor>[] constructors = filterPowerMockConstructor(unmockedType.getDeclaredConstructors());
Constructor> potentialConstructor = null;
for (Constructor> constructor : constructors) {
Class>[] paramTypes = constructor.getParameterTypes();
if ((arguments != null && (paramTypes.length == arguments.length))) {
if (paramTypes.length == 0) {
potentialConstructor = constructor;
break;
}
boolean constructorFound = checkArgumentTypesMatchParameterTypes(constructor.isVarArgs(), paramTypes, arguments);
if (constructorFound) {
if (potentialConstructor == null) {
potentialConstructor = constructor;
} else {
/*
* We've already found a constructor match before, this
* means that PowerMock cannot determine which method to
* expect since there are two methods with the same name
* and the same number of arguments but one is using
* wrapper types.
*/
throwExceptionWhenMultipleConstructorMatchesFound(new Constructor>[]{potentialConstructor,
constructor});
}
}
} else if (isPotentialVarArgsConstructor(constructor, arguments)) {
if (potentialConstructor == null) {
potentialConstructor = constructor;
} else {
/*
* We've already found a constructor match before, this
* means that PowerMock cannot determine which method to
* expect since there are two methods with the same name and
* the same number of arguments but one is using wrapper
* types.
*/
throwExceptionWhenMultipleConstructorMatchesFound(new Constructor>[]{potentialConstructor,
constructor});
}
break;
} else if (arguments != null && (paramTypes.length != arguments.length)) {
continue;
}
}
WhiteboxImpl.throwExceptionIfConstructorWasNotFound(type, potentialConstructor, arguments);
return potentialConstructor;
}
/**
* Convert argument types to primitive.
*
* @param paramTypes the param types
* @param arguments the arguments
* @return the class[]
*/
private static Class>[] convertArgumentTypesToPrimitive(Class>[] paramTypes, Object[] arguments) {
Class>[] types = new Class>[arguments.length];
for (int i = 0; i < arguments.length; i++) {
Class> argumentType = null;
if (arguments[i] == null) {
argumentType = paramTypes[i];
} else {
argumentType = getType(arguments[i]);
}
Class> primitiveWrapperType = PrimitiveWrapper.getPrimitiveFromWrapperType(argumentType);
if (primitiveWrapperType == null) {
types[i] = argumentType;
} else {
types[i] = primitiveWrapperType;
}
}
return types;
}
/**
* Throw exception if method was not found.
*
* @param type the type
* @param methodName the method name
* @param methodToMock the method to mock
* @param arguments the arguments
*/
public static void throwExceptionIfMethodWasNotFound(Class> type, String methodName, Method methodToMock,
Object... arguments) {
if (methodToMock == null) {
String methodNameData = "";
if (methodName != null) {
methodNameData = "with name '" + methodName + "' ";
}
throw new MethodNotFoundException("No method found " + methodNameData + "with parameter types: [ "
+ getArgumentTypesAsString(arguments) + " ] in class " + getUnmockedType(type).getName() + ".");
}
}
/**
* Throw exception if field was not found.
*
* @param type the type
* @param fieldName the field name
* @param field the field
*/
public static void throwExceptionIfFieldWasNotFound(Class> type, String fieldName, Field field) {
if (field == null) {
throw new FieldNotFoundException("No field was found with name '" + fieldName + "' in class "
+ getUnmockedType(type).getName() + ".");
}
}
/**
* Throw exception if constructor was not found.
*
* @param type the type
* @param potentialConstructor the potential constructor
* @param arguments the arguments
*/
static void throwExceptionIfConstructorWasNotFound(Class> type, Constructor> potentialConstructor,
Object... arguments) {
if (potentialConstructor == null) {
String message = "No constructor found in class '" + getUnmockedType(type).getName() + "' with "
+ "parameter types: [ " + getArgumentTypesAsString(arguments) + " ].";
throw new ConstructorNotFoundException(message);
}
}
/**
* Gets the argument types as string.
*
* @param arguments the arguments
* @return the argument types as string
*/
private static String getArgumentTypesAsString(Object... arguments) {
StringBuilder argumentsAsString = new StringBuilder();
final String noParameters = "";
if (arguments != null && arguments.length != 0) {
for (int i = 0; i < arguments.length; i++) {
String argumentName = null;
Object argument = arguments[i];
if (argument instanceof Class>) {
argumentName = ((Class>) argument).getName();
} else if (argument instanceof Class>[] && arguments.length == 1) {
Class>[] argumentArray = (Class>[]) argument;
if (argumentArray.length > 0) {
for (int j = 0; j < argumentArray.length; j++) {
appendArgument(argumentsAsString, j,
argumentArray[j] == null ? "null" : getType(argumentArray[j]).getName(),
argumentArray);
}
return argumentsAsString.toString();
} else {
argumentName = noParameters;
}
} else if (argument == null) {
argumentName = "null";
} else {
argumentName = getType(argument).getName();
}
appendArgument(argumentsAsString, i, argumentName, arguments);
}
} else {
argumentsAsString.append("");
}
return argumentsAsString.toString();
}
/**
* Append argument.
*
* @param argumentsAsString the arguments as string
* @param index the index
* @param argumentName the argument name
* @param arguments the arguments
*/
private static void appendArgument(StringBuilder argumentsAsString, int index, String argumentName,
Object[] arguments) {
argumentsAsString.append(argumentName);
if (index != arguments.length - 1) {
argumentsAsString.append(", ");
}
}
/**
* Invoke a constructor. Useful for testing classes with a private
* constructor when PowerMock cannot determine which constructor to invoke.
* This only happens if you have two constructors with the same number of
* arguments where one is using primitive data types and the other is using
* the wrapped counter part. For example:
*
*
* public class MyClass {
* private MyClass(Integer i) {
* ...
* }
*
* private MyClass(int i) {
* ...
* }
*
*
* This ought to be a really rare case. So for most situation, use
*
* @param the generic type
* @param classThatContainsTheConstructorToTest the class that contains the constructor to test
* @param parameterTypes the parameter types
* @param arguments the arguments
* @return The object created after the constructor has been invoked.
* @throws Exception If an exception occur when invoking the constructor.
* {@link #invokeConstructor(Class, Object...)} instead.
*/
public static T invokeConstructor(Class classThatContainsTheConstructorToTest, Class>[] parameterTypes,
Object[] arguments) throws Exception {
if (parameterTypes != null && arguments != null) {
if (parameterTypes.length != arguments.length) {
throw new IllegalArgumentException("parameterTypes and arguments must have the same length");
}
}
Constructor constructor = null;
try {
constructor = classThatContainsTheConstructorToTest.getDeclaredConstructor(parameterTypes);
} catch (Exception e) {
throw new ConstructorNotFoundException("Could not lookup the constructor", e);
}
return createInstance(constructor, arguments);
}
/**
* Invoke a constructor. Useful for testing classes with a private
* constructor.
*
* @param the generic type
* @param classThatContainsTheConstructorToTest the class that contains the constructor to test
* @param arguments the arguments
* @return The object created after the constructor has been invoked.
* @throws Exception If an exception occur when invoking the constructor.
*/
public static T invokeConstructor(Class classThatContainsTheConstructorToTest, Object... arguments)
throws Exception {
if (classThatContainsTheConstructorToTest == null) {
throw new IllegalArgumentException("The class should contain the constructor cannot be null.");
}
Class>[] argumentTypes = null;
if (arguments == null) {
argumentTypes = new Class>[0];
} else {
argumentTypes = new Class>[arguments.length];
for (int i = 0; i < arguments.length; i++) {
argumentTypes[i] = getType(arguments[i]);
}
}
Constructor constructor = null;
Constructor potentialContstructorWrapped = null;
Constructor potentialContstructorPrimitive = null;
try {
potentialContstructorWrapped = classThatContainsTheConstructorToTest.getDeclaredConstructor(argumentTypes);
} catch (Exception e) {
// Do nothing, we'll try with primitive type next.
}
try {
potentialContstructorPrimitive = classThatContainsTheConstructorToTest
.getDeclaredConstructor(PrimitiveWrapper.toPrimitiveType(argumentTypes));
} catch (Exception e) {
// Do nothing
}
if (potentialContstructorPrimitive == null && potentialContstructorWrapped == null) {
// Check if we can find a matching var args constructor.
constructor = getPotentialVarArgsConstructor(classThatContainsTheConstructorToTest, arguments);
if (constructor == null) {
throw new ConstructorNotFoundException("Failed to find a constructor with parameter types: ["
+ getArgumentTypesAsString(arguments) + "]");
}
} else if (potentialContstructorPrimitive == null && potentialContstructorWrapped != null) {
constructor = potentialContstructorWrapped;
} else if (potentialContstructorPrimitive != null && potentialContstructorWrapped == null) {
constructor = potentialContstructorPrimitive;
} else if (arguments == null || arguments.length == 0 && potentialContstructorPrimitive != null) {
constructor = potentialContstructorPrimitive;
} else {
throw new TooManyConstructorsFoundException(
"Could not determine which constructor to execute. Please specify the parameter types by hand.");
}
return createInstance(constructor, arguments);
}
/**
* Gets the potential var args constructor.
*
* @param the generic type
* @param classThatContainsTheConstructorToTest the class that contains the constructor to test
* @param arguments the arguments
* @return the potential var args constructor
*/
@SuppressWarnings("unchecked")
private static Constructor getPotentialVarArgsConstructor(Class classThatContainsTheConstructorToTest,
Object... arguments) {
Constructor[] declaredConstructors = (Constructor[]) classThatContainsTheConstructorToTest
.getDeclaredConstructors();
for (Constructor possibleVarArgsConstructor : declaredConstructors) {
if (possibleVarArgsConstructor.isVarArgs()) {
if (arguments == null || arguments.length == 0) {
return possibleVarArgsConstructor;
} else {
Class>[] parameterTypes = possibleVarArgsConstructor.getParameterTypes();
if (parameterTypes[parameterTypes.length - 1].getComponentType().isAssignableFrom(
getType(arguments[0]))) {
return possibleVarArgsConstructor;
}
}
}
}
return null;
}
/**
* Creates the instance.
*
* @param the generic type
* @param constructor the constructor
* @param arguments the arguments
* @return the t
* @throws Exception the exception
*/
private static T createInstance(Constructor constructor, Object... arguments) throws Exception {
if (constructor == null) {
throw new IllegalArgumentException("Constructor cannot be null");
}
constructor.setAccessible(true);
T createdObject = null;
try {
if (constructor.isVarArgs()) {
Class>[] parameterTypes = constructor.getParameterTypes();
final int varArgsIndex = parameterTypes.length - 1;
Class> varArgsType = parameterTypes[varArgsIndex].getComponentType();
Object varArgsArrayInstance = createAndPopulateVarArgsArray(varArgsType, varArgsIndex, arguments);
Object[] completeArgumentList = new Object[parameterTypes.length];
for (int i = 0; i < varArgsIndex; i++) {
completeArgumentList[i] = arguments[i];
}
completeArgumentList[completeArgumentList.length - 1] = varArgsArrayInstance;
createdObject = constructor.newInstance(completeArgumentList);
} else {
createdObject = constructor.newInstance(arguments);
}
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof Exception) {
throw (Exception) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
}
}
return createdObject;
}
/**
* Creates the and populate var args array.
*
* @param varArgsType the var args type
* @param varArgsStartPosition the var args start position
* @param arguments the arguments
* @return the object
*/
private static Object createAndPopulateVarArgsArray(Class> varArgsType, int varArgsStartPosition,
Object... arguments) {
Object arrayInstance = Array.newInstance(varArgsType, arguments.length - varArgsStartPosition);
for (int i = varArgsStartPosition; i < arguments.length; i++) {
Array.set(arrayInstance, i - varArgsStartPosition, arguments[i]);
}
return arrayInstance;
}
/**
* Get all declared constructors in the class and set accessible to
* true
.
*
* @param clazz The class whose constructors to get.
* @return All constructors declared in this class hierarchy.
*/
public static Constructor>[] getAllConstructors(Class> clazz) {
Constructor>[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor> constructor : declaredConstructors) {
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
}
return declaredConstructors;
}
/**
* Get all methods in a class hierarchy! Both declared an non-declared (no
* duplicates).
*
* @param clazz The class whose methods to get.
* @return All methods declared in this class hierarchy.
*/
public static Method[] getAllMethods(Class> clazz) {
if (clazz == null) {
throw new IllegalArgumentException("You must specify a class in order to get the methods.");
}
Set methods = new LinkedHashSet();
Class> thisType = clazz;
while (thisType != null) {
final Class> type = thisType;
final Method[] declaredMethods = AccessController.doPrivileged(new PrivilegedAction() {
public Method[] run() {
return type.getDeclaredMethods();
}
});
for (Method method : declaredMethods) {
method.setAccessible(true);
methods.add(method);
}
Collections.addAll(methods, type.getMethods());
thisType = thisType.getSuperclass();
}
return methods.toArray(new Method[methods.size()]);
}
/**
* Get all public methods for a class (no duplicates)! Note that the
* class-hierarchy will not be traversed.
*
* @param clazz The class whose methods to get.
* @return All public methods declared in class.
*/
private static Method[] getAllPublicMethods(Class> clazz) {
if (clazz == null) {
throw new IllegalArgumentException("You must specify a class in order to get the methods.");
}
Set methods = new LinkedHashSet();
for (Method method : clazz.getMethods()) {
method.setAccessible(true);
methods.add(method);
}
return methods.toArray(new Method[0]);
}
/**
* Get all fields in a class hierarchy! Both declared an non-declared (no
* duplicates).
*
* @param clazz The class whose fields to get.
* @return All fields declared in this class hierarchy.
*/
public static Field[] getAllFields(Class> clazz) {
if (clazz == null) {
throw new IllegalArgumentException("You must specify the class that contains the fields");
}
Set fields = new LinkedHashSet();
Class> thisType = clazz;
while (thisType != null) {
final Field[] declaredFields = thisType.getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
fields.add(field);
}
thisType = thisType.getSuperclass();
}
return fields.toArray(new Field[fields.size()]);
}
/**
* Get the first parent constructor defined in a super class of
* klass
.
*
* @param klass The class where the constructor is located. null
* ).
* @return A .
*/
public static Constructor> getFirstParentConstructor(Class> klass) {
try {
return getUnmockedType(klass).getSuperclass().getDeclaredConstructors()[0];
} catch (Exception e) {
throw new ConstructorNotFoundException("Failed to lookup constructor.", e);
}
}
/**
* Finds and returns a method based on the input parameters. If no
* parameterTypes
are present the method will return the first
* method with name methodNameToMock
. If no method was found,
* null
will be returned. If no methodName
is
* specified the method will be found based on the parameter types. If
* neither method name nor parameters are specified an
*
* @param the generic type
* @param type the type
* @param methodName the method name
* @param parameterTypes the parameter types
* @return the method {@link IllegalArgumentException} will be thrown.
*/
public static Method findMethod(Class type, String methodName, Class>... parameterTypes) {
if (methodName == null && parameterTypes == null) {
throw new IllegalArgumentException("You must specify a method name or parameter types.");
}
List matchingMethodsList = new LinkedList();
for (Method method : getAllMethods(type)) {
if (methodName == null || method.getName().equals(methodName)) {
if (parameterTypes != null && parameterTypes.length > 0) {
// If argument types was supplied, make sure that they
// match.
Class>[] paramTypes = method.getParameterTypes();
if (!checkIfParameterTypesAreSame(method.isVarArgs(), parameterTypes, paramTypes)) {
continue;
}
}
// Add the method to the matching methods list.
matchingMethodsList.add(method);
}
}
Method methodToMock = null;
if (matchingMethodsList.size() > 0) {
if (matchingMethodsList.size() == 1) {
// We've found a unique method match.
methodToMock = matchingMethodsList.get(0);
} else if ((parameterTypes != null ? parameterTypes.length : 0) == 0) {
/*
* If we've found several matches and we've supplied no
* parameter types, go through the list of found methods and see
* if we have a method with no parameters. In that case return
* that method.
*/
for (Method method : matchingMethodsList) {
if (method.getParameterTypes().length == 0) {
methodToMock = method;
break;
}
}
if (methodToMock == null) {
WhiteboxImpl.throwExceptionWhenMultipleMethodMatchesFound("argument parameter types",
matchingMethodsList.toArray(new Method[matchingMethodsList.size()]));
}
} else {
// We've found several matching methods.
WhiteboxImpl.throwExceptionWhenMultipleMethodMatchesFound("argument parameter types",
matchingMethodsList.toArray(new Method[matchingMethodsList.size()]));
}
}
return methodToMock;
}
/**
* Checks if is proxy.
*
* @param type the type
* @return true, if is proxy
*/
public static boolean isProxy(Class> type) {
return proxyFramework.isProxy(type);
}
/**
* Gets the unmocked type.
*
* @param the generic type
* @param type the type
* @return the unmocked type
*/
public static Class> getUnmockedType(Class type) {
if (type == null) {
throw new IllegalArgumentException("type cannot be null");
}
Class> unmockedType = null;
if (proxyFramework != null && proxyFramework.isProxy(type)) {
unmockedType = proxyFramework.getUnproxiedType(type);
} else if (Proxy.isProxyClass(type)) {
unmockedType = type.getInterfaces()[0];
} else {
unmockedType = type;
}
return unmockedType;
}
/**
* Throw exception when multiple method matches found.
*
* @param helpInfo the help info
* @param methods the methods
*/
static void throwExceptionWhenMultipleMethodMatchesFound(String helpInfo, Method[] methods) {
if (methods == null || methods.length < 2) {
throw new IllegalArgumentException(
"Internal error: throwExceptionWhenMultipleMethodMatchesFound needs at least two methods.");
}
StringBuilder sb = new StringBuilder();
sb.append("Several matching methods found, please specify the ");
sb.append(helpInfo);
sb.append(" so that PowerMock can determine which method you're referring to.\n");
sb.append("Matching methods in class ").append(methods[0].getDeclaringClass().getName()).append(" were:\n");
for (Method method : methods) {
sb.append(method.getReturnType().getName()).append(" ");
sb.append(method.getName()).append("( ");
final Class>[] parameterTypes = method.getParameterTypes();
for (Class> paramType : parameterTypes) {
sb.append(paramType.getName()).append(".class ");
}
sb.append(")\n");
}
throw new TooManyMethodsFoundException(sb.toString());
}
/**
* Throw exception when multiple constructor matches found.
*
* @param constructors the constructors
*/
static void throwExceptionWhenMultipleConstructorMatchesFound(Constructor>[] constructors) {
if (constructors == null || constructors.length < 2) {
throw new IllegalArgumentException(
"Internal error: throwExceptionWhenMultipleConstructorMatchesFound needs at least two constructors.");
}
StringBuilder sb = new StringBuilder();
sb.append("Several matching constructors found, please specify the argument parameter types so that PowerMock can determine which method you're referring to.\n");
sb.append("Matching constructors in class ").append(constructors[0].getDeclaringClass().getName())
.append(" were:\n");
for (Constructor> constructor : constructors) {
sb.append(constructor.getName()).append("( ");
final Class>[] parameterTypes = constructor.getParameterTypes();
for (Class> paramType : parameterTypes) {
sb.append(paramType.getName()).append(".class ");
}
sb.append(")\n");
}
throw new TooManyConstructorsFoundException(sb.toString());
}
/**
* Find method or throw exception.
*
* @param type the type
* @param methodName the method name
* @param parameterTypes the parameter types
* @return the method
*/
@SuppressWarnings("all")
public static Method findMethodOrThrowException(Class> type, String methodName, Class>... parameterTypes) {
Method methodToMock = findMethod(type, methodName, parameterTypes);
throwExceptionIfMethodWasNotFound(type, methodName, methodToMock, parameterTypes);
return methodToMock;
}
/**
* Get an array of {@link Method}'s that matches the supplied list of method
* names. Both instance and static methods are taken into account.
*
* @param clazz The class that should contain the methods.
* @param methodNames Names of the methods that will be returned.
* @return An array of Method's.
*/
public static Method[] getMethods(Class> clazz, String... methodNames) {
if (methodNames == null || methodNames.length == 0) {
throw new IllegalArgumentException("You must supply at least one method name.");
}
final List methodsToMock = new LinkedList();
Method[] allMethods = null;
if (clazz.isInterface()) {
allMethods = getAllPublicMethods(clazz);
} else {
allMethods = getAllMethods(clazz);
}
for (Method method : allMethods) {
for (String methodName : methodNames) {
if (method.getName().equals(methodName)) {
method.setAccessible(true);
methodsToMock.add(method);
}
}
}
final Method[] methodArray = methodsToMock.toArray(new Method[0]);
if (methodArray.length == 0) {
throw new MethodNotFoundException(String.format(
"No methods matching the name(s) %s were found in the class hierarchy of %s.",
concatenateStrings(methodNames), getType(clazz)));
}
return methodArray;
}
/**
* Get an array of {@link Method}'s that matches the method name and whose
* argument types are assignable from expectedTypes
. Both
* instance and static methods are taken into account.
*
* @param clazz The class that should contain the methods.
* @param methodName Names of the methods that will be returned.
* @param expectedTypes The methods must match
* @param exactParameterTypeMatch true
if the expectedTypes
must match
* the parameter types must match exactly, false
if
* the expectedTypes
are allowed to be converted
* into primitive types if they are of a wrapped type and still
* match.
* @return An array of Method's.
*/
public static Method[] getMethods(Class> clazz, String methodName, Class>[] expectedTypes,
boolean exactParameterTypeMatch) {
List matchingArgumentTypes = new LinkedList();
Method[] methods = getMethods(clazz, methodName);
for (Method method : methods) {
final Class>[] parameterTypes = method.getParameterTypes();
if (checkIfParameterTypesAreSame(method.isVarArgs(), expectedTypes, parameterTypes)
|| (!exactParameterTypeMatch && checkIfParameterTypesAreSame(method.isVarArgs(),
convertParameterTypesToPrimitive(expectedTypes), parameterTypes))) {
matchingArgumentTypes.add(method);
}
}
final Method[] methodArray = matchingArgumentTypes.toArray(new Method[0]);
if (methodArray.length == 0) {
throw new MethodNotFoundException(String.format(
"No methods matching the name(s) %s were found in the class hierarchy of %s.",
concatenateStrings(methodName), getType(clazz)));
}
return matchingArgumentTypes.toArray(new Method[matchingArgumentTypes.size()]);
}
/**
* Get an array of {@link Field}'s that matches the supplied list of field
* names. Both instance and static fields are taken into account.
*
* @param clazz The class that should contain the fields.
* @param fieldNames Names of the fields that will be returned.
* @return An array of Field's. May be of length 0 but not .
*/
public static Field[] getFields(Class> clazz, String... fieldNames) {
final List fields = new LinkedList();
for (Field field : getAllFields(clazz)) {
for (String fieldName : fieldNames) {
if (field.getName().equals(fieldName)) {
fields.add(field);
}
}
}
final Field[] fieldArray = fields.toArray(new Field[fields.size()]);
if (fieldArray.length == 0) {
throw new FieldNotFoundException(String.format(
"No fields matching the name(s) %s were found in the class hierarchy of %s.",
concatenateStrings(fieldNames), getType(clazz)));
}
return fieldArray;
}
/**
* Perform method invocation.
*
* @param the generic type
* @param tested the tested
* @param methodToInvoke the method to invoke
* @param arguments the arguments
* @return the t
* @throws Exception the exception
*/
@SuppressWarnings("unchecked")
public static T performMethodInvocation(Object tested, Method methodToInvoke, Object... arguments)
throws Exception {
final boolean accessible = methodToInvoke.isAccessible();
if (!accessible) {
methodToInvoke.setAccessible(true);
}
try {
if (isPotentialVarArgsMethod(methodToInvoke, arguments)) {
Class>[] parameterTypes = methodToInvoke.getParameterTypes();
final int varArgsIndex = parameterTypes.length - 1;
Class> varArgsType = parameterTypes[varArgsIndex].getComponentType();
Object varArgsArrayInstance = createAndPopulateVarArgsArray(varArgsType, varArgsIndex, arguments);
Object[] completeArgumentList = new Object[parameterTypes.length];
for (int i = 0; i < varArgsIndex; i++) {
completeArgumentList[i] = arguments[i];
}
completeArgumentList[completeArgumentList.length - 1] = varArgsArrayInstance;
return (T) methodToInvoke.invoke(tested, completeArgumentList);
} else {
return (T) methodToInvoke.invoke(tested, arguments == null ? new Object[]{arguments} : arguments);
}
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof Exception) {
throw (Exception) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
throw new MethodInvocationException(cause);
}
} finally {
if (!accessible) {
methodToInvoke.setAccessible(false);
}
}
}
/**
* Gets the all method except.
*
* @param the generic type
* @param type the type
* @param methodNames the method names
* @return the all method except
*/
public static Method[] getAllMethodExcept(Class type, String... methodNames) {
List methodsToMock = new LinkedList();
Method[] methods = getAllMethods(type);
iterateMethods:
for (Method method : methods) {
for (String methodName : methodNames) {
if (method.getName().equals(methodName)) {
continue iterateMethods;
}
}
methodsToMock.add(method);
}
return methodsToMock.toArray(new Method[0]);
}
/**
* Gets the all metods except.
*
* @param the generic type
* @param type the type
* @param methodNameToExclude the method name to exclude
* @param argumentTypes the argument types
* @return the all metods except
*/
public static Method[] getAllMetodsExcept(Class type, String methodNameToExclude, Class>[] argumentTypes) {
Method[] methods = getAllMethods(type);
List methodList = new ArrayList();
outer:
for (Method method : methods) {
if (method.getName().equals(methodNameToExclude)) {
if (argumentTypes != null && argumentTypes.length > 0) {
final Class>[] args = method.getParameterTypes();
if (args != null && args.length == argumentTypes.length) {
for (int i = 0; i < args.length; i++) {
if (args[i].isAssignableFrom(getUnmockedType(argumentTypes[i]))) {
/*
* Method was not found thus it should not be
* mocked. Continue to investigate the next
* method.
*/
continue outer;
}
}
}
} else {
continue;
}
}
methodList.add(method);
}
return methodList.toArray(new Method[0]);
}
/**
* Are all methods static.
*
* @param methods the methods
* @return true, if successful
*/
public static boolean areAllMethodsStatic(Method... methods) {
for (Method method : methods) {
if (!Modifier.isStatic(method.getModifiers())) {
return false;
}
}
return true;
}
/**
* Check if all arguments are of the same type.
*
* @param arguments the arguments
* @return true, if successful
*/
static boolean areAllArgumentsOfSameType(Object[] arguments) {
if (arguments == null || arguments.length <= 1) {
return true;
}
// Handle null values
int index = 0;
Object object = null;
while (object == null && index < arguments.length) {
object = arguments[index++];
}
if (object == null) {
return true;
}
// End of handling null values
final Class> firstArgumentType = getType(object);
for (int i = index; i < arguments.length; i++) {
final Object argument = arguments[i];
if (argument != null && !getType(argument).isAssignableFrom(firstArgumentType)) {
return false;
}
}
return true;
}
/**
* Check argument types match parameter types.
*
* @param isVarArgs If the last parameter is a var args.
* @param parameterTypes the parameter types
* @param arguments the arguments
* @return if all actual parameter types are assignable from the expected
* arguments, otherwise.
*/
private static boolean checkArgumentTypesMatchParameterTypes(boolean isVarArgs, Class>[] parameterTypes,
Object[] arguments) {
if (parameterTypes == null) {
throw new IllegalArgumentException("parameter types cannot be null");
} else if (!isVarArgs && arguments.length != parameterTypes.length) {
return false;
}
for (int i = 0; i < arguments.length; i++) {
Object argument = arguments[i];
if (argument == null) {
final int index;
if (i >= parameterTypes.length) {
index = parameterTypes.length - 1;
} else {
index = i;
}
final Class> type = parameterTypes[index];
if (type.isPrimitive()) {
// Primitives cannot be null
return false;
} else {
continue;
}
} else if (i >= parameterTypes.length) {
if (isAssignableFrom(parameterTypes[parameterTypes.length - 1], getType(argument))) {
continue;
} else {
return false;
}
} else {
boolean assignableFrom = isAssignableFrom(parameterTypes[i], getType(argument));
final boolean isClass = parameterTypes[i].equals(Class.class) && isClass(argument);
if (!assignableFrom && !isClass) {
return false;
}
}
}
return true;
}
private static boolean isAssignableFrom(Class> type, Class> from) {
boolean assignableFrom;
Class> theType = getComponentType(type);
Class> theFrom = getComponentType(from);
assignableFrom = theType.isAssignableFrom(theFrom);
if (!assignableFrom && PrimitiveWrapper.hasPrimitiveCounterPart(theFrom)) {
final Class> primitiveFromWrapperType = PrimitiveWrapper.getPrimitiveFromWrapperType(theFrom);
if (primitiveFromWrapperType != null) {
assignableFrom = theType.isAssignableFrom(primitiveFromWrapperType);
}
}
return assignableFrom;
}
private static Class> getComponentType(Class> type) {
Class> theType = type;
while (theType.isArray()) {
theType = theType.getComponentType();
}
return theType;
}
/**
* Gets the type.
*
* @param object the object
* @return The type of the of an object.
*/
public static Class> getType(Object object) {
Class> type = null;
if (isClass(object)) {
type = (Class>) object;
} else if (object != null) {
type = object.getClass();
}
return type == null ? null : getUnmockedType(type);
}
/**
* Get an inner class type.
*
* @param declaringClass The class in which the inner class is declared.
* @param name The unqualified name (simple name) of the inner class.
* @return The type.
* @throws ClassNotFoundException the class not found exception
*/
@SuppressWarnings("unchecked")
public static Class
© 2015 - 2024 Weber Informatics LLC | Privacy Policy