Skip to content

Commit f6f8ea5

Browse files
committed
Adapt Ops replacing transformations: first cut
TODO: fix tests
1 parent bc051ea commit f6f8ea5

13 files changed

Lines changed: 577 additions & 273 deletions

src/main/java/org/scijava/ops/OpService.java

Lines changed: 100 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,17 @@
3434
import java.lang.reflect.TypeVariable;
3535
import java.util.ArrayList;
3636
import java.util.Arrays;
37+
import java.util.Comparator;
3738
import java.util.EnumSet;
3839
import java.util.HashMap;
3940
import java.util.Iterator;
4041
import java.util.LinkedList;
4142
import java.util.List;
4243
import java.util.Map;
4344
import java.util.Map.Entry;
45+
import java.util.PriorityQueue;
46+
import java.util.Queue;
47+
import java.util.function.Function;
4448
import java.util.stream.Collectors;
4549

4650
import org.scijava.InstantiableException;
@@ -55,10 +59,8 @@
5559
import org.scijava.ops.matcher.OpMatcher;
5660
import org.scijava.ops.matcher.OpMatchingException;
5761
import org.scijava.ops.matcher.OpRef;
58-
import org.scijava.ops.transform.DefaultOpTransformationMatcher;
62+
import org.scijava.ops.transform.AdaptedOp;
5963
import org.scijava.ops.transform.OpRunner;
60-
import org.scijava.ops.transform.OpTransformationCandidate;
61-
import org.scijava.ops.transform.OpTransformationException;
6264
import org.scijava.ops.transform.OpTransformationMatcher;
6365
import org.scijava.ops.transform.OpTransformer;
6466
import org.scijava.ops.types.Nil;
@@ -212,12 +214,12 @@ private synchronized List<OpTransformer> getTransformerIndex() {
212214
return transformerIndex;
213215
}
214216

215-
private OpTransformationMatcher getTransformationMatcher() {
216-
if (transformationMatcher == null) {
217-
transformationMatcher = new DefaultOpTransformationMatcher(getOpMatcher());
218-
}
219-
return transformationMatcher;
220-
}
217+
// private OpTransformationMatcher getTransformationMatcher() {
218+
// if (transformationMatcher == null) {
219+
// transformationMatcher = new DefaultOpTransformationMatcher(getOpMatcher());
220+
// }
221+
// return transformationMatcher;
222+
// }
221223

