Skip to content

Commit a564748

Browse files
committed
Adapt Ops replacing transformations: first cut
We also generate a bunch of elementary adapt ops Fixes made to the matcher in the process: * Improve any checking in parameterizedTypes Now, a Function<Function<I, O>, Function<Iterable<I>, Iterable<O>>> can be matched to a Function<Any, Function<Iterable<Double>, Iterable<Double>>> * Improve TypeVariable assignability Sometimes TypeVariables are assigned to Anys and sometimes this is correct. Sometimes, however, this is incorrect, and we can later assign this TypeVariable to a concrete object. (We do have to be careful, though, to make sure that the TypeVariable actually is assignable to that Object).
1 parent c635614 commit a564748

33 files changed

Lines changed: 4625 additions & 285 deletions

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

Lines changed: 76 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,16 @@
3434
import java.lang.reflect.TypeVariable;
3535
import java.util.ArrayList;
3636
import java.util.Arrays;
37+
import java.util.Collections;
38+
import java.util.Comparator;
3739
import java.util.EnumSet;
3840
import java.util.HashMap;
3941
import java.util.Iterator;
4042
import java.util.LinkedList;
4143
import java.util.List;
4244
import java.util.Map;
4345
import java.util.Map.Entry;
46+
import java.util.function.Function;
4447
import java.util.stream.Collectors;
4548

4649
import org.scijava.InstantiableException;
@@ -55,11 +58,10 @@
5558
import org.scijava.ops.matcher.OpMatcher;
5659
import org.scijava.ops.matcher.OpMatchingException;
5760
import org.scijava.ops.matcher.OpRef;
58-
import org.scijava.ops.transform.DefaultOpTransformationMatcher;
59-
import org.scijava.ops.transform.OpTransformationCandidate;
60-
import org.scijava.ops.transform.OpTransformationException;
61+
import org.scijava.ops.transform.AdaptedOp;
6162
import org.scijava.ops.transform.OpTransformationMatcher;
6263
import org.scijava.ops.transform.OpTransformer;
64+
import org.scijava.ops.types.Any;
6365
import org.scijava.ops.types.Nil;
6466
import org.scijava.ops.types.TypeService;
6567
import org.scijava.ops.util.OpWrapper;
@@ -211,12 +213,12 @@ private synchronized List<OpTransformer> getTransformerIndex() {
211213
return transformerIndex;
212214
}
213215

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

