Skip to content

Commit bdf85b8

Browse files
Treiblesschorlectrueden
authored andcommitted
Add automatic helper Op injection of fields annotated with @OpDependency
1 parent d78c537 commit bdf85b8

File tree

13 files changed

+270
-12
lines changed

13 files changed

+270
-12
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.scijava.ops;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/** Annotates a helper op as a field that should be auto injected.*/
9+
@Retention(RetentionPolicy.RUNTIME)
10+
@Target(ElementType.FIELD)
11+
public @interface OpDependency {
12+
13+
/** The name of the Op to inject. */
14+
String name();
15+
}

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

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.lang.reflect.Type;
3333
import java.util.ArrayList;
3434
import java.util.Arrays;
35+
import java.util.EnumSet;
3536
import java.util.HashMap;
3637
import java.util.Iterator;
3738
import java.util.LinkedList;
@@ -53,15 +54,19 @@
5354
import org.scijava.ops.transform.OpTransformationCandidate;
5455
import org.scijava.ops.transform.OpTransformationException;
5556
import org.scijava.ops.transform.OpTransformerService;
57+
import org.scijava.param.FunctionalMethodType;
58+
import org.scijava.param.ParameterStructs;
5659
import org.scijava.plugin.Parameter;
5760
import org.scijava.plugin.Plugin;
5861
import org.scijava.plugin.PluginInfo;
5962
import org.scijava.plugin.PluginService;
6063
import org.scijava.service.AbstractService;
6164
import org.scijava.service.SciJavaService;
6265
import org.scijava.service.Service;
66+
import org.scijava.struct.ItemIO;
6367
import org.scijava.ops.types.Nil;
6468
import org.scijava.util.ClassUtils;
69+
import org.scijava.util.Types;
6570

6671
/**
6772
* Service to provide a list of available ops structured in a prefix tree and to
@@ -167,16 +172,71 @@ public Iterable<OpInfo> infos(String name) {
167172
public LogService logger() {
168173
return log;
169174
}
175+
176+
/**
177+
* Attempts to inject {@link OpDependency} annotated fields of the specified object by
178+
* looking for Ops matching the field type and the name specified in the
179+
* annotation. The field type is assumed to be functional.
180+
*
181+
* @param obj
182+
* @throws OpMatchingException
183+
* if the type of the specified object is not functional,
184+
* if the Op matching the functional type and the name could not be found,
185+
* if an exception occurs during injection
186+
*/
187+
public void resolveOpDependencies(Object obj) throws OpMatchingException {
188+
final Class<?> c = obj.getClass();
189+
final List<Field> opFields = ClassUtils.getAnnotatedFields(c, OpDependency.class);
190+
191+
for (final Field opField : opFields) {
192+
final String opName = opField.getAnnotation(OpDependency.class).name();
193+
final Type fieldType = Types.fieldType(opField, c);
194+
195+
OpRef inferredRef = inferOpRef(fieldType, opName);
196+
if (inferredRef == null) {
197+
throw new OpMatchingException("Could not infer functional "
198+
+ "method inputs and outputs of Op dependency field: "
199+
+ opField);
200+
}
201+
202+
Object matchedOp = null;
203+
try {
204+
matchedOp = findOpInstance(opName, inferredRef);
205+
} catch (Exception e) {
206+
throw new OpMatchingException(
207+
"Could not find Op that matches requested Op dependency field:"
208+
+ "\nOp class: " + c.getName()
209+
+ "\nDependency field: " + opField.getName()
210+
+ "\n\n Attempted request:\n"
211+
+ inferredRef, e);
212+
}
170213

214+
try {
215+
opField.setAccessible(true);
216+
opField.set(obj, matchedOp);
217+
} catch (IllegalArgumentException | IllegalAccessException e) {
218+
throw new OpMatchingException(
219+
"Exception trying to inject Op dependency field.\n"
220+
+ "\tOp dependency field to resolve: " + opField + "\n"
221+
+ "\tFound Op to inject: " + matchedOp.getClass().getName() + "\n"
222+
+ "\tWith inferred OpRef: " + inferredRef, e);
223+
}
224+
}
225+
}
226+
171227
@SuppressWarnings("unchecked")
172228
public <T> T findOpInstance(final String opName, final Nil<T> specialType, final Nil<?>[] inTypes,
173229
final Nil<?>[] outTypes, final Object... secondaryArgs) {
174230
final OpRef ref = OpRef.fromTypes(opName, toTypes(specialType), toTypes(outTypes), toTypes(inTypes));
231+
return (T) findOpInstance(opName, ref, secondaryArgs);
232+
}
175233

