Skip to content

Commit 953a54a

Browse files
authored
Merge pull request #216 from scijava/scijava-ops-indexer/yaml-param-nullability
Add Op Parameter Nullability, Java Field Op Parameter Types to YAML
2 parents b4d0b76 + 630d03c commit 953a54a

File tree

84 files changed

+1895
-687
lines changed

Some content is hidden

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

84 files changed

+1895
-687
lines changed

docs/ops/doc/WritingYourOwnOpPackage.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,13 @@ To declare a block of code as an Op, simply add the `@implNote` tag to that bloc
4545

4646
```java
4747
/**
48-
* @implNote op names='<names>' [priority='<priority>'] [type='<type>']
48+
* @implNote op names='<names>' [priority='<priority>']
4949
*/
5050
```
5151

5252
The arguments to the `@implNote op` syntax are described below:
5353
* `names='<names>'` provides the names that the Op will match. If you'd like this Op to be searchable under one name `foo.bar`, you can use the argument `names='foo.bar'`. If you'd like your Op to be searchable using multiple names, you can use a comma-delimited list. For example, if you want your Op to be searchable under the names `foo.bar` and `foo.baz`, then you can use the argument `names='foo.bar,foo.baz'`, you can use the argument `names='foo.bar'`. If you'd like your Op to be searchable using multiple names, you can use a comma-delimited list. For example, if you want your Op to be searchable under the names `foo.bar` and `foo.baz`, then you can use the argument `names='foo.bar,foo.baz'`.
5454
* `priority='<priority>'` provides a decimal-valued priority used to break ties when multiple Ops match a given Op request. *We advise against adding priorities unless you experience matching conflicts*. Op priorities should follow the SciJava Priority standards [insert link].
55-
* `type='<type>'` identifies the functional type of the Op **and is only required for Ops written as methods** - more information on that below [insert link].
5655

5756
### Declaring Ops as Methods
5857

@@ -61,7 +60,7 @@ Any `static` method can be easily declared as an Op by simply appending the `@im
6160
```java
6261
/**
6362
* My static method, which is also an Op
64-
* @implNote op names='my.op' type='java.util.function.BiFunction'
63+
* @implNote op names='my.op'
6564
* @param arg1 the first argument to the method
6665
* @param arg2 the first argument to the method
6766
* @return the result of the method
@@ -70,7 +69,10 @@ public static Double myStaticMethodOp(Double arg1, Double arg2) {
7069
...computation here...
7170
}
7271
```
73-
Note that the `type` argument in the `@implNote` syntax is **required** for Ops written as methods (and only for Ops written as methods), as the Op must be registered to a functional type. The recommended functional types are housed in the SciJava Functions library [insert link].
72+
Additional Op characteristics are specified by placing parentheticals **at the end** of `@param` tags:
73+
* If an Op input is allowed to be `null`, you can add `(nullable)` to the end. This tells SciJava Ops that your Op will function with our without that parameter.
74+
* If an Op is written as a computer, you must add `(container)` to the end of the `@param` tag corresponding to the preallocated output buffer parameter.
75+
* If an Op is written as an inplace, you must add `(mutable)` to the end of the `@param` tag corresponding to the mutable input parameter.
7476

7577
### Declaring Ops as Classes
7678

@@ -118,13 +120,22 @@ Any `Field` whose type is a `FunctionalInterface` (such as `java.util.function.F
118120
public class MyOpCollection {
119121

120122
/**
123+
* @input arg1 the first {@link Double}
124+
* @input arg2 the second {@link Double}
125+
* @output arg2 the second {@link Double}
121126
* @implNote op names='my.op'
122127
*/
123128
public final BiFunction<Double, Double, Double> myFieldOp =
124129
(arg1, arg2) -> {...computation...};
125130

