All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.callbackparams.combine.reflect.Combined Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 1.0-beta-6
Show newest version
/*
 * 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