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

com.nordstrom.automation.junit.MethodInterceptor Maven / Gradle / Ivy

There is a newer version: 17.1.1
Show newest version
package com.nordstrom.automation.junit;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;

import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.BindingPriority;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.implementation.bind.annotation.This;

/**
 * This class implements the method interceptor for JUnitBase test class objects. This interceptor enables
 * classes that implement the {@link MethodWatcher} interface to perform processing before and after methods
 * with the following JUnit annotations: @Test, @Before, @After, @BeforeClass, and @AfterClass.
 */
public final class MethodInterceptor {
    
    private static final Set> markedClasses = Collections.synchronizedSet(new HashSet<>());
    private static final Set> watcherSet = 
                    Collections.synchronizedSet(new HashSet<>());
    
    private static final List watchers = new ArrayList<>();
    private static final List methodWatchers = new ArrayList<>();
    private static final List methodWatchers2 = new ArrayList<>();
    
    private MethodInterceptor() {
        throw new AssertionError("JUnitMethodInterceptor is a static utility class that cannot be instantiated");
    }
    
    /**
     * This is the method that intercepts annotated methods in "enhanced" JUnit test classes.
     * 
     * @param clazz "enhanced" class upon which the method was invoked
     * @param method {@link Method} object for the invoked method
     * @param args method invocation arguments
     * @param proxy call-able proxy for the intercepted method
     * @return {@code anything} (the result of invoking the intercepted method)
     * @throws Exception {@code anything} (exception thrown by the intercepted method)
     */
    @RuntimeType
    @BindingPriority(1)
    public static Object intercept(@Origin Class clazz, @Origin Method method, @AllArguments Object[] args,
                    @SuperCall Callable proxy) throws Exception
    {
        Object result;
        attachWatchers(clazz);
        
        synchronized(methodWatchers2) {
            for (MethodWatcher2 watcher : methodWatchers2) {
                watcher.beforeInvocation(method, args);
            }
        }
        
        try {
            result = proxy.call();
        } finally {
            synchronized(methodWatchers2) {
                for (MethodWatcher2 watcher : methodWatchers2) {
                    watcher.afterInvocation(method, args);
                }
            }
        }
        
        return result;
    }
    
    /**
     * This is the method that intercepts annotated methods in "enhanced" JUnit test class objects.
     * 
     * @param obj "enhanced" object upon which the method was invoked
     * @param method {@link Method} object for the invoked method
     * @param args method invocation arguments
     * @param proxy call-able proxy for the intercepted method
     * @return {@code anything} (the result of invoking the intercepted method)
     * @throws Exception {@code anything} (exception thrown by the intercepted method)
     */
    @RuntimeType
    @BindingPriority(2)
    public static Object intercept(@This Object obj, @Origin Method method, @AllArguments Object[] args,
                    @SuperCall Callable proxy) throws Exception
    {
        Object result;
        
        synchronized(methodWatchers) {
            for (MethodWatcher watcher : methodWatchers) {
                watcher.beforeInvocation(obj, method, args);
            }
        }
        synchronized(methodWatchers2) {
            for (MethodWatcher2 watcher : methodWatchers2) {
                watcher.beforeInvocation(obj, method, args);
            }
        }
        
        try {
            result = proxy.call();
        } finally {
            synchronized(watchers) {
                synchronized(methodWatchers) {
                    for (MethodWatcher watcher : methodWatchers) {
                        watcher.afterInvocation(obj, method, args);
                    }
                }
                synchronized(methodWatchers2) {
                    for (MethodWatcher2 watcher : methodWatchers2) {
                        watcher.afterInvocation(obj, method, args);
                    }
                }
            }
        }
        
        return result;
    }
    
    /**
     * Get reference to an instance of the specified watcher type.
     * 
     * @param watcherType watcher type
     * @return optional watcher instance
     */
    public static Optional getAttachedWatcher(Class watcherType) {
        Objects.requireNonNull(watcherType, "[watcherType] must be non-null");
        for (MethodWatcher watcher : watchers) {
            if (watcher.getClass() == watcherType) {
                return Optional.of(watcher);
            }
        }
        return Optional.empty();
    }
    
    /**
     * Attach watchers that are active on the specified test class.
     * 
     * @param testClass test class
     */
    static void attachWatchers(Class testClass) {
        MethodWatchers annotation = testClass.getAnnotation(MethodWatchers.class);
        if (null != annotation) {
            Class markedClass = testClass;
            while (null == markedClass.getDeclaredAnnotation(MethodWatchers.class)) {
                markedClass = markedClass.getSuperclass();
            }
            if ( ! markedClasses.contains(markedClass)) {
                markedClasses.add(markedClass);
                for (Class watcher : annotation.value()) {
                    attachWatcher(watcher);
                }
            }
        }
    }
    
    /**
     * Wrap the current watcher chain with an instance of the specified watcher class.
* NOTE: The order in which watcher methods are invoked is determined by the * order in which watcher objects are added to the chain. Listener before methods * are invoked in last-added-first-called order. Listener after methods are invoked * in first-added-first-called order.
* NOTE: Only one instance of any given watcher class will be included in the chain. * * @param watcher watcher class to add to the chain */ private static void attachWatcher(Class watcher) { if ( ! watcherSet.contains(watcher)) { watcherSet.add(watcher); try { MethodWatcher watcherObj = watcher.newInstance(); synchronized(watchers) { watchers.add(watcherObj); } if (watcherObj instanceof MethodWatcher2) { synchronized(methodWatchers2) { methodWatchers2.add((MethodWatcher2) watcherObj); } } else if (watcherObj instanceof MethodWatcher) { synchronized(methodWatchers) { methodWatchers.add(watcherObj); } } } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException("Unable to instantiate watcher: " + watcher.getName(), e); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy