/** * Copyright 2004 - 2017 Syncleus, Inc. * * 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 com.syncleus.ferma.framefactories.annotation; import com.syncleus.ferma.typeresolvers.TypeResolver; import com.syncleus.ferma.*; import com.syncleus.ferma.annotations.Incidence; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.bind.annotation.Argument; import net.bytebuddy.implementation.bind.annotation.Origin; import net.bytebuddy.implementation.bind.annotation.RuntimeType; import net.bytebuddy.implementation.bind.annotation.This; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Iterator; import java.util.List; import java.util.Set; import net.bytebuddy.matcher.ElementMatchers; import org.apache.tinkerpop.gremlin.structure.Direction; /** * A TinkerPop method handler that implemented the Incidence Annotation. * * @since 2.0.0 */ public class IncidenceMethodHandler extends AbstractMethodHandler { @Override public Class<Incidence> getAnnotationType() { return Incidence.class; } @Override public <E> DynamicType.Builder<E> processMethod(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { final java.lang.reflect.Parameter[] arguments = method.getParameters(); if (ReflectionUtility.isAddMethod(method)) if (arguments == null || arguments.length == 0) return this.addEdgeDefault(builder, method, annotation); else if (arguments.length == 1) if (ClassInitializer.class.isAssignableFrom(arguments[0].getType())) return this.addEdgeByTypeUntypedEdge(builder, method, annotation); else return this.addEdgeByObjectUntypedEdge(builder, method, annotation); else if (arguments.length == 2) { if (!(ClassInitializer.class.isAssignableFrom(arguments[1].getType()))) throw new IllegalStateException(method.getName() + " was annotated with @Incidence, had two arguments, but the second argument was not of the type ClassInitializer"); if (ClassInitializer.class.isAssignableFrom(arguments[0].getType())) return this.addEdgeByTypeTypedEdge(builder, method, annotation); else return this.addEdgeByObjectTypedEdge(builder, method, annotation); } else throw new IllegalStateException(method.getName() + " was annotated with @Incidence but had more than 1 arguments."); if (ReflectionUtility.isGetMethod(method)) if (arguments == null || arguments.length == 0) { if (ReflectionUtility.returnsIterator(method)) return this.getEdgesIteratorDefault(builder, method, annotation); else if (ReflectionUtility.returnsList(method)) return this.getEdgesListDefault(builder, method, annotation); else if (ReflectionUtility.returnsSet(method)) return this.getEdgesSetDefault(builder, method, annotation); return this.getEdgeDefault(builder, method, annotation); } else if (arguments.length == 1) { if (!(Class.class.isAssignableFrom(arguments[0].getType()))) throw new IllegalStateException(method.getName() + " was annotated with @Incidence, had a single argument, but that argument was not of the type Class"); if (ReflectionUtility.returnsIterator(method)) return this.getEdgesIteratorByType(builder, method, annotation); else if (ReflectionUtility.returnsList(method)) return this.getEdgesListByType(builder, method, annotation); else if (ReflectionUtility.returnsSet(method)) return this.getEdgesSetByType(builder, method, annotation); return this.getEdgeByType(builder, method, annotation); } else throw new IllegalStateException(method.getName() + " was annotated with @Incidence but had more than 1 arguments."); else if (ReflectionUtility.isRemoveMethod(method)) if (arguments == null || arguments.length == 0) throw new IllegalStateException(method.getName() + " was annotated with @Incidence but had no arguments."); else if (arguments.length == 1) return this.removeEdge(builder, method, annotation); else throw new IllegalStateException(method.getName() + " was annotated with @Incidence but had more than 1 arguments."); else throw new IllegalStateException(method.getName() + " was annotated with @Incidence but did not begin with: get, remove"); } private <E> DynamicType.Builder<E> addEdgeDefault(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(AddEdgeDefaultInterceptor.class)); } private <E> DynamicType.Builder<E> addEdgeByTypeUntypedEdge(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(AddEdgeByTypeUntypedEdgeInterceptor.class)); } private <E> DynamicType.Builder<E> addEdgeByObjectUntypedEdge(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(AddEdgeByObjectUntypedEdgeInterceptor.class)); } private <E> DynamicType.Builder<E> addEdgeByTypeTypedEdge(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(AddEdgeByTypeTypedEdgeInterceptor.class)); } private <E> DynamicType.Builder<E> addEdgeByObjectTypedEdge(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(AddEdgeByObjectTypedEdgeInterceptor.class)); } private <E> DynamicType.Builder<E> getEdgesIteratorDefault(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetEdgesIteratorDefaultInterceptor.class)); } private <E> DynamicType.Builder<E> getEdgesListDefault(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetEdgesListDefaultInterceptor.class)); } private <E> DynamicType.Builder<E> getEdgesSetDefault(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetEdgesSetDefaultInterceptor.class)); } private <E> DynamicType.Builder<E> getEdgesIteratorByType(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetEdgesIteratorByTypeInterceptor.class)); } private <E> DynamicType.Builder<E> getEdgesListByType(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetEdgesListByTypeInterceptor.class)); } private <E> DynamicType.Builder<E> getEdgesSetByType(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetEdgesSetByTypeInterceptor.class)); } private <E> DynamicType.Builder<E> getEdgeDefault(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetEdgeDefaultInterceptor.class)); } private <E> DynamicType.Builder<E> getEdgeByType(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetEdgeByTypeInterceptor.class)); } private <E> DynamicType.Builder<E> removeEdge(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(RemoveEdgeInterceptor.class)); } public static final class AddEdgeDefaultInterceptor { @RuntimeType public static Object addEdge(@This final VertexFrame thiz, @Origin final Method method) { final VertexFrame newVertex = thiz.getGraph().addFramedVertex(); assert thiz instanceof CachesReflection; final Incidence annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Incidence.class); final Direction direction = annotation.direction(); final String label = annotation.label(); switch (direction) { case BOTH: throw new IllegalStateException(method.getName() + " is annotated with direction BOTH, this is not allowed for add methods annotated with @Incidence."); case IN: return thiz.getGraph().addFramedEdge(newVertex, thiz, label); case OUT: return thiz.getGraph().addFramedEdge(thiz, newVertex, label); default: throw new IllegalStateException(method.getName() + " is annotated with a direction other than BOTH, IN, or OUT."); } } } public static final class AddEdgeByTypeUntypedEdgeInterceptor { @RuntimeType public static Object addVertex(@This final VertexFrame thiz, @Origin final Method method, @RuntimeType @Argument(value = 0) final ClassInitializer vertexType) { final Object newNode = thiz.getGraph().addFramedVertex(vertexType); assert newNode instanceof VertexFrame; final VertexFrame newVertex = ((VertexFrame) newNode); assert thiz instanceof CachesReflection; final Incidence annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Incidence.class); final Direction direction = annotation.direction(); final String label = annotation.label(); assert vertexType.getInitializationType().isInstance(newNode); switch (direction) { case BOTH: throw new IllegalStateException(method.getName() + " is annotated with direction BOTH, this is not allowed for add methods annotated with @Incidence."); case IN: return thiz.getGraph().addFramedEdge(newVertex, thiz, label); case OUT: return thiz.getGraph().addFramedEdge(thiz, newVertex, label); default: throw new IllegalStateException(method.getName() + " is annotated with a direction other than BOTH, IN, or OUT."); } } } public static final class AddEdgeByTypeTypedEdgeInterceptor { @RuntimeType public static Object addVertex(@This final VertexFrame thiz, @Origin final Method method, @RuntimeType @Argument(0) final ClassInitializer vertexType, @RuntimeType @Argument(1) final ClassInitializer edgeType) { final Object newNode = thiz.getGraph().addFramedVertex(vertexType); assert newNode instanceof VertexFrame; final VertexFrame newVertex = ((VertexFrame) newNode); assert thiz instanceof CachesReflection; final Incidence annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Incidence.class); final Direction direction = annotation.direction(); final String label = annotation.label(); assert vertexType.getInitializationType().isInstance(newNode); switch (direction) { case BOTH: throw new IllegalStateException(method.getName() + " is annotated with direction BOTH, this is not allowed for add methods annotated with @Incidence."); case IN: return thiz.getGraph().addFramedEdge(newVertex, thiz, label, edgeType); case OUT: return thiz.getGraph().addFramedEdge(thiz, newVertex, label, edgeType); default: throw new IllegalStateException(method.getName() + " is annotated with a direction other than BOTH, IN, or OUT."); } } } public static final class AddEdgeByObjectUntypedEdgeInterceptor { @RuntimeType public static Object addVertex(@This final VertexFrame thiz, @Origin final Method method, @RuntimeType @Argument(0) final VertexFrame newVertex) { assert thiz instanceof CachesReflection; final Incidence annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Incidence.class); final Direction direction = annotation.direction(); final String label = annotation.label(); switch (direction) { case BOTH: throw new IllegalStateException(method.getName() + " is annotated with direction BOTH, this is not allowed for add methods annotated with @Incidence."); case IN: return thiz.getGraph().addFramedEdge(newVertex, thiz, label); case OUT: return thiz.getGraph().addFramedEdge(thiz, newVertex, label); default: throw new IllegalStateException(method.getName() + " is annotated with a direction other than BOTH, IN, or OUT."); } } } public static final class AddEdgeByObjectTypedEdgeInterceptor { @RuntimeType public static Object addVertex(@This final VertexFrame thiz, @Origin final Method method, @RuntimeType @Argument(0) final VertexFrame newVertex, @RuntimeType @Argument(1) final ClassInitializer edgeType) { assert thiz instanceof CachesReflection; final Incidence annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Incidence.class); final Direction direction = annotation.direction(); final String label = annotation.label(); switch (direction) { case BOTH: throw new IllegalStateException(method.getName() + " is annotated with direction BOTH, this is not allowed for add methods annotated with @Incidence."); case IN: return thiz.getGraph().addFramedEdge(newVertex, thiz, label, edgeType); case OUT: return thiz.getGraph().addFramedEdge(thiz, newVertex, label, edgeType); default: throw new IllegalStateException(method.getName() + " is annotated with a direction other than BOTH, IN, or OUT."); } } } public static final class GetEdgesIteratorDefaultInterceptor { @RuntimeType public static Iterator getEdges(@This final VertexFrame thiz, @Origin final Method method) { assert thiz instanceof CachesReflection; final Incidence annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Incidence.class); final Direction direction = annotation.direction(); final String label = annotation.label(); switch (direction) { case BOTH: return thiz.traverse(input -> input.bothE(label)).frame(VertexFrame.class); case IN: return thiz.traverse(input -> input.inE(label)).frame(VertexFrame.class); case OUT: return thiz.traverse(input -> input.outE(label)).frame(VertexFrame.class); default: throw new IllegalStateException(method.getName() + " is annotated with a direction other than BOTH, IN, or OUT."); } } } public static final class GetEdgesListDefaultInterceptor { @RuntimeType public static List getEdges(@This final VertexFrame thiz, @Origin final Method method) { assert thiz instanceof CachesReflection; final Incidence annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Incidence.class); final Direction direction = annotation.direction(); final String label = annotation.label(); switch (direction) { case BOTH: return thiz.traverse(input -> input.bothE(label)).toList(VertexFrame.class); case IN: return thiz.traverse(input -> input.inE(label)).toList(VertexFrame.class); case OUT: return thiz.traverse(input -> input.outE(label)).toList(VertexFrame.class); default: throw new IllegalStateException(method.getName() + " is annotated with a direction other than BOTH, IN, or OUT."); } } } public static final class GetEdgesSetDefaultInterceptor { @RuntimeType public static Set getEdges(@This final VertexFrame thiz, @Origin final Method method) { assert thiz instanceof CachesReflection; final Incidence annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Incidence.class); final Direction direction = annotation.direction(); final String label = annotation.label(); switch (direction) { case BOTH: return thiz.traverse(input -> input.bothE(label)).toSet(VertexFrame.class); case IN: return thiz.traverse(input -> input.inE(label)).toSet(VertexFrame.class); case OUT: return thiz.traverse(input -> input.outE(label)).toSet(VertexFrame.class); default: throw new IllegalStateException(method.getName() + " is annotated with a direction other than BOTH, IN, or OUT."); } } } public static final class GetEdgesIteratorByTypeInterceptor { @RuntimeType public static Iterator getEdges(@This final VertexFrame thiz, @Origin final Method method, @RuntimeType @Argument(0) final Class type) { assert thiz instanceof CachesReflection; final Incidence annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Incidence.class); final Direction direction = annotation.direction(); final String label = annotation.label(); final TypeResolver resolver = thiz.getGraph().getTypeResolver(); switch (direction) { case BOTH: return thiz.traverse(input -> resolver.hasType(input.bothE(label), type)).frame(type); case IN: return thiz.traverse(input -> resolver.hasType(input.inE(label), type)).frame(type); case OUT: return thiz.traverse(input -> resolver.hasType(input.outE(label), type)).frame(type); default: throw new IllegalStateException(method.getName() + " is annotated with a direction other than BOTH, IN, or OUT."); } } } public static final class GetEdgesListByTypeInterceptor { @RuntimeType public static List getEdges(@This final VertexFrame thiz, @Origin final Method method, @RuntimeType @Argument(0) final Class type) { assert thiz instanceof CachesReflection; final Incidence annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Incidence.class); final Direction direction = annotation.direction(); final String label = annotation.label(); final TypeResolver resolver = thiz.getGraph().getTypeResolver(); switch (direction) { case BOTH: return thiz.traverse(input -> resolver.hasType(input.bothE(label), type)).toList(type); case IN: return thiz.traverse(input -> resolver.hasType(input.inE(label), type)).toList(type); case OUT: return thiz.traverse(input -> resolver.hasType(input.outE(label), type)).toList(type); default: throw new IllegalStateException(method.getName() + " is annotated with a direction other than BOTH, IN, or OUT."); } } } public static final class GetEdgesSetByTypeInterceptor { @RuntimeType public static Set getEdges(@This final VertexFrame thiz, @Origin final Method method, @RuntimeType @Argument(0) final Class type) { assert thiz instanceof CachesReflection; final Incidence annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Incidence.class); final Direction direction = annotation.direction(); final String label = annotation.label(); final TypeResolver resolver = thiz.getGraph().getTypeResolver(); switch (direction) { case BOTH: return thiz.traverse(input -> resolver.hasType(input.bothE(label), type)).toSet(type); case IN: return thiz.traverse(input -> resolver.hasType(input.inE(label), type)).toSet(type); case OUT: return thiz.traverse(input -> resolver.hasType(input.outE(label), type)).toSet(type); default: throw new IllegalStateException(method.getName() + " is annotated with a direction other than BOTH, IN, or OUT."); } } } public static final class GetEdgeDefaultInterceptor { @RuntimeType public static Object getEdges(@This final VertexFrame thiz, @Origin final Method method) { assert thiz instanceof CachesReflection; final Incidence annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Incidence.class); final Direction direction = annotation.direction(); final String label = annotation.label(); switch (direction) { case BOTH: return thiz.traverse(input -> input.bothE(label)).next(VertexFrame.class); case IN: return thiz.traverse(input -> input.inE(label)).next(VertexFrame.class); case OUT: return thiz.traverse(input -> input.outE(label)).next(VertexFrame.class); default: throw new IllegalStateException(method.getName() + " is annotated with a direction other than BOTH, IN, or OUT."); } } } public static final class GetEdgeByTypeInterceptor { @RuntimeType public static Object getEdge(@This final VertexFrame thiz, @Origin final Method method, @RuntimeType @Argument(0) final Class type) { assert thiz instanceof CachesReflection; final Incidence annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Incidence.class); final Direction direction = annotation.direction(); final String label = annotation.label(); final TypeResolver resolver = thiz.getGraph().getTypeResolver(); switch (direction) { case BOTH: return thiz.traverse(input -> resolver.hasType(input.bothE(label), type)).next(type); case IN: return thiz.traverse(input -> resolver.hasType(input.inE(label), type)).next(type); case OUT: return thiz.traverse(input -> resolver.hasType(input.outE(label), type)).next(type); default: throw new IllegalStateException(method.getName() + " is annotated with a direction other than BOTH, IN, or OUT."); } } } public static final class RemoveEdgeInterceptor { @RuntimeType public static void removeEdge(@This final VertexFrame thiz, @Origin final Method method, @RuntimeType @Argument(0) final EdgeFrame edge) { edge.remove(); } } }