diff --git a/CHANGELOG.md b/CHANGELOG.md index c5d88b80f207229b3ee363fdb242f2edf9138fa3..3a0526019c5debb8d429a265d5926311a3a06dcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,9 @@ * Added `operation` parameter to the following annotations: `@Adjacency`, `@Incidence`, `@Property`. Setting the parameter will override the auto discovery of the method prefix previously used to discovery the operation of the method. -* Methods annotated with the `Adjacency` annotation can now return a `List` or a `Set` in addition to the usual +* Methods annotated with the `@Adjacency` annotation can now return a `List` or a `Set` in addition to the usual + `Iterator` return type. +* Methods annotated with the `@Incidence` annotation can now return a `List` or a `Set` in addition to the usual `Iterator` return type. ## 3.1.0 diff --git a/src/main/java/com/syncleus/ferma/framefactories/annotation/IncidenceMethodHandler.java b/src/main/java/com/syncleus/ferma/framefactories/annotation/IncidenceMethodHandler.java index 4503ba67cde3985cfbbe7c4ea3363e7a7cbbe092..1e1fa46f565ed869b94995187c5d7bcede3ac86d 100644 --- a/src/main/java/com/syncleus/ferma/framefactories/annotation/IncidenceMethodHandler.java +++ b/src/main/java/com/syncleus/ferma/framefactories/annotation/IncidenceMethodHandler.java @@ -15,7 +15,6 @@ */ package com.syncleus.ferma.framefactories.annotation; -import java.util.function.Function; import com.syncleus.ferma.typeresolvers.TypeResolver; import com.syncleus.ferma.*; import com.syncleus.ferma.annotations.Incidence; @@ -28,14 +27,11 @@ 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.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.structure.Direction; -import org.apache.tinkerpop.gremlin.structure.Element; -import org.apache.tinkerpop.gremlin.structure.Vertex; - -import javax.annotation.Nullable; /** * A TinkerPop method handler that implemented the Incidence Annotation. @@ -73,17 +69,26 @@ public class IncidenceMethodHandler implements MethodHandler { 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 (Iterator.class.isAssignableFrom(method.getReturnType())) - return this.getEdgesDefault(builder, method, annotation); - else - return this.getEdgeDefault(builder, method, annotation); + 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 (Iterator.class.isAssignableFrom(method.getReturnType())) - return this.getEdgesByType(builder, method, annotation); + 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); } @@ -120,12 +125,28 @@ public class IncidenceMethodHandler implements MethodHandler { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(AddEdgeByObjectTypedEdgeInterceptor.class)); } - private <E> DynamicType.Builder<E> getEdgesDefault(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { - return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetEdgesDefaultInterceptor.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> getEdgesByType(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { - return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetEdgesByTypeInterceptor.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) { @@ -263,7 +284,7 @@ public class IncidenceMethodHandler implements MethodHandler { } } - public static final class GetEdgesDefaultInterceptor { + public static final class GetEdgesIteratorDefaultInterceptor { @RuntimeType public static Iterator getEdges(@This final VertexFrame thiz, @Origin final Method method) { @@ -285,7 +306,51 @@ public class IncidenceMethodHandler implements MethodHandler { } } - public static final class GetEdgesByTypeInterceptor { + 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) { @@ -308,6 +373,52 @@ public class IncidenceMethodHandler implements MethodHandler { } } + 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 diff --git a/src/test/java/com/syncleus/ferma/annotations/God.java b/src/test/java/com/syncleus/ferma/annotations/God.java index 809ee8ad789153986bffab4a5bc77bd96bb70722..b986f6215db1923c396769722addd447f674335e 100644 --- a/src/test/java/com/syncleus/ferma/annotations/God.java +++ b/src/test/java/com/syncleus/ferma/annotations/God.java @@ -113,9 +113,21 @@ public interface God extends VertexFrame { @Incidence(label = "father", direction = Direction.IN) Iterator<? extends EdgeFrame> getSonEdges(); + @Incidence(label = "father", direction = Direction.IN) + List<? extends EdgeFrame> getSonEdgesList(); + + @Incidence(label = "father", direction = Direction.IN) + Set<? extends EdgeFrame> getSonEdgesSet(); + @Incidence(label = "father", direction = Direction.IN) <N extends FatherEdge> Iterator<? extends N> getSonEdges(Class<? extends N> type); + @Incidence(label = "father", direction = Direction.IN) + <N extends FatherEdge> List<? extends N> getSonEdgesList(Class<? extends N> type); + + @Incidence(label = "father", direction = Direction.IN) + <N extends FatherEdge> Set<? extends N> getSonEdgesSet(Class<? extends N> type); + @Incidence(label = "father", direction = Direction.IN) EdgeFrame getSonEdge(); diff --git a/src/test/java/com/syncleus/ferma/annotations/IncidenceMethodHandlerTest.java b/src/test/java/com/syncleus/ferma/annotations/IncidenceMethodHandlerTest.java index ba7558ce2b287e5f15405b82ff950857e5ffe6bb..88905570190551f9bc2b7a88fc2ee09e611922d1 100644 --- a/src/test/java/com/syncleus/ferma/annotations/IncidenceMethodHandlerTest.java +++ b/src/test/java/com/syncleus/ferma/annotations/IncidenceMethodHandlerTest.java @@ -55,6 +55,48 @@ public class IncidenceMethodHandlerTest { Assert.assertEquals(childEdge.getElement().outVertex().property("name").value(), "hercules"); } + @Test + public void testGetSonEdgesListDefault() { + final TinkerGraph godGraph = TinkerGraph.open(); + GodGraphLoader.load(godGraph); + + final FramedGraph framedGraph = new DelegatingFramedGraph(godGraph, TEST_TYPES); + + final List<? extends God> gods = framedGraph.traverse( + input -> input.V().has("name", "jupiter")).toList(God.class); + + final God father = gods.iterator().next(); + Assert.assertTrue(father != null); + final VertexFrame fatherVertex = father; + Assert.assertEquals(fatherVertex.getProperty("name"), "jupiter"); + + final List<? extends EdgeFrame> edgesList = father.getSonEdgesList(); + Assert.assertFalse(edgesList.isEmpty()); + final EdgeFrame childEdge = edgesList.get(0); + Assert.assertEquals(childEdge.getElement().outVertex().property("name").value(), "hercules"); + } + + @Test + public void testGetSonEdgesSetDefault() { + final TinkerGraph godGraph = TinkerGraph.open(); + GodGraphLoader.load(godGraph); + + final FramedGraph framedGraph = new DelegatingFramedGraph(godGraph, TEST_TYPES); + + final List<? extends God> gods = framedGraph.traverse( + input -> input.V().has("name", "jupiter")).toList(God.class); + + final God father = gods.iterator().next(); + Assert.assertTrue(father != null); + final VertexFrame fatherVertex = father; + Assert.assertEquals(fatherVertex.getProperty("name"), "jupiter"); + + final Set<? extends EdgeFrame> sonEdgesSet = father.getSonEdgesSet(); + Assert.assertFalse(sonEdgesSet.isEmpty()); + final EdgeFrame childEdge = sonEdgesSet.iterator().next(); + Assert.assertEquals(childEdge.getElement().outVertex().property("name").value(), "hercules"); + } + @Test public void testGetSonEdgesByType() { final TinkerGraph godGraph = TinkerGraph.open(); @@ -78,6 +120,52 @@ public class IncidenceMethodHandlerTest { Assert.assertEquals(edge.getElement().outVertex().property("name").value(), "hercules"); } + @Test + public void testGetSonEdgesListByType() { + final TinkerGraph godGraph = TinkerGraph.open(); + GodGraphLoader.load(godGraph); + + final FramedGraph framedGraph = new DelegatingFramedGraph(godGraph, TEST_TYPES); + + final List<? extends God> gods = framedGraph.traverse( + input -> input.V().has("name", "jupiter")).toList(God.class); + + final God father = gods.iterator().next(); + Assert.assertTrue(father != null); + final VertexFrame fatherVertex = father; + Assert.assertEquals(fatherVertex.getProperty("name"), "jupiter"); + + final List<? extends FatherEdge> childEdges = father.getSonEdgesList(FatherEdge.class); + Assert.assertFalse(childEdges.isEmpty()); + final FatherEdge childEdge = childEdges.get(0); + Assert.assertTrue(childEdge != null); + final EdgeFrame edge = childEdge; + Assert.assertEquals(edge.getElement().outVertex().property("name").value(), "hercules"); + } + + @Test + public void testGetSonEdgesSetByType() { + final TinkerGraph godGraph = TinkerGraph.open(); + GodGraphLoader.load(godGraph); + + final FramedGraph framedGraph = new DelegatingFramedGraph(godGraph, TEST_TYPES); + + final List<? extends God> gods = framedGraph.traverse( + input -> input.V().has("name", "jupiter")).toList(God.class); + + final God father = gods.iterator().next(); + Assert.assertTrue(father != null); + final VertexFrame fatherVertex = father; + Assert.assertEquals(fatherVertex.getProperty("name"), "jupiter"); + + final Set<? extends FatherEdge> childEdges = father.getSonEdgesSet(FatherEdge.class); + Assert.assertFalse(childEdges.isEmpty()); + final FatherEdge childEdge = childEdges.iterator().next(); + Assert.assertTrue(childEdge != null); + final EdgeFrame edge = childEdge; + Assert.assertEquals(edge.getElement().outVertex().property("name").value(), "hercules"); + } + @Test public void testObtainSonEdgesByType() { final TinkerGraph godGraph = TinkerGraph.open();