234+
public Object findOpInstance(final String opName, final OpRef ref, final Object... secondaryArgs) {
235+
Object op = null;
176236
try {
177237
// Find single match which matches the specified types
178238
OpCandidate match = matcher.findSingleMatch(this, ref);
179-
return (T) match.createOp(secondaryArgs);
239+
op = match.createOp(secondaryArgs);
180240
} catch (OpMatchingException e) {
181241
log.debug("No matching Op for request: " + ref + "\n");
182242
log.debug("Attempting Op transformation...");
@@ -191,13 +251,20 @@ public <T> T findOpInstance(final String opName, final Nil<T> specialType, final
191251
// If we found one, try to do transformation and return transformed op
192252
log.debug("Matching Op transformation found:\n" + transformation + "\n");
193253
try {
194-
return (T) transformation.exceute(this, secondaryArgs);
254+
op = transformation.exceute(this, secondaryArgs);
195255
} catch (OpMatchingException | OpTransformationException e1) {
196256
log.debug("Execution of Op transformatioon failed:\n");
197257
log.debug(e1);
198-
throw new IllegalArgumentException(e);
258+
199259
}
200260
}
261+
try {
262+
// Try to resolve annotated OpDependency fields
263+
resolveOpDependencies(op);
264+
} catch (OpMatchingException e) {
265+
throw new IllegalArgumentException(e);
266+
}
267+
return op;
201268
}
202269

203270
public <T> T findOp(final String opName, final Nil<T> specialType, final Nil<?>[] inTypes, final Nil<?>[] outTypes,
@@ -213,6 +280,39 @@ public <T> T findOp(final String opName, final Nil<T> specialType, final Nil<?>[
213280
private Type[] toTypes(Nil<?>... nils) {
214281
return Arrays.stream(nils).filter(n -> n != null).map(n -> n.getType()).toArray(Type[]::new);
215282
}
283+
284+
/**
285+
* Tries to infer a {@link OpRef} from a functional Op type. E.g. the type:
286+
* <pre>Computer&lt;Double[], Double[]&gt</pre>
287+
* Will result in the following {@link OpRef}:
288+
* <pre>
289+
* Name: 'specified name'
290+
* Types: [Computer&lt;Double, Double&gt]
291+
* InputTypes: [Double[], Double[]]
292+
* OutputTypes: [Double[]]
293+
* </pre>
294+
* Input and output types will be inferred by looking at the signature of the functional
295+
* method of the specified type. Also see {@link ParameterStructs#getFunctionalMethodTypes(Type)}.
296+
*
297+
* @param type
298+
* @param name
299+
* @return null if
300+
* the specified type has no functional method
301+
*/
302+
private OpRef inferOpRef(Type type, String name) {
303+
List<FunctionalMethodType> fmts = ParameterStructs.getFunctionalMethodTypes(type);
304+
if (fmts == null) return null;
305+
306+
EnumSet<ItemIO> inIos = EnumSet.of(ItemIO.BOTH, ItemIO.INPUT);
307+
EnumSet<ItemIO> outIos = EnumSet.of(ItemIO.BOTH, ItemIO.OUTPUT);
308+
309+
Type[] inputs = fmts.stream().filter(fmt -> inIos.contains(fmt.itemIO()))
310+
.map(fmt -> fmt.type()).toArray(Type[]::new);
311+
Type[] outputs = fmts.stream().filter(fmt -> outIos.contains(fmt.itemIO()))
312+
.map(fmt -> fmt.type()).toArray(Type[]::new);
313+
314+
return new OpRef(name, new Type[]{type}, outputs, inputs);
315+
}
216316

217317
/**
218318
* Updates alias map using the specified String list. The first String in

src/main/java/org/scijava/ops/core/BiComputer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package org.scijava.ops.core;
22

3+
import org.scijava.param.Mutable;
4+
35
@FunctionalInterface
46
public interface BiComputer<I1, I2, O> extends TriConsumer<I1, I2, O> {
5-
void compute(I1 in1, I2 in2, O out);
7+
void compute(I1 in1, I2 in2, @Mutable O out);
68

79
@Override
810
default void accept(I1 t, I2 u, O v) {

src/main/java/org/scijava/ops/core/BiInplace1.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import java.util.function.BiConsumer;
44

5+
import org.scijava.param.Mutable;
6+
57
@FunctionalInterface
68
public interface BiInplace1<IO, I2> extends BiConsumer<IO, I2> {
7-
void mutate(IO io, I2 in2);
9+
void mutate(@Mutable IO io, I2 in2);
810

911
@Override
1012
default void accept(IO io, I2 in2) {

src/main/java/org/scijava/ops/core/BiInplace2.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import java.util.function.BiConsumer;
44

5+
import org.scijava.param.Mutable;
6+
57
@FunctionalInterface
68
public interface BiInplace2<I1, IO> extends BiConsumer<I1, IO> {
7-
void mutate(I1 in1, IO io);
9+
void mutate(I1 in1, @Mutable IO io);
810

911
@Override
1012
default void accept(I1 in1, IO io) {

src/main/java/org/scijava/ops/core/Computer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import java.util.function.BiConsumer;
44

5+
import org.scijava.param.Mutable;
6+
57
@FunctionalInterface
68
public interface Computer<I1, O> extends BiConsumer<I1, O> {
7-
void compute(I1 in1, O out);
9+
void compute(I1 in1, @Mutable O out);
810

911
@Override
1012
default void accept(I1 t, O u) {

src/main/java/org/scijava/ops/core/Inplace.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import java.util.function.Consumer;
44

5+
import org.scijava.param.Mutable;
6+
57
@FunctionalInterface
68
public interface Inplace<I> extends Consumer<I> {
7-
void mutate(I in1);
9+
void mutate(@Mutable I in1);
810

911
@Override
1012
default void accept(I in) {

src/main/java/org/scijava/ops/core/NullaryComputer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import java.util.function.Consumer;
44

5+
import org.scijava.param.Mutable;
6+
57
@FunctionalInterface
68
public interface NullaryComputer<O> extends Consumer<O> {
7-
void compute(O out);
9+
void compute(@Mutable O out);
810

911
@Override
1012
default void accept(O u) {

src/main/java/org/scijava/ops/matcher/OpMatchingException.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@ public class OpMatchingException extends Exception {
1212
public OpMatchingException(String message) {
1313
super(message);
1414
}
15+
16+
public OpMatchingException(String message, Throwable cause) {
17+
super(message, cause);
18+
}
1519
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.scijava.param;
2+
3+
import java.lang.reflect.Type;
4+
5+
import org.scijava.struct.ItemIO;
6+
7+
/**
8+
* Wrapper to pair a type of a method signature with its {@link ItemIO}.
9+
*
10+
* @author David Kolb
11+
*/
12+
public class FunctionalMethodType {
13+
14+
private final Type type;
15+
private final ItemIO itemIO;
16+
17+
public FunctionalMethodType(final Type type, final ItemIO itemIO) {
18+
this.type = type;
19+
this.itemIO = itemIO;
20+
}
21+
22+
public Type type() {
23+
return this.type;
24+
}
25+
public ItemIO itemIO(){
26+
return this.itemIO;
27+
}
28+
29+
@Override
30+
public String toString() {
31+
return itemIO + " : " + type.getTypeName();
32+
}
33+
}

0 commit comments

Comments
 (0)