From 217cd1112250e921521175769836e9c906533035 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Fri, 15 Oct 2021 15:24:11 -0500 Subject: [PATCH] Test Therapi-Based Ops --- .../discovery/therapi/TherapiDiscoverer.java | 5 +- .../impl/OpCollectionInfoGenerator.java | 3 +- .../engine/impl/TagBasedOpInfoGenerator.java | 87 +++++++++++-------- .../ops/engine/matcher/impl/OpMethodInfo.java | 16 ++-- .../struct/MethodParameterMemberParser.java | 14 ++- .../ops/engine/impl/TherapiBasedOpTest.java | 81 +++++++++++++++++ 6 files changed, 155 insertions(+), 51 deletions(-) create mode 100644 scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/impl/TherapiBasedOpTest.java diff --git a/scijava/scijava-discovery-therapi/src/main/java/org/scijava/discovery/therapi/TherapiDiscoverer.java b/scijava/scijava-discovery-therapi/src/main/java/org/scijava/discovery/therapi/TherapiDiscoverer.java index e059636a5..6775c09be 100644 --- a/scijava/scijava-discovery-therapi/src/main/java/org/scijava/discovery/therapi/TherapiDiscoverer.java +++ b/scijava/scijava-discovery-therapi/src/main/java/org/scijava/discovery/therapi/TherapiDiscoverer.java @@ -56,7 +56,6 @@ public List> elementsTaggedWith(String tagType) { tagType, javadocData); List> taggedFields = discoverTaggedFields( tagType, javadocData); - // return concatenation of classes, methods, and fields. return Stream.of(taggedClasses, taggedMethods, taggedFields) // .flatMap(Collection::stream) // @@ -199,8 +198,6 @@ private static List getJarContent(String jarPath) throws IOException { private Map itemsFromTag(String tagType, String tag) { String tagBody = tag.substring(tag.indexOf(tagType) + tagType.length()).trim(); - System.out.println("Parser: " + parser); - System.out.println("Tag Body: " + tagBody); return parser.parse(tagBody.replaceAll("\\s+",""), true).asMap(); } @@ -289,7 +286,7 @@ private List> discoverTaggedClasses( Class taggedClass = getClass(e.getValue()); Optional tag = getTag.apply(e.getKey(), tagType); if (tag.isEmpty()) return null; - Supplier> tagOptions = () -> itemsFromTag(tagType, e.getValue()); + Supplier> tagOptions = () -> itemsFromTag(tagType, tag.get()); return new Discovery<>(taggedClass, tagType, tagOptions); } catch (ClassNotFoundException exc) { diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpCollectionInfoGenerator.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpCollectionInfoGenerator.java index b2eb495e4..237007790 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpCollectionInfoGenerator.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpCollectionInfoGenerator.java @@ -58,11 +58,12 @@ public List generateInfos() { final List methods = ClassUtils.getAnnotatedMethods(cls, OpMethod.class); for (final Method method: methods) { OpMethod annotation = method.getAnnotation(OpMethod.class); + Class opType = annotation.type(); String unparsedOpNames = annotation.names(); String[] parsedOpNames = OpUtils.parseOpNames(unparsedOpNames); Hints hints = formHints(method.getAnnotation(OpHints.class)); double priority = annotation.priority(); - collectionInfos.add(new OpMethodInfo(method, version, hints, priority, parsedOpNames)); + collectionInfos.add(new OpMethodInfo(method, opType, version, hints, priority, parsedOpNames)); } return collectionInfos; } catch (InstantiationException | IllegalAccessException exc) { diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/TagBasedOpInfoGenerator.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/TagBasedOpInfoGenerator.java index 208df394c..cbea29469 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/TagBasedOpInfoGenerator.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/TagBasedOpInfoGenerator.java @@ -10,10 +10,10 @@ import java.util.Objects; import java.util.stream.Collectors; +import org.scijava.Context; import org.scijava.Priority; import org.scijava.discovery.Discoverer; import org.scijava.discovery.Discovery; -import org.scijava.function.Functions; import org.scijava.log.LogService; import org.scijava.ops.api.OpInfo; import org.scijava.ops.api.OpInfoGenerator; @@ -45,51 +45,64 @@ public class TagBasedOpInfoGenerator implements OpInfoGenerator { private final LogService log; private final List discoverers; - public TagBasedOpInfoGenerator(LogService log, Discoverer... d) { + public TagBasedOpInfoGenerator(final LogService log, Discoverer... d) { this.log = log; this.discoverers = Arrays.asList(d); } - Functions.Arity3, Double, String[], OpClassInfo> opClassGenerator = // - (cls, priority, names) -> { - String version = VersionUtils.getVersion(cls); - return new OpClassInfo(cls, version, new DefaultHints(), priority, names); - }; - - Functions.Arity3 opMethodGenerator = // - (m, priority, names) -> { - String version = VersionUtils.getVersion(m.getDeclaringClass()); - return new OpMethodInfo(m, version, new DefaultHints(), names); - }; - - Functions.Arity3 opFieldGenerator = // - (f, priority, names) -> { - String version = VersionUtils.getVersion(f.getDeclaringClass()); - Object instance; - try { - instance = f.getDeclaringClass().getDeclaredConstructor().newInstance(); - } - catch (InstantiationException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException - | NoSuchMethodException | SecurityException exc) + private OpInfo opClassGenerator(Class cls, double priority, + String[] names) + { + String version = VersionUtils.getVersion(cls); + return new OpClassInfo(cls, version, new DefaultHints(), priority, names); + } + + private OpInfo opMethodGenerator(Method m, String opType, double priority, + String[] names) + { + Class cls; + try { + cls = Context.getClassLoader().loadClass(opType); + } + catch (ClassNotFoundException exc) { + log.warn("Skipping method " + m + ": Cannot load Class" + opType); + return null; + } + String version = VersionUtils.getVersion(m.getDeclaringClass()); + return new OpMethodInfo(m, cls, version, new DefaultHints(), priority, + names); + } + + private OpInfo opFieldGenerator(Field f, double priority, String[] names) { + String version = VersionUtils.getVersion(f.getDeclaringClass()); + Object instance; + try { + instance = f.getDeclaringClass().getDeclaredConstructor().newInstance(); + } + catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException exc) { - return null; - } - return new OpFieldInfo(instance, f, version, new DefaultHints(), names); - }; + return null; + } + return new OpFieldInfo(instance, f, version, new DefaultHints(), priority, + names); + } @Override public List generateInfos() { + try { List infos = discoverers.stream() // .flatMap(d -> d.elementsTaggedWith(TAGTYPE).stream()) // .map(discovery -> { // Obtain op metadata String[] names; + String opType; double priority; try { names = getOpNames(discovery); - System.out.println(names); + opType = getOpType(discovery); priority = getOpPriority(discovery); } catch (IllegalArgumentException e) { @@ -101,19 +114,23 @@ public List generateInfos() { // Delegate to proper constructor AnnotatedElement e = discovery.discovery(); if (e instanceof Class) { - return opClassGenerator.apply((Class) e, priority, names); + return opClassGenerator((Class) e, priority, names); } else if (e instanceof Method) { - return opMethodGenerator.apply((Method) e, priority, names); + return opMethodGenerator((Method) e, opType, priority, names); } else if (e instanceof Field) { - return opFieldGenerator.apply((Field) e, priority, names); + return opFieldGenerator((Field) e, priority, names); } else return null; }) // .filter(Objects::nonNull) // .collect(Collectors.toList()); return infos; + } catch(NullPointerException e) { + e.printStackTrace(); + return null; + } } private String[] getOpNames(Discovery d) { @@ -123,10 +140,8 @@ private String[] getOpNames(Discovery d) { throw new IllegalArgumentException("Op discovery " + d + " does not record any names!"); } if (!names.isEmpty()) { - System.out.println(names); return OpUtils.parseOpNames(names); } - System.out.println(name); return OpUtils.parseOpNames(name); } @@ -135,4 +150,8 @@ private static double getOpPriority(Discovery d) { return priority.isEmpty() ? Priority.NORMAL : Double.parseDouble(priority); } + private static String getOpType(Discovery d) { + return d.option("type"); + } + } diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpMethodInfo.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpMethodInfo.java index 3a982601d..42638823a 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpMethodInfo.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpMethodInfo.java @@ -89,11 +89,11 @@ public class OpMethodInfo implements OpInfo { private final Hints hints; - public OpMethodInfo(final Method method, final String version, final Hints hints, final String... names) { - this(method, version, hints, Priority.NORMAL, names); + public OpMethodInfo(final Method method, final Class opType, final String version, final Hints hints, final String... names) { + this(method, opType, version, hints, Priority.NORMAL, names); } - public OpMethodInfo(final Method method, final String version, final Hints hints, final double priority, final String... names) { + public OpMethodInfo(final Method method, final Class opType, final String version, final Hints hints, final double priority, final String... names) { this.method = method; this.version = version; this.names = Arrays.asList(names); @@ -103,10 +103,8 @@ public OpMethodInfo(final Method method, final String version, final Hints hints final List problems = new ArrayList<>(); checkModifiers(method, problems); - // determine the functional interface this Op should implement - final OpMethod methodAnnotation = method.getAnnotation(OpMethod.class); - this.opType = findOpType(method, methodAnnotation, problems); - this.struct = generateStruct(method, problems, new MethodParameterMemberParser(), new MethodOpDependencyMemberParser()); + this.opType = findOpType(method, opType, problems); + this.struct = generateStruct(method, problems, new MethodParameterMemberParser(opType), new MethodOpDependencyMemberParser()); validityException = problems.isEmpty() ? null : new ValidityException( problems); @@ -124,11 +122,11 @@ private Struct generateStruct(Method m, List problems, } } - private Type findOpType(Method m, OpMethod methodAnnotation, + private Type findOpType(Method m, Class opType, List problems) { try { - return OpMethodUtils.getOpMethodType(methodAnnotation.type(), + return OpMethodUtils.getOpMethodType(opType, method); } catch (IllegalArgumentException e) { diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/struct/MethodParameterMemberParser.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/struct/MethodParameterMemberParser.java index 9a69bdaf3..357dceb71 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/struct/MethodParameterMemberParser.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/struct/MethodParameterMemberParser.java @@ -16,6 +16,15 @@ public class MethodParameterMemberParser implements MemberParser> { + private final Class opType; + + /** + * HACK: We need the opType here to determine the functional type. + * @param opType + */ + public MethodParameterMemberParser(Class opType) { + this.opType = opType; + } @Override public List> parse(Method source) @@ -30,16 +39,15 @@ public List> parse(Method source) final ArrayList> items = new ArrayList<>(); final ArrayList problems = new ArrayList<>(); - final OpMethod methodAnnotation = source.getAnnotation(OpMethod.class); // Determine functional type Type functionalType; try { - functionalType = OpMethodUtils.getOpMethodType(methodAnnotation.type(), source); + functionalType = OpMethodUtils.getOpMethodType(opType, source); } catch (IllegalArgumentException e) { problems.add(new ValidityProblem(e.getMessage())); - functionalType = Types.parameterizeRaw(methodAnnotation.type()); + functionalType = Types.parameterizeRaw(opType); } // Parse method level @Parameter annotations. diff --git a/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/impl/TherapiBasedOpTest.java b/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/impl/TherapiBasedOpTest.java new file mode 100644 index 000000000..41b50b94b --- /dev/null +++ b/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/impl/TherapiBasedOpTest.java @@ -0,0 +1,81 @@ +package org.scijava.ops.engine.impl; + +import org.junit.Assert; +import org.junit.Test; +import org.scijava.function.Producer; +import org.scijava.ops.engine.AbstractTestEnvironment; + +public class TherapiBasedOpTest extends AbstractTestEnvironment { + + private static final String FIELD_STRING = "This OpField is discoverable using Therapi!"; + static final String CLASS_STRING = "This OpClass is discoverable using Therapi!"; + private static final String METHOD_STRING = "This OpMethod is discoverable using Therapi!"; + + /** + * @implNote op names='test.therapiOpField' + */ + public final Producer therapiFunction = () -> FIELD_STRING; + + @Test + public void therapiOpFieldTest() { + String actual = ops.op("test.therapiOpField").input().outType(String.class).create(); + String expected = FIELD_STRING; + Assert.assertEquals(expected, actual); + } + + @Test + public void therapiOpClassTest() { + String actual = ops.op("test.therapiOpClass").input().outType(String.class).create(); + String expected = CLASS_STRING; + Assert.assertEquals(expected, actual); + } + + @Test + public void therapiOpMethodTest() { + String actual = ops.op("test.therapiOpMethod").input().outType(String.class).create(); + String expected = METHOD_STRING; + Assert.assertEquals(expected, actual); + } + + /** + * @implNote op names='test.therapiOpMethod', + * type='org.scijava.function.Producer' + * @return a {@link String} + */ + public static String therapiMethod() { + return METHOD_STRING; + } + + private static final String HIGH_PRIORITY_STRING = "High Priority"; + private static final String LOW_PRIORITY_STRING = "Low Priority"; + + /** + * @implNote op names='test.therapiPriority', priority='10.0' + */ + public final Producer therapiHighPriorityFunction = () -> HIGH_PRIORITY_STRING; + + /** + * @implNote op names='test.therapiPriority', priority='1.0' + */ + public final Producer therapiLowPriorityFunction = () -> LOW_PRIORITY_STRING; + + @Test + public void therapiOpFieldPriorityTest() { + String actual = ops.op("test.therapiPriority").input().outType(String.class).create(); + String expected = HIGH_PRIORITY_STRING; + Assert.assertEquals(expected, actual); + } + +} + +/** + * @implNote op names='test.therapiOpClass' + */ +class TherapiOpClass implements Producer { + + @Override + public String create() { + return TherapiBasedOpTest.CLASS_STRING; + } + +}