221223
/**
222224
* Attempts to inject {@link OpDependency} annotated fields of the specified
@@ -264,49 +266,91 @@ public <T> T findOpInstance(final String opName, final Nil<T> specialType, final
264266
public Object findOpInstance(final String opName, final OpRef ref, boolean adaptable) {
265267
Object op = null;
266268
OpCandidate match = null;
267-
OpTransformationCandidate transformation = null;
269+
AdaptedOp adaptation = null;
268270
try {
269271
// Find single match which matches the specified types
270272
match = getOpMatcher().findSingleMatch(this, ref);
271273
final List<Object> dependencies = resolveOpDependencies(match);
272274
op = match.createOp(dependencies);
273275
} catch (OpMatchingException e) {
274276
log.debug("No matching Op for request: " + ref + "\n");
275-
if(adaptable == false) {
277+
if (adaptable == false) {
276278
throw new IllegalArgumentException(opName + " cannot be adapted (adaptation is disabled)");
277279
}
278-
log.debug("Attempting Op transformation...");
279-
280-
// If we can't find an op matching the original request, we try to find a
281-
// transformation
282-
transformation = getTransformationMatcher().findTransformation(this, getTransformerIndex(), ref);
283-
if (transformation == null) {
284-
log.debug("No matching Op transformation found");
285-
throw new IllegalArgumentException(e);
286-
}
287-
288-
// If we found one, try to do transformation and return transformed op
289-
log.debug("Matching Op transformation found:\n" + transformation + "\n");
280+
log.debug("Attempting Op adaptation...");
290281
try {
291-
final List<Object> dependencies = resolveOpDependencies(transformation.getSourceOp());
292-
op = transformation.exceute(this, dependencies);
293-
} catch (OpMatchingException | OpTransformationException e1) {
294-
throw new IllegalArgumentException("Execution of Op transformatioon failed:\n" + e1);
282+
adaptation = adaptOp(ref);
283+
op = adaptation.op();
284+
} catch (OpMatchingException e1) {
285+
log.debug("No suitable Op adaptation found");
286+
throw new IllegalArgumentException(e1);
295287
}
288+
296289
}
297290
try {
298291
// Try to resolve annotated OpDependency fields
292+
// N.B. Adapted Op dependency fields are already matched.
299293
if (match != null)
300294
resolveOpDependencies(match);
301-
else if (transformation != null)
302-
resolveOpDependencies(transformation.getSourceOp());
303295
} catch (OpMatchingException e) {
304296
throw new IllegalArgumentException(e);
305297
}
306-
Object wrappedOp = wrapOp(op, match, transformation);
298+
// TODO: THIS IS WRONG! We need the OpInfo of the output of the adaptation!
299+
// This gives the OpInfo of the original (unadapted) op!
300+
OpInfo adaptedInfo = adaptation == null ? null : adaptation.srcInfo();
301+
Object wrappedOp = wrapOp(op, match, adaptedInfo);
307302
return wrappedOp;
308303
}
309304

305+
private AdaptedOp adaptOp(OpRef ref) throws OpMatchingException {
306+
307+
// TODO: support all types of ref
308+
// create an OpCandidate list of suitable adaptors
309+
Type adaptTo = ref.getTypes()[0];
310+
Type adaptFrom = new Any();
311+
Type refType = Types.parameterize(Function.class, new Type[] {adaptFrom, adaptTo});
312+
OpRef adaptorRef = new OpRef("adapt", new Type[] {refType}, adaptTo, new Type[] {adaptFrom});
313+
List<OpCandidate> adaptorCandidates = getOpMatcher().findCandidates(this, adaptorRef);
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+
Collections.sort(adaptorCandidates, comp);
318+
319+
while (adaptorCandidates.size() > 0) {
320+
OpCandidate adaptor = adaptorCandidates.remove(0);
321+
try {
322+
// resolve adaptor dependencies and get the adaptor (as a function) //TODO
323+
final List<Object> dependencies = resolveOpDependencies(adaptor);
324+
// adaptor.setStatus(StatusCode.MATCH);
325+
Object adaptorOp = adaptor.opInfo().createOpInstance(dependencies).object();
326+
327+
// grab the first type parameter (from the OpCandidate?) and search for an Op
328+
// that will then be adapted (this will be the first (only) type in the args of
329+
// the adaptor)
330+
Type srcOpType = adaptor.opInfo().inputs().get(0).getType();
331+
final OpRef srcOpRef;
332+
srcOpRef = inferOpRef(srcOpType, ref.getName(), adaptor.typeVarAssigns());
333+
// TODO: export this to another function (also done in findOpInstance).
334+
// We need this here because we need to grab the OpInfo.
335+
// TODO: is there a better way to do this?
336+
final OpCandidate srcCandidate = getOpMatcher().findSingleMatch(this, srcOpRef);
337+
final List<Object> srcDependencies = resolveOpDependencies(srcCandidate);
338+
final Object fromOp = srcCandidate.opInfo().createOpInstance(srcDependencies).object();
339+
340+
// get adapted Op by applying adaptor on unadapted Op, then return
341+
// TODO: can we make this safer?
342+
@SuppressWarnings("unchecked")
343+
Object toOp = ((Function<Object, Object>) adaptorOp).apply(fromOp);
344+
return new AdaptedOp(toOp, srcCandidate.opInfo(), adaptor.opInfo());
345+
} catch (OpMatchingException e1) {
346+
continue;
347+
}
348+
349+
}
350+
// no adaptors available.
351+
throw new OpMatchingException("Op adaptation failed: no adaptable Ops of type " + ref.getName());
352+
}
353+
310354
/**
311355
* Wraps the matched op into an {@link Op} that knows its generic typing and
312356
* {@link OpInfo}.
@@ -321,11 +365,11 @@ else if (transformation != null)
321365
* needed.
322366
* @return an {@link Op} wrapping of op.
323367
*/
324-
private Object wrapOp(Object op, OpCandidate match, OpTransformationCandidate transformation) {
368+
private Object wrapOp(Object op, OpCandidate match, OpInfo adaptationSrcInfo) {
325369
if (wrappers == null)
326370
initWrappers();
327371

328-
OpInfo opInfo = match == null ? transformation.getSourceOp().opInfo() : match.opInfo();
372+
OpInfo opInfo = match == null ? adaptationSrcInfo : match.opInfo();
329373
// FIXME: this type is not necessarily Computer, Function, etc. but often
330374
// something more specific (like the class of an Op).
331375
Type type = opInfo.opType();
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* #%L
3+
* SciJava Operations: a framework for reusable algorithms.
4+
* %%
5+
* Copyright (C) 2016 - 2019 SciJava Ops developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
/*
31+
* This is autogenerated source code -- DO NOT EDIT. Instead, edit the
32+
* corresponding template in templates/ and rerun bin/generate.groovy.
33+
*/
34+
35+
package org.scijava.ops.adapt.complexLift;
36+
37+
import java.util.function.Function;
38+
39+
import org.scijava.core.Priority;
40+
import org.scijava.ops.OpDependency;
41+
import org.scijava.ops.core.Op;
42+
import org.scijava.ops.function.Computers;
43+
import org.scijava.ops.function.Functions;
44+
import org.scijava.ops.function.Producer;
45+
import org.scijava.param.Parameter;
46+
import org.scijava.plugin.Plugin;
47+
48+
/**
49+
* Collection of adaptation Ops to convert {@link Computers} into
50+
* {@link Functions} with the use of a {@link Producer} that creates the output
51+
* using the first input as a model.
52+
*
53+
* @author Gabriel Selzer
54+
*/
55+
public class ComputersToFunctionsAndLift {
56+
57+
@Plugin(type = Op.class, name = "adapt", priority = Priority.LOW)
58+
@Parameter(key = "fromOp")
59+
@Parameter(key = "toOp")
60+
public static class Computer1ToFunction1AndLiftViaSource<I, O>
61+
implements Function<Computers.Arity1<I, O>, Function<Iterable<I>, Iterable<O>>> {
62+
63+
@OpDependency(name = "create", adaptable = false)
64+
Producer<O> creator;
65+
@OpDependency(name = "adapt", adaptable = false)
66+
Function<Function<I, O>, Function<Iterable<I>, Iterable<O>>> lifter;
67+
68+
@Override
69+
public Function<Iterable<I>, Iterable<O>> apply(Computers.Arity1<I, O> computer) {
70+
Function<I, O> adapted = (in) -> {
71+
O out = creator.create();
72+
computer.compute(in, out);
73+
return out;
74+
};
75+
return lifter.apply(adapted);
76+
}
77+
78+
}
79+
80+
@Plugin(type = Op.class, name = "adapt")
81+
@Parameter(key = "fromOp")
82+
@Parameter(key = "toOp")
83+
public static class Computer1ToFunction1AndLiftViaFunction<I, O>
84+
implements Function<Computers.Arity1<I, O>, Function<Iterable<I>, Iterable<O>>> {
85+
86+
@OpDependency(name = "create", adaptable = false)
87+
Function<I, O> creator;
88+
@OpDependency(name = "adapt", adaptable = false)
89+
Function<Function<I, O>, Function<Iterable<I>, Iterable<O>>> lifter;
90+
91+
@Override
92+
public Function<Iterable<I>, Iterable<O>> apply(Computers.Arity1<I, O> computer) {
93+
Function<I, O> adapted = (in) -> {
94+
O out = creator.apply(in);
95+
computer.compute(in, out);
96+
return out;
97+
};
98+
return lifter.apply(adapted);
99+
}
100+
101+
}
102+
}

0 commit comments

Comments
 (0)