package com.tinkerpop.frames;
import java.lang.annotation.Annotation;
import java.lang.ref.SoftReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.Element;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.util.ElementHelper;
import com.tinkerpop.frames.annotations.AnnotationHandler;
import com.tinkerpop.frames.modules.MethodHandler;
/**
* The proxy class of a framed element.
*
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
public class FramedElement implements InvocationHandler {
private final Direction direction;
protected final FramedGraph framedGraph;
private final Object id;
private final boolean isVertex;
protected SoftReference<Element> elementReference;
private static Method hashCodeMethod;
private static Method equalsMethod;
private static Method toStringMethod;
private static Method asVertexMethod;
private static Method asEdgeMethod;
private static Map<MethodCallEntry, MethodHandlerEntry> methodCallCache = Collections
.synchronizedMap(new HashMap<MethodCallEntry, MethodHandlerEntry>());
static {
try {
hashCodeMethod = Object.class.getMethod("hashCode");
equalsMethod = Object.class.getMethod("equals", new Class[]{Object.class});
toStringMethod = Object.class.getMethod("toString");
asVertexMethod = VertexFrame.class.getMethod("asVertex");
asEdgeMethod = EdgeFrame.class.getMethod("asEdge");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
/**
* @deprecated The direction field will be dropped in the next major release
*/
public FramedElement(final FramedGraph framedGraph, final Element element, final Direction direction) {
if (null == framedGraph) {
throw new IllegalArgumentException("FramedGraph can not be null");
}
if (null == element) {
throw new IllegalArgumentException("Element can not be null");
}
this.id = element.getId();
this.isVertex = element instanceof Vertex;
this.elementReference = new SoftReference<Element>(element);
this.framedGraph = framedGraph;
this.direction = direction;
}
public FramedElement(final FramedGraph framedGraph, final Element element) {
this(framedGraph, element, Direction.OUT);
}
public Object invoke(final Object proxy, final Method originalMethod, final Object[] arguments) {
MethodCallEntry methodCallEntry = new MethodCallEntry(proxy.getClass(), originalMethod);
MethodHandlerEntry methodHandlerEntry = methodCallCache.get(methodCallEntry);
if (methodHandlerEntry != null)
{
// hitCount.incrementAndGet();
if (methodHandlerEntry.methodHandler != null)
return methodHandlerEntry.methodHandler.processElement(proxy, methodHandlerEntry.method, arguments, methodHandlerEntry.annotation,
this.framedGraph,
this.getElement());
else if (methodHandlerEntry.annotationHandler != null)
return methodHandlerEntry.annotationHandler.processElement(methodHandlerEntry.annotation, methodHandlerEntry.method, arguments,
this.framedGraph, this.getElement(),
this.direction);
}
Method method = null;
Class<?> methodInterface = null;
// try to find the method on one of the proxy's interfaces
// (the passed in Method is often from a superclass or from the {@link Proxy} object itself,
// so we need to make sure we find the method that the user actually intended)
for (Class<?> c : proxy.getClass().getInterfaces())
{
if (method != null && c.isAssignableFrom(methodInterface))
{
// don't search this class if we already have found a method from a subclass of it
continue;
}
for (Method interfaceMethod : c.getMethods())
{
if (compareMethods(originalMethod, interfaceMethod))
{
if (interfaceMethod.getAnnotations().length > 0)
{
method = interfaceMethod;
methodInterface = c;
}
break;
}
}
}
if (method == null)
{
method = originalMethod;
}
Annotation[] annotations = method.getAnnotations();
Map<Class<? extends Annotation>, AnnotationHandler<?>> annotationHandlers = this.framedGraph.getConfig().getAnnotationHandlers();
Map<Class<? extends Annotation>, MethodHandler<?>> methodHandlers = this.framedGraph.getConfig().getMethodHandlers();
for (final Annotation annotation : annotations)
{
MethodHandler methodHandler = methodHandlers.get(annotation.annotationType());
if (methodHandler != null)
{
methodCallCache.put(methodCallEntry, new MethodHandlerEntry(method, annotation, methodHandler));
return methodHandler.processElement(proxy, method, arguments, annotation, this.framedGraph, this.getElement());
}
}
for (final Annotation annotation : annotations)
{
AnnotationHandler annotationHandler = annotationHandlers.get(annotation.annotationType());
if (annotationHandler != null)
{
methodCallCache.put(methodCallEntry, new MethodHandlerEntry(method, annotation, annotationHandler));
return annotationHandler.processElement(annotation, method, arguments, this.framedGraph, this.getElement(), this.direction);
}
}
// Now that we have checked for annotations, check if it is one of the default methods that we
// have builtin support for
if (originalMethod.equals(hashCodeMethod)) {
return this.getElement().hashCode();
} else if (originalMethod.equals(equalsMethod)) {
return this.proxyEquals(arguments[0]);
} else if (originalMethod.equals(toStringMethod)) {
return this.getElement().toString();
} else if (originalMethod.equals(asVertexMethod) || originalMethod.equals(asEdgeMethod)) {
return this.getElement();
}
if(method.getAnnotations().length == 0) {
throw new UnhandledMethodException("The method " + method.getDeclaringClass().getName() + "." + method.getName() + " has no annotations, therefore frames cannot handle the method.");
}
throw new UnhandledMethodException("The method " + method.getDeclaringClass().getName() + "." + method.getName() + " was not annotated with any annotations that the framed graph is configured for. Please check your frame interface and/or graph configuration.");
}
/**
* Returns true if the two methods have the same arguments, return types, and method names.
*/
private boolean compareMethods(Method m1, Method m2) {
if (!m1.getName().equals(m2.getName())) {
return false;
}
if (!m1.getReturnType().equals(m2.getReturnType())) {
return false;
}
Class<?>[] params1 = m1.getParameterTypes();
Class<?>[] params2 = m2.getParameterTypes();
if (params1.length == params2.length) {
for (int i = 0; i < params1.length; i++) {
if (params1[i] != params2[i])
return false;
}
return true;
}
return false;
}
private Boolean proxyEquals(final Object other) {
if (other instanceof VertexFrame) {
return this.getElement().equals(((VertexFrame) other).asVertex());
} if (other instanceof EdgeFrame) {
return this.getElement().equals(((EdgeFrame) other).asEdge());
} else if (other instanceof Element) {
return ElementHelper.areEqual(this.getElement(), other);
} else {
return Boolean.FALSE;
}
}
public Element getElement() {
Element element = elementReference.get();
if (element == null) {
if (this.isVertex)
element = framedGraph.getBaseGraph().getVertex(this.id);
else
element = framedGraph.getBaseGraph().getEdge(this.id);
elementReference = new SoftReference<Element>(element);
}
return element;
}
private class MethodCallEntry
{
private Class clazz;
private Method method;
public MethodCallEntry(Class clazz, Method method)
{
this.clazz = clazz;
this.method = method;
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
MethodCallEntry that = (MethodCallEntry) o;
if (clazz != null ? !clazz.equals(that.clazz) : that.clazz != null)
return false;
return !(method != null ? !method.equals(that.method) : that.method != null);
}
@Override
public int hashCode()
{
int result = clazz != null ? clazz.hashCode() : 0;
result = 31 * result + (method != null ? method.hashCode() : 0);
return result;
}
}
private class MethodHandlerEntry
{
private final Method method;
private final Annotation annotation;
private MethodHandler methodHandler;
private AnnotationHandler annotationHandler;
public MethodHandlerEntry(Method method, Annotation annotation, MethodHandler<?> methodHandler)
{
this.method = method;
this.annotation = annotation;
this.methodHandler = methodHandler;
}
public MethodHandlerEntry(Method method, Annotation annotation, AnnotationHandler<?> annotationHandler)
{
this.method = method;
this.annotation = annotation;
this.annotationHandler = annotationHandler;
}
}
}