diff --git a/CHANGELOG.md b/CHANGELOG.md index d2d48d753cb21fb2bb5c6c58cc0d21accf82bb70..c5d88b80f207229b3ee363fdb242f2edf9138fa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ * 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 + `Iterator` return type. ## 3.1.0 diff --git a/src/main/java/com/syncleus/ferma/framefactories/annotation/AdjacencyMethodHandler.java b/src/main/java/com/syncleus/ferma/framefactories/annotation/AdjacencyMethodHandler.java index ab5eebf7abda70cc8070310b5cfca1103240b128..986caf9c3209072afe1149f50b176c453b84b0f8 100644 --- a/src/main/java/com/syncleus/ferma/framefactories/annotation/AdjacencyMethodHandler.java +++ b/src/main/java/com/syncleus/ferma/framefactories/annotation/AdjacencyMethodHandler.java @@ -15,6 +15,8 @@ */ package com.syncleus.ferma.framefactories.annotation; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import com.syncleus.ferma.ClassInitializer; import com.syncleus.ferma.VertexFrame; import com.syncleus.ferma.annotations.Adjacency; @@ -32,6 +34,8 @@ import org.apache.tinkerpop.gremlin.structure.Edge; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Iterator; +import java.util.List; +import java.util.Set; import java.util.function.Consumer; /** @@ -71,8 +75,12 @@ public class AdjacencyMethodHandler implements MethodHandler { throw new IllegalStateException(method.getName() + " was annotated with @Adjacency but had more than 1 arguments."); else if (ReflectionUtility.isGetMethod(method)) if (arguments == null || arguments.length == 0) { - if (Iterator.class.isAssignableFrom(method.getReturnType())) - return this.getVertexesDefault(builder, method, annotation); + if( ReflectionUtility.returnsIterator(method) ) + return this.getVertexesIteratorDefault(builder, method, annotation); + else if( ReflectionUtility.returnsList(method) ) + return this.getVertexesListDefault(builder, method, annotation); + else if( ReflectionUtility.returnsSet(method) ) + return this.getVertexesSetDefault(builder, method, annotation); return this.getVertexDefault(builder, method, annotation); } @@ -80,8 +88,12 @@ public class AdjacencyMethodHandler implements MethodHandler { if (!(Class.class.isAssignableFrom(arguments[0].getType()))) throw new IllegalStateException(method.getName() + " was annotated with @Adjacency, had a single argument, but that argument was not of the type Class"); - if (Iterator.class.isAssignableFrom(method.getReturnType())) - return this.getVertexesByType(builder, method, annotation); + if (ReflectionUtility.returnsIterator(method)) + return this.getVertexesIteratorByType(builder, method, annotation); + else if( ReflectionUtility.returnsList(method) ) + return this.getVertexesListByType(builder, method, annotation); + else if( ReflectionUtility.returnsSet(method) ) + return this.getVertexesSetByType(builder, method, annotation); return this.getVertexByType(builder, method, annotation); } @@ -109,16 +121,32 @@ public class AdjacencyMethodHandler implements MethodHandler { throw new IllegalStateException(method.getName() + " was annotated with @Adjacency but did not begin with either of the following keywords: add, get, remove"); } - private <E> DynamicType.Builder<E> getVertexesDefault(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { - return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetVertexesDefaultInterceptor.class)); + private <E> DynamicType.Builder<E> getVertexesIteratorDefault(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { + return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetVertexesIteratorDefaultInterceptor.class)); + } + + private <E> DynamicType.Builder<E> getVertexesListDefault(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { + return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetVertexesListDefaultInterceptor.class)); + } + + private <E> DynamicType.Builder<E> getVertexesSetDefault(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { + return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetVertexesSetDefaultInterceptor.class)); } private <E> DynamicType.Builder<E> getVertexDefault(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetVertexDefaultInterceptor.class)); } - private <E> DynamicType.Builder<E> getVertexesByType(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { - return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetVertexesByTypeInterceptor.class)); + private <E> DynamicType.Builder<E> getVertexesIteratorByType(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { + return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetVertexesIteratorByTypeInterceptor.class)); + } + + private <E> DynamicType.Builder<E> getVertexesListByType(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { + return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetVertexesListByTypeInterceptor.class)); + } + + private <E> DynamicType.Builder<E> getVertexesSetByType(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { + return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(GetVertexesSetByTypeInterceptor.class)); } private <E> DynamicType.Builder<E> getVertexByType(final DynamicType.Builder<E> builder, final Method method, final Annotation annotation) { @@ -153,7 +181,7 @@ public class AdjacencyMethodHandler implements MethodHandler { return builder.method(ElementMatchers.is(method)).intercept(MethodDelegation.to(RemoveVertexInterceptor.class)); } - public static final class GetVertexesDefaultInterceptor { + public static final class GetVertexesIteratorDefaultInterceptor { @RuntimeType public static Iterator getVertexes(@This final VertexFrame thiz, @Origin final Method method) { @@ -177,7 +205,55 @@ public class AdjacencyMethodHandler implements MethodHandler { } } - public static final class GetVertexesByTypeInterceptor { + public static final class GetVertexesListDefaultInterceptor { + + @RuntimeType + public static List getVertexes(@This final VertexFrame thiz, @Origin final Method method) { + assert thiz instanceof CachesReflection; + final Adjacency annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Adjacency.class); + final Direction direction = annotation.direction(); + final String label = annotation.label(); + + return Lists.newArrayList(thiz.traverse(input -> { + switch (direction) { + case IN: + return input.in(label); + case OUT: + return input.out(label); + case BOTH: + return input.both(label); + default: + throw new IllegalStateException("Direction not recognized."); + } + }).frame(VertexFrame.class)); + } + } + + public static final class GetVertexesSetDefaultInterceptor { + + @RuntimeType + public static Set getVertexes(@This final VertexFrame thiz, @Origin final Method method) { + assert thiz instanceof CachesReflection; + final Adjacency annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Adjacency.class); + final Direction direction = annotation.direction(); + final String label = annotation.label(); + + return Sets.newHashSet(thiz.traverse(input -> { + switch (direction) { + case IN: + return input.in(label); + case OUT: + return input.out(label); + case BOTH: + return input.both(label); + default: + throw new IllegalStateException("Direction not recognized."); + } + }).frame(VertexFrame.class)); + } + } + + public static final class GetVertexesIteratorByTypeInterceptor { @RuntimeType public static Iterator getVertexes(@This final VertexFrame thiz, @Origin final Method method, @RuntimeType @Argument(0) final Class type) { @@ -202,6 +278,56 @@ public class AdjacencyMethodHandler implements MethodHandler { } } + public static final class GetVertexesListByTypeInterceptor { + + @RuntimeType + public static List getVertexes(@This final VertexFrame thiz, @Origin final Method method, @RuntimeType @Argument(0) final Class type) { + assert thiz instanceof CachesReflection; + final Adjacency annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Adjacency.class); + final Direction direction = annotation.direction(); + final String label = annotation.label(); + final TypeResolver resolver = thiz.getGraph().getTypeResolver(); + + return Lists.newArrayList(thiz.traverse(input -> { + switch(direction) { + case IN: + return resolver.hasType(input.in(label), type); + case OUT: + return resolver.hasType(input.out(label), type); + case BOTH: + return resolver.hasType(input.both(label), type); + default: + throw new IllegalStateException("Direction not recognized."); + } + }).frame(type)); + } + } + + public static final class GetVertexesSetByTypeInterceptor { + + @RuntimeType + public static Set getVertexes(@This final VertexFrame thiz, @Origin final Method method, @RuntimeType @Argument(0) final Class type) { + assert thiz instanceof CachesReflection; + final Adjacency annotation = ((CachesReflection) thiz).getReflectionCache().getAnnotation(method, Adjacency.class); + final Direction direction = annotation.direction(); + final String label = annotation.label(); + final TypeResolver resolver = thiz.getGraph().getTypeResolver(); + + return Sets.newHashSet(thiz.traverse(input -> { + switch(direction) { + case IN: + return resolver.hasType(input.in(label), type); + case OUT: + return resolver.hasType(input.out(label), type); + case BOTH: + return resolver.hasType(input.both(label), type); + default: + throw new IllegalStateException("Direction not recognized."); + } + }).frame(type)); + } + } + public static final class GetVertexDefaultInterceptor { @RuntimeType diff --git a/src/main/java/com/syncleus/ferma/framefactories/annotation/ReflectionUtility.java b/src/main/java/com/syncleus/ferma/framefactories/annotation/ReflectionUtility.java index c31b4209cdaf26edf8a2eb9ce108c24cf8706cb9..8514105445fcee4202cee3563ff5604cecf9431f 100644 --- a/src/main/java/com/syncleus/ferma/framefactories/annotation/ReflectionUtility.java +++ b/src/main/java/com/syncleus/ferma/framefactories/annotation/ReflectionUtility.java @@ -22,7 +22,9 @@ import org.apache.tinkerpop.gremlin.structure.Vertex; import java.lang.reflect.*; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Set; public class ReflectionUtility { @@ -168,6 +170,14 @@ public class ReflectionUtility { return Iterator.class.isAssignableFrom(method.getReturnType()); } + public static boolean returnsList(final Method method) { + return List.class.isAssignableFrom(method.getReturnType()); + } + + public static boolean returnsSet(final Method method) { + return Set.class.isAssignableFrom(method.getReturnType()); + } + public static boolean returnsVertex(final Method method) { return Vertex.class.isAssignableFrom(method.getReturnType()); } diff --git a/src/test/java/com/syncleus/ferma/annotations/AdjacencyMethodHandlerTest.java b/src/test/java/com/syncleus/ferma/annotations/AdjacencyMethodHandlerTest.java index 0b425f1b0e6adb0c4dcf64cc8ceaa24c65749b93..09b812969047bc5a0af1bfd1b77b6b423ec27bb8 100644 --- a/src/test/java/com/syncleus/ferma/annotations/AdjacencyMethodHandlerTest.java +++ b/src/test/java/com/syncleus/ferma/annotations/AdjacencyMethodHandlerTest.java @@ -55,6 +55,54 @@ public class AdjacencyMethodHandlerTest { Assert.assertTrue(child instanceof GodExtended); } + @Test + public void testGetSonsListDefault() { + 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 God> children = father.getSonsList(); + Assert.assertFalse(children.isEmpty()); + final God child = children.get(0); + Assert.assertTrue(child != null); + final VertexFrame childVertex = child; + Assert.assertEquals(childVertex.getElement().property("name").value(), "hercules"); + Assert.assertTrue(child instanceof GodExtended); + } + + @Test + public void testGetSonsSetDefault() { + 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 God> children = father.getSonsSet(); + Assert.assertFalse(children.isEmpty()); + final God child = children.iterator().next(); + Assert.assertTrue(child != null); + final VertexFrame childVertex = child; + Assert.assertEquals(childVertex.getElement().property("name").value(), "hercules"); + Assert.assertTrue(child instanceof GodExtended); + } + @Test public void testGetSonsByType() { final TinkerGraph godGraph = TinkerGraph.open(); @@ -79,6 +127,54 @@ public class AdjacencyMethodHandlerTest { Assert.assertTrue(child instanceof GodExtended); } + @Test + public void testGetSonsListByType() { + 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 God> children = father.getSonsList(God.class); + Assert.assertFalse(children.isEmpty()); + final God child = children.get(0); + Assert.assertTrue(child != null); + final VertexFrame childVertex = child; + Assert.assertEquals(childVertex.getElement().property("name").value(), "hercules"); + Assert.assertTrue(child instanceof GodExtended); + } + + @Test + public void testGetSonsSetByType() { + 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 God> children = father.getSonsSet(God.class); + Assert.assertFalse(children.isEmpty()); + final God child = children.iterator().next(); + Assert.assertTrue(child != null); + final VertexFrame childVertex = child; + Assert.assertEquals(childVertex.getElement().property("name").value(), "hercules"); + Assert.assertTrue(child instanceof GodExtended); + } + @Test public void testObtainSonsByType() { final TinkerGraph godGraph = TinkerGraph.open(); diff --git a/src/test/java/com/syncleus/ferma/annotations/God.java b/src/test/java/com/syncleus/ferma/annotations/God.java index 480a27a5d41ab3c72d2ac80f70a286a2167402dc..809ee8ad789153986bffab4a5bc77bd96bb70722 100644 --- a/src/test/java/com/syncleus/ferma/annotations/God.java +++ b/src/test/java/com/syncleus/ferma/annotations/God.java @@ -19,6 +19,8 @@ import com.syncleus.ferma.*; import org.apache.tinkerpop.gremlin.structure.Direction; import java.util.Iterator; +import java.util.List; +import java.util.Set; @GraphElement public interface God extends VertexFrame { @@ -51,6 +53,12 @@ public interface God extends VertexFrame { @Adjacency(label = "father", direction = Direction.IN) Iterator<? extends God> getSons(); + @Adjacency(label = "father", direction = Direction.IN) + List<? extends God> getSonsList(); + + @Adjacency(label = "father", direction = Direction.IN) + Set<? extends God> getSonsSet(); + @Adjacency(label = "father", direction = Direction.IN, operation = Adjacency.Operation.GET) Iterator<? extends God> obtainSons(); @@ -60,6 +68,12 @@ public interface God extends VertexFrame { @Adjacency(label = "father", direction = Direction.IN) <N extends God> Iterator<? extends N> getSons(Class<? extends N> type); + @Adjacency(label = "father", direction = Direction.IN) + <N extends God> List<? extends N> getSonsList(Class<? extends N> type); + + @Adjacency(label = "father", direction = Direction.IN) + <N extends God> Set<? extends N> getSonsSet(Class<? extends N> type); + @Adjacency(label = "father", direction = Direction.OUT) <N extends God> Iterator<? extends N> getParents();