package com.tinkerpop.frames.modules.javahandler;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Element;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.frames.EdgeFrame;
import com.tinkerpop.frames.FrameInitializer;
import com.tinkerpop.frames.FramedGraph;
import com.tinkerpop.frames.VertexFrame;
import com.tinkerpop.frames.util.ExceptionUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.ExecutionException;
/**
* Calls the methods annotated with {@link Initializer} on frame
* implementations.
*
* @author Bryn Cooke
*
* Implementation made public and fixed because it was completely broken
* before, framing the passed Element
* to execute the method then ignoring the resulting framed object
*
*/
public class JavaFrameInitializer implements FrameInitializer {
private JavaHandlerModule module;
/**
* Caches lists of InitializerMethods for a given frame class. See "doLoad"
* method below.
*/
private LoadingCache<Class<?>, List<InitializerMethod>> initializerCache = CacheBuilder.newBuilder().build(
new CacheLoader<Class<?>, List<InitializerMethod>>() {
@Override
public List<InitializerMethod> load(final Class<?> frameClass) throws Exception {
return doLoad(frameClass);
}
});
public JavaFrameInitializer(JavaHandlerModule module) {
this.module = module;
}
/**
* Captures the work of calling an "@Initializer" annotated method.
*/
private class InitializerMethod {
private final Class<?> h;
private final Method method;
private InitializerMethod(Class<?> h, Method method) {
this.h = h;
this.method = method;
}
void execute(Object framedElement, FramedGraph<?> framedGraph, Element element)
throws InvocationTargetException, IllegalAccessException {
Object handler = module.createHandler(framedElement, framedGraph, element, h, method);
method.invoke(handler);
}
}
public void initElement(Class<?> kind, FramedGraph<?> framedGraph, Object framedElement) {
try {
for (InitializerMethod method : initializerCache.get(kind)) {
try {
if (framedElement instanceof VertexFrame) {
method.execute(framedElement, framedGraph, ((VertexFrame) framedElement).asVertex());
} else {
method.execute(framedElement, framedGraph, ((EdgeFrame) framedElement).asEdge());
}
} catch (IllegalArgumentException e) {
throw new JavaHandlerException("Problem calling Java handler", e);
} catch (IllegalAccessException e) {
throw new JavaHandlerException("Problem calling Java handler", e);
} catch (InvocationTargetException e) {
ExceptionUtils.sneakyThrow(e.getTargetException());
}
}
} catch (ExecutionException e) {
throw new JavaHandlerException("Problem calling Java handler", e);
}
}
@Override
public void initElement(Class<?> kind, FramedGraph<?> framedGraph, Element element) {
Object framedElement;
if (element instanceof Vertex) {
framedElement = framedGraph.frame((Vertex) element, kind);
} else {
framedElement = framedGraph.frame((Edge) element, kind);
}
try {
for (InitializerMethod method : initializerCache.get(kind)) {
try {
method.execute(framedElement, framedGraph, element);
} catch (IllegalArgumentException e) {
throw new JavaHandlerException("Problem calling Java handler", e);
} catch (IllegalAccessException e) {
throw new JavaHandlerException("Problem calling Java handler", e);
} catch (InvocationTargetException e) {
ExceptionUtils.sneakyThrow(e.getTargetException());
}
}
} catch (ExecutionException e) {
throw new JavaHandlerException("Problem calling Java handler", e);
}
}
/**
* Finds all the relevant @Initializer methods for the given class.
*/
private List<InitializerMethod> doLoad(Class<?> kind) {
// We have to order this correctly. Dependencies should be initialised
// first so we first recursively collect an an array of classes to call
// and then reverse the array before putting them in a linked hash set.
// That way the classes discovered last will be called first.
List<Class<?>> classes = new ArrayList<Class<?>>();
depthFirstClassSearch(classes, kind);
Collections.reverse(classes);
LinkedHashSet<Class<?>> hierarchy = new LinkedHashSet<Class<?>>(classes);
List<InitializerMethod> methods = Lists.newArrayList();
// Now we can store InitializerMethod objects for each method call.
for (Class<?> h : hierarchy) {
try {
try {
Class<?> implKind = module.getHandlerClass(h);
for (Method method : implKind.getDeclaredMethods()) {
if (method.isAnnotationPresent(Initializer.class)) {
if (method.getParameterTypes().length != 0) {
throw new JavaHandlerException("Java handler initializer " + method
+ "cannot have parameters");
}
methods.add(new InitializerMethod(h, method));
}
}
} catch (ClassNotFoundException e) {
// There was no impl class to check
}
} catch (IllegalArgumentException e) {
throw new JavaHandlerException("Problem calling Java handler", e);
}
}
return methods;
}
private void depthFirstClassSearch(List<Class<?>> initializers, Class<?> kind) {
if (kind == null || kind == Object.class) {
return;
}
initializers.add(kind);
for (Class<?> i : kind.getInterfaces()) {
depthFirstClassSearch(initializers, i);
}
depthFirstClassSearch(initializers, kind.getSuperclass());
}
}