Skip to content

Commit a628c13

Browse files
gselzerctrueden
authored andcommitted
Add registerInfosFrom, clean DefaultOpEnvironment
1 parent 7554004 commit a628c13

2 files changed

Lines changed: 59 additions & 163 deletions

File tree

scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/OpEnvironment.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ default OpBuilder op(final String opName) {
216216
*/
217217
void register(OpInfo info);
218218

219+
/**
220+
* Generates all {@link OpInfo}s this {@link OpEnvironment} knows how to create,
221+
* and makes them known to this {@link OpEnvironment}.
222+
*
223+
* @param o the {@link Object} <b>containing</b> some {@link OpInfo}s. If it is
224+
* instead desired to turn this {@link Object} into an {@code op}, use
225+
* {{@link #opify(Class, double, String...)}} instead.
226+
*/
227+
void registerInfosFrom(Object o);
228+
219229
/**
220230
* Sets the {@link Hints} for the {@link OpEnvironment}. Every Call to
221231
* {@link #op} that <b>does not</b> pass a {@link Hints} will <b>copy</b> the

scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java

Lines changed: 49 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,23 @@
3333
import java.lang.reflect.Type;
3434
import java.lang.reflect.TypeVariable;
3535
import java.util.*;
36-
import java.util.function.Consumer;
37-
import java.util.function.Function;
36+
import java.util.function.BiConsumer;
3837
import java.util.stream.Collectors;
3938

4039
import org.scijava.Priority;
4140
import org.scijava.discovery.Discoverer;
4241
import org.scijava.log2.Logger;
4342
import org.scijava.ops.api.*;
44-
import org.scijava.ops.api.OpCandidate.StatusCode;
4543
import org.scijava.ops.api.features.*;
4644
import org.scijava.ops.api.features.BaseOpHints.Adaptation;
4745
import org.scijava.ops.api.features.BaseOpHints.DependencyMatching;
4846
import org.scijava.ops.api.features.BaseOpHints.History;
4947
import org.scijava.ops.api.features.BaseOpHints.Simplification;
5048
import org.scijava.ops.engine.hint.DefaultHints;
51-
import org.scijava.ops.engine.matcher.impl.*;
52-
import org.scijava.ops.engine.simplify.SimplifiedOpInfo;
49+
import org.scijava.ops.engine.matcher.impl.DefaultOpMatcher;
50+
import org.scijava.ops.engine.matcher.impl.DefaultOpRef;
51+
import org.scijava.ops.engine.matcher.impl.InfoMatchingOpRef;
52+
import org.scijava.ops.engine.matcher.impl.OpClassInfo;
5353
import org.scijava.ops.engine.struct.FunctionalParameters;
5454
import org.scijava.ops.spi.Op;
5555
import org.scijava.ops.spi.OpCollection;
@@ -59,7 +59,6 @@
5959
import org.scijava.types.Nil;
6060
import org.scijava.types.TypeReifier;
6161
import org.scijava.types.Types;
62-
import org.scijava.types.inference.GenericAssignability;
6362
import org.scijava.util.VersionUtils;
6463

6564
/**
@@ -72,18 +71,13 @@ public class DefaultOpEnvironment implements OpEnvironment {
7271

7372
private final List<Discoverer> discoverers;
7473

75-
private OpMatcher matcher;
74+
private final OpMatcher matcher;
7675

77-
private Logger log;
76+
private final Logger log;
7877

79-
private TypeReifier typeService;
78+
private final TypeReifier typeService;
8079

81-
private OpHistory history;
82-
83-
/**
84-
* The {@link OpInfoGenerator}s providing {@link OpInfo}s to this environment
85-
*/
86-
private List<OpInfoGenerator> infoGenerators;
80+
private final OpHistory history;
8781

8882
/**
8983
* Data structure storing all known Ops, grouped by name. This reduces the
@@ -99,7 +93,7 @@ public class DefaultOpEnvironment implements OpEnvironment {
9993

10094
/**
10195
* Map containing pairs of {@link MatchingConditions} (i.e. the {@link OpRef}
102-
* and {@Hints} used to find an Op) and the {@link OpInstance} (wrapping an Op
96+
* and {@link Hints} used to find an Op) and the {@link OpInstance} (wrapping an Op
10397
* with its backing {@link OpInfo}) that matched those requests. Used to
10498
* quickly return Ops when the matching conditions are identical to those of a
10599
* previous call.
@@ -152,7 +146,7 @@ public static List<MatchingRoutine> getMatchingRoutines(
152146
@Override
153147
public Set<OpInfo> infos() {
154148
if (opDirectory == null) initOpDirectory();
155-
return opDirectory.values().stream().flatMap(list -> list.stream()).collect(
149+
return opDirectory.values().stream().flatMap(Collection::stream).collect(
156150
Collectors.toSet());
157151
}
158152

@@ -291,7 +285,15 @@ public <T> T bakeLambdaType(T op, Type type) {
291285
@Override
292286
public void register(final OpInfo info) {
293287
if (opDirectory == null) initOpDirectory();
294-
addToOpIndex.accept(info);
288+
addToOpIndex.accept(info, log);
289+
}
290+
291+
@Override
292+
public void registerInfosFrom(Object o) {
293+
List<OpInfo> infos = discoverers.parallelStream() //
294+
.flatMap(d -> d.discover(OpInfoGenerator.class).stream()) //
295+
.flatMap(g -> g.generateInfosFrom(o).stream()).collect(Collectors.toList());
296+
infos.forEach(this::register);
295297
}
296298

297299
@SuppressWarnings("unchecked")
@@ -313,17 +315,11 @@ private <T> OpInstance<T> findOp(final OpInfo info, final Nil<T> specialType,
313315
return (OpInstance<T>) getInstance(conditions);
314316
}
315317

316-
@SuppressWarnings("unchecked")
317-
private <T> RichOp<T> findRichOp(final OpInfo info, final Nil<T> specialType,
318-
Hints hints) throws OpMatchingException
319-
{
320-
OpInstance<T> instance = findOp(info, specialType, hints);
321-
return (RichOp<T>) wrap(instance, hints);
322-
}
323-
324318
private Type[] toTypes(Nil<?>... nils) {
325-
return Arrays.stream(nils).filter(n -> n != null).map(n -> n.getType())
326-
.toArray(Type[]::new);
319+
return Arrays.stream(nils) //
320+
.filter(Objects::nonNull) //
321+
.map(Nil::getType) //
322+
.toArray(Type[]::new);
327323
}
328324

329325
/**
@@ -338,7 +334,6 @@ private Type[] toTypes(Nil<?>... nils) {
338334
* conditions1
339335
* @return the {@link MatchingConditions} that will return the Op described by
340336
* {@code info} from the op cache
341-
* @throws OpMatchingException
342337
*/
343338
private MatchingConditions insertCacheHit(final OpRef ref, final Hints hints,
344339
final OpInfo info)
@@ -360,8 +355,7 @@ private RichOp<?> wrapViaCache(MatchingConditions conditions) {
360355
}
361356

362357
private RichOp<?> wrap(OpInstance<?> instance, Hints hints) {
363-
RichOp<?> wrappedOp = wrapOp(instance, hints);
364-
return wrappedOp;
358+
return wrapOp(instance, hints);
365359
}
366360

367361
/**
@@ -421,7 +415,8 @@ private OpCandidate findOpCandidate(OpRef ref, Hints hints) {
421415
* Creates an instance of the Op from the {@link OpCandidate} <b>with its
422416
* required {@link OpDependency} fields</b>.
423417
*
424-
* @param candidate
418+
* @param candidate the {@link OpCandidate} to be instantiated
419+
* @param hints the {@link Hints} to use in instantiation
425420
* @return an Op with all needed dependencies
426421
*/
427422
private OpInstance<?> instantiateOp(final OpCandidate candidate,
@@ -480,10 +475,10 @@ private Class<?> getWrapperClass(Object op, OpInfo info) {
480475
" does not match a wrappable Op type.");
481476
if (filteredWrappers.size() > 1) throw new IllegalArgumentException(
482477
"Matched op Type " + info.opType().getClass() +
483-
" matches multiple Op types: " + filteredWrappers.toString());
478+
" matches multiple Op types: " + filteredWrappers);
484479
if (!Types.isAssignable(Types.raw(info.opType()), filteredWrappers.get(0)))
485480
throw new IllegalArgumentException(Types.raw(info.opType()) +
486-
"cannot be wrapped as a " + filteredWrappers.get(0).getClass());
481+
"cannot be wrapped as a " + filteredWrappers.get(0));
487482
return filteredWrappers.get(0);
488483
}
489484

