|
| 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