Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions docs/ops/doc/WritingYourOwnOpPackage.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,13 @@ To declare a block of code as an Op, simply add the `@implNote` tag to that bloc

```java
/**
* @implNote op names='<names>' [priority='<priority>'] [type='<type>']
* @implNote op names='<names>' [priority='<priority>']
*/
```

The arguments to the `@implNote op` syntax are described below:
* `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'`.
* `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].
* `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].

### Declaring Ops as Methods

Expand All @@ -61,7 +60,7 @@ Any `static` method can be easily declared as an Op by simply appending the `@im
```java
/**
* My static method, which is also an Op
* @implNote op names='my.op' type='java.util.function.BiFunction'
* @implNote op names='my.op'
* @param arg1 the first argument to the method
* @param arg2 the first argument to the method
* @return the result of the method
Expand All @@ -70,7 +69,10 @@ public static Double myStaticMethodOp(Double arg1, Double arg2) {
...computation here...
}
```
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].
Additional Op characteristics are specified by placing parentheticals **at the end** of `@param` tags:
* 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.
* 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.
* 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.

### Declaring Ops as Classes

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

/**
* @input arg1 the first {@link Double}
* @input arg2 the second {@link Double}
* @output arg2 the second {@link Double}
* @implNote op names='my.op'
*/
public final BiFunction<Double, Double, Double> myFieldOp =
(arg1, arg2) -> {...computation...};

}
```
To describe each Op parameter, add the following tags to its javadoc:

* To describe a pure input, add the Javadoc tag `@input <parameter_name> <description>`
* To describe a pure output (for a function Op), add the Javadoc tag `@output <description>`
* To describe a conatiner (for a computer Op), add the Javadoc tag `@container <parameter_name> <description>`
* To describe a mutable input (for an inplace Op), add the Javadoc tag `@mutable <parameter_name> <description>`

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:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
import org.scijava.ops.engine.matcher.impl.DefaultOpMatcher;
import org.scijava.ops.engine.matcher.impl.DefaultOpRequest;
import org.scijava.ops.engine.matcher.impl.InfoMatchingOpRequest;
import org.scijava.ops.engine.matcher.impl.OpClassInfo;
import org.scijava.ops.engine.matcher.impl.DefaultOpClassInfo;
import org.scijava.ops.engine.struct.FunctionalParameters;
import org.scijava.ops.engine.util.Infos;
import org.scijava.ops.spi.Op;
Expand Down Expand Up @@ -259,7 +259,7 @@ public Type genericType(Object obj) {
public OpInfo opify(final Class<?> opClass, final double priority,
String... names)
{
return new OpClassInfo( //
return new DefaultOpClassInfo( //
opClass, //
Versions.getVersion(opClass), //
"", //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import org.scijava.ops.api.Hints;
import org.scijava.ops.api.OpInfo;
import org.scijava.ops.engine.OpInfoGenerator;
import org.scijava.ops.engine.matcher.impl.OpClassInfo;
import org.scijava.ops.engine.matcher.impl.DefaultOpClassInfo;
import org.scijava.ops.engine.util.Infos;
import org.scijava.ops.spi.Op;
import org.scijava.ops.spi.OpClass;
Expand All @@ -52,7 +52,7 @@ private Hints formHints(OpHints h) {
protected List<OpInfo> processClass(Class<?> c) {
OpClass p = c.getAnnotation(OpClass.class);
if (p == null) return Collections.emptyList();
return Collections.singletonList(new OpClassInfo( //
return Collections.singletonList(new DefaultOpClassInfo( //
c, //
Versions.getVersion(c), //
p.description(), //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
import org.scijava.ops.api.Hints;
import org.scijava.ops.api.OpInfo;
import org.scijava.ops.engine.OpInfoGenerator;
import org.scijava.ops.engine.matcher.impl.OpFieldInfo;
import org.scijava.ops.engine.matcher.impl.OpMethodInfo;
import org.scijava.ops.engine.matcher.impl.DefaultOpFieldInfo;
import org.scijava.ops.engine.matcher.impl.DefaultOpMethodInfo;
import org.scijava.ops.engine.util.Infos;
import org.scijava.ops.spi.OpCollection;
import org.scijava.ops.spi.OpField;
Expand All @@ -66,15 +66,15 @@ protected List<OpInfo> processClass(Class<?> cls) {
OpField.class);
final Optional<Object> instance = getInstance(cls);
if (instance.isPresent()) {
final List<OpFieldInfo> fieldInfos = //
final List<DefaultOpFieldInfo> fieldInfos = //
fields.parallelStream() //
.map(f -> generateFieldInfo(f, instance.get(), version)) //
.collect(Collectors.toList());
collectionInfos.addAll(fieldInfos);
}
// add OpMethodInfos
//
final List<OpMethodInfo> methodInfos = //
final List<DefaultOpMethodInfo> methodInfos = //
Annotations.getAnnotatedMethods(cls, OpMethod.class).parallelStream() //
.map(m -> generateMethodInfo(m, version)) //
.collect(Collectors.toList());
Expand All @@ -91,12 +91,12 @@ private Optional<Object> getInstance(Class<?> c) {
}
}

private OpFieldInfo generateFieldInfo(Field field, Object instance,
private DefaultOpFieldInfo generateFieldInfo(Field field, Object instance,
String version)
{
final boolean isStatic = Modifier.isStatic(field.getModifiers());
OpField annotation = field.getAnnotation(OpField.class);
return new OpFieldInfo( //
return new DefaultOpFieldInfo( //
isStatic ? null : instance, //
field, //
version, //
Expand All @@ -107,9 +107,11 @@ private OpFieldInfo generateFieldInfo(Field field, Object instance,
);
}

private OpMethodInfo generateMethodInfo(Method method, String version) {
private DefaultOpMethodInfo generateMethodInfo(Method method,
String version)
{
OpMethod annotation = method.getAnnotation(OpMethod.class);
return new OpMethodInfo( //
return new DefaultOpMethodInfo( //
method, //
annotation.type(), //
version, //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
* @author Curtis Rueden
* @author David Kolb
*/
public class OpClassInfo implements OpInfo {
public class DefaultOpClassInfo implements OpInfo {

private final List<String> names;
private final Class<?> opClass;
Expand All @@ -63,7 +63,7 @@ public class OpClassInfo implements OpInfo {
private final String description;
private final Hints hints;

public OpClassInfo( //
public DefaultOpClassInfo( //
final Class<?> opClass, //
final String version, //
final String description, //
Expand Down Expand Up @@ -176,7 +176,7 @@ public AnnotatedElement getAnnotationBearer() {

@Override
public boolean equals(final Object o) {
if (!(o instanceof OpClassInfo)) return false;
if (!(o instanceof DefaultOpClassInfo)) return false;
final OpInfo that = (OpInfo) o;
return struct().equals(that.struct());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
*
* @author Curtis Rueden
*/
public class OpFieldInfo implements OpInfo {
public class DefaultOpFieldInfo implements OpInfo {

private final Object instance;
private final Field field;
Expand All @@ -65,7 +65,7 @@ public class OpFieldInfo implements OpInfo {
private final Struct struct;
private final Hints hints;

public OpFieldInfo( //
public DefaultOpFieldInfo( //
final Object instance, //
final Field field, //
final String version, //
Expand Down Expand Up @@ -217,7 +217,7 @@ public String id() {

@Override
public boolean equals(final Object o) {
if (!(o instanceof OpFieldInfo)) return false;
if (!(o instanceof DefaultOpFieldInfo)) return false;
final OpInfo that = (OpInfo) o;
return struct().equals(that.struct());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,12 @@
import org.scijava.ops.engine.struct.MethodOpDependencyMemberParser;
import org.scijava.ops.engine.struct.MethodParameterMemberParser;
import org.scijava.ops.engine.util.Infos;
import org.scijava.ops.engine.util.Lambdas;
import org.scijava.ops.engine.util.internal.OpMethodUtils;
import org.scijava.ops.spi.OpMethod;
import org.scijava.struct.Member;
import org.scijava.struct.Struct;
import org.scijava.struct.StructInstance;
import org.scijava.struct.Structs;
import org.scijava.types.Types;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
Expand All @@ -58,7 +53,7 @@
/**
* @author Marcel Wiedenmann
*/
public class OpMethodInfo implements OpInfo {
public class DefaultOpMethodInfo implements OpInfo {

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

private final Hints hints;

public OpMethodInfo( //
public DefaultOpMethodInfo( //
final Method method, //
final Class<?> opType, //
final String version, //
Expand Down Expand Up @@ -163,26 +158,7 @@ public String implementationName() {

@Override
public StructInstance<?> createOpInstance(final List<?> dependencies) {
// NB LambdaMetaFactory only works if this Module (org.scijava.ops.engine)
// can read the Module containing the Op. So we also have to check that.
Module methodModule = method.getDeclaringClass().getModule();
Module opsEngine = this.getClass().getModule();
opsEngine.addReads(methodModule);
try {
method.setAccessible(true);
MethodHandle handle = MethodHandles.lookup().unreflect(method);
Object op = Lambdas.lambdaize( //
Types.raw(opType), //
handle, //
Infos.dependencies(this).stream().map(Member::getRawType).toArray(
Class[]::new), dependencies.toArray() //
);
return struct().createInstance(op);
}
catch (Throwable exc) {
throw new IllegalStateException("Failed to invoke Op method: " + method,
exc);
}
return OpMethodUtils.createOpInstance(this, method, dependencies);
}

@Override
Expand Down Expand Up @@ -215,7 +191,7 @@ public String id() {

@Override
public boolean equals(final Object o) {
if (!(o instanceof OpMethodInfo)) return false;
if (!(o instanceof DefaultOpMethodInfo)) return false;
final OpInfo that = (OpInfo) o;
return struct().equals(that.struct());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,34 @@

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

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.scijava.common3.Classes;
import org.scijava.ops.api.OpInfo;
import org.scijava.ops.engine.exceptions.impl.FunctionalTypeOpException;
import org.scijava.ops.engine.util.Infos;
import org.scijava.ops.engine.util.Lambdas;
import org.scijava.ops.spi.OpDependency;
import org.scijava.struct.Member;
import org.scijava.struct.StructInstance;
import org.scijava.types.Types;
import org.scijava.types.inference.FunctionalInterfaces;
import org.scijava.types.inference.GenericAssignability;

/**
* Common code used by Ops backed by {@link Method}s.
*
* @author Gabriel Selzer
*/
public final class OpMethodUtils {

private OpMethodUtils() {
Expand Down Expand Up @@ -108,4 +121,39 @@ public static Type[] getOpParamTypes(
.toArray(Type[]::new);
}

/**
* Converts an {@link OpInfo} backed by a {@link Method} reference into an Op,
* given a list of its dependencies.
*
* @param info the {@link OpInfo}
* @param method the {@link Method} containing the Op code
* @param dependencies all Op dependencies required to execute the Op
* @return a {@link StructInstance}
*/
public static StructInstance<?> createOpInstance(final OpInfo info,
Comment thread
gselzer marked this conversation as resolved.
final Method method, final List<?> dependencies)
{
// NB LambdaMetaFactory only works if this Module (org.scijava.ops.engine)
// can read the Module containing the Op. So we also have to check that.
Module methodModule = method.getDeclaringClass().getModule();
Module opsEngine = OpMethodUtils.class.getModule();

opsEngine.addReads(methodModule);
try {
method.setAccessible(true);
MethodHandle handle = MethodHandles.lookup().unreflect(method);
Object op = Lambdas.lambdaize( //
Types.raw(info.opType()), //
handle, //
Infos.dependencies(info).stream().map(Member::getRawType).toArray(
Class[]::new), dependencies.toArray() //
);
return info.struct().createInstance(op);
}
catch (Throwable exc) {
throw new IllegalStateException("Failed to invoke Op method: " + method,
exc);
}
}

}
Loading