Skip to content

Commit ceca8fc

Browse files
authored
Merge pull request #44 from scijava/adapt-ops
Replace existing Transformation system with Op-based adaptations
2 parents ae8405f + b35e57b commit ceca8fc

File tree

112 files changed

+25349
-14865
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

112 files changed

+25349
-14865
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ public OpDependency getAnnotation() {
6161
public String getDependencyName() {
6262
return annotation.name();
6363
}
64+
65+
@Override
66+
public boolean isAdaptable() {
67+
return annotation.adaptable();
68+
}
6469

6570
// -- Member methods --
6671

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@
1212

1313
/** The name of the Op to inject. */
1414
String name();
15+
16+
/** Set to false if the dependency should not be adapted */
17+
boolean adaptable() default true;
1518
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
public interface OpDependencyMember<T> extends Member<T> {
4040

4141
String getDependencyName();
42+
43+
boolean isAdaptable();
4244

4345
// -- Member methods --
4446

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

Lines changed: 105 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -30,36 +30,36 @@
3030

3131
import java.lang.reflect.Field;
3232
import java.lang.reflect.Modifier;
33+
import java.lang.reflect.ParameterizedType;
3334
import java.lang.reflect.Type;
3435
import java.lang.reflect.TypeVariable;
3536
import java.util.ArrayList;
3637
import java.util.Arrays;
38+
import java.util.Collections;
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;
4750
import org.scijava.log.LogService;
51+
import org.scijava.ops.adapt.AdaptedOp;
4852
import org.scijava.ops.core.Op;
4953
import org.scijava.ops.core.OpCollection;
5054
import org.scijava.ops.matcher.DefaultOpMatcher;
55+
import org.scijava.ops.matcher.MatchingUtils;
5156
import org.scijava.ops.matcher.OpCandidate;
5257
import org.scijava.ops.matcher.OpClassInfo;
5358
import org.scijava.ops.matcher.OpFieldInfo;
5459
import org.scijava.ops.matcher.OpInfo;
5560
import org.scijava.ops.matcher.OpMatcher;
5661
import org.scijava.ops.matcher.OpMatchingException;
5762
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.OpTransformationMatcher;
62-
import org.scijava.ops.transform.OpTransformer;
6363
import org.scijava.ops.types.Nil;
6464
import org.scijava.ops.types.TypeService;
6565
import org.scijava.ops.util.OpWrapper;
@@ -93,8 +93,6 @@ public class OpService extends AbstractService implements SciJavaService, OpEnvi
9393
@Parameter
9494
private LogService log;
9595

96-
private OpTransformationMatcher transformationMatcher;
97-
9896
@Parameter
9997
private TypeService typeService;
10098

@@ -109,8 +107,6 @@ public class OpService extends AbstractService implements SciJavaService, OpEnvi
109107
*/
110108
private Map<String, List<OpInfo>> opCache;
111109

112-
private List<OpTransformer> transformerIndex;
113-
114110
private Map<Class<?>, OpWrapper<?>> wrappers;
115111

116112
private void initOpCache() {
@@ -171,10 +167,6 @@ private void addToOpIndex(final OpInfo opInfo, final String opNames) {
171167
}
172168
}
173169

174-
public synchronized void initTransformerIndex() {
175-
transformerIndex = pluginService.createInstancesOfType(OpTransformer.class);
176-
}
177-
178170
@Override
179171
public Iterable<OpInfo> infos() {
180172
if (opCache == null) {
@@ -204,20 +196,6 @@ private OpMatcher getOpMatcher() {
204196
return opMatcher;
205197
}
206198

207-
private synchronized List<OpTransformer> getTransformerIndex() {
208-
if (transformerIndex == null) {
209-
initTransformerIndex();
210-
}
211-
return transformerIndex;
212-
}
213-
214-
private OpTransformationMatcher getTransformationMatcher() {
215-
if (transformationMatcher == null) {
216-
transformationMatcher = new DefaultOpTransformationMatcher(getOpMatcher());
217-
}
218-
return transformationMatcher;
219-
}
220-
221199
/**
222200
* Attempts to inject {@link OpDependency} annotated fields of the specified
223201
* object by looking for Ops matching the field type and the name specified in
@@ -242,7 +220,7 @@ private List<Object> resolveOpDependencies(OpCandidate op) throws OpMatchingExce
242220
+ "method inputs and outputs of Op dependency field: " + dependency.getKey());
243221
}
244222
try {
245-
resolvedDependencies.add(findOpInstance(dependencyName, inferredRef));
223+
resolvedDependencies.add(findOpInstance(dependencyName, inferredRef, dependency.isAdaptable()));
246224
} catch (final Exception e) {
247225
throw new OpMatchingException("Could not find Op that matches requested Op dependency:" + "\nOp class: "
248226
+ op.opInfo().implementationName() + //
@@ -258,52 +236,128 @@ public <T> T findOpInstance(final String opName, final Nil<T> specialType, final
258236
final Nil<?> outType) {
259237
final OpRef ref = OpRef.fromTypes(opName, toTypes(specialType), outType != null ? outType.getType() : null,
260238
toTypes(inTypes));
261-
return (T) findOpInstance(opName, ref);
239+
return (T) findOpInstance(opName, ref, true);
262240
}
263241

264-
public Object findOpInstance(final String opName, final OpRef ref) {
242+
public Object findOpInstance(final String opName, final OpRef ref, boolean adaptable) {
265243
Object op = null;
266244
OpCandidate match = null;
267-
OpTransformationCandidate transformation = null;
245+
AdaptedOp adaptation = null;
268246
try {
269247
// Find single match which matches the specified types
270248
match = getOpMatcher().findSingleMatch(this, ref);
271249
final List<Object> dependencies = resolveOpDependencies(match);
272250
op = match.createOp(dependencies);
273251
} catch (OpMatchingException e) {
274252
log.debug("No matching Op for request: " + ref + "\n");
275-
log.debug("Attempting Op transformation...");
276-
277-
// If we can't find an op matching the original request, we try to find a
278-
// transformation
279-
transformation = getTransformationMatcher().findTransformation(this, getTransformerIndex(), ref);
280-
if (transformation == null) {
281-
log.debug("No matching Op transformation found");
282-
throw new IllegalArgumentException(e);
253+
if (!adaptable) {
254+
throw new IllegalArgumentException(opName + " cannot be adapted (adaptation is disabled)");
283255
}
284-
285-
// If we found one, try to do transformation and return transformed op
286-
log.debug("Matching Op transformation found:\n" + transformation + "\n");
256+
log.debug("Attempting Op adaptation...");
287257
try {
288-
final List<Object> dependencies = resolveOpDependencies(transformation.getSourceOp());
289-
op = transformation.exceute(this, dependencies);
290-
} catch (OpMatchingException | OpTransformationException e1) {
291-
throw new IllegalArgumentException("Execution of Op transformatioon failed:\n" + e1);
258+
adaptation = adaptOp(ref);
259+
op = adaptation.op();
260+
} catch (OpMatchingException e1) {
261+
log.debug("No suitable Op adaptation found");
262+
throw new IllegalArgumentException(e1);
292263
}
264+
293265
}
294266
try {
295267
// Try to resolve annotated OpDependency fields
268+
// N.B. Adapted Op dependency fields are already matched.
296269
if (match != null)
297270
resolveOpDependencies(match);
298-
else if (transformation != null)
299-
resolveOpDependencies(transformation.getSourceOp());
300271
} catch (OpMatchingException e) {
301272
throw new IllegalArgumentException(e);
302273
}
303-
Object wrappedOp = wrapOp(op, match, transformation);
274+
OpInfo adaptedInfo = adaptation == null ? null : adaptation.opInfo();
275+
Object wrappedOp = wrapOp(op, match, adaptedInfo);
304276
return wrappedOp;
305277
}
306278

279+
/**
280+
* Adapts an Op with the name of ref into a type that can be SAFELY cast to ref.
281+
*
282+
* @param ref
283+
* - the type of Op that we are looking to adapt to.
284+
* @return {@link AdaptedOp} - an Op that has been adapted to conform the the
285+
* ref type.
286+
* @throws OpMatchingException
287+
*/
288+
private AdaptedOp adaptOp(OpRef ref) throws OpMatchingException {
289+
Type opType = ref.getTypes()[0];
290+
List<OpInfo> adaptors = new ArrayList<>(opCache.get("adapt"));
291+
Collections.sort(adaptors, (OpInfo i1, OpInfo i2) -> i1.priority() < i2.priority() ? 1 : i1.priority() == i2.priority() ? 0 : -1);
292+
293+
for (final OpInfo adaptor : adaptors) {
294+
Type adaptTo = adaptor.output().getType();
295+
Map<TypeVariable<?>, Type> map = new HashMap<>();
296+
// make sure that the adaptor outputs the correct type
297+
if (opType instanceof ParameterizedType) {
298+
// TODO: remove try/catch
299+
try {
300+
if (!MatchingUtils.checkGenericAssignability(adaptTo, (ParameterizedType) opType, map, true))
301+
continue;
302+
} catch (IllegalArgumentException e) {
303+
continue;
304+
}
305+
} else if (!Types.isAssignable(opType, adaptTo, map)) {
306+
continue;
307+
}
308+
// make sure that the adaptor is a Function (so we can cast it later)
309+
if (Types.isInstance(adaptor.opType(), Function.class)) {
310+
log.debug(adaptor + " is an illegal adaptor Op: must be a Function");
311+
continue;
312+
}
313+
// build the type of fromOp (we know there must be one input because the adaptor
314+
// is a Function)
315+
Type adaptFrom = adaptor.inputs().get(0).getType();
316+
Type refAdaptTo = Types.substituteTypeVariables(adaptTo, map);
317+
Type refAdaptFrom = Types.substituteTypeVariables(adaptFrom, map);
318+
319+
// build the OpRef of the adaptor.
320+
Type refType = Types.parameterize(Function.class, new Type[] { refAdaptFrom, refAdaptTo });
321+
OpRef adaptorRef = new OpRef("adapt", new Type[] { refType }, refAdaptTo, new Type[] { refAdaptFrom });
322+
323+
// make an OpCandidate
324+
OpCandidate candidate = new OpCandidate(this, log, adaptorRef, adaptor, map);
325+
326+
try {
327+
// resolve adaptor dependencies and get the adaptor (as a function)
328+
final List<Object> dependencies = resolveOpDependencies(candidate);
329+
Object adaptorOp = adaptor.createOpInstance(dependencies).object();
330+
331+
// grab the first type parameter (from the OpCandidate?) and search for an Op
332+
// that will then be adapted (this will be the first (only) type in the args of
333+
// the adaptor)
334+
Type srcOpType = adaptor.inputs().get(0).getType();
335+
final OpRef srcOpRef;
336+
srcOpRef = inferOpRef(srcOpType, ref.getName(), candidate.typeVarAssigns());
337+
// TODO: export this to another function (also done in findOpInstance).
338+
// We need this here because we need to grab the OpInfo.
339+
// TODO: is there a better way to do this?
340+
final OpCandidate srcCandidate = getOpMatcher().findSingleMatch(this, srcOpRef);
341+
final List<Object> srcDependencies = resolveOpDependencies(srcCandidate);
342+
final Object fromOp = srcCandidate.opInfo().createOpInstance(srcDependencies).object();
343+
344+
// get adapted Op by applying adaptor on unadapted Op, then return
345+
// TODO: can we make this safer?
346+
@SuppressWarnings("unchecked")
347+
Object toOp = ((Function<Object, Object>) adaptorOp).apply(fromOp);
348+
// construct type of adapted op
349+
Type adapterOpType = Types.substituteTypeVariables(adaptor.output().getType(),
350+
srcCandidate.typeVarAssigns());
351+
return new AdaptedOp(toOp, adapterOpType, srcCandidate.opInfo(), adaptor);
352+
} catch (OpMatchingException e1) {
353+
log.trace(e1);
354+
}
355+
}
356+
357+
// no adaptors available.
358+
throw new OpMatchingException("Op adaptation failed: no adaptable Ops of type " + ref.getName());
359+
}
360+
307361
/**
308362
* Wraps the matched op into an {@link Op} that knows its generic typing and
309363
* {@link OpInfo}.
@@ -318,11 +372,11 @@ else if (transformation != null)
318372
* needed.
319373
* @return an {@link Op} wrapping of op.
320374
*/
321-
private Object wrapOp(Object op, OpCandidate match, OpTransformationCandidate transformation) {
375+
private Object wrapOp(Object op, OpCandidate match, OpInfo adaptationSrcInfo) {
322376
if (wrappers == null)
323377
initWrappers();
324378

325-
OpInfo opInfo = match == null ? transformation.getSourceOp().opInfo() : match.opInfo();
379+
OpInfo opInfo = match == null ? adaptationSrcInfo : match.opInfo();
326380
// FIXME: this type is not necessarily Computer, Function, etc. but often
327381
// something more specific (like the class of an Op).
328382
Type type = opInfo.opType();
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
package org.scijava.ops.adapt;
3+
4+
import java.lang.reflect.Type;
5+
6+
import org.scijava.ops.matcher.OpAdaptationInfo;
7+
import org.scijava.ops.matcher.OpInfo;
8+
9+
/**
10+
* Wrapper class combining an adapted Op with its adapted type and associated
11+
* {@link OpInfo}s
12+
*
13+
* @author Gabriel Selzer
14+
*/
15+
public class AdaptedOp {
16+
17+
private Object op;
18+
private Type type;
19+
private OpInfo srcInfo;
20+
private OpInfo adaptorInfo;
21+
22+
private OpInfo opInfo;
23+
24+
public AdaptedOp(Object op, Type type, OpInfo srcInfo, OpInfo adaptorInfo) {
25+
this.op = op;
26+
this.type = type;
27+
this.srcInfo = srcInfo;
28+
this.adaptorInfo= adaptorInfo;
29+
this.opInfo = new OpAdaptationInfo(srcInfo, this.type);
30+
31+
}
32+
33+
public Object op() {
34+
return op;
35+
}
36+
37+
public OpInfo srcInfo() {
38+
return srcInfo;
39+
}
40+
41+
public OpInfo adaptorInfo() {
42+
return adaptorInfo;
43+
}
44+
45+
public OpInfo opInfo() {
46+
return opInfo;
47+
}
48+
49+
@Override
50+
public String toString() {
51+
StringBuilder s = new StringBuilder();
52+
s.append("Source Op:\n\n" + srcInfo + "\n");
53+
s.append("With transformation: \n" + adaptorInfo);
54+
return s.toString();
55+
}
56+
}

0 commit comments

Comments
 (0)