@@ -563,125 +558,14 @@ private List<RichOp<?>> resolveOpDependencies(OpInfo info,
563558
return dependencyChains;
564559
}
565560

566-
/**
567-
* Adapts an Op with the name of ref into a type that can be SAFELY cast to
568-
* ref.
569-
* <p>
570-
* NB This method <b>cannot</b> use the {@link OpMatcher} to find a suitable
571-
* {@code adapt} Op. The premise of adaptation depends on the ability to
572-
* examine the applicability of all {@code adapt} Ops with the correct output
573-
* type. We need to check all of them because we do not know whether:
574-
* <ul>
575-
* <li>The dependencies will exist for a particular {@code adapt} Op
576-
* <li>The Op we want exists with the correct type for the input of the
577-
* {@code adapt} Op.
578-
* </ul>
579-
*
580-
* @param ref - the type of Op that we are looking to adapt to.
581-
* @return {@link OpCandidate} - an Op that has been adapted to conform the
582-
* the ref type (if one exists).
583-
*/
584-
private OpCandidate adaptOp(OpRef ref, Hints hints) {
585-
586-
List<DependencyMatchingException> depExceptions = new ArrayList<>();
587-
for (final OpInfo adaptor : infos("adapt")) {
588-
Type adaptTo = adaptor.output().getType();
589-
Map<TypeVariable<?>, Type> map = new HashMap<>();
590-
// make sure that the adaptor outputs the correct type
591-
if (!adaptOpOutputSatisfiesRefTypes(adaptTo, map, ref)) continue;
592-
// make sure that the adaptor is a Function (so we can cast it later)
593-
if (Types.isInstance(adaptor.opType(), Function.class)) {
594-
log.debug(adaptor + " is an illegal adaptor Op: must be a Function");
595-
continue;
596-
}
597-
598-
if (adaptor instanceof SimplifiedOpInfo) {
599-
log.debug(adaptor + " has been simplified. This is likely a typo.");
600-
}
601-
602-
try {
603-
// resolve adaptor dependencies
604-
final List<RichOp<?>> dependencies = resolveOpDependencies(adaptor, map,
605-
hints);
606-
InfoChain adaptorChain = new DependencyRichOpInfoChain(adaptor,
607-
dependencies);
608-
609-
// grab the first type parameter from the OpInfo and search for
610-
// an Op that will then be adapted (this will be the only input of the
611-
// adaptor since we know it is a Function)
612-
Type srcOpType = Types.substituteTypeVariables(adaptor.inputs().get(0)
613-
.getType(), map);
614-
final OpRef srcOpRef = inferOpRef(srcOpType, ref.getName(), map);
615-
final OpCandidate srcCandidate = findAdaptationCandidate(srcOpRef,
616-
hints);
617-
map.putAll(srcCandidate.typeVarAssigns());
618-
Type adapterOpType = Types.substituteTypeVariables(adaptor.output()
619-
.getType(), map);
620-
OpAdaptationInfo adaptedInfo = new OpAdaptationInfo(srcCandidate
621-
.opInfo(), adapterOpType, adaptorChain);
622-
OpCandidate adaptedCandidate = new OpCandidate(this, ref, adaptedInfo,
623-
map);
624-
adaptedCandidate.setStatus(StatusCode.MATCH);
625-
return adaptedCandidate;
626-
}
627-
catch (DependencyMatchingException d) {
628-
depExceptions.add(d);
629-
}
630-
catch (OpMatchingException e1) {
631-
log.trace(e1);
632-
}
633-
}
634-
635-
// no adaptors available.
636-
if (depExceptions.size() == 0) {
637-
throw new OpMatchingException(
638-
"Op adaptation failed: no adaptable Ops of type " + ref.getName());
639-
}
640-
StringBuilder sb = new StringBuilder();
641-
for (DependencyMatchingException d : depExceptions) {
642-
sb.append("\n\n" + d.getMessage());
643-
}
644-
throw new DependencyMatchingException(sb.toString());
645-
}
646-
647-
private OpCandidate findAdaptationCandidate(final OpRef srcOpRef,
648-
final Hints hints)
649-
{
650-
Hints adaptationHints = hints.plus(Adaptation.IN_PROGRESS);
651-
final OpCandidate srcCandidate = findOpCandidate(srcOpRef, adaptationHints);
652-
return srcCandidate;
653-
}
654-
655-
private boolean adaptOpOutputSatisfiesRefTypes(Type adaptTo,
656-
Map<TypeVariable<?>, Type> map, OpRef ref)
657-
{
658-
Type opType = ref.getType();
659-
// TODO: clean this logic -- can this just be ref.typesMatch() ?
660-
if (opType instanceof ParameterizedType) {
661-
if (!GenericAssignability.checkGenericAssignability(adaptTo,
662-
(ParameterizedType) opType, map, true))
663-
{
664-
return false;
665-
}
666-
}
667-
else if (!Types.isAssignable(opType, adaptTo, map)) {
668-
return false;
669-
}
670-
return true;
671-
}
672-
673561
private OpRef inferOpRef(OpDependencyMember<?> dependency,
674562
Map<TypeVariable<?>, Type> typeVarAssigns)
675563
{
676564
final Type mappedDependencyType = Types.mapVarToTypes(new Type[] {
677565
dependency.getType() }, typeVarAssigns)[0];
678566
final String dependencyName = dependency.getDependencyName();
679-
final OpRef inferredRef = inferOpRef(mappedDependencyType, dependencyName,
567+
return inferOpRef(mappedDependencyType, dependencyName,
680568
typeVarAssigns);
681-
if (inferredRef != null) return inferredRef;
682-
throw new OpMatchingException("Could not infer functional " +
683-
"method inputs and outputs of Op dependency field: " + dependency
684-
.getKey());
685569
}
686570

687571
/**
@@ -704,23 +588,25 @@ private OpRef inferOpRef(OpDependencyMember<?> dependency,
704588
* functional method of the specified type. Also see
705589
* {@link FunctionalParameters#findFunctionalMethodTypes(Type)}.
706590
*
707-
* @param type
708-
* @param name
591+
* @param type the functional {@link Type} of the {@code op} we're looking for
592+
* @param name the name of the {@code op} we're looking for
593+
* @param typeVarAssigns the mappings of {@link TypeVariable}s to {@link Type}s
709594
* @return null if the specified type has no functional method
710595
*/
711-
private OpRef inferOpRef(Type type, String name, Map<TypeVariable<?>, Type> typeVarAssigns)
712-
{
596+
private OpRef inferOpRef(Type type, String name, Map<TypeVariable<?>, Type> typeVarAssigns) {
713597
List<FunctionalMethodType> fmts = FunctionalParameters.findFunctionalMethodTypes(type);
714-
if (fmts == null)
715-
return null;
716598

717599
EnumSet<ItemIO> inIos = EnumSet.of(ItemIO.INPUT, ItemIO.CONTAINER, ItemIO.MUTABLE);
718600
EnumSet<ItemIO> outIos = EnumSet.of(ItemIO.OUTPUT, ItemIO.CONTAINER, ItemIO.MUTABLE);
719601

720-
Type[] inputs = fmts.stream().filter(fmt -> inIos.contains(fmt.itemIO())).map(fmt -> fmt.type())
602+
Type[] inputs = fmts.stream() //
603+
.filter(fmt -> inIos.contains(fmt.itemIO())) //
604+
.map(FunctionalMethodType::type) //
721605
.toArray(Type[]::new);
722606

723-
Type[] outputs = fmts.stream().filter(fmt -> outIos.contains(fmt.itemIO())).map(fmt -> fmt.type())
607+
Type[] outputs = fmts.stream() //
608+
.filter(fmt -> outIos.contains(fmt.itemIO())) //
609+
.map(FunctionalMethodType::type) //
724610
.toArray(Type[]::new);
725611

726612
Type[] mappedInputs = Types.mapVarToTypes(inputs, typeVarAssigns);
@@ -742,7 +628,7 @@ private synchronized void initOpDirectory() {
742628
if (opDirectory != null) return;
743629
opDirectory = new HashMap<>();
744630
// add all OpInfos that are directly discoverable
745-
discoverers.stream().flatMap(d -> d.discover(OpInfo.class).stream()).forEach(addToOpIndex);
631+
discoverers.stream().flatMap(d -> d.discover(OpInfo.class).stream()).forEach(info -> addToOpIndex.accept(info, log));
746632
List<OpInfoGenerator> generators = infoGenerators();
747633
discoverers.stream().flatMap(d -> d.discover(Op.class).stream()).forEach(o -> registerOpsFrom(o, generators));
748634
discoverers.stream().flatMap(d -> d.discover(OpCollection.class).stream()).forEach(o -> registerOpsFrom(o, generators));
@@ -762,7 +648,7 @@ private List<OpInfo> opsFromObject(Object o, List<OpInfoGenerator> generators) {
762648
}
763649

764650
private void registerOpsFrom(Object o, List<OpInfoGenerator> generators) {
765-
opsFromObject(o, generators).stream().forEach(addToOpIndex);
651+
opsFromObject(o, generators).forEach(info -> addToOpIndex.accept(info, log));
766652
}
767653

768654
private List<OpInfoGenerator> infoGenerators() {
@@ -776,12 +662,12 @@ private synchronized void initIdDirectory() {
776662
idDirectory = new HashMap<>();
777663
if (opDirectory == null) initOpDirectory();
778664

779-
opDirectory.values().stream().flatMap(c -> c.stream()).forEach(info -> {
780-
idDirectory.put(info.id(), info);
781-
});
665+
opDirectory.values().stream() //
666+
.flatMap(Collection::stream) //
667+
.forEach(info -> idDirectory.put(info.id(), info));
782668
}
783669

784-
private final Consumer<OpInfo> addToOpIndex = (final OpInfo opInfo) -> {
670+
private final BiConsumer<OpInfo, Logger> addToOpIndex = (final OpInfo opInfo, final Logger log) -> {
785671
if (opInfo.names() == null || opInfo.names().size() == 0) {
786672
log.error("Skipping Op " + opInfo.implementationName() + ":\n" +
787673
"Op implementation must provide name.");
@@ -806,13 +692,13 @@ private Set<OpInfo> opsOfName(final String name) {
806692
}
807693

808694
/**
809-
* Sets the default {@Hints} used for finding Ops.
695+
* Sets the default {@link Hints} used for finding Ops.
810696
* <p>
811697
* Note that this method is <b>not</b> thread safe and is provided for
812-
* convenience. If the user wishes to use {@Hints} in a thread-safe manner,
698+
* convenience. If the user wishes to use {@link Hints} in a thread-safe manner,
813699
* they should use
814700
* {@link DefaultOpEnvironment#op(String, Nil, Nil[], Nil, Hints)} if using
815-
* different {@Hint}s for different calls. Alternatively, this method can be
701+
* different {@link Hints} for different calls. Alternatively, this method can be
816702
* called before all Ops called in parallel without issues.
817703
*/
818704
@Override

0 commit comments

Comments
 (0)