Skip to content

Commit d610c70

Browse files
gselzerhinerm
authored andcommitted
Write an "create your own Ops library" page
1 parent 92f7235 commit d610c70

1 file changed

Lines changed: 211 additions & 0 deletions

File tree

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Writing Your Own Op Package
2+
3+
Right now, there are two ways to write and expose your own Ops.
4+
5+
## Ops Through Javadoc
6+
7+
Ops can be declared directly through Javadoc - this yields a couple of benefits, namely:
8+
* No additional runtime dependencies needed for simple Ops
9+
10+
This mechanism of Op declaration is used by the ImageJ Ops2 project
11+
12+
### Adding the SciJava Javadoc Parser to your POM
13+
14+
Ops written through Javadoc are discovered by the SciJava Javadoc Parser, which creates a file `op.yaml` containing all of the data needed to import each Op you declare.
15+
16+
Until the SciJava 3 annotation processor is added to pom-scijava, developers must add the following block of code to the `build` section of your POM:
17+
18+
TODO: Replace with the pom-scijava version needed to grab this annotation processor.
19+
TODO: Replace the SciJava Javadoc Parser version with the correct initial version
20+
```xml
21+
<build>
22+
<plugins>
23+
<plugin>
24+
<artifactId>maven-compiler-plugin</artifactId>
25+
<configuration>
26+
<annotationProcessorPaths>
27+
<path>
28+
<groupId>org.scijava</groupId>
29+
<artifactId>scijava-javadoc-parser</artifactId>
30+
<version>${project.version}</version>
31+
</path>
32+
</annotationProcessorPaths>
33+
<fork>true</fork>
34+
<showWarnings>true</showWarnings>
35+
<compilerArgs>
36+
<arg>-Ajavadoc.packages="-"</arg>
37+
</compilerArgs>
38+
</configuration>
39+
</plugin>
40+
</plugins>
41+
</build>
42+
```
43+
44+
### Declaring Ops with the `@implNote` syntax
45+
46+
To declare a block of code as an Op, simply add the `@implNote` tag to that block's Javadoc. The `@implNote` schema for declaring Ops is as follows:
47+
48+
```java
49+
/**
50+
* @implNote op names='<names>' [priority='<priority>'] [type='<type>']
51+
*/
52+
```
53+
54+
The arguments to the `@implNote op` syntax are described below:
55+
* `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'`.
56+
* `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].
57+
* `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].
58+
59+
### Declaring Ops as Methods
60+
61+
Any `static` method can be easily declared as an Op by simply appending the `@implNote` tag to the method's Javadoc:
62+
63+
```java
64+
/**
65+
* My static method, which is also an Op
66+
* @implNote op names='my.op' type='java.util.function.BiFunction'
67+
* @param arg1 the first argument to the method
68+
* @param arg2 the first argument to the method
69+
* @return the result of the method
70+
*/
71+
public static Double myStaticMethodOp(Double arg1, Double arg2) {
72+
...computation here...
73+
}
74+
```
75+
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].
76+
77+
### Declaring Ops as Classes
78+
79+
Any `Class` implementing a `FunctionalInterface` (such as `java.util.function.Function`, `java.util.function.BiFunction`, `org.scijava.computers.Computers.Arity1`, etc.) can be declared as an Op using the `@implNote` syntax within the Javadoc *of that class*, as shown in the example below:
80+
81+
```java
82+
/**
83+
* My class, which is also an Op
84+
*
85+
* @implNote op names='my.op'
86+
*/
87+
public class MyClassOp
88+
implements java.util.function.BiFunction<Double, Double, Double>
89+
{
90+
91+
/**
92+
* The functional method of my Op
93+
* @param arg1 the first argument to the Op
94+
* @param arg2 the first argument to the Op
95+
* @return the result of the Op
96+
*/
97+
@Override
98+
public Double apply(Double arg1, Double arg2) {
99+
return null;
100+
}
101+
}
102+
```
103+
104+
Note 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:
105+
106+
```xml
107+
<dependencies>
108+
<dependency>
109+
<groupId>org.scijava</groupId>
110+
<artifactId>scijava-function</artifactId>
111+
</dependency>
112+
</dependencies>
113+
```
114+
115+
### Declaring Ops as Fields
116+
117+
Any `Field` whose type is a `FunctionalInterface` (such as `java.util.function.Function`, `java.util.function.BiFunction`, `org.scijava.computers.Computers.Arity1`, etc.) can also be declared as an Op. Function Ops are useful for very simple Ops, such as [Lambda Expressions](https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html) or [Method references](https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html). For `Field`s, the `@implNote` syntax should be placed on Javadoc on the Field, as shown below:
118+
119+
```java
120+
public class MyOpCollection {
121+
122+
/**
123+
* @implNote op names='my.op'
124+
*/
125+
public final BiFunction<Double, Double, Double> myFieldOp =
126+
(arg1, arg2) -> {...computation...};
127+
128+
}
129+
```
130+
131+
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:
132+
133+
```xml
134+
<dependencies>
135+
<dependency>
136+
<groupId>org.scijava</groupId>
137+
<artifactId>scijava-function</artifactId>
138+
</dependency>
139+
</dependencies>
140+
```
141+
142+
## Ops using JPMS
143+
144+
The other way to expose Ops is by using the [Java Platform Module System](https://www.oracle.com/corporate/features/understanding-java-9-modules.html). This mechanism is used to expose the Ops declared within SciJava Ops Engine, and may be preferred for its usage of plain Java mechanisms:
145+
146+
In opposition to the Javadoc mechanism, all projects wishing to declare Ops using JPMS must add a dependency on the SciJava Ops SPI library:
147+
148+
```xml
149+
<dependencies>
150+
<dependency>
151+
<groupId>org.scijava</groupId>
152+
<artifactId>scijava-ops-spi</artifactId>
153+
</dependency>
154+
</dependencies>
155+
```
156+
157+
### Declaring Ops as Classes
158+
159+
Using JPMS, Ops can be declared as Classes using the `OpClass` annotation and the `Op` interface, as shown below:
160+
161+
```java
162+
@OpClass(names = "my.op")
163+
public class MyClassOp implements BiFunction<Double, Double, Double>, Op {
164+
@Override
165+
public Double apply(Double arg1, Double arg2) {
166+
...computation...
167+
}
168+
}
169+
```
170+
Note the following:
171+
* The `@OpClass` annotation provides the names of the Op
172+
* The `BiFunction` interface determines the functional type of the Op
173+
* The `Op` interface allows us to declare the Class as a service using JPMS
174+
175+
Below, we'll see how to expose the Op within the `module-info.java`.
176+
177+
### Declaring Ops as Fields and Methods
178+
179+
Using JPMS, Ops can be declared as Fields using the `OpField` annotation, or as Methods using the `OpMethod` annotation, within any class that implements the `OpCollection` interface. An example is shown below:
180+
181+
```java
182+
public class MyOpCollection implements OpCollection {
183+
@OpField(names="my.fieldOp")
184+
public final BiFunction<Double, Double, Double> myFieldOp =
185+
(arg1, arg2) -> {...computation...};
186+
187+
@OpMethod(names="my.methodOp", type=BiFunction.class)
188+
public Double myMethodOp(final Double arg1, final Double arg2) {
189+
...computation...
190+
}
191+
}
192+
```
193+
Note the following:
194+
* The `OpCollection` interface allows us to declare the Class as a service using JPMS
195+
* The `@OpField` annotation declares the `Field` as an Op and also specifies the name(s) of the Op
196+
* The `@OpMethod` annotation declares the `Method` as an Op and also specifies the name(s) of the Op. **In addition**, it specifies the functional interface of the resulting Op.
197+
198+
### Exposing the Ops to SciJava Ops using the `module-info.java`
199+
200+
The last step of declaring Ops using JPMS is to declare them in a `module-info.java`, located in the root package directory of your project (`src/main/java` for Maven projects).
201+
202+
For an example module `com.example.ops` declaring the above Ops, we use the [`provides...with`] syntax to declare our `Op`s and our `OpCollection`s:
203+
204+
```java
205+
module com.example.ops {
206+
provides org.scijava.ops.spi.Op with com.example.ops.MyClassOp;
207+
208+
provides org.scijava.ops.spi.OpCollection with com.example.ops.MyOpCollection;
209+
}
210+
```
211+

0 commit comments

Comments
 (0)