222224
/**
223225
* Attempts to inject {@link OpDependency} annotated fields of the specified
@@ -265,49 +267,115 @@ public <T> T findOpInstance(final String opName, final Nil<T> specialType, final
265267
public Object findOpInstance(final String opName, final OpRef ref, boolean adaptable) {
266268
Object op = null;
267269
OpCandidate match = null;
268-
OpTransformationCandidate transformation = null;
270+
AdaptedOp adaptation = null;
269271
try {
270272
// Find single match which matches the specified types
271273
match = getOpMatcher().findSingleMatch(this, ref);
272274
final List<Object> dependencies = resolveOpDependencies(match);
273275
op = match.createOp(dependencies);
274276
} catch (OpMatchingException e) {
275277
log.debug("No matching Op for request: " + ref + "\n");
276-
if(adaptable == false) {
278+
if (adaptable == false) {
277279
throw new IllegalArgumentException(opName + " cannot be adapted (adaptation is disabled)");
278280
}
279-
log.debug("Attempting Op transformation...");
280-
281-
// If we can't find an op matching the original request, we try to find a
282-
// transformation
283-
transformation = getTransformationMatcher().findTransformation(this, getTransformerIndex(), ref);
284-
if (transformation == null) {
285-
log.debug("No matching Op transformation found");
286-
throw new IllegalArgumentException(e);
287-
}
288-
289-
// If we found one, try to do transformation and return transformed op
290-
log.debug("Matching Op transformation found:\n" + transformation + "\n");
281+
log.debug("Attempting Op adaptation...");
291282
try {
292-
final List<Object> dependencies = resolveOpDependencies(transformation.getSourceOp());
293-
op = transformation.exceute(this, dependencies);
294-
} catch (OpMatchingException | OpTransformationException e1) {
295-
throw new IllegalArgumentException("Execution of Op transformatioon failed:\n" + e1);
283+
adaptation = adaptOp(opName, ref);
284+
op = adaptation.op();
285+
} catch (OpMatchingException e1) {
286+
log.debug("No suitable Op adaptation found");
287+
throw new IllegalArgumentException(e1);
296288
}
289+
297290
}
298291
try {
299292
// Try to resolve annotated OpDependency fields
300293
if (match != null)
301294
resolveOpDependencies(match);
302-
else if (transformation != null)
303-
resolveOpDependencies(transformation.getSourceOp());
304295
} catch (OpMatchingException e) {
305296
throw new IllegalArgumentException(e);
306297
}
307-
Object wrappedOp = wrapOp(op, match, transformation);
298+
OpInfo adaptedInfo = adaptation == null ? null : adaptation.srcInfo();
299+
Object wrappedOp = wrapOp(op, match, adaptedInfo);
308300
return wrappedOp;
309301
}
310302

303+
public AdaptedOp adaptOp(String opName, OpRef ref) throws OpMatchingException {
304+
305+
// TODO: support all types of ref
306+
// TODO: multi-stage adaptations (do we need this if we are not doing OpRunners
307+
// anymore?)
308+
// TODO: prevent searching for Op types that have already been searched for
309+
// TODO: export code to helper method.
310+
Type opType = ref.getTypes()[0];
311+
List<OpInfo> adaptors = opCache.get("adapt");
312+
313+
// create a priority queue to store suitable transformations.
314+
Comparator<OpCandidate> comp = (OpCandidate i1,
315+
OpCandidate i2) -> i1.opInfo().priority() < i2.opInfo().priority() ? -1
316+
: i1.opInfo().priority() == i2.opInfo().priority() ? 0 : 1;
317+
Queue<OpCandidate> suitableAdaptors = new PriorityQueue<>(comp);
318+
319+
// create an OpCandidate list of suitable adaptors
320+
for (OpInfo adaptor : adaptors) {
321+
Type adaptTo = adaptor.output().getType();
322+
Map<TypeVariable<?>, Type> map = new HashMap<>();
323+
// make sure that the adaptor outputs the correct type
324+
if (!Types.isAssignable(opType, adaptTo, map))
325+
continue;
326+
// make sure that the adaptor is a Function (so we can cast it later)
327+
if (Types.isInstance(adaptor.opType(), Function.class)) {
328+
log.debug(adaptor + " is an illegal adaptor Op: must be a Function");
329+
continue;
330+
}
331+
// build the type of fromOp (we know there must be one input because the adaptor
332+
// is a Function)
333+
Type adaptFrom = adaptor.inputs().get(0).getType();
334+
Type refAdaptTo = Types.substituteTypeVariables(adaptTo, map);
335+
Type refAdaptFrom = Types.substituteTypeVariables(adaptFrom, map);
336+
337+
// build the OpRef of the adaptor.
338+
Type refType = Types.parameterize(Function.class, new Type[] { refAdaptFrom, refAdaptTo });
339+
OpRef adaptorRef = new OpRef("adapt", new Type[] { refType }, refAdaptTo, new Type[] { refAdaptFrom });
340+
341+
// make an OpCandidate
342+
suitableAdaptors.add(new OpCandidate(this, log, adaptorRef, adaptor, map));
343+
}
344+
345+
while (suitableAdaptors.size() > 0) {
346+
OpCandidate adaptor = suitableAdaptors.remove();
347+
try {
348+
// resolve adaptor dependencies and get the adaptor (as a function) //TODO
349+
final List<Object> dependencies = resolveOpDependencies(adaptor);
350+
// adaptor.setStatus(StatusCode.MATCH);
351+
Object adaptorOp = adaptor.opInfo().createOpInstance(dependencies).object();
352+
353+
// grab the first type parameter (from the OpCandidate?) and search for an Op
354+
// that will then be adapted (this will be the first (only) type in the args of
355+
// the adaptor)
356+
Type adaptFrom = adaptor.paddedArgs()[0];
357+
final OpRef inferredRef = inferOpRef(adaptFrom, opName, adaptor.typeVarAssigns());
358+
// TODO: export this to another function (also done in findOpInstance).
359+
// We need this here because we need to grab the OpInfo.
360+
// TODO: is there a better way to do this?
361+
final OpCandidate srcCandidate = getOpMatcher().findSingleMatch(this, inferredRef);
362+
final List<Object> srcDependencies = resolveOpDependencies(srcCandidate);
363+
final Object fromOp = srcCandidate.opInfo().createOpInstance(srcDependencies).object();
364+
365+
// get adapted Op by applying adaptor on unadapted Op, then return
366+
// TODO: can we make this safer?
367+
@SuppressWarnings("unchecked")
368+
Object toOp = ((Function<Object, Object>) adaptorOp).apply(fromOp);
369+
return new AdaptedOp(toOp, srcCandidate.opInfo(), adaptor.opInfo());
370+
} catch (OpMatchingException e1) {
371+
continue;
372+
}
373+
374+
}
375+
// no adaptors available.
376+
throw new OpMatchingException("Op adaptation failed: no adaptable Ops of type " + opName);
377+
}
378+
311379
/**
312380
* Wraps the matched op into an {@link Op} that knows its generic typing and
313381
* {@link OpInfo}.
@@ -322,15 +390,15 @@ else if (transformation != null)
322390
* needed.
323391
* @return an {@link Op} wrapping of op.
324392
*/
325-
private Object wrapOp(Object op, OpCandidate match, OpTransformationCandidate transformation) {
393+
private Object wrapOp(Object op, OpCandidate match, OpInfo adaptationSrcInfo) {
326394
if (wrappers == null)
327395
initWrappers();
328396

329397
// TODO: we don't want to wrap OpRunners, do we? What is the point?
330398
if (OpRunner.class.isInstance(op))
331399
return op;
332400

333-
OpInfo opInfo = match == null ? transformation.getSourceOp().opInfo() : match.opInfo();
401+
OpInfo opInfo = match == null ? adaptationSrcInfo : match.opInfo();
334402
// FIXME: this type is not necessarily Computer, Function, etc. but often
335403
// something more specific (like the class of an Op).
336404
Type type = opInfo.opType();
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package org.scijava.ops.adapt;
2+
3+
import java.util.function.BiFunction;
4+
import java.util.function.Function;
5+
6+
import org.scijava.ops.OpDependency;
7+
import org.scijava.ops.core.Op;
8+
import org.scijava.ops.function.Computers;
9+
import org.scijava.ops.function.Computers.Arity1;
10+
import org.scijava.ops.function.Computers.Arity2;
11+
import org.scijava.ops.function.Functions;
12+
import org.scijava.ops.function.Producer;
13+
import org.scijava.param.Parameter;
14+
import org.scijava.plugin.Plugin;
15+
16+
public class ComputersToFunctions {
17+
18+
@Plugin(type = Op.class, name = "adapt")
19+
@Parameter(key = "fromOp")
20+
@Parameter(key = "toOp")
21+
public static class ComputerToFunctionViaFunction<I, O>
22+
implements Function<Computers.Arity1<I, O>, Function<I, O>> {
23+
24+
@OpDependency(name = "create", adaptable = false)
25+
Function<I, O> creator;
26+
27+
@Override
28+
public Function<I, O> apply(Arity1<I, O> computer) {
29+
return (in) -> {
30+
O out = creator.apply(in);
31+
computer.compute(in, out);
32+
return out;
33+
};
34+
}
35+
36+
}
37+
38+
// TODO: move to another file
39+
@Plugin(type = Op.class, name = "adapt")
40+
@Parameter(key = "fromOp")
41+
@Parameter(key = "toOp")
42+
public static class ComputerToFunctionViaSource<I, O> implements Function<Computers.Arity1<I, O>, Function<I, O>> {
43+
44+
@OpDependency(name = "create", adaptable = false)
45+
Producer<O> creator;
46+
47+
@Override
48+
public Function<I, O> apply(Arity1<I, O> computer) {
49+
return (in) -> {
50+
O out = creator.create();
51+
computer.compute(in, out);
52+
return out;
53+
};
54+
}
55+
56+
}
57+
58+
@Plugin(type = Op.class, name = "adapt")
59+
@Parameter(key = "fromOp")
60+
@Parameter(key = "toOp")
61+
public static class BiComputerToBiFunctionViaFunction<I1, I2, O>
62+
implements Function<Computers.Arity2<I1, I2, O>, BiFunction<I1, I2, O>> {
63+
64+
@OpDependency(name = "create", adaptable = false)
65+
BiFunction<I1, I2, O> creator;
66+
67+
@Override
68+
public BiFunction<I1, I2, O> apply(Arity2<I1, I2, O> computer) {
69+
return (in1, in2) -> {
70+
O out = creator.apply(in1, in2);
71+
computer.compute(in1, in2, out);
72+
return out;
73+
};
74+
}
75+
76+
}
77+
78+
// TODO: move to another file
79+
@Plugin(type = Op.class, name = "adapt")
80+
@Parameter(key = "fromOp")
81+
@Parameter(key = "toOp")
82+
public static class BiComputerToFunctionViaSource<I1, I2, O>
83+
implements Function<Computers.Arity2<I1, I2, O>, BiFunction<I1, I2, O>> {
84+
85+
@OpDependency(name = "create", adaptable = false)
86+
Producer<O> creator;
87+
88+
@Override
89+
public BiFunction<I1, I2, O> apply(Arity2<I1, I2, O> computer) {
90+
return (in1, in2) -> {
91+
O out = creator.create();
92+
computer.compute(in1, in2, out);
93+
return out;
94+
};
95+
}
96+
97+
}
98+
99+
@Plugin(type = Op.class, name = "adapt")
100+
@Parameter(key = "fromOp")
101+
@Parameter(key = "toOp")
102+
public static class Computer3ToFunction3ViaFunction<I1, I2, I3, O>
103+
implements Function<Computers.Arity3<I1, I2, I3, O>, Functions.Arity3<I1, I2, I3, O>> {
104+
105+
@OpDependency(name = "create", adaptable = false)
106+
Functions.Arity3<I1, I2, I3, O> creator;
107+
108+
@Override
109+
public Functions.Arity3<I1, I2, I3, O> apply(Computers.Arity3<I1, I2, I3, O> computer) {
110+
return (in1, in2, in3) -> {
111+
O out = creator.apply(in1, in2, in3);
112+
computer.compute(in1, in2, in3, out);
113+
return out;
114+
};
115+
}
116+
117+
}
118+
119+
// TODO: move to another file
120+
@Plugin(type = Op.class, name = "adapt")
121+
@Parameter(key = "fromOp")
122+
@Parameter(key = "toOp")
123+
public static class Computer3ToFunction3ViaSource<I1, I2, I3, O>
124+
implements Function<Computers.Arity3<I1, I2, I3, O>, Functions.Arity3<I1, I2, I3, O>> {
125+
126+
@OpDependency(name = "create", adaptable = false)
127+
Producer<O> creator;
128+
129+
@Override
130+
public Functions.Arity3<I1, I2, I3, O> apply(Computers.Arity3<I1, I2, I3, O> computer) {
131+
return (in1, in2, in3) -> {
132+
O out = creator.create();
133+
computer.compute(in1, in2, in3, out);
134+
return out;
135+
};
136+
}
137+
138+
}
139+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.scijava.ops.adapt;
2+
3+
import java.util.function.Function;
4+
5+
import org.scijava.ops.OpDependency;
6+
import org.scijava.ops.core.Op;
7+
import org.scijava.ops.function.Computers;
8+
import org.scijava.ops.function.Computers.Arity1;
9+
import org.scijava.param.Parameter;
10+
import org.scijava.plugin.Plugin;
11+
12+
public class FunctionsToComputers {
13+
14+
@Plugin(type = Op.class, name = "adapt")
15+
@Parameter(key = "fromOp")
16+
@Parameter(key = "toOp")
17+
public static class FunctionToComputer<I, O> implements Function<Function<I, O>, Computers.Arity1<I, O>> {
18+
19+
@OpDependency(name = "copy", adaptable = false)
20+
Arity1<O, O> copyOp;
21+
22+
@Override
23+
public Arity1<I, O> apply(Function<I, O> function) {
24+
return (in, out) -> {
25+
O temp = function.apply(in);
26+
copyOp.compute(temp, out);
27+
};
28+
}
29+
30+
}
31+
}

0 commit comments

Comments
 (0)