From 4c7278ce09e56171a80dcb214ded8da0958013b3 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Thu, 2 Sep 2021 13:34:51 -0500 Subject: [PATCH 1/3] Move MatchingConditions/MatchingRoutine to API --- .../scijava/ops/api/features}/MatchingConditions.java | 2 +- .../org/scijava/ops/api/features}/MatchingResult.java | 11 +++++------ .../org/scijava/ops/api/features/MatchingRoutine.java | 8 ++++++++ .../scijava/ops/engine/impl/DefaultOpEnvironment.java | 1 + .../org/scijava/ops/engine/matcher/OpMatcher.java | 2 +- .../ops/engine/matcher/impl/DefaultOpMatcher.java | 2 +- .../ops/engine/struct/SynthesizedParameterMember.java | 6 ------ .../org/scijava/ops/engine/impl/OpCachingTest.java | 1 + 8 files changed, 18 insertions(+), 15 deletions(-) rename scijava/{scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl => scijava-ops-api/src/main/java/org/scijava/ops/api/features}/MatchingConditions.java (91%) rename scijava/{scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher => scijava-ops-api/src/main/java/org/scijava/ops/api/features}/MatchingResult.java (92%) create mode 100644 scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingRoutine.java diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/MatchingConditions.java b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingConditions.java similarity index 91% rename from scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/MatchingConditions.java rename to scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingConditions.java index 10dc1fe6c..9680540e8 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/MatchingConditions.java +++ b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingConditions.java @@ -1,5 +1,5 @@ -package org.scijava.ops.engine.impl; +package org.scijava.ops.api.features; import java.util.Objects; diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/MatchingResult.java b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingResult.java similarity index 92% rename from scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/MatchingResult.java rename to scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingResult.java index 1a8fb4548..f8443594c 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/MatchingResult.java +++ b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingResult.java @@ -1,4 +1,4 @@ -package org.scijava.ops.engine.matcher; +package org.scijava.ops.api.features; import java.util.ArrayList; import java.util.List; @@ -7,13 +7,12 @@ import org.scijava.ops.api.OpRef; import org.scijava.ops.api.OpUtils; import org.scijava.ops.api.OpCandidate.StatusCode; -import org.scijava.ops.api.features.OpMatchingException; /** - * Class representing the result from type matching done by the - * {@link OpMatcher}. Contains the original candidates which match - * the types specified by {@link OpRef} and the final matches that match all - * inputs, outputs, and arguments. + * Class representing the result from type matching done by a + * {@link MatchingRoutine}. Contains the original candidates which match the + * types specified by {@link OpRef} and the final matches that match all inputs, + * outputs, and arguments. * * @author David Kolb */ diff --git a/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingRoutine.java b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingRoutine.java new file mode 100644 index 000000000..69f8192d9 --- /dev/null +++ b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingRoutine.java @@ -0,0 +1,8 @@ +package org.scijava.ops.api.features; + + +public interface MatchingRoutine { + + MatchingResult match(MatchingConditions conditions); + +} diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java index 3d49cb6bb..245021380 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java @@ -65,6 +65,7 @@ import org.scijava.ops.api.OpWrapper; import org.scijava.ops.api.RichOp; import org.scijava.ops.api.features.DependencyMatchingException; +import org.scijava.ops.api.features.MatchingConditions; import org.scijava.ops.api.features.OpMatchingException; import org.scijava.ops.api.features.BaseOpHints.Adaptation; import org.scijava.ops.api.features.BaseOpHints.DependencyMatching; diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/OpMatcher.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/OpMatcher.java index e937b4622..9fc8cdcd4 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/OpMatcher.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/OpMatcher.java @@ -35,7 +35,7 @@ import org.scijava.ops.api.OpCandidate; import org.scijava.ops.api.OpEnvironment; import org.scijava.ops.api.OpRef; -import org.scijava.ops.engine.matcher.MatchingResult; +import org.scijava.ops.api.features.MatchingResult; /** * Finds Ops which match an {@link OpRef}. diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMatcher.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMatcher.java index 7719c6e67..9bbd4f752 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMatcher.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMatcher.java @@ -50,9 +50,9 @@ import org.scijava.ops.api.OpRef; import org.scijava.ops.api.OpUtils; import org.scijava.ops.api.OpCandidate.StatusCode; +import org.scijava.ops.api.features.MatchingResult; import org.scijava.ops.api.features.BaseOpHints.Simplification; import org.scijava.ops.engine.hint.DefaultHints; -import org.scijava.ops.engine.matcher.MatchingResult; import org.scijava.ops.engine.matcher.OpMatcher; import org.scijava.ops.engine.simplify.InfoSimplificationGenerator; import org.scijava.service.AbstractService; diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/struct/SynthesizedParameterMember.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/struct/SynthesizedParameterMember.java index 7aa227b26..99d5d1f76 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/struct/SynthesizedParameterMember.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/struct/SynthesizedParameterMember.java @@ -37,12 +37,6 @@ public SynthesizedParameterMember(final FunctionalMethodType fmt, final Producer this.descriptionGenerator = () -> synthesizerGenerator.create().description(fmt); } - public SynthesizedParameterMember(final Type itemType, final String name, - final String description, final ItemIO ioType) - { - throw new UnsupportedOperationException(); - } - // -- Member methods -- @Override diff --git a/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/impl/OpCachingTest.java b/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/impl/OpCachingTest.java index d5fd0ae16..540fb13e7 100644 --- a/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/impl/OpCachingTest.java +++ b/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/impl/OpCachingTest.java @@ -42,6 +42,7 @@ import org.scijava.function.Producer; import org.scijava.ops.api.OpEnvironment; import org.scijava.ops.api.OpInstance; +import org.scijava.ops.api.features.MatchingConditions; import org.scijava.ops.engine.AbstractTestEnvironment; import org.scijava.ops.engine.OpService; import org.scijava.ops.spi.Op; From f6dba31507fd69444ee6a90187e6b06fd81ed19f Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Thu, 2 Sep 2021 16:03:54 -0500 Subject: [PATCH 2/3] Clean up Matcher/MatchingRoutines --- .../org/scijava/ops/api/OpEnvironment.java | 2 + .../ops/api/features/MatchingRoutine.java | 56 ++- .../scijava/ops/api/features}/OpMatcher.java | 28 +- .../src/main/java/module-info.java | 3 +- .../ops/engine/impl/DefaultOpEnvironment.java | 293 ++++++++------- .../ops/engine/impl/ManualOpCandidate.java | 2 +- .../impl/AdaptationMatchingRoutine.java | 233 ++++++++++++ .../engine/matcher/impl/DefaultOpMatcher.java | 308 +++++----------- .../ops/engine/matcher/impl/DefaultOpRef.java | 2 +- .../ops/engine/matcher/impl/OpClassInfo.java | 2 +- .../impl/RuntimeSafeMatchingRoutine.java | 338 ++++++++++++++++++ .../impl/SimplificationMatchingRoutine.java | 57 +++ .../matcher/DefaultMatchingErrorTest.java | 7 +- 13 files changed, 950 insertions(+), 381 deletions(-) rename scijava/{scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher => scijava-ops-api/src/main/java/org/scijava/ops/api/features}/OpMatcher.java (65%) create mode 100644 scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/AdaptationMatchingRoutine.java create mode 100644 scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/RuntimeSafeMatchingRoutine.java create mode 100644 scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/SimplificationMatchingRoutine.java diff --git a/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/OpEnvironment.java b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/OpEnvironment.java index cb9b94dda..8590c77ea 100644 --- a/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/OpEnvironment.java +++ b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/OpEnvironment.java @@ -144,6 +144,8 @@ InfoChain infoChain(final String opName, final Nil specialType, InfoChain chainFromInfo(final OpInfo info, final Nil specialType); + InfoChain chainFromInfo(final OpInfo info, final Nil specialType, final Hints hints); + default OpBuilder op(final String opName) { return new OpBuilder(this, opName); } diff --git a/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingRoutine.java b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingRoutine.java index 69f8192d9..6c7000909 100644 --- a/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingRoutine.java +++ b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/MatchingRoutine.java @@ -1,8 +1,60 @@ + package org.scijava.ops.api.features; +import org.scijava.ops.api.OpCandidate; +import org.scijava.ops.api.OpEnvironment; +import org.scijava.plugin.Plugin; +import org.scijava.plugin.SciJavaPlugin; + +/** + * A plugin type employing a particular strategy to generate an + * {@link OpCandidate}. + * + * @author Gabriel Selzer + */ +public interface MatchingRoutine extends SciJavaPlugin, + Comparable +{ + + void checkSuitability(MatchingConditions conditions) + throws OpMatchingException; + + @Override + default int compareTo(MatchingRoutine o) { + return (int) (priority() - o.priority()); + } + + OpCandidate findMatch(MatchingConditions conditions, OpMatcher matcher, + OpEnvironment env) throws OpMatchingException; -public interface MatchingRoutine { + /** + * Generates an {@link OpCandidate} from the Ops in the provided + * {@link OpEnvironment}, conforming to the provided + * {@link MatchingConditions} + * + * @param conditions the {@link MatchingConditions} the returned Op must + * conform to + * @param matcher the {@link OpMatcher} responsible for matching + * @param env the {@link OpEnvironment} containing the Ops able to be matched + * @return an {@OpCandidate} + */ + default OpCandidate match(MatchingConditions conditions, OpMatcher matcher, + OpEnvironment env) + { + checkSuitability(conditions); + return findMatch(conditions, matcher, env); + } - MatchingResult match(MatchingConditions conditions); + /** + * Since {@link MatchingRoutine}s should be {@link Plugin}s, we should be able + * to scrape the priority off of the plugin annotation. + * + * @return the priority + */ + default double priority() { + Plugin annotation = this.getClass().getAnnotation(Plugin.class); + if (annotation == null) return 0.; + return annotation.priority(); + } } diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/OpMatcher.java b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/OpMatcher.java similarity index 65% rename from scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/OpMatcher.java rename to scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/OpMatcher.java index 9fc8cdcd4..2d7a17dab 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/OpMatcher.java +++ b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/OpMatcher.java @@ -27,15 +27,11 @@ * #L% */ -package org.scijava.ops.engine.matcher; +package org.scijava.ops.api.features; -import java.util.List; - -import org.scijava.ops.api.Hints; import org.scijava.ops.api.OpCandidate; import org.scijava.ops.api.OpEnvironment; import org.scijava.ops.api.OpRef; -import org.scijava.ops.api.features.MatchingResult; /** * Finds Ops which match an {@link OpRef}. @@ -45,27 +41,7 @@ //TODO javadoc public interface OpMatcher { - OpCandidate findSingleMatch(OpEnvironment env, OpRef ref); - - OpCandidate findSingleMatch(OpEnvironment env, OpRef ref, Hints hints); - - MatchingResult findMatch(OpEnvironment env, OpRef ref); - - MatchingResult findMatch(OpEnvironment env, OpRef ref, Hints hints); - - MatchingResult findMatch(OpEnvironment env, List refs); - - MatchingResult findMatch(OpEnvironment env, List refs, Hints hints); - - List findCandidates(OpEnvironment env, OpRef ref); - - List findCandidates(OpEnvironment env, OpRef ref, Hints hints); - - List findCandidates(OpEnvironment env, List refs); - - List findCandidates(OpEnvironment env, List refs, Hints hints); - - List filterMatches(List candidates); + OpCandidate match(MatchingConditions conditions, OpEnvironment env); boolean typesMatch(OpCandidate candidate); } diff --git a/scijava/scijava-ops-engine/src/main/java/module-info.java b/scijava/scijava-ops-engine/src/main/java/module-info.java index 7b796c6f1..4ca198d1c 100644 --- a/scijava/scijava-ops-engine/src/main/java/module-info.java +++ b/scijava/scijava-ops-engine/src/main/java/module-info.java @@ -18,7 +18,6 @@ opens org.scijava.ops.engine.conversionLoss to therapi.runtime.javadoc; opens org.scijava.ops.engine.copy to therapi.runtime.javadoc; opens org.scijava.ops.engine.log to therapi.runtime.javadoc; - opens org.scijava.ops.engine.matcher to therapi.runtime.javadoc; opens org.scijava.ops.engine.simplify to therapi.runtime.javadoc; opens org.scijava.ops.engine.impl to therapi.runtime.javadoc, org.scijava; opens org.scijava.ops.engine.conversionLoss.impl to therapi.runtime.javadoc, org.scijava; @@ -31,6 +30,8 @@ opens org.scijava.ops.engine.util to therapi.runtime.javadoc; opens org.scijava.ops.engine.math to therapi.runtime.javadoc; + requires java.desktop; + requires org.scijava; requires org.scijava.discovery; requires org.scijava.function; diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java index 245021380..6452bcf9d 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java @@ -29,6 +29,7 @@ package org.scijava.ops.engine.impl; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; @@ -64,15 +65,16 @@ import org.scijava.ops.api.OpRef; import org.scijava.ops.api.OpWrapper; import org.scijava.ops.api.RichOp; -import org.scijava.ops.api.features.DependencyMatchingException; -import org.scijava.ops.api.features.MatchingConditions; -import org.scijava.ops.api.features.OpMatchingException; import org.scijava.ops.api.features.BaseOpHints.Adaptation; import org.scijava.ops.api.features.BaseOpHints.DependencyMatching; import org.scijava.ops.api.features.BaseOpHints.History; import org.scijava.ops.api.features.BaseOpHints.Simplification; +import org.scijava.ops.api.features.DependencyMatchingException; +import org.scijava.ops.api.features.MatchingConditions; +import org.scijava.ops.api.features.MatchingRoutine; +import org.scijava.ops.api.features.OpMatcher; +import org.scijava.ops.api.features.OpMatchingException; import org.scijava.ops.engine.hint.DefaultHints; -import org.scijava.ops.engine.matcher.OpMatcher; import org.scijava.ops.engine.matcher.impl.DefaultOpMatcher; import org.scijava.ops.engine.matcher.impl.DefaultOpRef; import org.scijava.ops.engine.matcher.impl.InfoMatchingOpRef; @@ -152,28 +154,56 @@ public class DefaultOpEnvironment implements OpEnvironment { */ private Hints environmentHints = null; - public DefaultOpEnvironment(final TypeService typeService, final LogService log, final OpHistory history, final List infoGenerators, final List d) { + public DefaultOpEnvironment(final TypeService typeService, + final LogService log, final OpHistory history, + final List infoGenerators, final List d) + { this.discoverers = d; this.typeService = typeService; this.log = log; this.history = history; this.infoGenerators = infoGenerators; - matcher = new DefaultOpMatcher(); + matcher = new DefaultOpMatcher(getMatchingRoutines(this.discoverers)); } - public DefaultOpEnvironment(final TypeService typeService, final LogService log, final OpHistory history, final List infoGenerators, final Discoverer... d) { + public DefaultOpEnvironment(final TypeService typeService, + final LogService log, final OpHistory history, + final List infoGenerators, final Discoverer... d) + { this.discoverers = Arrays.asList(d); this.typeService = typeService; this.log = log; this.history = history; this.infoGenerators = infoGenerators; - matcher = new DefaultOpMatcher(); + matcher = new DefaultOpMatcher(getMatchingRoutines(this.discoverers)); + } + + public static List getMatchingRoutines( + final List discoverers) + { + List matchers = new ArrayList<>(); + for (Discoverer d : discoverers) { + List> implementingClasses = d.implementingClasses( + MatchingRoutine.class); + List routines = implementingClasses.parallelStream().map( + c -> { + try { + return c.getConstructor().newInstance(); + } + catch (Exception e) { + return null; + } + }).filter(r -> r != null).collect(Collectors.toList()); + matchers.addAll(routines); + } + return matchers; } @Override public Set infos() { if (opDirectory == null) initOpDirectory(); - return opDirectory.values().stream().flatMap(list -> list.stream()).collect(Collectors.toSet()); + return opDirectory.values().stream().flatMap(list -> list.stream()).collect( + Collectors.toSet()); } @Override @@ -196,14 +226,16 @@ public Set infos(String name, Hints hints) { private Set filterInfos(Set infos, Hints hints) { boolean adapting = hints.contains(Adaptation.IN_PROGRESS); boolean simplifying = hints.contains(Simplification.IN_PROGRESS); - // if we aren't doing any + // if we aren't doing any if (!(adapting || simplifying)) return infos; return infos.parallelStream() // - // filter out unadaptable ops - .filter(info -> !adapting || !info.declaredHints().contains(Adaptation.FORBIDDEN)) // - // filter out unadaptable ops - .filter(info -> !simplifying || !info.declaredHints().contains(Simplification.FORBIDDEN)) // - .collect(Collectors.toSet()); + // filter out unadaptable ops + .filter(info -> !adapting || !info.declaredHints().contains( + Adaptation.FORBIDDEN)) // + // filter out unadaptable ops + .filter(info -> !simplifying || !info.declaredHints().contains( + Simplification.FORBIDDEN)) // + .collect(Collectors.toSet()); } @Override @@ -212,7 +244,7 @@ public T op(final String opName, final Nil specialType, { return op(opName, specialType, inTypes, outType, getDefaultHints()); } - + @Override public T op(final String opName, final Nil specialType, final Nil[] inTypes, final Nil outType, Hints hints) @@ -233,7 +265,8 @@ public InfoChain infoChain(String opName, Nil specialType, { try { return findOp(opName, specialType, inTypes, outType, hints).infoChain(); - } catch (OpMatchingException e) { + } + catch (OpMatchingException e) { throw new IllegalArgumentException(e); } } @@ -244,21 +277,30 @@ public InfoChain chainFromInfo(OpInfo info, Nil specialType) { } @Override - public T opFromSignature(final String signature, final Nil specialType) { + public InfoChain chainFromInfo(OpInfo info, Nil specialType, Hints hints) { + return findOp(info, specialType, hints).infoChain(); + } + + @Override + public T opFromSignature(final String signature, + final Nil specialType) + { InfoChain info = chainFromID(signature); return opFromInfoChain(info, specialType); } @Override - public T opFromInfoChain(final InfoChain chain, final Nil specialType) { - if (!(specialType.getType() instanceof ParameterizedType)) + public T opFromInfoChain(final InfoChain chain, + final Nil specialType) + { + if (!(specialType.getType() instanceof ParameterizedType)) throw new IllegalArgumentException("TODO"); @SuppressWarnings("unchecked") OpInstance instance = (OpInstance) chain.op(specialType.getType()); Hints hints = getDefaultHints(); RichOp wrappedOp = wrapOp(instance, hints); return wrappedOp.asOpType(); - + } @Override @@ -288,8 +330,11 @@ public OpInfo opify(final Class opClass) { } @Override - public OpInfo opify(final Class opClass, final double priority, final String... names) { - return new OpClassInfo(opClass, VersionUtils.getVersion(opClass), priority, names); + public OpInfo opify(final Class opClass, final double priority, + final String... names) + { + return new OpClassInfo(opClass, VersionUtils.getVersion(opClass), priority, + names); } @Override @@ -299,16 +344,18 @@ public void register(final OpInfo info) { } @SuppressWarnings("unchecked") - private RichOp findOp(final String opName, final Nil specialType, final Nil[] inTypes, - final Nil outType, Hints hints) { - final OpRef ref = DefaultOpRef.fromTypes(opName, specialType.getType(), outType != null ? outType.getType() : null, - toTypes(inTypes)); + private RichOp findOp(final String opName, final Nil specialType, + final Nil[] inTypes, final Nil outType, Hints hints) + { + final OpRef ref = DefaultOpRef.fromTypes(opName, specialType.getType(), + outType != null ? outType.getType() : null, toTypes(inTypes)); MatchingConditions conditions = generateCacheHit(ref, hints); return (RichOp) wrapViaCache(conditions); } @SuppressWarnings("unchecked") - private OpInstance findOp(final OpInfo info, final Nil specialType, Hints hints) throws OpMatchingException + private OpInstance findOp(final OpInfo info, final Nil specialType, + Hints hints) throws OpMatchingException { OpRef ref = new InfoMatchingOpRef(info, specialType); MatchingConditions conditions = insertCacheHit(ref, hints, info); @@ -316,14 +363,16 @@ private OpInstance findOp(final OpInfo info, final Nil specialType, Hi } @SuppressWarnings("unchecked") - private RichOp findRichOp(final OpInfo info, final Nil specialType, Hints hints) throws OpMatchingException + private RichOp findRichOp(final OpInfo info, final Nil specialType, + Hints hints) throws OpMatchingException { OpInstance instance = findOp(info, specialType, hints); return (RichOp) wrap(instance, hints); } private Type[] toTypes(Nil... nils) { - return Arrays.stream(nils).filter(n -> n != null).map(n -> n.getType()).toArray(Type[]::new); + return Arrays.stream(nils).filter(n -> n != null).map(n -> n.getType()) + .toArray(Type[]::new); } /** @@ -340,28 +389,28 @@ private Type[] toTypes(Nil... nils) { * {@code info} from the op cache * @throws OpMatchingException */ - private MatchingConditions insertCacheHit (final OpRef ref, final Hints hints, final OpInfo info) { + private MatchingConditions insertCacheHit(final OpRef ref, final Hints hints, + final OpInfo info) + { MatchingConditions conditions = MatchingConditions.from(ref, hints); // create new OpCandidate from ref and info - OpCandidate candidate = new ManualOpCandidate(this, ref, info, this.matcher); + OpCandidate candidate = new ManualOpCandidate(this, ref, info, + this.matcher); instantiateAndCache(conditions, candidate); return conditions; } - - private RichOp wrapViaCache(MatchingConditions conditions) - { + + private RichOp wrapViaCache(MatchingConditions conditions) { OpInstance instance = getInstance(conditions); return wrap(instance, conditions.hints()); } - private RichOp wrap( - OpInstance instance, Hints hints) - { + private RichOp wrap(OpInstance instance, Hints hints) { RichOp wrappedOp = wrapOp(instance, hints); - return wrappedOp; + return wrappedOp; } /** @@ -377,15 +426,15 @@ private RichOp wrap( * @return the {@link MatchingConditions} that will return the Op found from * the op cache */ - private MatchingConditions generateCacheHit(OpRef ref, Hints hints) - { + private MatchingConditions generateCacheHit(OpRef ref, Hints hints) { MatchingConditions conditions = MatchingConditions.from(ref, hints); // see if the ref has been matched already OpInstance cachedOp = getInstance(conditions); if (cachedOp != null) return conditions; // obtain suitable OpCandidate - OpCandidate candidate = findOpCandidate(conditions.ref(), conditions.hints()); + OpCandidate candidate = findOpCandidate(conditions.ref(), conditions + .hints()); instantiateAndCache(conditions, candidate); @@ -397,7 +446,7 @@ private void instantiateAndCache(MatchingConditions conditions, { // obtain Op instance (with dependencies) OpInstance op = instantiateOp(candidate, conditions.hints()); - + // cache instance cacheOp(conditions, op); } @@ -412,46 +461,9 @@ private OpInstance getInstance(MatchingConditions conditions) { } return opCache.get(conditions); } - - private OpCandidate findOpCandidate(OpRef ref, Hints hints) { - try { - // attempt to find a direct match - return matcher.findSingleMatch(this, ref, hints); - } - catch (OpMatchingException e1) { - // no direct match; find an adapted match - try { - if (hints.containsNone(Adaptation.IN_PROGRESS, Adaptation.FORBIDDEN)) // - return adaptOp(ref, hints); - throw new OpMatchingException("No matching Op for request: " + ref + - "\n(adaptation is disabled)", e1); - } - catch (OpMatchingException e2) { - try { - if (hints.containsNone(Simplification.IN_PROGRESS, Simplification.FORBIDDEN)) // - return findSimplifiedOp(ref, hints); - throw new OpMatchingException("No matching Op for request: " + ref + - "\n(simplification is disabled)", e1); - } - catch (OpMatchingException e3) { - // NB: It is important that the adaptation and simplification errors be - // suppressed here. If a failure occurs in Op matching, it - // is not the fault of our adaptation/simplification process but is - // instead due to the incongruity between the request and the set of - // available Ops. Thus the error stemming from the direct match - // attempt will provide the user with more information on how to fix - // their Op request. - e1.addSuppressed(e2); - e1.addSuppressed(e3); - throw e1; - } - } - } - } - private OpCandidate findSimplifiedOp(OpRef ref, Hints hints) { - Hints simplificationHints = hints.plus(Simplification.IN_PROGRESS); - return matcher.findSingleMatch(this, ref, simplificationHints); + private OpCandidate findOpCandidate(OpRef ref, Hints hints) { + return matcher.match(MatchingConditions.from(ref, hints), this); } /** @@ -461,10 +473,12 @@ private OpCandidate findSimplifiedOp(OpRef ref, Hints hints) { * @param candidate * @return an Op with all needed dependencies */ - private OpInstance instantiateOp(final OpCandidate candidate, Hints hints) + private OpInstance instantiateOp(final OpCandidate candidate, + Hints hints) { final List> conditions = resolveOpDependencies(candidate, hints); - final List depChains = conditions.stream().map(op -> op.metadata().info()).collect(Collectors.toList()); + final List depChains = conditions.stream().map(op -> op + .metadata().info()).collect(Collectors.toList()); final InfoChain chain = new InfoChain(candidate.opInfo(), depChains); return chain.op(); } @@ -474,27 +488,35 @@ private OpInstance instantiateOp(final OpCandidate candidate, Hints hints) * * @param instance - the {@link OpInstance} to wrap. * @param hints - the {@link Hints} used to create the {@link OpInstance} - * * @return an {@link Op} wrapping of op. */ @SuppressWarnings("unchecked") - private RichOp wrapOp(OpInstance instance, Hints hints) throws IllegalArgumentException { - if (wrappers == null) - initWrappers(); + private RichOp wrapOp(OpInstance instance, Hints hints) + throws IllegalArgumentException + { + if (wrappers == null) initWrappers(); try { // find the opWrapper that wraps this type of Op - Class wrapper = getWrapperClass(instance.op(), instance.infoChain().info()); - // obtain the generic type of the Op w.r.t. the Wrapper class - Type reifiedSuperType = Types.getExactSuperType(instance.getType(), wrapper); - OpMetadata metadata = new OpMetadata(reifiedSuperType, instance.infoChain(), hints, history); + Class wrapper = getWrapperClass(instance.op(), instance.infoChain() + .info()); + // obtain the generic type of the Op w.r.t. the Wrapper class + Type reifiedSuperType = Types.getExactSuperType(instance.getType(), + wrapper); + OpMetadata metadata = new OpMetadata(reifiedSuperType, instance + .infoChain(), hints, history); // wrap the Op - final OpWrapper opWrapper = (OpWrapper) wrappers.get(Types.raw(reifiedSuperType)); + final OpWrapper opWrapper = (OpWrapper) wrappers.get(Types.raw( + reifiedSuperType)); return opWrapper.wrap(instance, metadata); - } catch (IllegalArgumentException | SecurityException exc) { - throw new IllegalArgumentException(exc.getMessage() != null ? exc.getMessage() : "Cannot wrap " + instance.op().getClass()); - } catch (NullPointerException e) { - throw new IllegalArgumentException("No wrapper exists for " + Types.raw(instance.getType()).toString() + "."); + } + catch (IllegalArgumentException | SecurityException exc) { + throw new IllegalArgumentException(exc.getMessage() != null ? exc + .getMessage() : "Cannot wrap " + instance.op().getClass()); + } + catch (NullPointerException e) { + throw new IllegalArgumentException("No wrapper exists for " + Types.raw( + instance.getType()).toString() + "."); } } @@ -531,8 +553,11 @@ private List> filterWrapperSuperclasses( return list; } - private List> resolveOpDependencies(OpCandidate candidate, Hints hints) { - return resolveOpDependencies(candidate.opInfo(), candidate.typeVarAssigns(), hints); + private List> resolveOpDependencies(OpCandidate candidate, + Hints hints) + { + return resolveOpDependencies(candidate.opInfo(), candidate.typeVarAssigns(), + hints); } @SuppressWarnings("rawtypes") @@ -540,15 +565,13 @@ private synchronized void initWrappers() { if (wrappers != null) return; wrappers = new HashMap<>(); for (Discoverer d : discoverers) - for (Class cls : d.implementingClasses(OpWrapper.class)) - { + for (Class cls : d.implementingClasses(OpWrapper.class)) { OpWrapper wrapper; try { wrapper = cls.getDeclaredConstructor().newInstance(); wrappers.put(wrapper.type(), wrapper); } - catch (Throwable t) - { + catch (Throwable t) { log.warn("OpWrapper " + cls + " not instantiated. Due to " + t); } } @@ -558,15 +581,15 @@ private synchronized void initInfoChainGenerators() { if (infoChainGenerators != null) return; Set generators = new HashSet<>(); for (Discoverer d : discoverers) - for (Class cls : d.implementingClasses(InfoChainGenerator.class)) + for (Class cls : d.implementingClasses( + InfoChainGenerator.class)) { InfoChainGenerator wrapper; try { wrapper = cls.getDeclaredConstructor().newInstance(); generators.add(wrapper); } - catch (Throwable t) - { + catch (Throwable t) { log.warn("OpWrapper " + cls + " not instantiated. Due to " + t); } } @@ -583,7 +606,9 @@ private synchronized void initInfoChainGenerators() { * @param typeVarAssigns - the mapping of {@link TypeVariable}s in the * {@code OpInfo} to {@link Type}s given in the request. */ - private List> resolveOpDependencies(OpInfo info, Map, Type> typeVarAssigns, Hints hints) { + private List> resolveOpDependencies(OpInfo info, + Map, Type> typeVarAssigns, Hints hints) + { final List> dependencies = info.dependencies(); final List> dependencyChains = new ArrayList<>(); @@ -594,18 +619,20 @@ private List> resolveOpDependencies(OpInfo info, Map, // TODO: Consider a new Hint implementation Hints hintsCopy = hints.plus(DependencyMatching.IN_PROGRESS, History.SKIP_RECORDING, Simplification.FORBIDDEN); - if(!dependency.isAdaptable()) { + if (!dependency.isAdaptable()) { hintsCopy = hintsCopy.plus(Adaptation.FORBIDDEN); } - MatchingConditions conditions = generateCacheHit(dependencyRef, hintsCopy); + MatchingConditions conditions = generateCacheHit(dependencyRef, + hintsCopy); dependencyChains.add(wrapViaCache(conditions)); } catch (final OpMatchingException e) { String message = DependencyMatchingException.message(info .implementationName(), dependency.getKey(), dependencyRef); if (e instanceof DependencyMatchingException) { - throw new DependencyMatchingException(message, (DependencyMatchingException) e); + throw new DependencyMatchingException(message, + (DependencyMatchingException) e); } throw new DependencyMatchingException(message); } @@ -628,8 +655,8 @@ private List> resolveOpDependencies(OpInfo info, Map, * * * @param ref - the type of Op that we are looking to adapt to. - * @return {@link OpCandidate} - an Op that has been adapted to conform - * the the ref type (if one exists). + * @return {@link OpCandidate} - an Op that has been adapted to conform the + * the ref type (if one exists). */ private OpCandidate adaptOp(OpRef ref, Hints hints) { @@ -645,15 +672,16 @@ private OpCandidate adaptOp(OpRef ref, Hints hints) { continue; } - if(adaptor instanceof SimplifiedOpInfo) { + if (adaptor instanceof SimplifiedOpInfo) { log.debug(adaptor + " has been simplified. This is likely a typo."); } try { // resolve adaptor dependencies - final List> dependencies = resolveOpDependencies(adaptor, - map, hints); - InfoChain adaptorChain = new DependencyInstantiatedInfoChain(adaptor, dependencies); + final List> dependencies = resolveOpDependencies(adaptor, map, + hints); + InfoChain adaptorChain = new DependencyInstantiatedInfoChain(adaptor, + dependencies); // grab the first type parameter from the OpInfo and search for // an Op that will then be adapted (this will be the only input of the @@ -661,13 +689,15 @@ private OpCandidate adaptOp(OpRef ref, Hints hints) { Type srcOpType = Types.substituteTypeVariables(adaptor.inputs().get(0) .getType(), map); final OpRef srcOpRef = inferOpRef(srcOpType, ref.getName(), map); - final OpCandidate srcCandidate = findAdaptationCandidate(srcOpRef, hints); + final OpCandidate srcCandidate = findAdaptationCandidate(srcOpRef, + hints); map.putAll(srcCandidate.typeVarAssigns()); Type adapterOpType = Types.substituteTypeVariables(adaptor.output() .getType(), map); OpAdaptationInfo adaptedInfo = new OpAdaptationInfo(srcCandidate .opInfo(), adapterOpType, adaptorChain); - OpCandidate adaptedCandidate = new OpCandidate(this, ref, adaptedInfo, map); + OpCandidate adaptedCandidate = new OpCandidate(this, ref, adaptedInfo, + map); adaptedCandidate.setStatus(StatusCode.MATCH); return adaptedCandidate; } @@ -679,7 +709,7 @@ private OpCandidate adaptOp(OpRef ref, Hints hints) { } } - //no adaptors available. + // no adaptors available. if (depExceptions.size() == 0) { throw new OpMatchingException( "Op adaptation failed: no adaptable Ops of type " + ref.getName()); @@ -691,14 +721,17 @@ private OpCandidate adaptOp(OpRef ref, Hints hints) { throw new DependencyMatchingException(sb.toString()); } - private OpCandidate findAdaptationCandidate(final OpRef srcOpRef, final Hints hints) + private OpCandidate findAdaptationCandidate(final OpRef srcOpRef, + final Hints hints) { Hints adaptationHints = hints.plus(Adaptation.IN_PROGRESS); final OpCandidate srcCandidate = findOpCandidate(srcOpRef, adaptationHints); return srcCandidate; } - private boolean adaptOpOutputSatisfiesRefTypes(Type adaptTo, Map, Type> map, OpRef ref) { + private boolean adaptOpOutputSatisfiesRefTypes(Type adaptTo, + Map, Type> map, OpRef ref) + { Type opType = ref.getType(); // TODO: clean this logic -- can this just be ref.typesMatch() ? if (opType instanceof ParameterizedType) { @@ -803,24 +836,26 @@ private synchronized void initIdDirectory() { private final Consumer addToOpIndex = (final OpInfo opInfo) -> { if (opInfo.names() == null || opInfo.names().size() == 0) { - log.error("Skipping Op " + opInfo.implementationName() + ":\n" + "Op implementation must provide name."); + log.error("Skipping Op " + opInfo.implementationName() + ":\n" + + "Op implementation must provide name."); return; } if (!opInfo.isValid()) { - log.error("Skipping invalid Op " + opInfo.implementationName() + ":\n" - + opInfo.getValidityException().getMessage()); + log.error("Skipping invalid Op " + opInfo.implementationName() + ":\n" + + opInfo.getValidityException().getMessage()); return; } for (String opName : opInfo.names()) { - if (!opDirectory.containsKey(opName)) - opDirectory.put(opName, new TreeSet<>()); + if (!opDirectory.containsKey(opName)) opDirectory.put(opName, + new TreeSet<>()); boolean success = opDirectory.get(opName).add(opInfo); - if(!success) System.out.println("Did not add OpInfo "+ opInfo); + if (!success) System.out.println("Did not add OpInfo " + opInfo); } }; private Set opsOfName(final String name) { - final Set ops = opDirectory.getOrDefault(name, Collections.emptySet()); + final Set ops = opDirectory.getOrDefault(name, Collections + .emptySet()); return Collections.unmodifiableSet(ops); } @@ -841,7 +876,7 @@ public void setDefaultHints(Hints hints) { @Override public Hints getDefaultHints() { - if(this.environmentHints != null) return this.environmentHints.copy(); + if (this.environmentHints != null) return this.environmentHints.copy(); return new DefaultHints(); } diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/ManualOpCandidate.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/ManualOpCandidate.java index 2c0d67568..5fede5343 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/ManualOpCandidate.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/ManualOpCandidate.java @@ -10,7 +10,7 @@ import org.scijava.ops.api.OpEnvironment; import org.scijava.ops.api.OpInfo; import org.scijava.ops.api.OpRef; -import org.scijava.ops.engine.matcher.OpMatcher; +import org.scijava.ops.api.features.OpMatcher; public class ManualOpCandidate extends OpCandidate { diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/AdaptationMatchingRoutine.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/AdaptationMatchingRoutine.java new file mode 100644 index 000000000..ed579347d --- /dev/null +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/AdaptationMatchingRoutine.java @@ -0,0 +1,233 @@ + +package org.scijava.ops.engine.matcher.impl; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.scijava.Priority; +import org.scijava.ops.api.Hints; +import org.scijava.ops.api.InfoChain; +import org.scijava.ops.api.OpCandidate; +import org.scijava.ops.api.OpDependencyMember; +import org.scijava.ops.api.OpCandidate.StatusCode; +import org.scijava.ops.api.OpEnvironment; +import org.scijava.ops.api.OpInfo; +import org.scijava.ops.api.OpRef; +import org.scijava.ops.api.RichOp; +import org.scijava.ops.api.features.BaseOpHints.Adaptation; +import org.scijava.ops.api.features.DependencyMatchingException; +import org.scijava.ops.api.features.MatchingConditions; +import org.scijava.ops.api.features.MatchingRoutine; +import org.scijava.ops.api.features.OpMatcher; +import org.scijava.ops.api.features.OpMatchingException; +import org.scijava.ops.engine.impl.DependencyInstantiatedInfoChain; +import org.scijava.ops.engine.simplify.SimplifiedOpInfo; +import org.scijava.ops.engine.struct.FunctionalParameters; +import org.scijava.plugin.Plugin; +import org.scijava.struct.FunctionalMethodType; +import org.scijava.struct.ItemIO; +import org.scijava.types.Nil; +import org.scijava.types.Types; +import org.scijava.types.inference.GenericAssignability; + +@Plugin(type = MatchingRoutine.class, priority = Priority.LOW) +public class AdaptationMatchingRoutine implements MatchingRoutine { + + @Override + public void checkSuitability(MatchingConditions conditions) + throws OpMatchingException + { + if (conditions.hints().containsAny(Adaptation.IN_PROGRESS, + Adaptation.FORBIDDEN)) // + throw new OpMatchingException( + "Adaptation is not suitable: Adaptation is disabled"); + } + + /** + * Adapts an Op with the name of ref into a type that can be SAFELY cast to + * ref. + *

+ * NB This method cannot use the {@link OpMatcher} to find a suitable + * {@code adapt} Op. The premise of adaptation depends on the ability to + * examine the applicability of all {@code adapt} Ops with the correct output + * type. We need to check all of them because we do not know whether: + *

    + *
  • The dependencies will exist for a particular {@code adapt} Op + *
  • The Op we want exists with the correct type for the input of the + * {@code adapt} Op. + *
+ * + * @param conditions the {@link MatchingConditions} the return must satisfy + * @param matcher the {@link OpMatcher} performing the matching + * @param env the {@link OpEnvironment} containing matchable Ops + * @return an {@link OpCandidate} describing the match + * @throws OpMatchingException when no match can be found + */ + @Override + public OpCandidate findMatch(MatchingConditions conditions, OpMatcher matcher, + OpEnvironment env) throws OpMatchingException + { + Hints adaptationHints = conditions.hints().plus(Adaptation.IN_PROGRESS); + List depExceptions = new ArrayList<>(); + for (final OpInfo adaptor : env.infos("adapt")) { + Type adaptTo = adaptor.output().getType(); + Map, Type> map = new HashMap<>(); + // make sure that the adaptor outputs the correct type + if (!adaptOpOutputSatisfiesRefTypes(adaptTo, map, conditions.ref())) + continue; + // make sure that the adaptor is a Function (so we can cast it later) + if (Types.isInstance(adaptor.opType(), Function.class)) { +// log.debug(adaptor + " is an illegal adaptor Op: must be a Function"); + continue; + } + + if (adaptor instanceof SimplifiedOpInfo) { +// log.debug(adaptor + " has been simplified. This is likely a typo."); + } + + try { + // resolve adaptor dependencies +// Type reifiedAdaptTo = Types.substituteTypeVariables(adaptor.output().getType(), map); +// Type reifiedAdaptFrom = Types.substituteTypeVariables(adaptor.inputs().get(0).getType(), map); +// Type reifiedAdaptorType = Types.parameterize(Function.class, new Type[] {reifiedAdaptFrom, reifiedAdaptTo}); +// +// InfoChain adaptorChain = env.chainFromInfo(adaptor, Nil.of( +// reifiedAdaptorType, conditions.hints())); + List depChains = adaptor.dependencies().stream().map(d -> inferOpRef(d, map)).map(ref -> { + Nil type = Nil.of(ref.getType()); + Nil[] args = Arrays.stream(ref.getArgs()).map(t -> Nil.of(t)).toArray(Nil[]::new); + Nil outType = Nil.of(ref.getOutType()); + return env.infoChain(ref.getName(), type, args, outType, adaptationHints); + }).collect(Collectors.toList()); + InfoChain adaptorChain = new InfoChain(adaptor, + depChains); + + // grab the first type parameter from the OpInfo and search for + // an Op that will then be adapted (this will be the only input of the + // adaptor since we know it is a Function) + Type srcOpType = Types.substituteTypeVariables(adaptor.inputs().get(0) + .getType(), map); + final OpRef srcOpRef = inferOpRef(srcOpType, conditions.ref().getName(), + map); + final OpCandidate srcCandidate = matcher.match(MatchingConditions.from( + srcOpRef, adaptationHints), env); + map.putAll(srcCandidate.typeVarAssigns()); + Type adapterOpType = Types.substituteTypeVariables(adaptor.output() + .getType(), map); + OpAdaptationInfo adaptedInfo = new OpAdaptationInfo(srcCandidate + .opInfo(), adapterOpType, adaptorChain); + OpCandidate adaptedCandidate = new OpCandidate(env, conditions.ref(), + adaptedInfo, map); + adaptedCandidate.setStatus(StatusCode.MATCH); + return adaptedCandidate; + } + catch (DependencyMatchingException d) { + depExceptions.add(d); + } + catch (OpMatchingException | IllegalArgumentException e1) { +// log.trace(e1); + } + } + throw new OpMatchingException("Unable to find an Op adaptation for " + + conditions); + } + + private OpRef inferOpRef(OpDependencyMember dependency, + Map, Type> typeVarAssigns) + { + final Type mappedDependencyType = Types.mapVarToTypes(new Type[] { + dependency.getType() }, typeVarAssigns)[0]; + final String dependencyName = dependency.getDependencyName(); + final OpRef inferredRef = inferOpRef(mappedDependencyType, dependencyName, + typeVarAssigns); + if (inferredRef != null) return inferredRef; + throw new OpMatchingException("Could not infer functional " + + "method inputs and outputs of Op dependency field: " + dependency + .getKey()); + } + + private boolean adaptOpOutputSatisfiesRefTypes(Type adaptTo, + Map, Type> map, OpRef ref) + { + Type opType = ref.getType(); + // TODO: clean this logic -- can this just be ref.typesMatch() ? + if (opType instanceof ParameterizedType) { + if (!GenericAssignability.checkGenericAssignability(adaptTo, + (ParameterizedType) opType, map, true)) + { + return false; + } + } + else if (!Types.isAssignable(opType, adaptTo, map)) { + return false; + } + return true; + } + + /** + * Tries to infer a {@link OpRef} from a functional Op type. E.g. the type: + * + *
+	 * Computer<Double[], Double[]>
+	 * 
+ * + * Will result in the following {@link OpRef}: + * + *
+	 * Name: 'specified name'
+	 * Types:       [Computer<Double, Double>]
+	 * InputTypes:  [Double[], Double[]]
+	 * OutputTypes: [Double[]]
+	 * 
+ * + * Input and output types will be inferred by looking at the signature of the + * functional method of the specified type. Also see + * {@link FunctionalParameters#findFunctionalMethodTypes(Type)}. + * + * @param type + * @param name + * @return null if the specified type has no functional method + */ + private OpRef inferOpRef(Type type, String name, + Map, Type> typeVarAssigns) + { + List fmts = FunctionalParameters + .findFunctionalMethodTypes(type); + if (fmts == null) return null; + + EnumSet inIos = EnumSet.of(ItemIO.INPUT, ItemIO.CONTAINER, + ItemIO.MUTABLE); + EnumSet outIos = EnumSet.of(ItemIO.OUTPUT, ItemIO.CONTAINER, + ItemIO.MUTABLE); + + Type[] inputs = fmts.stream().filter(fmt -> inIos.contains(fmt.itemIO())) + .map(fmt -> fmt.type()).toArray(Type[]::new); + + Type[] outputs = fmts.stream().filter(fmt -> outIos.contains(fmt.itemIO())) + .map(fmt -> fmt.type()).toArray(Type[]::new); + + Type[] mappedInputs = Types.mapVarToTypes(inputs, typeVarAssigns); + Type[] mappedOutputs = Types.mapVarToTypes(outputs, typeVarAssigns); + + final int numOutputs = mappedOutputs.length; + if (numOutputs != 1) { + String error = "Op '" + name + "' of type " + type + " specifies "; + error += numOutputs == 0 // + ? "no outputs" // + : "multiple outputs: " + Arrays.toString(outputs); + error += ". This is not supported."; + throw new OpMatchingException(error); + } + return new DefaultOpRef(name, type, mappedOutputs[0], mappedInputs); + } + +} diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMatcher.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMatcher.java index 9bbd4f752..2765d7a44 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMatcher.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMatcher.java @@ -34,27 +34,20 @@ import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.function.Predicate; -import org.scijava.ops.api.Hints; import org.scijava.ops.api.OpCandidate; +import org.scijava.ops.api.OpCandidate.StatusCode; import org.scijava.ops.api.OpEnvironment; -import org.scijava.ops.api.OpInfo; import org.scijava.ops.api.OpRef; import org.scijava.ops.api.OpUtils; -import org.scijava.ops.api.OpCandidate.StatusCode; -import org.scijava.ops.api.features.MatchingResult; -import org.scijava.ops.api.features.BaseOpHints.Simplification; -import org.scijava.ops.engine.hint.DefaultHints; -import org.scijava.ops.engine.matcher.OpMatcher; -import org.scijava.ops.engine.simplify.InfoSimplificationGenerator; +import org.scijava.ops.api.features.MatchingConditions; +import org.scijava.ops.api.features.MatchingRoutine; +import org.scijava.ops.api.features.OpMatcher; +import org.scijava.ops.api.features.OpMatchingException; import org.scijava.service.AbstractService; import org.scijava.struct.Member; import org.scijava.types.Types; @@ -68,129 +61,22 @@ */ public class DefaultOpMatcher extends AbstractService implements OpMatcher { - @Override - public OpCandidate findSingleMatch(final OpEnvironment env, final OpRef ref) { - return findMatch(env, ref).singleMatch(); - } - - @Override - public OpCandidate findSingleMatch(final OpEnvironment env, final OpRef ref, final Hints hints) { - return findMatch(env, ref, hints).singleMatch(); - } - - @Override - public MatchingResult findMatch(final OpEnvironment env, final OpRef ref) { - return findMatch(env, Collections.singletonList(ref)); - } + private final List matchers; - @Override - public MatchingResult findMatch(final OpEnvironment env, final OpRef ref, final Hints hints) { - return findMatch(env, Collections.singletonList(ref), hints); + public DefaultOpMatcher(Collection matchers) { + this.matchers = new ArrayList<>(matchers); + Collections.sort(this.matchers, Collections.reverseOrder()); } - @Override - public MatchingResult findMatch(final OpEnvironment env, final List refs) { - return findMatch(env, refs, new DefaultHints()); - } - - @Override - public MatchingResult findMatch(final OpEnvironment env, final List refs, final Hints hints) { - // find candidates with matching name & type - final List candidates = findCandidates(env, refs, hints); - if (candidates.isEmpty()) { - return MatchingResult.empty(refs); - } - // narrow down candidates to the exact matches - final List matches = filterMatches(candidates); - return new MatchingResult(candidates, matches, refs); - } - - @Override - public List findCandidates(final OpEnvironment env, final OpRef ref) { - return findCandidates(env, Collections.singletonList(ref)); - } - - @Override - public List findCandidates(final OpEnvironment env, final OpRef ref, final Hints hints) { - return findCandidates(env, Collections.singletonList(ref), hints); - } - - @Override - public List findCandidates(final OpEnvironment env, final List refs) { - return findCandidates(env, refs, new DefaultHints()); - } - - @Override - public List findCandidates(final OpEnvironment env, final List refs, final Hints hints) { - final ArrayList candidates = new ArrayList<>(); - for (final OpRef ref : refs) { - for (final OpInfo info : getInfos(env, ref, hints)) { - Map, Type> typeVarAssigns = new HashMap<>(); - if (ref.typesMatch(info.opType(), typeVarAssigns)) { - OpCandidate candidate = info.createCandidate(env, ref, typeVarAssigns); - candidates.add(candidate); - } - } - } - return candidates; - } - - private Iterable getInfos(OpEnvironment env, OpRef ref, Hints hints) { - Iterable suitableInfos = env.infos(ref.getName(), hints); - if(hints.contains(Simplification.IN_PROGRESS) && !hints.contains(Simplification.FORBIDDEN)) { - Set simpleInfos = new HashSet<>(); - for(OpInfo info: suitableInfos) { - boolean functionallyAssignable = Types.isAssignable(Types.raw(info.opType()), Types.raw(ref.getType())); - if(!functionallyAssignable) continue; - try { - InfoSimplificationGenerator gen = new InfoSimplificationGenerator(info, env); - simpleInfos.add(gen.generateSuitableInfo(env, ref, hints)); - } catch(Throwable e) {continue; } - } - // TODO: should this also return suitableInfos (i.e. combine the two)? - return simpleInfos; - } - return suitableInfos; - } - - @Override - public List filterMatches(final List candidates) { - final List validCandidates = checkCandidates(candidates); - - // List of valid candidates needs to be sorted according to priority. - // This is used as an optimization in order to not look at ops with - // lower priority than the already found one. - validCandidates.sort((c1, c2) -> Double.compare(c2.priority(), c1.priority())); - - List matches; - matches = filterMatches(validCandidates, (cand) -> typesPerfectMatch(cand)); - if (!matches.isEmpty()) - return matches; - - matches = filterMatches(validCandidates, (cand) -> typesMatch(cand)); - return matches; - } - - /** - * Checks whether the arg types of the candidate satisfy the padded arg - * types of the candidate. Sets candidate status code if there are too many, - * to few,not matching arg types or if a match was found. - * - * @param candidate - * the candidate to check args for - * @return whether the arg types are satisfied - */ - @Override - public boolean typesMatch(final OpCandidate candidate) { - HashMap, TypeVarInfo> typeBounds = new HashMap<>(); - if (!inputsMatch(candidate, typeBounds)) { - return false; + private OpMatchingException agglomeratedException( + List list) + { + OpMatchingException agglomerated = new OpMatchingException( + "No MatchingRoutine was able to produce a match!"); + for (int i = 0; i < list.size(); i++) { + agglomerated.addSuppressed(list.get(i)); } - if (!outputsMatch(candidate, typeBounds)) { - return false; - } - candidate.setStatus(StatusCode.MATCH); - return true; + return agglomerated; } // -- Helper methods -- @@ -224,92 +110,6 @@ private List checkCandidates(final List candidates) { return validCandidates; } - /** - * Filters specified list of candidates using specified predicate. This - * method stops filtering when the priority decreases. Expects list of candidates - * to be sorted in non-ascending order. - * - * @param candidates - * the candidates to filter - * @param filter - * the condition - * @return candidates passing test of condition and having highest priority - */ - private List filterMatches(final List candidates, final Predicate filter) { - final ArrayList matches = new ArrayList<>(); - double priority = Double.NaN; - for (final OpCandidate candidate : candidates) { - final double p = OpUtils.getPriority(candidate); - if (p != priority && !matches.isEmpty()) { - // NB: Lower priority was reached; stop looking for any more - // matches. - break; - } - priority = p; - - if (filter.test(candidate)) { - matches.add(candidate); - } - } - return matches; - } - - /** - * Determines if the candidate has some arguments missing. - *

- * Helper method of {@link #filterMatches(List)}. - *

- */ - private boolean missArgs(final OpCandidate candidate, final Type[] paddedArgs) { - int i = 0; - for (final Member member : OpUtils.inputs(candidate)) { - if (paddedArgs[i++] == null && member.isRequired()) { - candidate.setStatus(StatusCode.REQUIRED_ARG_IS_NULL, null, member); - return true; - } - } - return false; - } - - /** - * Determine if the arguments and the output types of the candidate - * perfectly match with the reference. - */ - private boolean typesPerfectMatch(final OpCandidate candidate) { - int i = 0; - Type[] paddedArgs = candidate.paddedArgs(); - for (final Type t : OpUtils.inputTypes(candidate)) { - if (paddedArgs[i] != null) { - if (!t.equals(paddedArgs[i])) - return false; - } - i++; - } - - final Type outputType = candidate.getRef().getOutType(); - if (!Objects.equals(outputType, OpUtils.outputType(candidate))) - return false; - - candidate.setStatus(StatusCode.MATCH); - return true; - } - - /** - * Determines if the specified candidate is valid and sets status code if - * not. - * - * @param candidate - * the candidate to check - * @return whether the candidate is valid - */ - private boolean isValid(final OpCandidate candidate) { - if (candidate.opInfo().isValid()) { - return true; - } - candidate.setStatus(StatusCode.INVALID_STRUCT); - return false; - } - /** * Checks whether the output types of the candidate are applicable to the * input types of the {@link OpRef}. Sets candidate status code if there are @@ -367,6 +167,56 @@ private boolean inputsMatch(final OpCandidate candidate, HashMap return true; } + /** + * Determines if the specified candidate is valid and sets status code if + * not. + * + * @param candidate + * the candidate to check + * @return whether the candidate is valid + */ + private boolean isValid(final OpCandidate candidate) { + if (candidate.opInfo().isValid()) { + return true; + } + candidate.setStatus(StatusCode.INVALID_STRUCT); + return false; + } + + @Override + public OpCandidate match(MatchingConditions conditions, OpEnvironment env) { + List exceptions = new ArrayList<>(matchers.size()); + // in priority order, search for a match + for (MatchingRoutine r : matchers) { + try { + return r.match(conditions, this, env); + } + catch (OpMatchingException e) { + exceptions.add(e); + } + } + + // in the case of no matches, throw an agglomerated exception + throw agglomeratedException(exceptions); + } + + /** + * Determines if the candidate has some arguments missing. + *

+ * Helper method of {@link #filterMatches(List)}. + *

+ */ + private boolean missArgs(final OpCandidate candidate, final Type[] paddedArgs) { + int i = 0; + for (final Member member : OpUtils.inputs(candidate)) { + if (paddedArgs[i++] == null && member.isRequired()) { + candidate.setStatus(StatusCode.REQUIRED_ARG_IS_NULL, null, member); + return true; + } + } + return false; + } + /** * Checks whether the output type of the candidate matches the output type * of the {@link OpRef}. Sets candidate status code if they are not matching. @@ -393,4 +243,26 @@ private boolean outputsMatch(final OpCandidate candidate, HashMap, TypeVarInfo> typeBounds = new HashMap<>(); + if (!inputsMatch(candidate, typeBounds)) { + return false; + } + if (!outputsMatch(candidate, typeBounds)) { + return false; + } + candidate.setStatus(StatusCode.MATCH); + return true; + } } diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpRef.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpRef.java index e5872e1a3..73cd78b24 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpRef.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpRef.java @@ -36,7 +36,7 @@ import java.util.Map; import org.scijava.ops.api.OpRef; -import org.scijava.ops.engine.matcher.OpMatcher; +import org.scijava.ops.api.features.OpMatcher; import org.scijava.ops.spi.Op; import org.scijava.types.Types; import org.scijava.types.inference.GenericAssignability; diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpClassInfo.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpClassInfo.java index 16e07fbcf..0d1f6b0d5 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpClassInfo.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpClassInfo.java @@ -101,7 +101,7 @@ public List names() { public Type opType() { // TODO: Check whether this is correct! return Types.parameterizeRaw(opClass); - //return opClass; +// return opClass; } @Override diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/RuntimeSafeMatchingRoutine.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/RuntimeSafeMatchingRoutine.java new file mode 100644 index 000000000..46094bcd7 --- /dev/null +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/RuntimeSafeMatchingRoutine.java @@ -0,0 +1,338 @@ + +package org.scijava.ops.engine.matcher.impl; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; + +import org.scijava.Priority; +import org.scijava.ops.api.Hints; +import org.scijava.ops.api.OpCandidate; +import org.scijava.ops.api.OpCandidate.StatusCode; +import org.scijava.ops.api.OpEnvironment; +import org.scijava.ops.api.OpInfo; +import org.scijava.ops.api.OpRef; +import org.scijava.ops.api.OpUtils; +import org.scijava.ops.api.features.BaseOpHints.Simplification; +import org.scijava.ops.api.features.MatchingConditions; +import org.scijava.ops.api.features.MatchingResult; +import org.scijava.ops.api.features.MatchingRoutine; +import org.scijava.ops.api.features.OpMatcher; +import org.scijava.ops.api.features.OpMatchingException; +import org.scijava.ops.engine.simplify.InfoSimplificationGenerator; +import org.scijava.plugin.Plugin; +import org.scijava.struct.Member; +import org.scijava.types.Types; +import org.scijava.types.Types.TypeVarInfo; +import org.scijava.types.inference.GenericAssignability; + +@Plugin(type = MatchingRoutine.class, priority = Priority.HIGH) +public class RuntimeSafeMatchingRoutine implements MatchingRoutine { + + @Override + public void checkSuitability(MatchingConditions conditions) + throws OpMatchingException + { + // NB: Runtime-safe matching should always be allowed + } + + @Override + public OpCandidate findMatch(MatchingConditions conditions, OpMatcher matcher, + OpEnvironment env) + { + final ArrayList candidates = new ArrayList<>(); + + for (final OpInfo info : getInfos(env, conditions)) { + Map, Type> typeVarAssigns = new HashMap<>(); + if (typesMatch(info.opType(), conditions.ref().getType(), + typeVarAssigns)) + { + OpCandidate candidate = info.createCandidate(env, conditions.ref(), + typeVarAssigns); + candidates.add(candidate); + } + } + List refs = Collections.singletonList(conditions.ref()); + if (candidates.isEmpty()) { + return MatchingResult.empty(refs).singleMatch(); + } + // narrow down candidates to the exact matches + final List matches = filterMatches(candidates); + return new MatchingResult(candidates, matches, refs).singleMatch(); + + } + + /** + * Performs several checks, whether the specified candidate:
+ *
+ * * {@link #isValid(OpCandidate)}
+ * * {@link #outputsMatch(OpCandidate, HashMap)}
+ * * has a matching number of args
+ * * {@link #missArgs(OpCandidate, Type[])}
+ *
+ * then returns the candidates which fulfill this criteria. + * + * @param candidates the candidates to check + * @return candidates passing checks + */ + private List checkCandidates( + final List candidates) + { + final ArrayList validCandidates = new ArrayList<>(); + for (final OpCandidate candidate : candidates) { + if (!isValid(candidate)) continue; + final Type[] args = candidate.paddedArgs(); + if (args == null) continue; + if (missArgs(candidate, args)) continue; + validCandidates.add(candidate); + } + return validCandidates; + } + + private List filterMatches(final List candidates) { + final List validCandidates = checkCandidates(candidates); + + // List of valid candidates needs to be sorted according to priority. + // This is used as an optimization in order to not look at ops with + // lower priority than the already found one. + validCandidates.sort((c1, c2) -> Double.compare(c2.priority(), c1 + .priority())); + + List matches; + matches = filterMatches(validCandidates, (cand) -> typesPerfectMatch(cand)); + if (!matches.isEmpty()) return matches; + + matches = filterMatches(validCandidates, (cand) -> typesMatch(cand)); + return matches; + } + + /** + * Filters specified list of candidates using specified predicate. This method + * stops filtering when the priority decreases. Expects list of candidates to + * be sorted in non-ascending order. + * + * @param candidates the candidates to filter + * @param filter the condition + * @return candidates passing test of condition and having highest priority + */ + private List filterMatches(final List candidates, + final Predicate filter) + { + final ArrayList matches = new ArrayList<>(); + double priority = Double.NaN; + for (final OpCandidate candidate : candidates) { + final double p = OpUtils.getPriority(candidate); + if (p != priority && !matches.isEmpty()) { + // NB: Lower priority was reached; stop looking for any more + // matches. + break; + } + priority = p; + + if (filter.test(candidate)) { + matches.add(candidate); + } + } + return matches; + } + + protected Iterable getInfos(OpEnvironment env, + MatchingConditions conditions) + { + return env.infos(conditions.ref().getName(), conditions.hints()); + } + + /** + * Checks whether the output types of the candidate are applicable to the + * input types of the {@link OpRef}. Sets candidate status code if there are + * too many, to few, or not matching types. + * + * @param candidate the candidate to check inputs for + * @param typeBounds possibly predetermined type bounds for type variables + * @return whether the input types match + */ + private boolean inputsMatch(final OpCandidate candidate, + HashMap, TypeVarInfo> typeBounds) + { + if (checkCandidates(Collections.singletonList(candidate)).isEmpty()) + return false; + final Type[] refArgTypes = candidate.paddedArgs(); + final Type refType = candidate.getRef().getType(); + final Type infoType = candidate.opInfo().opType(); + Type[] candidateArgTypes = OpUtils.inputTypes(candidate); + Type implementedInfoType = Types.getExactSuperType(infoType, Types.raw( + refType)); + if (!(implementedInfoType instanceof ParameterizedType)) { + throw new UnsupportedOperationException( + "Op type is not a ParameterizedType; we don't know how to deal with these yet."); + } + Type[] implTypeParams = ((ParameterizedType) implementedInfoType) + .getActualTypeArguments(); + candidateArgTypes = candidate.opInfo().struct().members().stream()// + .map(member -> member.isInput() ? member.getType() : null) // + .toArray(Type[]::new); + for (int i = 0; i < implTypeParams.length; i++) { + if (candidateArgTypes[i] == null) implTypeParams[i] = null; + } + candidateArgTypes = Arrays.stream(implTypeParams) // + .filter(t -> t != null).toArray(Type[]::new); + + if (refArgTypes == null) return true; // no constraints on output types + + if (candidateArgTypes.length < refArgTypes.length) { + candidate.setStatus(StatusCode.TOO_FEW_ARGS); + return false; + } + else if (candidateArgTypes.length > refArgTypes.length) { + candidate.setStatus(StatusCode.TOO_MANY_ARGS); + return false; + } + + int conflictingIndex = Types.isApplicable(refArgTypes, candidateArgTypes, + typeBounds); + if (conflictingIndex != -1) { + final Type to = refArgTypes[conflictingIndex]; + final Type from = candidateArgTypes[conflictingIndex]; + candidate.setStatus(StatusCode.ARG_TYPES_DO_NOT_MATCH, // + "request=" + to.getTypeName() + ", actual=" + from.getTypeName()); + return false; + } + return true; + } + + /** + * Determines if the specified candidate is valid and sets status code if not. + * + * @param candidate the candidate to check + * @return whether the candidate is valid + */ + private boolean isValid(final OpCandidate candidate) { + if (candidate.opInfo().isValid()) { + return true; + } + candidate.setStatus(StatusCode.INVALID_STRUCT); + return false; + } + + /** + * Determines if the candidate has some arguments missing. + *

+ * Helper method of {@link #filterMatches(List)}. + *

+ */ + private boolean missArgs(final OpCandidate candidate, + final Type[] paddedArgs) + { + int i = 0; + for (final Member member : OpUtils.inputs(candidate)) { + if (paddedArgs[i++] == null && member.isRequired()) { + candidate.setStatus(StatusCode.REQUIRED_ARG_IS_NULL, null, member); + return true; + } + } + return false; + } + + /** + * Checks whether the output type of the candidate matches the output type of + * the {@link OpRef}. Sets candidate status code if they are not matching. + * + * @param candidate the candidate to check output for + * @param typeBounds possibly predetermined type bounds for type variables + * @return whether the output types match + */ + private boolean outputsMatch(final OpCandidate candidate, + HashMap, TypeVarInfo> typeBounds) + { + final Type refOutType = candidate.getRef().getOutType(); + if (refOutType == null) return true; // no constraints on output types + + if (candidate.opInfo().output().isInput()) return true; + final Type candidateOutType = OpUtils.outputType(candidate); + final int conflictingIndex = MatchingUtils.checkGenericOutputsAssignability( + new Type[] { candidateOutType }, new Type[] { refOutType }, typeBounds); + if (conflictingIndex != -1) { + candidate.setStatus(StatusCode.OUTPUT_TYPES_DO_NOT_MATCH, // + "request=" + refOutType.getTypeName() + ", actual=" + candidateOutType + .getTypeName()); + return false; + } + return true; + } + + /** + * Checks whether the arg types of the candidate satisfy the padded arg types + * of the candidate. Sets candidate status code if there are too many, to + * few,not matching arg types or if a match was found. + * + * @param candidate the candidate to check args for + * @return whether the arg types are satisfied + */ + private boolean typesMatch(final OpCandidate candidate) { + HashMap, TypeVarInfo> typeBounds = new HashMap<>(); + if (!inputsMatch(candidate, typeBounds)) { + return false; + } + if (!outputsMatch(candidate, typeBounds)) { + return false; + } + candidate.setStatus(StatusCode.MATCH); + return true; + } + + /** + * Determines whether the specified type satisfies the op's required types + * using {@link Types#isApplicable(Type[], Type[])}. + */ + private boolean typesMatch(final Type opType, final Type refType, + final Map, Type> typeVarAssigns) + { + if (refType == null) return true; + if (refType instanceof ParameterizedType) { + if (!GenericAssignability.checkGenericAssignability(opType, + (ParameterizedType) refType, typeVarAssigns, true)) + { + return false; + } + } + else { + if (!Types.isAssignable(opType, refType)) { + return false; + } + } + return true; + } + + /** + * Determine if the arguments and the output types of the candidate perfectly + * match with the reference. + */ + private boolean typesPerfectMatch(final OpCandidate candidate) { + int i = 0; + Type[] paddedArgs = candidate.paddedArgs(); + for (final Type t : OpUtils.inputTypes(candidate)) { + if (paddedArgs[i] != null) { + if (!t.equals(paddedArgs[i])) return false; + } + i++; + } + + final Type outputType = candidate.getRef().getOutType(); + if (!Objects.equals(outputType, OpUtils.outputType(candidate))) + return false; + + candidate.setStatus(StatusCode.MATCH); + return true; + } + +} diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/SimplificationMatchingRoutine.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/SimplificationMatchingRoutine.java new file mode 100644 index 000000000..cd4baf901 --- /dev/null +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/SimplificationMatchingRoutine.java @@ -0,0 +1,57 @@ + +package org.scijava.ops.engine.matcher.impl; + +import java.util.HashSet; +import java.util.Set; + +import org.scijava.Priority; +import org.scijava.ops.api.Hints; +import org.scijava.ops.api.OpEnvironment; +import org.scijava.ops.api.OpInfo; +import org.scijava.ops.api.OpRef; +import org.scijava.ops.api.features.BaseOpHints.Simplification; +import org.scijava.ops.api.features.MatchingConditions; +import org.scijava.ops.api.features.MatchingRoutine; +import org.scijava.ops.api.features.OpMatchingException; +import org.scijava.ops.engine.simplify.InfoSimplificationGenerator; +import org.scijava.plugin.Plugin; +import org.scijava.types.Types; + +@Plugin(type = MatchingRoutine.class, priority = Priority.LOW - 1) +public class SimplificationMatchingRoutine extends RuntimeSafeMatchingRoutine { + + @Override + public void checkSuitability(MatchingConditions conditions) + throws OpMatchingException + { + if (conditions.hints().containsAny(Simplification.IN_PROGRESS, + Simplification.FORBIDDEN)) // + throw new OpMatchingException( + "Simplification is not suitable: Simplification is disabled"); + } + + @Override + protected Iterable getInfos(OpEnvironment env, + MatchingConditions conditions) + { + OpRef ref = conditions.ref(); + Hints hints = conditions.hints().plus(Simplification.IN_PROGRESS); + Iterable suitableInfos = env.infos(ref.getName(), hints); + Set simpleInfos = new HashSet<>(); + for (OpInfo info : suitableInfos) { + boolean functionallyAssignable = Types.isAssignable(Types.raw(info + .opType()), Types.raw(ref.getType())); + if (!functionallyAssignable) continue; + try { + InfoSimplificationGenerator gen = new InfoSimplificationGenerator(info, + env); + simpleInfos.add(gen.generateSuitableInfo(env, ref, hints)); + } + catch (Throwable e) { + continue; + } + } + return simpleInfos; + } + +} diff --git a/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/DefaultMatchingErrorTest.java b/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/DefaultMatchingErrorTest.java index 4eae00cde..80d3159b0 100644 --- a/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/DefaultMatchingErrorTest.java +++ b/scijava/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/DefaultMatchingErrorTest.java @@ -1,6 +1,7 @@ package org.scijava.ops.engine.matcher; +import java.util.Arrays; import java.util.function.Function; import org.junit.Assert; @@ -37,9 +38,11 @@ public void duplicateErrorRegressionTest() { } catch (OpMatchingException e) { Assert.assertTrue(e.getMessage().startsWith( - "Multiple 'test.duplicateOp/" + + "No MatchingRoutine was able to produce a match!")); + Assert.assertTrue(Arrays.stream(e.getSuppressed()).anyMatch(s -> s + .getMessage().startsWith("Multiple 'test.duplicateOp/" + "java.util.function.Function' " + - "ops of priority 0.0:")); + "ops of priority 0.0:"))); } } From bb317a54d173d5ccd81adf829fbc71997c66f660 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Thu, 16 Sep 2021 13:15:09 -0500 Subject: [PATCH 3/3] Remove typesMatch() from OpMatcher typesMatch() shouldn't really pertain to the OpMatcher. It might instead belong in OpCandidate, or in RuntimeSafeMatchingRoutine... TBD. --- .../scijava/ops/api/features/OpMatcher.java | 2 - .../ops/engine/impl/ManualOpCandidate.java | 3 - .../engine/matcher/impl/DefaultOpMatcher.java | 196 +----------------- 3 files changed, 8 insertions(+), 193 deletions(-) diff --git a/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/OpMatcher.java b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/OpMatcher.java index 2d7a17dab..d5aa7ff82 100644 --- a/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/OpMatcher.java +++ b/scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/features/OpMatcher.java @@ -42,6 +42,4 @@ public interface OpMatcher { OpCandidate match(MatchingConditions conditions, OpEnvironment env); - - boolean typesMatch(OpCandidate candidate); } diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/ManualOpCandidate.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/ManualOpCandidate.java index 5fede5343..383a8009e 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/ManualOpCandidate.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/ManualOpCandidate.java @@ -18,9 +18,6 @@ public ManualOpCandidate(OpEnvironment env, OpRef ref, OpInfo info, OpMatcher matcher) { super(env, ref, info, generateTypeVarAssigns(ref, info)); - if (!matcher.typesMatch(this)) throw new IllegalArgumentException( - "OpInfo " + info + - " cannot satisfy the requirements contained within OpRef " + ref); } private static Map, Type> generateTypeVarAssigns(OpRef ref, diff --git a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMatcher.java b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMatcher.java index 2765d7a44..ec30aaab9 100644 --- a/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMatcher.java +++ b/scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMatcher.java @@ -29,29 +29,19 @@ package org.scijava.ops.engine.matcher.impl; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; import org.scijava.ops.api.OpCandidate; -import org.scijava.ops.api.OpCandidate.StatusCode; import org.scijava.ops.api.OpEnvironment; import org.scijava.ops.api.OpRef; -import org.scijava.ops.api.OpUtils; import org.scijava.ops.api.features.MatchingConditions; import org.scijava.ops.api.features.MatchingRoutine; import org.scijava.ops.api.features.OpMatcher; import org.scijava.ops.api.features.OpMatchingException; import org.scijava.service.AbstractService; -import org.scijava.struct.Member; -import org.scijava.types.Types; -import org.scijava.types.Types.TypeVarInfo; /** * Default implementation of {@link OpMatcher}. Used for finding Ops which match @@ -68,121 +58,6 @@ public DefaultOpMatcher(Collection matchers) { Collections.sort(this.matchers, Collections.reverseOrder()); } - private OpMatchingException agglomeratedException( - List list) - { - OpMatchingException agglomerated = new OpMatchingException( - "No MatchingRoutine was able to produce a match!"); - for (int i = 0; i < list.size(); i++) { - agglomerated.addSuppressed(list.get(i)); - } - return agglomerated; - } - - // -- Helper methods -- - - /** - * Performs several checks, whether the specified candidate:
- *
- * * {@link #isValid(OpCandidate)}
- * * {@link #outputsMatch(OpCandidate, HashMap)}
- * * has a matching number of args
- * * {@link #missArgs(OpCandidate, Type[])}
- *
- * then returns the candidates which fulfill this criteria. - * - * @param candidates - * the candidates to check - * @return candidates passing checks - */ - private List checkCandidates(final List candidates) { - final ArrayList validCandidates = new ArrayList<>(); - for (final OpCandidate candidate : candidates) { - if (!isValid(candidate)) - continue; - final Type[] args = candidate.paddedArgs(); - if (args == null) - continue; - if (missArgs(candidate, args)) - continue; - validCandidates.add(candidate); - } - return validCandidates; - } - - /** - * Checks whether the output types of the candidate are applicable to the - * input types of the {@link OpRef}. Sets candidate status code if there are - * too many, to few, or not matching types. - * - * @param candidate - * the candidate to check inputs for - * @param typeBounds - * possibly predetermined type bounds for type variables - * @return whether the input types match - */ - private boolean inputsMatch(final OpCandidate candidate, HashMap, TypeVarInfo> typeBounds) { - if (checkCandidates(Collections.singletonList(candidate)).isEmpty()) - return false; - final Type[] refArgTypes = candidate.paddedArgs(); - final Type refType = candidate.getRef().getType(); - final Type infoType = candidate.opInfo().opType(); - Type[] candidateArgTypes = OpUtils.inputTypes(candidate); - Type implementedInfoType = Types.getExactSuperType(infoType, Types.raw( - refType)); - if (!(implementedInfoType instanceof ParameterizedType)) { - throw new UnsupportedOperationException( - "Op type is not a ParameterizedType; we don't know how to deal with these yet."); - } - Type[] implTypeParams = ((ParameterizedType) implementedInfoType) - .getActualTypeArguments(); - candidateArgTypes = candidate.opInfo().struct().members().stream()// - .map(member -> member.isInput() ? member.getType() : null) // - .toArray(Type[]::new); - for (int i = 0; i < implTypeParams.length; i++) { - if (candidateArgTypes[i] == null) implTypeParams[i] = null; - } - candidateArgTypes = Arrays.stream(implTypeParams) // - .filter(t -> t != null).toArray(Type[]::new); - - if (refArgTypes == null) - return true; // no constraints on output types - - if (candidateArgTypes.length < refArgTypes.length) { - candidate.setStatus(StatusCode.TOO_FEW_ARGS); - return false; - } else if (candidateArgTypes.length > refArgTypes.length) { - candidate.setStatus(StatusCode.TOO_MANY_ARGS); - return false; - } - - int conflictingIndex = Types.isApplicable(refArgTypes, candidateArgTypes, typeBounds); - if (conflictingIndex != -1) { - final Type to = refArgTypes[conflictingIndex]; - final Type from = candidateArgTypes[conflictingIndex]; - candidate.setStatus(StatusCode.ARG_TYPES_DO_NOT_MATCH, // - "request=" + to.getTypeName() + ", actual=" + from.getTypeName()); - return false; - } - return true; - } - - /** - * Determines if the specified candidate is valid and sets status code if - * not. - * - * @param candidate - * the candidate to check - * @return whether the candidate is valid - */ - private boolean isValid(final OpCandidate candidate) { - if (candidate.opInfo().isValid()) { - return true; - } - candidate.setStatus(StatusCode.INVALID_STRUCT); - return false; - } - @Override public OpCandidate match(MatchingConditions conditions, OpEnvironment env) { List exceptions = new ArrayList<>(matchers.size()); @@ -200,69 +75,14 @@ public OpCandidate match(MatchingConditions conditions, OpEnvironment env) { throw agglomeratedException(exceptions); } - /** - * Determines if the candidate has some arguments missing. - *

- * Helper method of {@link #filterMatches(List)}. - *

- */ - private boolean missArgs(final OpCandidate candidate, final Type[] paddedArgs) { - int i = 0; - for (final Member member : OpUtils.inputs(candidate)) { - if (paddedArgs[i++] == null && member.isRequired()) { - candidate.setStatus(StatusCode.REQUIRED_ARG_IS_NULL, null, member); - return true; - } - } - return false; - } - - /** - * Checks whether the output type of the candidate matches the output type - * of the {@link OpRef}. Sets candidate status code if they are not matching. - * - * @param candidate - * the candidate to check output for - * @param typeBounds - * possibly predetermined type bounds for type variables - * @return whether the output types match - */ - private boolean outputsMatch(final OpCandidate candidate, HashMap, TypeVarInfo> typeBounds) { - final Type refOutType = candidate.getRef().getOutType(); - if (refOutType == null) - return true; // no constraints on output types - - if(candidate.opInfo().output().isInput()) return true; - final Type candidateOutType = OpUtils.outputType(candidate); - final int conflictingIndex = MatchingUtils.checkGenericOutputsAssignability(new Type[] { candidateOutType }, - new Type[] { refOutType }, typeBounds); - if (conflictingIndex != -1) { - candidate.setStatus(StatusCode.OUTPUT_TYPES_DO_NOT_MATCH, // - "request=" + refOutType.getTypeName() + ", actual=" + candidateOutType.getTypeName()); - return false; - } - return true; - } - - /** - * Checks whether the arg types of the candidate satisfy the padded arg - * types of the candidate. Sets candidate status code if there are too many, - * to few,not matching arg types or if a match was found. - * - * @param candidate - * the candidate to check args for - * @return whether the arg types are satisfied - */ - @Override - public boolean typesMatch(final OpCandidate candidate) { - HashMap, TypeVarInfo> typeBounds = new HashMap<>(); - if (!inputsMatch(candidate, typeBounds)) { - return false; - } - if (!outputsMatch(candidate, typeBounds)) { - return false; + private OpMatchingException agglomeratedException( + List list) + { + OpMatchingException agglomerated = new OpMatchingException( + "No MatchingRoutine was able to produce a match!"); + for (int i = 0; i < list.size(); i++) { + agglomerated.addSuppressed(list.get(i)); } - candidate.setStatus(StatusCode.MATCH); - return true; + return agglomerated; } }