package org.jenkins.tools.test.model.hook;
import org.reflections.Reflections;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Loads and executes hooks for modifying the state of the Plugin Compat Tester at different
* points along the setup. Hooks extec a particular subclass defining their type and
* implement the PluginCompatTesterHook interface.
*
* Note: the classes will not have a parameterized constructor or at least that
* constructor will never be called
*/
public class PluginCompatTesterHooks {
private List<String> hookPrefixes = new ArrayList<String>();
private Map<String, Map<String, Queue<PluginCompatTesterHook>>> hooksByType = new HashMap<String, Map<String, Queue<PluginCompatTesterHook>>>();
/**
* Create and prepopulate the various hooks for this run of the plugin-compat-tester.
*
*/
public PluginCompatTesterHooks() {
this(new ArrayList<String>());
}
public PluginCompatTesterHooks(List<String> extraPrefixes) {
if(extraPrefixes != null) {
hookPrefixes.addAll(extraPrefixes);
}
for(String stage : Arrays.asList("checkout", "execution", "compilation")) {
hooksByType.put(stage, findHooks(stage));
}
}
public Map<String, Object> runBeforeCheckout(Map<String, Object> elements) {
return runHooks("checkout", elements);
}
public Map<String, Object> runBeforeCompilation(Map<String, Object> elements) {
return runHooks("compilation", elements);
}
public Map<String, Object> runBeforeExecution(Map<String, Object> elements) {
return runHooks("execution", elements);
}
/**
* Evaluate and execute hooks for a given stage. There is 1 required object for evaluting any
* hook. [String, "pluginName"]
*
* @param stage stage which
* @param elements relevant information to hooks at various stages.
*/
private Map<String, Object> runHooks(String stage, Map<String, Object> elements) throws RuntimeException {
String pluginName = (String)elements.get("pluginName");
Queue<PluginCompatTesterHook> beforeHooks = hooksByType.get(stage).get("all") != null ? hooksByType.get(stage).get("all") : new LinkedList<PluginCompatTesterHook>();
if(hooksByType.get(stage).get(pluginName) != null) {
beforeHooks.addAll(hooksByType.get(stage).get(pluginName));
}
// Loop through hooks in a series run in no particular order
// Modifications build on each other, pertenent checks should be handled in the hook
for(PluginCompatTesterHook hook : beforeHooks) {
try {
System.out.println("Processing " + hook.getClass().getName());
if(hook.check(elements)) {
elements = hook.action(elements);
hook.validate(elements);
} else {
System.out.println("Hook not triggered. Continuing.");
}
} catch (RuntimeException re) {
//this type of exception should stop processing the plugins. Throw it up the chain
throw re;
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("Cannot make transformation; continue.");
}
}
return elements;
}
private Map<String, Queue<PluginCompatTesterHook>> findHooks(String stage) {
Map<String, Queue<PluginCompatTesterHook>> sortedHooks = new HashMap<String, Queue<PluginCompatTesterHook>>();
// Search for all hooks defined within the given classpath prefix
for(String prefix : hookPrefixes) {
Reflections reflections = new Reflections(prefix);
Set<Class<? extends PluginCompatTesterHook>> subTypes;
// Find all steps for a given stage. Long due to casting
switch(stage) {
case "compilation" :
Set<Class<? extends PluginCompatTesterHookBeforeCompile>> compSteps = reflections.getSubTypesOf(PluginCompatTesterHookBeforeCompile.class);
subTypes = compSteps.stream()
.map(elt -> casting(elt))
.collect(Collectors.toSet());
break;
case "execution" :
Set<Class<? extends PluginCompatTesterHookBeforeExecution>> exeSteps = reflections.getSubTypesOf(PluginCompatTesterHookBeforeExecution.class);
subTypes = exeSteps.stream()
.map(elt -> casting(elt))
.collect(Collectors.toSet());
break;
case "checkout" :
Set<Class<? extends PluginCompatTesterHookBeforeCheckout>> checkSteps = reflections.getSubTypesOf(PluginCompatTesterHookBeforeCheckout.class);
subTypes = checkSteps.stream()
.map(elt -> casting(elt))
.collect(Collectors.toSet());
break;
default: // Not valid; nothing will get executed
return new HashMap<String, Queue<PluginCompatTesterHook>>();
}
for(Class c : subTypes) {
try {
System.out.println("Hook: " + c.getName());
Constructor<?> constructor = c.getConstructor();
PluginCompatTesterHook hook = (PluginCompatTesterHook)constructor.newInstance();
List<String> plugins = hook.transformedPlugins();
for(String plugin : plugins) {
Queue<PluginCompatTesterHook> allForType = sortedHooks.get(plugin);
if(allForType == null){
allForType = new LinkedList<PluginCompatTesterHook>();
}
allForType.add(hook);
sortedHooks.put(plugin, allForType);
}
} catch (Exception ex) {
System.out.println("Error when loading " + c.getName());
ex.printStackTrace();
continue;
}
}
}
return sortedHooks;
}
/**
* Seems rediculous, but is needed to actually convert between the two types of Sets.
* Gets around generics error: {@code incompatible types: inference variable T has incompatible bounds}
*/
private Class<? extends PluginCompatTesterHook> casting(Class<? extends PluginCompatTesterHook> c) {
return (Class<? extends PluginCompatTesterHook>)c;
}
}