126131
}
127132
```
133+
To describe each Op parameter, add the following tags to its javadoc:
134+
135+
* To describe a pure input, add the Javadoc tag `@input <parameter_name> <description>`
136+
* To describe a pure output (for a function Op), add the Javadoc tag `@output <description>`
137+
* To describe a conatiner (for a computer Op), add the Javadoc tag `@container <parameter_name> <description>`
138+
* To describe a mutable input (for an inplace Op), add the Javadoc tag `@mutable <parameter_name> <description>`
128139

129140
Note again that the only supported functional interfaces that can be used without additional dependencies are `java.util.function.Function` and `java.util.function.BiFunction` - if you'd like to write an Op requiring more than two inputs, or to write an Op that takes a pre-allocated output buffer, you'll need to depend on the SciJava Function library:
130141

scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
import org.scijava.ops.engine.matcher.impl.DefaultOpMatcher;
4242
import org.scijava.ops.engine.matcher.impl.DefaultOpRequest;
4343
import org.scijava.ops.engine.matcher.impl.InfoMatchingOpRequest;
44-
import org.scijava.ops.engine.matcher.impl.OpClassInfo;
44+
import org.scijava.ops.engine.matcher.impl.DefaultOpClassInfo;
4545
import org.scijava.ops.engine.struct.FunctionalParameters;
4646
import org.scijava.ops.engine.util.Infos;
4747
import org.scijava.ops.spi.Op;
@@ -259,7 +259,7 @@ public Type genericType(Object obj) {
259259
public OpInfo opify(final Class<?> opClass, final double priority,
260260
String... names)
261261
{
262-
return new OpClassInfo( //
262+
return new DefaultOpClassInfo( //
263263
opClass, //
264264
Versions.getVersion(opClass), //
265265
"", //

scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpClassOpInfoGenerator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import org.scijava.ops.api.Hints;
3434
import org.scijava.ops.api.OpInfo;
3535
import org.scijava.ops.engine.OpInfoGenerator;
36-
import org.scijava.ops.engine.matcher.impl.OpClassInfo;
36+
import org.scijava.ops.engine.matcher.impl.DefaultOpClassInfo;
3737
import org.scijava.ops.engine.util.Infos;
3838
import org.scijava.ops.spi.Op;
3939
import org.scijava.ops.spi.OpClass;
@@ -52,7 +52,7 @@ private Hints formHints(OpHints h) {
5252
protected List<OpInfo> processClass(Class<?> c) {
5353
OpClass p = c.getAnnotation(OpClass.class);
5454
if (p == null) return Collections.emptyList();
55-
return Collections.singletonList(new OpClassInfo( //
55+
return Collections.singletonList(new DefaultOpClassInfo( //
5656
c, //
5757
Versions.getVersion(c), //
5858
p.description(), //

scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpCollectionInfoGenerator.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@
4242
import org.scijava.ops.api.Hints;
4343
import org.scijava.ops.api.OpInfo;
4444
import org.scijava.ops.engine.OpInfoGenerator;
45-
import org.scijava.ops.engine.matcher.impl.OpFieldInfo;
46-
import org.scijava.ops.engine.matcher.impl.OpMethodInfo;
45+
import org.scijava.ops.engine.matcher.impl.DefaultOpFieldInfo;
46+
import org.scijava.ops.engine.matcher.impl.DefaultOpMethodInfo;
4747
import org.scijava.ops.engine.util.Infos;
4848
import org.scijava.ops.spi.OpCollection;
4949
import org.scijava.ops.spi.OpField;
@@ -66,15 +66,15 @@ protected List<OpInfo> processClass(Class<?> cls) {
6666
OpField.class);
6767
final Optional<Object> instance = getInstance(cls);
6868
if (instance.isPresent()) {
69-
final List<OpFieldInfo> fieldInfos = //
69+
final List<DefaultOpFieldInfo> fieldInfos = //
7070
fields.parallelStream() //
7171
.map(f -> generateFieldInfo(f, instance.get(), version)) //
7272
.collect(Collectors.toList());
7373
collectionInfos.addAll(fieldInfos);
7474
}
7575
// add OpMethodInfos
7676
//
77-
final List<OpMethodInfo> methodInfos = //
77+
final List<DefaultOpMethodInfo> methodInfos = //
7878
Annotations.getAnnotatedMethods(cls, OpMethod.class).parallelStream() //
7979
.map(m -> generateMethodInfo(m, version)) //
8080
.collect(Collectors.toList());
@@ -91,12 +91,12 @@ private Optional<Object> getInstance(Class<?> c) {
9191
}
9292
}
9393

94-
private OpFieldInfo generateFieldInfo(Field field, Object instance,
94+
private DefaultOpFieldInfo generateFieldInfo(Field field, Object instance,
9595
String version)
9696
{
9797
final boolean isStatic = Modifier.isStatic(field.getModifiers());
9898
OpField annotation = field.getAnnotation(OpField.class);
99-
return new OpFieldInfo( //
99+
return new DefaultOpFieldInfo( //
100100
isStatic ? null : instance, //
101101
field, //
102102
version, //
@@ -107,9 +107,11 @@ private OpFieldInfo generateFieldInfo(Field field, Object instance,
107107
);
108108
}
109109

110-
private OpMethodInfo generateMethodInfo(Method method, String version) {
110+
private DefaultOpMethodInfo generateMethodInfo(Method method,
111+
String version)
112+
{
111113
OpMethod annotation = method.getAnnotation(OpMethod.class);
112-
return new OpMethodInfo( //
114+
return new DefaultOpMethodInfo( //
113115
method, //
114116
annotation.type(), //
115117
version, //

scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpClassInfo.java renamed to scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpClassInfo.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
* @author Curtis Rueden
5454
* @author David Kolb
5555
*/
56-
public class OpClassInfo implements OpInfo {
56+
public class DefaultOpClassInfo implements OpInfo {
5757

5858
private final List<String> names;
5959
private final Class<?> opClass;
@@ -63,7 +63,7 @@ public class OpClassInfo implements OpInfo {
6363
private final String description;
6464
private final Hints hints;
6565

66-
public OpClassInfo( //
66+
public DefaultOpClassInfo( //
6767
final Class<?> opClass, //
6868
final String version, //
6969
final String description, //
@@ -176,7 +176,7 @@ public AnnotatedElement getAnnotationBearer() {
176176

177177
@Override
178178
public boolean equals(final Object o) {
179-
if (!(o instanceof OpClassInfo)) return false;
179+
if (!(o instanceof DefaultOpClassInfo)) return false;
180180
final OpInfo that = (OpInfo) o;
181181
return struct().equals(that.struct());
182182
}

scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpFieldInfo.java renamed to scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpFieldInfo.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
*
5454
* @author Curtis Rueden
5555
*/
56-
public class OpFieldInfo implements OpInfo {
56+
public class DefaultOpFieldInfo implements OpInfo {
5757

5858
private final Object instance;
5959
private final Field field;
@@ -65,7 +65,7 @@ public class OpFieldInfo implements OpInfo {
6565
private final Struct struct;
6666
private final Hints hints;
6767

68-
public OpFieldInfo( //
68+
public DefaultOpFieldInfo( //
6969
final Object instance, //
7070
final Field field, //
7171
final String version, //
@@ -217,7 +217,7 @@ public String id() {
217217

218218
@Override
219219
public boolean equals(final Object o) {
220-
if (!(o instanceof OpFieldInfo)) return false;
220+
if (!(o instanceof DefaultOpFieldInfo)) return false;
221221
final OpInfo that = (OpInfo) o;
222222
return struct().equals(that.struct());
223223
}

scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpMethodInfo.java renamed to scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMethodInfo.java

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,12 @@
3737
import org.scijava.ops.engine.struct.MethodOpDependencyMemberParser;
3838
import org.scijava.ops.engine.struct.MethodParameterMemberParser;
3939
import org.scijava.ops.engine.util.Infos;
40-
import org.scijava.ops.engine.util.Lambdas;
4140
import org.scijava.ops.engine.util.internal.OpMethodUtils;
4241
import org.scijava.ops.spi.OpMethod;
43-
import org.scijava.struct.Member;
4442
import org.scijava.struct.Struct;
4543
import org.scijava.struct.StructInstance;
4644
import org.scijava.struct.Structs;
47-
import org.scijava.types.Types;
4845

49-
import java.lang.invoke.MethodHandle;
50-
import java.lang.invoke.MethodHandles;
5146
import java.lang.reflect.AnnotatedElement;
5247
import java.lang.reflect.Method;
5348
import java.lang.reflect.Modifier;
@@ -58,7 +53,7 @@
5853
/**
5954
* @author Marcel Wiedenmann
6055
*/
61-
public class OpMethodInfo implements OpInfo {
56+
public class DefaultOpMethodInfo implements OpInfo {
6257

6358
private final Method method;
6459
private final String description;
@@ -70,7 +65,7 @@ public class OpMethodInfo implements OpInfo {
7065

7166
private final Hints hints;
7267

73-
public OpMethodInfo( //
68+
public DefaultOpMethodInfo( //
7469
final Method method, //
7570
final Class<?> opType, //
7671
final String version, //
@@ -163,26 +158,7 @@ public String implementationName() {
163158

164159
@Override
165160
public StructInstance<?> createOpInstance(final List<?> dependencies) {
166-
// NB LambdaMetaFactory only works if this Module (org.scijava.ops.engine)
167-
// can read the Module containing the Op. So we also have to check that.
168-
Module methodModule = method.getDeclaringClass().getModule();
169-
Module opsEngine = this.getClass().getModule();
170-
opsEngine.addReads(methodModule);
171-
try {
172-
method.setAccessible(true);
173-
MethodHandle handle = MethodHandles.lookup().unreflect(method);
174-
Object op = Lambdas.lambdaize( //
175-
Types.raw(opType), //
176-
handle, //
177-
Infos.dependencies(this).stream().map(Member::getRawType).toArray(
178-
Class[]::new), dependencies.toArray() //
179-
);
180-
return struct().createInstance(op);
181-
}
182-
catch (Throwable exc) {
183-
throw new IllegalStateException("Failed to invoke Op method: " + method,
184-
exc);
185-
}
161+
return OpMethodUtils.createOpInstance(this, method, dependencies);
186162
}
187163

188164
@Override
@@ -215,7 +191,7 @@ public String id() {
215191

216192
@Override
217193
public boolean equals(final Object o) {
218-
if (!(o instanceof OpMethodInfo)) return false;
194+
if (!(o instanceof DefaultOpMethodInfo)) return false;
219195
final OpInfo that = (OpInfo) o;
220196
return struct().equals(that.struct());
221197
}

scijava-ops-engine/src/main/java/org/scijava/ops/engine/util/internal/OpMethodUtils.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,34 @@
2929

3030
package org.scijava.ops.engine.util.internal;
3131

32+
import java.lang.invoke.MethodHandle;
33+
import java.lang.invoke.MethodHandles;
3234
import java.lang.reflect.Method;
3335
import java.lang.reflect.Parameter;
3436
import java.lang.reflect.Type;
3537
import java.lang.reflect.TypeVariable;
3638
import java.util.Arrays;
3739
import java.util.HashMap;
40+
import java.util.List;
3841
import java.util.Map;
3942

4043
import org.scijava.common3.Classes;
44+
import org.scijava.ops.api.OpInfo;
4145
import org.scijava.ops.engine.exceptions.impl.FunctionalTypeOpException;
46+
import org.scijava.ops.engine.util.Infos;
47+
import org.scijava.ops.engine.util.Lambdas;
4248
import org.scijava.ops.spi.OpDependency;
49+
import org.scijava.struct.Member;
50+
import org.scijava.struct.StructInstance;
4351
import org.scijava.types.Types;
4452
import org.scijava.types.inference.FunctionalInterfaces;
4553
import org.scijava.types.inference.GenericAssignability;
4654

55+
/**
56+
* Common code used by Ops backed by {@link Method}s.
57+
*
58+
* @author Gabriel Selzer
59+
*/
4760
public final class OpMethodUtils {
4861

4962
private OpMethodUtils() {
@@ -108,4 +121,39 @@ public static Type[] getOpParamTypes(
108121
.toArray(Type[]::new);
109122
}
110123

124+
/**
125+
* Converts an {@link OpInfo} backed by a {@link Method} reference into an Op,
126+
* given a list of its dependencies.
127+
*
128+
* @param info the {@link OpInfo}
129+
* @param method the {@link Method} containing the Op code
130+
* @param dependencies all Op dependencies required to execute the Op
131+
* @return a {@link StructInstance}
132+
*/
133+
public static StructInstance<?> createOpInstance(final OpInfo info,
134+
final Method method, final List<?> dependencies)
135+
{
136+
// NB LambdaMetaFactory only works if this Module (org.scijava.ops.engine)
137+
// can read the Module containing the Op. So we also have to check that.
138+
Module methodModule = method.getDeclaringClass().getModule();
139+
Module opsEngine = OpMethodUtils.class.getModule();
140+
141+
opsEngine.addReads(methodModule);
142+
try {
143+
method.setAccessible(true);
144+
MethodHandle handle = MethodHandles.lookup().unreflect(method);
145+
Object op = Lambdas.lambdaize( //
146+
Types.raw(info.opType()), //
147+
handle, //
148+
Infos.dependencies(info).stream().map(Member::getRawType).toArray(
149+
Class[]::new), dependencies.toArray() //
150+
);
151+
return info.struct().createInstance(op);
152+
}
153+
catch (Throwable exc) {
154+
throw new IllegalStateException("Failed to invoke Op method: " + method,
155+
exc);
156+
}
157+
}
158+
111159
}

0 commit comments

Comments
 (0)