
org.callbackparams.combine.reflect.Combined Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of callbackparams Show documentation
Show all versions of callbackparams Show documentation
CallbackParams is a JUnit-extension for writing parameterized tests.
It unlocks new innovative patterns that offer elegant solutions to many
obstacles that are traditionally associated with parameterized testing.
/*
* Copyright 2011-2012 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.callbackparams.combine.reflect;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Map;
/**
* @author Henrik Kaipe
*/
public class Combined {
private static Class[] METHOD_HAS_NO_PARAMETERS = {};
private final Object coreValue;
private Method staticMethod;
private final int index;
/**
* The field subIndex comes to play when callback-records are specified with
* the
* annotation {@link org.callbackparams.combine.annotation.CallbackRecords}.
* @see org.callbackparams.combine.annotation.CallbackRecords
*/
private final int subIndex;
/**
* Might be set to false by {@link #toBeInjected(Field, boolean)}
*/
private boolean isCallback = true;
private Field valueField = null;
public Combined(Object coreValue) {
this(null, -1, -1, coreValue);
}
Combined(Method staticMethod, int index) {
this(staticMethod, index, -1);
}
Combined(Method staticMethod, int index, int subIndex) {
this(staticMethod, index, subIndex, null);
}
Combined(Method staticMethod, int index, int subIndex, Object originalValue) {
this.staticMethod = staticMethod;
this.index = index;
this.subIndex = subIndex;
this.coreValue = originalValue;
try {
if (null != staticMethod) {
staticMethod.setAccessible(true);
}
} catch (SecurityException ignoreAndContinueAnyway) {}
}
public static Combined resurrectFromOtherClassLoader(Object combined2reload) {
if (combined2reload instanceof Combined) {
return (Combined) combined2reload;
} else {
Combined resurrected = new Combined(
(Method) valueOfField("staticMethod", combined2reload),
((Integer)valueOfField("index", combined2reload)).intValue(),
((Integer)valueOfField("subIndex", combined2reload)).intValue(),
valueOfField("coreValue", combined2reload));
resurrected.toBeInjected(
(Field) valueOfField("valueField", combined2reload),
Boolean
/* Must use "equals" here or integration with class-reloading
* 3rd-party JUnit-runners might break! Operator '==' does
* not work under PowerMockRunner because its class-loader
* seems to internally handle the state of boolean field
* "isCallback" with a Boolean-instance that has been
* created separately. */
.TRUE.equals(valueOfField("isCallback", combined2reload)));
return resurrected;
}
}
private static Object valueOfField(String fieldName, Object obj) {
Class c = obj.getClass();
try {
Field f = c.getDeclaredField(fieldName);
f.setAccessible(true);
return f.get(obj);
} catch (Exception ex) {
throw new Error(ex);
}
}
void toBeInjected(Field valueField, boolean alsoAvailableAsCallback) {
if (null != this.valueField) {
throw new IllegalStateException(
"Field for value-injection has already been set: " + this.valueField);
}
this.valueField = valueField;
this.isCallback = alsoAvailableAsCallback;
try {
if (null != valueField) {
this.valueField.setAccessible(true);
}
} catch (SecurityException ignoreAndContinueAnyway) {}
}
public Object getCoreValue() {
return null!=coreValue || null==staticMethod ? coreValue : getCoreValue(
staticMethod.getDeclaringClass().getClassLoader(),
new IdentityHashMap(1));
}
private Object getCoreValue(
ClassLoader cl, Map/*Method,Object[]*/ valuesCache) {
if (null == this.staticMethod) {
/*
* Does not quite work for all situations, such as when
* wrapping another runner ...
*/
return this.coreValue;
}
/*
* Get a local instance of staticMethod to avoid concurrency problems
* for multi-threaded contexts. (The field staticMethod is somewhat
* volatile.)
*/
Method staticMethod = getStaticMethodOnClassLoader(cl);
if (false == valuesCache.containsValue(staticMethod)) {
try {
valuesCache.put(staticMethod, staticMethod
.invoke(null, (Object[])METHOD_HAS_NO_PARAMETERS));
} catch (InvocationTargetException ex) {
Throwable cause = ex.getTargetException();
if (cause instanceof Error) {
throw (Error)cause;
} else if (cause instanceof RuntimeException) {
throw (RuntimeException)cause;
} else {
throw new Error(ex);
}
} catch (Exception ex) {
throw new Error(ex);
}
}
Object valuesElement = Array.get(valuesCache.get(staticMethod), index);
if (subIndex < 0) {
return valuesElement;
} else if (valuesElement.getClass().isArray()) {
return Array.get(valuesElement, subIndex);
} else {
return ((Collection)valuesElement).toArray()[subIndex];
}
}
/**
* Retrieves the core value of this Combined instance and does at least one
* out of two things:
*
1) Has the core value injected as a parameterized value into the
* specified {@link #valueField}
*
2) Returns the core value in case it is to be available as callback,
* as specified by {@link #isCallback}
* @param testInstance the test instance, onto which a possible parameterized value
* is to be injected; or the test-class
* @param valuesCache maps static methods to their return-value arrays as an
* attempt to avoid multiple invocations of {@link #staticMethod} - in case
* it is an expensive method that needs to access the file-system on every
* invocation or something
* @return the callback-value that is represented by this Combined instance;
* or null in case {@link #isCallback} is false and therewith specifies that
* the core value of this Combined instance is not a callback
*/
public Object retrieve(Object testInstance, Map/*Method,Object[]*/ valuesCache) {
final ClassLoader cl = testInstance.getClass().getClassLoader();
final Object coreValue = getCoreValue(
testInstance.getClass().getClassLoader(), valuesCache);
if (null != valueField) {
try {
getFieldOnClassLoader(cl).set(testInstance, coreValue);
} catch (Exception ex) {
throw new Error("Unable to inject " + valueField
+ " with core value: " + coreValue, ex);
}
}
return isCallback ? coreValue : null;
}
private Method getStaticMethodOnClassLoader(ClassLoader cl) {
Method staticMethod = this.staticMethod;
if (null == cl) {
/* Seems to be possible for JDK-classes ... */
return staticMethod;
}
try {
final Class declaringClass = cl
.loadClass(staticMethod.getDeclaringClass().getName());
if (declaringClass != staticMethod.getDeclaringClass()) {
staticMethod = declaringClass.getDeclaredMethod(
staticMethod.getName(), METHOD_HAS_NO_PARAMETERS);
staticMethod.setAccessible(true);
/*
* Replace static method on this combine instance for easier
* retrieval next time ...
*/
this.staticMethod = staticMethod;
}
} catch (SecurityException ignoreAndContinueAnyway) {
} catch (Exception ex) {
throw new Error("Cannot reach static method on test class-loader: "
+ staticMethod, ex);
}
return staticMethod;
}
private Field getFieldOnClassLoader(ClassLoader cl) {
Field valueField = this.valueField;
try {
final Class declaringClass = cl
.loadClass(valueField.getDeclaringClass().getName());
if (declaringClass != valueField.getDeclaringClass()) {
valueField = declaringClass.getDeclaredField(valueField.getName());
valueField.setAccessible(true);
/*
* Replace static method on this combine instance for easier
* retrieval next time ...
*/
this.valueField = valueField;
}
} catch (SecurityException ignoreAndContinueAnyway) {
} catch (Exception ex) {
throw new Error(
"Cannot reach @ParameterizedValue-field on test class-loader: "
+ valueField, ex);
}
return valueField;
}
// @Override (if it had been Java-5)
public String toString() {
return (null == valueField ? "" : valueField.getName() + '=')
+ getCoreValue();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy