Skip to content

Commit 7f4366c

Browse files
gselzerctrueden
authored andcommitted
Support functions as parameters
And improve generic type reasoning as needed to facilitate it: * isApplicable: better ParameterizedType checking * isAssignable: better typeVariable checking * Improve Op wrapping * Register anys in isSafeAssignable * Allow Any upper and lower bounds Still need to ensure correct bounds checking in Types.isAssignable and Types.isApplicable. Signed-off-by: Curtis Rueden <ctrueden@wisc.edu>
1 parent 4ed2b33 commit 7f4366c

25 files changed

Lines changed: 2518 additions & 160 deletions

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

Lines changed: 129 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
package org.scijava.ops;
3030

3131
import java.lang.reflect.Field;
32+
import java.lang.reflect.InvocationTargetException;
33+
import java.lang.reflect.Modifier;
3234
import java.lang.reflect.Type;
3335
import java.lang.reflect.TypeVariable;
3436
import java.util.ArrayList;
@@ -45,6 +47,34 @@
4547
import org.scijava.log.LogService;
4648
import org.scijava.ops.core.Op;
4749
import org.scijava.ops.core.OpCollection;
50+
import org.scijava.ops.core.computer.ComputerOps.BiComputerOp;
51+
import org.scijava.ops.core.computer.ComputerOps.Computer3Op;
52+
import org.scijava.ops.core.computer.ComputerOps.Computer4Op;
53+
import org.scijava.ops.core.computer.ComputerOps.Computer5Op;
54+
import org.scijava.ops.core.computer.ComputerOps.Computer6Op;
55+
import org.scijava.ops.core.computer.ComputerOps.Computer7Op;
56+
import org.scijava.ops.core.computer.ComputerOps.Computer8Op;
57+
import org.scijava.ops.core.computer.ComputerOps.ComputerOp;
58+
import org.scijava.ops.core.function.FunctionOps.BiFunctionOp;
59+
import org.scijava.ops.core.function.FunctionOps.Function3Op;
60+
import org.scijava.ops.core.function.FunctionOps.Function4Op;
61+
import org.scijava.ops.core.function.FunctionOps.Function5Op;
62+
import org.scijava.ops.core.function.FunctionOps.Function6Op;
63+
import org.scijava.ops.core.function.FunctionOps.Function7Op;
64+
import org.scijava.ops.core.function.FunctionOps.Function8Op;
65+
import org.scijava.ops.core.function.FunctionOps.Function9Op;
66+
import org.scijava.ops.core.function.FunctionOps.FunctionOp;
67+
import org.scijava.ops.core.function.SourceOp;
68+
import org.scijava.ops.core.inplace.InplaceOps.BiInplaceFirstOp;
69+
import org.scijava.ops.core.inplace.InplaceOps.BiInplaceSecondOp;
70+
import org.scijava.ops.core.inplace.InplaceOps.Inplace3FirstOp;
71+
import org.scijava.ops.core.inplace.InplaceOps.Inplace3SecondOp;
72+
import org.scijava.ops.core.inplace.InplaceOps.Inplace3ThirdOp;
73+
import org.scijava.ops.core.inplace.InplaceOps.Inplace4FirstOp;
74+
import org.scijava.ops.core.inplace.InplaceOps.Inplace5FirstOp;
75+
import org.scijava.ops.core.inplace.InplaceOps.Inplace6FirstOp;
76+
import org.scijava.ops.core.inplace.InplaceOps.Inplace7SecondOp;
77+
import org.scijava.ops.core.inplace.InplaceOps.InplaceOp;
4878
import org.scijava.ops.matcher.OpCandidate;
4979
import org.scijava.ops.matcher.OpClassInfo;
5080
import org.scijava.ops.matcher.OpFieldInfo;
@@ -107,6 +137,46 @@ public class OpService extends AbstractService implements SciJavaService, OpEnvi
107137
*/
108138
private Map<String, String> opAliases = new HashMap<>();
109139

140+
private static Map<Class<?>, Class<?>> wrappers = wrappers();
141+
142+
private static Map<Class<?>, Class<?>> wrappers() {
143+
final Map<Class<?>, Class<?>> result = new HashMap<>();
144+
final Class<?>[] wrapperClasses = { //
145+
FunctionOp.class, //
146+
BiFunctionOp.class, //
147+
Function3Op.class, //
148+
Function4Op.class, //
149+
Function5Op.class, //
150+
Function6Op.class, //
151+
Function7Op.class, //
152+
Function8Op.class, //
153+
Function9Op.class, //
154+
ComputerOp.class, //
155+
BiComputerOp.class, //
156+
Computer3Op.class, //
157+
Computer4Op.class, //
158+
Computer5Op.class, //
159+
Computer6Op.class, //
160+
Computer7Op.class, //
161+
Computer8Op.class, //
162+
InplaceOp.class, //
163+
BiInplaceFirstOp.class, //
164+
BiInplaceSecondOp.class, //
165+
Inplace3FirstOp.class, //
166+
Inplace3SecondOp.class, //
167+
Inplace3ThirdOp.class, //
168+
Inplace4FirstOp.class, //
169+
Inplace5FirstOp.class, //
170+
Inplace6FirstOp.class, //
171+
Inplace7SecondOp.class, //
172+
SourceOp.class
173+
};
174+
for (final Class<?> c : wrapperClasses) {
175+
result.put(c.getInterfaces()[0], c);
176+
}
177+
return result;
178+
}
179+
110180
public void initOpCache() {
111181
opCache = new PrefixTree<>();
112182

@@ -124,12 +194,18 @@ public void initOpCache() {
124194
// Add Ops contained in an OpCollection
125195
for (final PluginInfo<OpCollection> pluginInfo : pluginService.getPluginsOfType(OpCollection.class)) {
126196
try {
127-
final List<Field> fields = ClassUtils.getAnnotatedFields(pluginInfo.loadClass(), OpField.class);
197+
Class<? extends OpCollection> c = pluginInfo.loadClass();
198+
final List<Field> fields = ClassUtils.getAnnotatedFields(c, OpField.class);
199+
Object instance = null;
128200
for (Field field : fields) {
129-
OpInfo opInfo = new OpFieldInfo(field);
201+
final boolean isStatic = Modifier.isStatic(field.getModifiers());
202+
if (!isStatic && instance == null) {
203+
instance = field.getDeclaringClass().newInstance();
204+
}
205+
OpInfo opInfo = new OpFieldInfo(isStatic ? null : instance, field);
130206
addToCache(opInfo, field.getAnnotation(OpField.class).names());
131207
}
132-
} catch (InstantiableException exc) {
208+
} catch (InstantiableException | InstantiationException | IllegalAccessException exc) {
133209
log.error("Can't load class from plugin info: " + pluginInfo.toString(), exc);
134210
}
135211
}
@@ -268,7 +344,53 @@ else if (transformation != null)
268344
} catch (OpMatchingException e) {
269345
throw new IllegalArgumentException(e);
270346
}
271-
return op;
347+
Object wrappedOp = wrapOp(op, match, transformation);
348+
return wrappedOp;
349+
}
350+
351+
/**
352+
* Wraps the matched op into an {@link Op} that knows its generic typing and
353+
* {@link OpInfo}.
354+
*
355+
* @param op
356+
* - the matched op to wrap.
357+
* @param match
358+
* - where to retrieve the {@link OpInfo} if no transformation is
359+
* needed.
360+
* @param transformation
361+
* - where to retrieve the {@link OpInfo} if a transformation is
362+
* needed.
363+
* @return an {@link Op} wrapping of op.
364+
*/
365+
private Object wrapOp(Object op, OpCandidate match, OpTransformationCandidate transformation) {
366+
// TODO: we don't want to wrap OpRunners, do we? What is the point?
367+
if(OpRunner.class.isInstance(op)) return op;
368+
369+
OpInfo opInfo = match == null ? transformation.getSourceOp().opInfo() : match.opInfo();
370+
// FIXME: this type is not necessarily Computer, Function, etc. but often
371+
// something more specific (like the class of an Op).
372+
Type type = opInfo.opType();
373+
try {
374+
// determine the Op wrappers that could wrap the matched Op
375+
Class<?>[] suitableWrappers = wrappers.keySet().stream().filter(wrapper -> wrapper.isInstance(op))
376+
.toArray(Class[]::new);
377+
if (suitableWrappers.length == 0)
378+
throw new IllegalArgumentException(opInfo.implementationName() + ": matched op Type " + type.getClass()
379+
+ " does not match a wrappable Op type.");
380+
if (suitableWrappers.length > 1)
381+
throw new IllegalArgumentException(
382+
"Matched op Type " + type.getClass() + " matches multiple Op types: " + wrappers.toString());
383+
// get the wrapper and wrap up the Op
384+
Class<?> wrapper = wrappers.get(suitableWrappers[0]);
385+
return wrapper.getConstructors()[0].newInstance(op, type, opInfo);
386+
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
387+
| SecurityException exc) {
388+
log.error(exc.getMessage() != null ? exc.getMessage() : "Cannot wrap " + op.getClass());
389+
return op;
390+
} catch (NullPointerException e) {
391+
log.error("No wrapper exists for " + Types.raw(type).toString() + ".");
392+
return op;
393+
}
272394
}
273395

274396
public <T> T findOp(final String opName, final Nil<T> specialType, final Nil<?>[] inTypes, final Nil<?> outType) {
@@ -358,9 +480,9 @@ private void addAliases(String[] opNames, String opImpl) {
358480
}
359481
if (opAliases.containsKey(alias)) {
360482
if (!opAliases.get(alias).equals(opName)) {
361-
log.warn("Possible naming clash for op '" + opImpl + "' detected. Attempting to add alias '" + alias
362-
+ "' for op name '" + opName + "'. However the alias '" + alias + "' is already "
363-
+ "associated with op name '" + opAliases.get(alias) + "'.");
483+
// log.warn("Possible naming clash for op '" + opImpl + "' detected. Attempting to add alias '" + alias
484+
// + "' for op name '" + opName + "'. However the alias '" + alias + "' is already "
485+
// + "associated with op name '" + opAliases.get(alias) + "'.");
364486
}
365487
continue;
366488
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.scijava.ops.core;
2+
3+
import org.scijava.ops.matcher.OpInfo;
4+
import org.scijava.ops.types.GenericTyped;
5+
6+
public abstract class GenericOp implements GenericTyped{
7+
8+
OpInfo opInfo;
9+
10+
public GenericOp(OpInfo opInfo) {
11+
this.opInfo = opInfo;
12+
}
13+
14+
public OpInfo getOpInfo() {
15+
return opInfo;
16+
}
17+
18+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.scijava.ops.core.computer;
2+
3+
import org.scijava.ops.core.Consumer10;
4+
import org.scijava.param.Mutable;
5+
6+
@FunctionalInterface
7+
public interface Computer9<I1, I2, I3, I4, I5, I6, I7, I8, I9, O> extends Consumer10<I1, I2, I3, I4, I5, I6, I7, I8, I9, O> {
8+
void compute(I1 in1, I2 in2, I3 in3, I4 in4, I5 in5, I6 in6, I7 in7, I8 in8, I9 in9, @Mutable O out);
9+
10+
@Override
11+
default void accept(I1 in1, I2 in2, I3 in3, I4 in4, I5 in5, I6 in6, I7 in7, I8 in8, I9 in9, O out) {
12+
compute(in1, in2, in3, in4, in5, in6, in7, in8, in9, out);
13+
}
14+
}

0 commit comments

Comments
 (0)