diff --git a/docs/ops/doc/WritingYourOwnOpPackage.md b/docs/ops/doc/WritingYourOwnOpPackage.md index 20f71c801..57c4341a7 100644 --- a/docs/ops/doc/WritingYourOwnOpPackage.md +++ b/docs/ops/doc/WritingYourOwnOpPackage.md @@ -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='' [priority=''] [type=''] + * @implNote op names='' [priority=''] */ ``` The arguments to the `@implNote op` syntax are described below: * `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=''` 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=''` 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 @@ -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 @@ -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 @@ -118,6 +120,9 @@ 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 myFieldOp = @@ -125,6 +130,12 @@ public class MyOpCollection { } ``` +To describe each Op parameter, add the following tags to its javadoc: + +* To describe a pure input, add the Javadoc tag `@input ` +* To describe a pure output (for a function Op), add the Javadoc tag `@output ` +* To describe a conatiner (for a computer Op), add the Javadoc tag `@container ` +* To describe a mutable input (for an inplace Op), add the Javadoc tag `@mutable ` 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: diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java index 933ad7bb8..504c752fc 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/DefaultOpEnvironment.java @@ -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; @@ -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), // "", // diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpClassOpInfoGenerator.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpClassOpInfoGenerator.java index 393cf24db..8f5c08ce2 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpClassOpInfoGenerator.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpClassOpInfoGenerator.java @@ -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; @@ -52,7 +52,7 @@ private Hints formHints(OpHints h) { protected List 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(), // diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpCollectionInfoGenerator.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpCollectionInfoGenerator.java index f727c7d2e..428344533 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpCollectionInfoGenerator.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/impl/OpCollectionInfoGenerator.java @@ -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; @@ -66,7 +66,7 @@ protected List processClass(Class cls) { OpField.class); final Optional instance = getInstance(cls); if (instance.isPresent()) { - final List fieldInfos = // + final List fieldInfos = // fields.parallelStream() // .map(f -> generateFieldInfo(f, instance.get(), version)) // .collect(Collectors.toList()); @@ -74,7 +74,7 @@ protected List processClass(Class cls) { } // add OpMethodInfos // - final List methodInfos = // + final List methodInfos = // Annotations.getAnnotatedMethods(cls, OpMethod.class).parallelStream() // .map(m -> generateMethodInfo(m, version)) // .collect(Collectors.toList()); @@ -91,12 +91,12 @@ private Optional 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, // @@ -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, // diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpClassInfo.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpClassInfo.java similarity index 97% rename from scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpClassInfo.java rename to scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpClassInfo.java index deffcb16c..9caf4ac6a 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpClassInfo.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpClassInfo.java @@ -53,7 +53,7 @@ * @author Curtis Rueden * @author David Kolb */ -public class OpClassInfo implements OpInfo { +public class DefaultOpClassInfo implements OpInfo { private final List names; private final Class opClass; @@ -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, // @@ -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()); } diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpFieldInfo.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpFieldInfo.java similarity index 98% rename from scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpFieldInfo.java rename to scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpFieldInfo.java index 47fdeae50..f3786c3e4 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpFieldInfo.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpFieldInfo.java @@ -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; @@ -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, // @@ -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()); } diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpMethodInfo.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMethodInfo.java similarity index 84% rename from scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpMethodInfo.java rename to scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMethodInfo.java index 57a8d1398..fe8446b99 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/OpMethodInfo.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/DefaultOpMethodInfo.java @@ -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; @@ -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; @@ -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, // @@ -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 @@ -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()); } diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/util/internal/OpMethodUtils.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/util/internal/OpMethodUtils.java index 39718b62f..8daff3d05 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/util/internal/OpMethodUtils.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/util/internal/OpMethodUtils.java @@ -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() { @@ -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, + 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); + } + } + } diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/AbstractYAMLOpInfoCreator.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/AbstractYAMLOpInfoCreator.java deleted file mode 100644 index dca45b518..000000000 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/AbstractYAMLOpInfoCreator.java +++ /dev/null @@ -1,260 +0,0 @@ -/*- - * #%L - * Java implementation of the SciJava Ops matching engine. - * %% - * Copyright (C) 2016 - 2024 SciJava developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package org.scijava.ops.engine.yaml; - -import java.lang.reflect.Type; -import java.net.URI; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.scijava.ops.api.OpInfo; -import org.scijava.priority.Priority; -import org.scijava.struct.ItemIO; -import org.scijava.struct.Member; -import org.scijava.struct.MemberInstance; -import org.scijava.struct.Struct; - -/** - * An abstract base class for parsing the YAML into values common to - * {@link OpInfo}s. - * - * @author Gabriel Selzer - */ -public abstract class AbstractYAMLOpInfoCreator implements YAMLOpInfoCreator { - - static final Set outputKeys = new HashSet<>(List.of( - "OUTPUT, CONTAINER, MUTABLE")); - - @Override - public OpInfo create(final URI identifier, final Map yaml) { - // Parse source - start after the leading slash - final String srcString = identifier.getPath().substring(1); - // Parse version - final String version = yaml.get("version").toString(); - // Parse names - final String[] names = parseNames(yaml, identifier); - final String description = yaml.get("description").toString(); - // Create the OpInfo - OpInfo info; - try { - info = create(srcString, names, description, parsePriority(yaml), version, - yaml); - } - catch (Exception e) { - throw new RuntimeException(e); - } - // If we have parameter information, bake it in. - if (yaml.containsKey("parameters")) { - List> params = (List>) yaml.get( - "parameters"); - Iterator> paramItr = params.iterator(); - - List> members = info.struct().members(); - for (int i = 0; i < members.size(); i++) { - Member m = members.get(i); - if (m.isInput() || m.isOutput()) { - if (!paramItr.hasNext()) break; - Map paramMap = paramItr.next(); - members.set(i, wrapMember(m, paramMap)); - } - } - } - - return info; - } - - /** - * Parses the names out of the YAML - * - * @param yaml the YAML, stored in a {@link Map} - * @param identifier the {@link URI} identifying the source code for the Op - * @return the names stored in the YAML - * @throws IllegalArgumentException if there are no names in the YAML, or if - * the names element is not a (collection of) String. - */ - private String[] parseNames(Map yaml, URI identifier) { - final String[] names; - // Construct names - if (yaml.containsKey("name")) { - names = new String[] { (String) yaml.get("name") }; - } - else if (yaml.containsKey("names")) { - var tmp = yaml.get("names"); - if (tmp instanceof List) { - names = ((List) tmp).toArray(String[]::new); - } - else if (tmp instanceof String) { - names = new String[] { (String) tmp }; - } - else { - throw new IllegalArgumentException("Cannot convert" + tmp + - "to a String[]!"); - } - } - else { - throw new IllegalArgumentException("Op " + identifier + - " declares no names!"); - } - // Trim names - for (int i = 0; i < names.length; i++) { - names[i] = names[i].trim(); - } - // Return names - return names; - } - - /** - * Parses the priority out of the YAML - * - * @param yaml the YAML, stored in a {@link Map} - * @return the priority stored in the YAML, or otherwise - * {@link Priority#NORMAL} - */ - private double parsePriority(Map yaml) { - // Parse priority - if (yaml.containsKey("priority")) { - Object p = yaml.get("priority"); - if (p instanceof Number) return ((Number) p).doubleValue(); - else if (p instanceof String) { - return Double.parseDouble((String) p); - } - else { - throw new IllegalArgumentException("Op priority " + p + - " not parsable"); - } - } - // Return default priority - return Priority.NORMAL; - } - - private Member wrapMember(final Member member, - final Map map) - { - String name = member.getKey(); - if (member.isInput() && !member.isOutput()) { - var newName = (String) map.get("name"); - if (newName != null) { - name = newName; - } - } - else { - for (String key : outputKeys) { - if (map.containsKey(key)) { - name = (String) map.get(key); - break; - } - } - } - String desc = ((String) map.getOrDefault("description", "")).trim(); - return new RenamedMember<>(member, name, desc); - } - - protected abstract OpInfo create( // - final String identifier, // - final String[] names, // - final String description, // - final double priority, // - final String version, // - Map yaml // - ) throws Exception; - - private static class RenamedMember implements Member { - - private final Member src; - private final String name; - private final String desc; - - public RenamedMember(final Member src, final String name, - final String desc) - { - this.src = src; - this.name = name; - this.desc = desc; - } - - @Override - public String getKey() { - return this.name; - } - - @Override - public String getDescription() { - return this.desc; - } - - @Override - public Type getType() { - return src.getType(); - } - - @Override - public Class getRawType() { - return src.getRawType(); - } - - @Override - public ItemIO getIOType() { - return src.getIOType(); - } - - @Override - public boolean isInput() { - return src.isInput(); - } - - @Override - public boolean isOutput() { - return src.isOutput(); - } - - @Override - public boolean isStruct() { - return src.isStruct(); - } - - @Override - public boolean isRequired() { - return src.isRequired(); - } - - @Override - public Struct childStruct() { - return src.childStruct(); - } - - @Override - public MemberInstance createInstance(Object o) { - return src.createInstance(o); - } - } -} diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/AbstractYAMLOpInfo.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/AbstractYAMLOpInfo.java new file mode 100644 index 000000000..2c94b59e7 --- /dev/null +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/AbstractYAMLOpInfo.java @@ -0,0 +1,170 @@ +/*- + * #%L + * Java implementation of the SciJava Ops matching engine. + * %% + * Copyright (C) 2016 - 2024 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.ops.engine.yaml.impl; + +import org.scijava.ops.api.Hints; +import org.scijava.ops.api.OpInfo; +import org.scijava.ops.engine.util.Infos; +import org.scijava.priority.Priority; +import org.scijava.struct.Struct; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public abstract class AbstractYAMLOpInfo implements OpInfo { + + protected final Map yaml; + protected final String identifier; + protected final List names; + protected final String description; + protected final String version; + protected final Double priority; + protected final Hints hints; + + public AbstractYAMLOpInfo(final Map yaml, + final String identifier) + { + this.yaml = yaml; + this.identifier = identifier; + this.names = parseNames(); + this.priority = parsePriority(); + this.description = yaml.getOrDefault("description", "").toString(); + this.version = (String) yaml.get("version"); + this.hints = new Hints(); + } + + /** + * Parses the names out of the YAML + * + * @return the names stored in the YAML + * @throws IllegalArgumentException if there are no names in the YAML, or if + * the names element is not a (collection of) String. + */ + protected List parseNames() { + List names = new ArrayList<>(); + // Construct names + if (yaml.containsKey("name")) { + names.add((String) yaml.get("name")); + } + else if (yaml.containsKey("names")) { + var tmp = yaml.get("names"); + if (tmp instanceof List) { + names = (List) tmp; + } + else if (tmp instanceof String) { + names.add((String) tmp); + } + else { + throw new IllegalArgumentException("Cannot convert" + tmp + + "to a String[]!"); + } + } + else { + throw new IllegalArgumentException("Op " + identifier + + " declares no names!"); + } + // Trim names + for (int i = 0; i < names.size(); i++) { + names.set(i, names.get(i).trim()); + } + // Return names + return names; + } + + /** + * Parses the priority out of the YAML + * + * @return the priority stored in the YAML, or otherwise + * {@link Priority#NORMAL} + */ + protected double parsePriority() { + // Parse priority + if (yaml.containsKey("priority")) { + Object p = yaml.get("priority"); + if (p instanceof Number) return ((Number) p).doubleValue(); + else if (p instanceof String) { + return Double.parseDouble((String) p); + } + else { + throw new IllegalArgumentException("Op priority " + p + + " not parsable"); + } + } + // Return default priority + return Priority.NORMAL; + } + + @Override + public List names() { + return names; + } + + @Override + public String description() { + return description; + } + + @Override + public Hints declaredHints() { + return hints; + } + + @Override + public double priority() { + return priority; + } + + @Override + public String version() { + return version; + } + + @Override + public boolean equals(final Object o) { + if (!(o instanceof YAMLOpMethodInfo)) return false; + final OpInfo that = (OpInfo) o; + return struct().equals(that.struct()); + } + + @Override + public abstract Struct struct(); + + @Override + public int hashCode() { + return struct().hashCode(); + } + + @Override + public String toString() { + return Infos.describe(this); + } +} diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/JavaClassYAMLOpInfoCreator.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/JavaClassYAMLOpInfoCreator.java index 6a9fb5354..aac5c0e7d 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/JavaClassYAMLOpInfoCreator.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/JavaClassYAMLOpInfoCreator.java @@ -32,11 +32,7 @@ import java.net.URI; import java.util.Map; -import org.scijava.common3.Classes; -import org.scijava.ops.api.Hints; import org.scijava.ops.api.OpInfo; -import org.scijava.ops.engine.matcher.impl.OpClassInfo; -import org.scijava.ops.engine.yaml.AbstractYAMLOpInfoCreator; import org.scijava.ops.engine.yaml.YAMLOpInfoCreator; /** @@ -44,7 +40,7 @@ * * @author Gabriel Selzer */ -public class JavaClassYAMLOpInfoCreator extends AbstractYAMLOpInfoCreator { +public class JavaClassYAMLOpInfoCreator implements YAMLOpInfoCreator { @Override public boolean canCreateFrom(URI identifier) { @@ -52,14 +48,10 @@ public boolean canCreateFrom(URI identifier) { } @Override - protected OpInfo create(final String identifier, final String[] names, - final String description, final double priority, String version, - Map yaml) throws Exception + public OpInfo create(URI identifier, Map yaml) + throws Exception { - // parse class - Class src = Classes.load(identifier); - // Create the OpInfo - return new OpClassInfo(src, version, description, new Hints(), priority, - names); + final String srcString = identifier.getPath().substring(1); + return new YAMLOpClassInfo(yaml, srcString); } } diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/JavaFieldYAMLOpInfoCreator.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/JavaFieldYAMLOpInfoCreator.java index 9b7bf3a2d..840e1897d 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/JavaFieldYAMLOpInfoCreator.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/JavaFieldYAMLOpInfoCreator.java @@ -33,11 +33,7 @@ import java.net.URI; import java.util.Map; -import org.scijava.common3.Classes; -import org.scijava.ops.api.Hints; import org.scijava.ops.api.OpInfo; -import org.scijava.ops.engine.matcher.impl.OpFieldInfo; -import org.scijava.ops.engine.yaml.AbstractYAMLOpInfoCreator; import org.scijava.ops.engine.yaml.YAMLOpInfoCreator; /** @@ -45,7 +41,7 @@ * * @author Gabriel Selzer */ -public class JavaFieldYAMLOpInfoCreator extends AbstractYAMLOpInfoCreator { +public class JavaFieldYAMLOpInfoCreator implements YAMLOpInfoCreator { @Override public boolean canCreateFrom(URI identifier) { @@ -53,32 +49,10 @@ public boolean canCreateFrom(URI identifier) { } @Override - protected OpInfo create( // - final String identifier, // - final String[] names, // - final String description, // - final double priority, // - final String version, // - final Map yaml // - ) throws Exception { - // parse class - int clsIndex = identifier.indexOf('$'); - String clsString = identifier.substring(0, clsIndex); - Class cls = Classes.load(clsString); - Object instance = cls.getConstructor().newInstance(); - // parse Field - String fieldString = identifier.substring(clsIndex + 1); - Field field = cls.getDeclaredField(fieldString); - - // Create the OpInfo - return new OpFieldInfo( // - instance, // - field, // - version, // - description, // - new Hints(), // - priority, // - names // - ); + public OpInfo create(URI identifier, Map yaml) + throws Exception + { + final String srcString = identifier.getPath().substring(1); + return new YAMLOpFieldInfo(yaml, srcString); } } diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/JavaMethodYAMLInfoCreator.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/JavaMethodYAMLInfoCreator.java index 6d639cc4c..b357573ea 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/JavaMethodYAMLInfoCreator.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/JavaMethodYAMLInfoCreator.java @@ -29,28 +29,19 @@ package org.scijava.ops.engine.yaml.impl; +import org.scijava.ops.api.OpInfo; +import org.scijava.ops.engine.yaml.YAMLOpInfoCreator; + import java.lang.reflect.Method; -import java.lang.reflect.Parameter; import java.net.URI; import java.util.Map; -import org.scijava.common3.Classes; -import org.scijava.function.Computers; -import org.scijava.function.Functions; -import org.scijava.function.Inplaces; -import org.scijava.ops.api.Hints; -import org.scijava.ops.api.OpInfo; -import org.scijava.ops.engine.matcher.impl.OpMethodInfo; -import org.scijava.ops.engine.yaml.AbstractYAMLOpInfoCreator; -import org.scijava.ops.engine.yaml.YAMLOpInfoCreator; -import org.scijava.ops.spi.OpDependency; - /** * A {@link YAMLOpInfoCreator} specialized for Java {@link Method}s. * * @author Gabriel Selzer */ -public class JavaMethodYAMLInfoCreator extends AbstractYAMLOpInfoCreator { +public class JavaMethodYAMLInfoCreator implements YAMLOpInfoCreator { @Override public boolean canCreateFrom(URI identifier) { @@ -58,129 +49,11 @@ public boolean canCreateFrom(URI identifier) { } @Override - protected OpInfo create(String identifier, String[] names, String description, - double priority, String version, Map yaml) throws Exception - { - // first, remove generics - String rawIdentifier = sanitizeGenerics(identifier); - - // parse class - int clsIndex = rawIdentifier.lastIndexOf('.', rawIdentifier.indexOf('(')); - String clsString = rawIdentifier.substring(0, clsIndex); - Class src = Classes.load(clsString); - // parse method - String methodString = rawIdentifier.substring(clsIndex + 1, rawIdentifier - .indexOf('(')); - String[] paramStrings = rawIdentifier.substring(rawIdentifier.indexOf('(') + - 1, rawIdentifier.indexOf(')')).split("\\s*,\\s*"); - Class[] paramClasses = new Class[paramStrings.length]; - for (int i = 0; i < paramStrings.length; i++) { - paramClasses[i] = deriveType(identifier, paramStrings[i]); - } - Method method = src.getMethod(methodString, paramClasses); - // parse op type - Class opType; - Map tags = ((Map) yaml.get("tags")); - String typeString = (String) tags.getOrDefault("type", ""); - opType = deriveOpType(identifier, typeString, method); - - return new OpMethodInfo( // - method, // - opType, // - version, // - description, // - new Hints(), // - priority, // - names // - ); - } - - private Class deriveOpType(String identifier, String typeString, - Method method) + public OpInfo create(URI identifier, Map yaml) + throws Exception { - int parameterCount = method.getParameterCount(); - for (Parameter p : method.getParameters()) { - if (p.isAnnotationPresent(OpDependency.class)) { - parameterCount--; - } - } - // Handle pure inference - if (typeString.isBlank()) { - if (method.getReturnType() != void.class) { - return Functions.functionOfArity(parameterCount); - } - else { - throw new RuntimeException("Op " + identifier + - " could not be loaded: Computers and Inplaces must declare their Op type in their @implNote annotation For example, if your Inplace is designed to mutate the first argument, please write \"type='Inplace1'\""); - } - } - - Class alias = findAlias(typeString.trim(), parameterCount); - if (alias != null) return alias; - - // Finally, pass off to the class loader function. - return deriveType(identifier, typeString); + final String srcString = identifier.getPath().substring(1); + return new YAMLOpMethodInfo(yaml, srcString); } - private Class findAlias(String typeString, int parameterCount) { - // We match any aliases matching the regex pattern "(or_of_aliases)(\\d*)" - int ioPosition = parameterCount - 1; - int aliasEnd = typeString.length() - 1; - // If the last char is a digit, we have a positional suffix - if (Character.isDigit(typeString.charAt(aliasEnd))) { - // Find the positional suffix - for (; aliasEnd >= 0; aliasEnd--) { - if (!Character.isDigit(typeString.charAt(aliasEnd))) { - ioPosition = Integer.parseInt(typeString.substring(aliasEnd + 1)) - 1; - break; - } - } - } - // Check if (typeString - positional suffix) is an alias - switch (typeString.substring(0, aliasEnd + 1)) { - case "Computer": - return Computers.computerOfArity(parameterCount - 1, ioPosition); - case "Function": - return Functions.functionOfArity(parameterCount); - case "Inplace": - return Inplaces.inplaceOfArity(parameterCount, ioPosition); - default: - return null; - } - } - - private Class deriveType(String identifier, String typeString) { - try { - return Classes.load(typeString, false); - } - catch (Throwable t) { - if (typeString.lastIndexOf('.') > -1) { - var lastIndex = typeString.lastIndexOf('.'); - return deriveType(identifier, typeString.substring(0, lastIndex) + '$' + - typeString.substring(lastIndex + 1)); - } - else { - throw new RuntimeException("Op " + identifier + - " could not be loaded: Could not load class " + typeString, t); - } - } - } - - private static String sanitizeGenerics(String method) { - int nested = 0; - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < method.length(); i++) { - char c = method.charAt(i); - if (c == '<') { - nested++; - } - if (nested == 0) { - sb.append(c); - } - if (c == '>' && nested > 0) { - nested--; - } - } - return sb.toString(); - } } diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/YAMLOpClassInfo.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/YAMLOpClassInfo.java new file mode 100644 index 000000000..848d253d5 --- /dev/null +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/YAMLOpClassInfo.java @@ -0,0 +1,183 @@ +/* + * #%L + * Java implementation of the SciJava Ops matching engine. + * %% + * Copyright (C) 2016 - 2024 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.ops.engine.yaml.impl; + +import org.scijava.common3.Annotations; +import org.scijava.common3.Classes; +import org.scijava.ops.api.OpInfo; +import org.scijava.ops.engine.OpDependencyMember; +import org.scijava.ops.engine.exceptions.impl.FinalOpDependencyFieldException; +import org.scijava.ops.engine.struct.FieldOpDependencyMember; +import org.scijava.ops.engine.struct.FunctionalParameters; +import org.scijava.ops.engine.struct.SynthesizedParameterMember; +import org.scijava.ops.engine.util.Infos; +import org.scijava.ops.spi.OpDependency; +import org.scijava.struct.Member; +import org.scijava.struct.Struct; +import org.scijava.struct.StructInstance; +import org.scijava.types.Types; + +import java.lang.reflect.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * An {@link OpInfo}, backed by some {@code public} {@link Class}, described via + * YAML that is stored within a {@link Map}. + * + * @author Gabriel Selzer + */ +public class YAMLOpClassInfo extends AbstractYAMLOpInfo { + + private final Class cls; + private final Struct struct; + + public YAMLOpClassInfo( // + final Map yaml, // + final String identifier) + { + super(yaml, identifier); + // parse class + this.cls = Classes.load(identifier); + this.struct = createStruct(yaml); + + // Validate general OpInfo features + Infos.validate(this); + } + + // -- OpInfo methods -- + + @Override + public Type opType() { + return Types.parameterizeRaw(cls); + } + + @Override + public Struct struct() { + return struct; + } + + @Override + public String implementationName() { + return cls.getName(); + } + + @Override + public AnnotatedElement getAnnotationBearer() { + return cls; + } + + /** + * For a {@link Class}, we define the implementation as the concatenation of: + *
    + *
  1. The fully qualified name of the class
  2. + *
  3. The version of the class containing the field, with a preceding + * {@code @}
  4. + *
+ *

+ * For example, for a field class {@code com.example.foo.Bar}, you might have + *

+ * {@code com.example.foo.Bar@1.0.0} + *

+ */ + @Override + public String id() { + return OpInfo.IMPL_DECLARATION + implementationName() + "@" + version(); + } + + private Struct createStruct(Map yaml) { + List> members = new ArrayList<>(); + List> params = (List>) yaml.get( + "parameters"); + var fmts = FunctionalParameters.findFunctionalMethodTypes(cls); + for (int i = 0; i < params.size(); i++) { + var pMap = params.get(i); + var fmt = fmts.get(i); + String name = (String) pMap.get("name"); + String description = (String) pMap.get("description"); + boolean nullable = (boolean) pMap.getOrDefault("nullable", false); + members.add(new SynthesizedParameterMember<>(fmt, name, !nullable, + description)); + } + + // Add Op Dependencies + final List fields = Annotations.getAnnotatedFields(cls, + OpDependency.class); + for (final Field f : fields) { + f.setAccessible(true); + if (Modifier.isFinal(f.getModifiers())) { + // Final fields are bad because they cannot be modified. + throw new FinalOpDependencyFieldException(f); + } + final FieldOpDependencyMember item = new FieldOpDependencyMember<>(f, + cls); + members.add(item); + } + + return () -> members; + } + + @Override + public StructInstance createOpInstance(List dependencies) { + final Object op; + try { + Constructor ctor = cls.getDeclaredConstructor(); + ctor.setAccessible(true); + op = ctor.newInstance(); + } + catch (final InstantiationException | IllegalAccessException + | NoSuchMethodException | SecurityException | IllegalArgumentException + | InvocationTargetException e) + { + throw new IllegalStateException("Unable to instantiate op: '" + cls + .getName() + "' Ensure that the Op has a no-args constructor.", e); + } + final var dependencyMembers = Infos.dependencies(this); + for (int i = 0; i < dependencyMembers.size(); i++) { + final OpDependencyMember dependencyMember = dependencyMembers.get(i); + try { + dependencyMember.createInstance(op).set(dependencies.get(i)); + } + catch (final Exception ex) { + // TODO: Improve error message. Used to include exact OpRequest of Op + // dependency. + throw new IllegalStateException( + "Exception trying to inject Op dependency field.\n" + + "\tOp dependency field to resolve: " + dependencyMember.getKey() + + "\n" + "\tFound Op to inject: " + dependencies.get(i).getClass() + .getName() + // + "\n" + "\tField signature: " + dependencyMember.getType(), ex); + } + } + return struct().createInstance(op); + } + +} diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/YAMLOpFieldInfo.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/YAMLOpFieldInfo.java new file mode 100644 index 000000000..3c824cbe0 --- /dev/null +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/YAMLOpFieldInfo.java @@ -0,0 +1,171 @@ +/* + * #%L + * Java implementation of the SciJava Ops matching engine. + * %% + * Copyright (C) 2016 - 2024 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.ops.engine.yaml.impl; + +import org.scijava.common3.Classes; +import org.scijava.ops.api.OpInfo; +import org.scijava.ops.engine.struct.FunctionalParameters; +import org.scijava.ops.engine.struct.SynthesizedParameterMember; +import org.scijava.ops.engine.util.Infos; +import org.scijava.struct.Member; +import org.scijava.struct.Struct; +import org.scijava.struct.StructInstance; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * An {@link OpInfo}, backed by some {@code public final} {@link Field}, + * described via YAML that is stored within a {@link Map}. + * + * @author Gabriel Selzer + */ +public class YAMLOpFieldInfo extends AbstractYAMLOpInfo { + + private final Object instance; + private final Field field; + private final Struct struct; + + public YAMLOpFieldInfo( // + final Map yaml, // + final String identifier // + ) throws Exception { + super(yaml, identifier); + + // parse class + int clsIndex = identifier.indexOf('$'); + String clsString = identifier.substring(0, clsIndex); + Class cls = Classes.load(clsString); + this.instance = cls.getConstructor().newInstance(); + // parse Field + String fieldString = identifier.substring(clsIndex + 1); + this.field = cls.getDeclaredField(fieldString); + // parse Struct + this.struct = createStruct(yaml); + + // Validate general OpInfo features + Infos.validate(this); + } + + // -- OpInfo methods -- + + @Override + public Type opType() { + return field.getGenericType(); + } + + @Override + public Struct struct() { + return struct; + } + + @Override + public String implementationName() { + // Get generic string without modifiers and return type + String fullyQualifiedField = field.toGenericString(); + int lastDotPos = fullyQualifiedField.lastIndexOf('.'); + fullyQualifiedField = fullyQualifiedField.substring(0, lastDotPos) + "$" + + fullyQualifiedField.substring(lastDotPos + 1); + String packageName = field.getDeclaringClass().getPackageName(); + int classNameIndex = fullyQualifiedField.lastIndexOf(packageName); + return fullyQualifiedField.substring(classNameIndex); + } + + @Override + public AnnotatedElement getAnnotationBearer() { + return field; + } + + /** + * For a {@link Class}, we define the implementation as the concatenation of: + *

    + *
  1. The fully qualified name of the class
  2. + *
  3. The version of the class containing the field, with a preceding + * {@code @}
  4. + *
+ *

+ * For example, for a field class {@code com.example.foo.Bar}, you might have + *

+ * {@code com.example.foo.Bar@1.0.0} + *

+ */ + @Override + public String id() { + return OpInfo.IMPL_DECLARATION + implementationName() + "@" + version(); + } + + private Struct createStruct(Map yaml) { + List> members = new ArrayList<>(); + List> params = (List>) yaml.get( + "parameters"); + var fmts = FunctionalParameters.findFunctionalMethodTypes(opType()); + for (int i = 0; i < params.size(); i++) { + var pMap = params.get(i); + var fmt = fmts.get(i); + String name = (String) pMap.get("name"); + String description = (String) pMap.get("description"); + boolean nullable = (boolean) pMap.getOrDefault("nullable", false); + members.add(new SynthesizedParameterMember<>(fmt, name, !nullable, + description)); + } + + return () -> members; + } + + @Override + public StructInstance createOpInstance(List dependencies) { + // NB: dependencies are not allowed on field Ops, since field Ops can only + // point to a single instance, which allows successive matching calls to + // overwrite the dependencies used on earlier matching calls. + // This can happen if (a) a single Field is matched multiple times, using + // different dependencies, or if (b) multiple fields point to the same + // object. + if (dependencies != null && !dependencies.isEmpty()) + throw new IllegalArgumentException( + "Op fields are not allowed to have any Op dependencies."); + // NB: In general, there is no way to create a new instance of the field + // value. + // Calling clone() may or may not work; it does not work with e.g. lambdas. + // Better to just use the same value directly, rather than trying to copy. + try { + final Object object = field.get(instance); + // TODO: Wrap object in a generic holder with the same interface. + return struct().createInstance(object); + } + catch (final IllegalAccessException exc) { + throw new RuntimeException(exc); + } + } + +} diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/YAMLOpMethodInfo.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/YAMLOpMethodInfo.java new file mode 100644 index 000000000..8c7b7c138 --- /dev/null +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/yaml/impl/YAMLOpMethodInfo.java @@ -0,0 +1,267 @@ +/* + * #%L + * Java implementation of the SciJava Ops matching engine. + * %% + * Copyright (C) 2016 - 2024 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.ops.engine.yaml.impl; + +import org.scijava.common3.Classes; +import org.scijava.function.Computers; +import org.scijava.function.Functions; +import org.scijava.function.Inplaces; +import org.scijava.ops.api.OpInfo; +import org.scijava.ops.engine.exceptions.impl.InstanceOpMethodException; +import org.scijava.ops.engine.exceptions.impl.PrivateOpException; +import org.scijava.ops.engine.exceptions.impl.UnreadableOpException; +import org.scijava.ops.engine.struct.FunctionalParameters; +import org.scijava.ops.engine.struct.MethodParameterOpDependencyMember; +import org.scijava.ops.engine.struct.SynthesizedParameterMember; +import org.scijava.ops.engine.util.Infos; +import org.scijava.ops.engine.util.internal.OpMethodUtils; +import org.scijava.ops.spi.OpDependency; +import org.scijava.ops.spi.OpMethod; +import org.scijava.struct.Member; +import org.scijava.struct.Struct; +import org.scijava.struct.StructInstance; + +import java.lang.reflect.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * An {@link OpInfo}, backed by some {@code public static} {@link Method}, + * described via YAML that is stored within a {@link Map}. + * + * @author Gabriel Selzer + */ +public class YAMLOpMethodInfo extends AbstractYAMLOpInfo implements OpInfo { + + private final Type opType; + private final Method method; + private final Struct struct; + + public YAMLOpMethodInfo( // + final Map yaml, // + final String identifier) throws NoSuchMethodException + { + super(yaml, identifier); + this.method = parseMethod(identifier); + this.struct = createStruct(yaml); + + this.opType = OpMethodUtils.getOpMethodType(deriveOpType(), method); + // Validate opMethod-specific features + checkModifiers(method); + // Validate general OpInfo features + Infos.validate(this); + } + + // -- OpInfo methods -- + + @Override + public Type opType() { + return opType; + } + + @Override + public Struct struct() { + return struct; + } + + @Override + public String implementationName() { + // Get generic string without modifiers and return type + String fullyQualifiedMethod = method.toGenericString(); + String packageName = method.getDeclaringClass().getPackageName(); + int classNameIndex = fullyQualifiedMethod.indexOf(packageName); + return fullyQualifiedMethod.substring(classNameIndex); + } + + @Override + public StructInstance createOpInstance(final List dependencies) { + return OpMethodUtils.createOpInstance(this, method, dependencies); + } + + /** + * For an {@link OpMethod}, we define the implementation as the concatenation + * of: + *

    + *
  1. The fully qualified name of the class containing the method
  2. + *
  3. The method name
  4. + *
  5. The method parameters
  6. + *
  7. The version of the class containing the method, with a preceding + * {@code @}
  8. + *
+ *

+ * For example, for a method {@code baz(Double in1, String in2)} in class + * {@code com.example.foo.Bar}, you might have + *

+ * {@code com.example.foo.Bar.baz(Double in1,String in2)@1.0.0} + */ + @Override + public String id() { + return OpInfo.IMPL_DECLARATION + implementationName() + "@" + version(); + } + + // -- Object methods -- + + @Override + public AnnotatedElement getAnnotationBearer() { + return method; + } + + // -- Helper methods -- // + + private static Method parseMethod(final String identifier) + throws NoSuchMethodException + { + // first, remove generics + String rawIdentifier = sanitizeGenerics(identifier); + + // parse class + int clsIndex = rawIdentifier.lastIndexOf('.', rawIdentifier.indexOf('(')); + String clsString = rawIdentifier.substring(0, clsIndex); + Class src = Classes.load(clsString); + // parse method + String methodString = rawIdentifier.substring(clsIndex + 1, rawIdentifier + .indexOf('(')); + String[] paramStrings = rawIdentifier.substring(rawIdentifier.indexOf('(') + + 1, rawIdentifier.indexOf(')')).split("\\s*,\\s*"); + Class[] paramClasses = new Class[paramStrings.length]; + for (int i = 0; i < paramStrings.length; i++) { + paramClasses[i] = deriveType(identifier, paramStrings[i]); + } + return src.getMethod(methodString, paramClasses); + } + + private Class deriveOpType() { + List> params = (List>) yaml.get( + "parameters"); + for (int i = params.size() - 1; i >= 0; i--) { + var pMap = params.get(i); + switch (((String) pMap.get("parameter type"))) { + case "OUTPUT": + return Functions.functionOfArity(params.size() - 1); + case "MUTABLE": + return Inplaces.inplaceOfArity(params.size(), i); + case "CONTAINER": + return Computers.computerOfArity(params.size() - 1, i); + } + } + throw new IllegalStateException( + "Could not determine functional type of Op " + method); + } + + private static Class deriveType(String identifier, String typeString) { + try { + return Classes.load(typeString, false); + } + catch (Throwable t) { + if (typeString.lastIndexOf('.') > -1) { + var lastIndex = typeString.lastIndexOf('.'); + return deriveType(identifier, typeString.substring(0, lastIndex) + '$' + + typeString.substring(lastIndex + 1)); + } + else { + throw new RuntimeException("Op " + identifier + + " could not be loaded: Could not load class " + typeString, t); + } + } + } + + private static String sanitizeGenerics(String method) { + int nested = 0; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < method.length(); i++) { + char c = method.charAt(i); + if (c == '<') { + nested++; + } + if (nested == 0) { + sb.append(c); + } + if (c == '>' && nested > 0) { + nested--; + } + } + return sb.toString(); + } + + private static void checkModifiers(Method method) { + // Reject all non public methods + if (!Modifier.isPublic(method.getModifiers())) { + throw new PrivateOpException(method); + } + if (!Modifier.isStatic(method.getModifiers())) { + // TODO: We can't properly infer the generic types of static methods at + // the moment. This might be a Java limitation. + throw new InstanceOpMethodException(method); + } + + // If the Op is not in the SciJava Ops Engine module, check visibility + Module methodModule = method.getDeclaringClass().getModule(); + if (methodModule != YAMLOpMethodInfo.class.getModule()) { + String packageName = method.getDeclaringClass().getPackageName(); + if (!methodModule.isOpen(packageName, methodModule)) { + throw new UnreadableOpException(packageName); + } + } + } + + private Struct createStruct(Map yaml) { + List> members = new ArrayList<>(); + List> params = (List>) yaml.get( + "parameters"); + Parameter[] methodParams = method.getParameters(); + // Skip any Op dependencies + // HACK - some components in RuntimeSafeMatchingRoutine expect dependencies + // to come last in the struct. + var fmts = FunctionalParameters.findFunctionalMethodTypes(OpMethodUtils + .getOpMethodType(deriveOpType(), method)); + for (int i = 0; i < params.size(); i++) { + var pMap = params.get(i); + var fmt = fmts.get(i); + String name = (String) pMap.get("name"); + String description = (String) pMap.get("description"); + boolean nullable = (boolean) pMap.getOrDefault("nullable", false); + members.add(new SynthesizedParameterMember<>(fmt, name, !nullable, + description)); + } + + for (Parameter methodParam : methodParams) { + if (!methodParam.isAnnotationPresent(OpDependency.class)) break; + members.add(new MethodParameterOpDependencyMember<>( // + methodParam.getName(), // + methodParam.getParameterizedType(), // + methodParam.getAnnotation(OpDependency.class) // + )); + } + + return () -> members; + } + +} diff --git a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/impl/OpMethodDependencyPositionTest.java b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/impl/OpMethodDependencyPositionTest.java index 9672e02db..c55ec41d2 100644 --- a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/impl/OpMethodDependencyPositionTest.java +++ b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/impl/OpMethodDependencyPositionTest.java @@ -39,7 +39,7 @@ import org.scijava.ops.api.Hints; import org.scijava.ops.engine.AbstractTestEnvironment; import org.scijava.ops.engine.exceptions.impl.OpDependencyPositionException; -import org.scijava.ops.engine.matcher.impl.OpMethodInfo; +import org.scijava.ops.engine.matcher.impl.DefaultOpMethodInfo; import org.scijava.ops.spi.OpCollection; import org.scijava.ops.spi.OpDependency; @@ -66,7 +66,7 @@ public void testOpDependencyBefore() throws NoSuchMethodException { List.class // ); // The below call should not throw an Exception - Assertions.assertDoesNotThrow(() -> new OpMethodInfo( // + Assertions.assertDoesNotThrow(() -> new DefaultOpMethodInfo( // m, // Computers.Arity1.class, // "1.0", // @@ -126,7 +126,7 @@ public void testOpDependencyBeforeAndAfter() throws NoSuchMethodException { */ private void createInvalidInfo(Method m, Class arity, String... names) { Assertions.assertThrows(OpDependencyPositionException.class, - () -> new OpMethodInfo( // + () -> new DefaultOpMethodInfo( // m, // arity, // "1.0", // diff --git a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/adapt/OpAdaptationInfoTest.java b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/adapt/OpAdaptationInfoTest.java index 1968fd7f7..974a2fc8a 100644 --- a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/adapt/OpAdaptationInfoTest.java +++ b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/adapt/OpAdaptationInfoTest.java @@ -29,22 +29,17 @@ package org.scijava.ops.engine.matcher.adapt; -import java.lang.reflect.Type; import java.util.function.BiFunction; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.scijava.function.Computers; -import org.scijava.ops.api.Hints; import org.scijava.ops.api.Ops; import org.scijava.ops.engine.AbstractTestEnvironment; import org.scijava.ops.engine.adapt.functional.FunctionsToComputers; import org.scijava.ops.engine.copy.CopyOpCollection; -import org.scijava.ops.engine.matcher.impl.OpClassInfo; import org.scijava.ops.spi.OpCollection; import org.scijava.ops.spi.OpMethod; -import org.scijava.types.Types; public class OpAdaptationInfoTest extends AbstractTestEnvironment implements OpCollection diff --git a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpFieldInfoTest.java b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/DefaultOpFieldInfoTest.java similarity index 95% rename from scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpFieldInfoTest.java rename to scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/DefaultOpFieldInfoTest.java index 88e1aef2e..daac9110b 100644 --- a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpFieldInfoTest.java +++ b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/DefaultOpFieldInfoTest.java @@ -40,7 +40,7 @@ import org.scijava.ops.engine.exceptions.impl.PrivateOpException; import org.scijava.ops.spi.OpCollection; -public class OpFieldInfoTest implements OpCollection { +public class DefaultOpFieldInfoTest implements OpCollection { private final BiFunction foo = Double::sum; @@ -48,7 +48,7 @@ public class OpFieldInfoTest implements OpCollection { public void testPrivateField() throws NoSuchFieldException { var field = this.getClass().getDeclaredField("foo"); Assertions.assertThrows(PrivateOpException.class, // - () -> new OpFieldInfo(// + () -> new DefaultOpFieldInfo(// this, // field, // Versions.getVersion(this.getClass()), // @@ -65,7 +65,7 @@ public void testPrivateField() throws NoSuchFieldException { public void testImmutableOutput() throws NoSuchFieldException { var field = this.getClass().getDeclaredField("bar"); Assertions.assertThrows(FunctionalTypeOpException.class, // - () -> new OpFieldInfo(// + () -> new DefaultOpFieldInfo(// this, // field, // Versions.getVersion(this.getClass()), // diff --git a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpClassInfoTest.java b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpClassInfoTest.java index 9142ba558..ce7389c73 100644 --- a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpClassInfoTest.java +++ b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpClassInfoTest.java @@ -55,7 +55,7 @@ public String create() { @Test public void testFinalDependency() { Assertions.assertThrows(FinalOpDependencyFieldException.class, () -> // - new OpClassInfo( // + new DefaultOpClassInfo( // FinalOpDependency.class, // "1.0", // "", // @@ -74,7 +74,7 @@ public void compute(Double in, @Container Double out) {} @Test public void testImmutableOutput() { Assertions.assertThrows(FunctionalTypeOpException.class, - () -> new OpClassInfo( // + () -> new DefaultOpClassInfo( // ImmutableOutput.class, // "1.0", // "", // diff --git a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpDescriptionTest.java b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpDescriptionTest.java index 61738f5ed..bc420c833 100644 --- a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpDescriptionTest.java +++ b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpDescriptionTest.java @@ -54,7 +54,7 @@ public Double apply(Double t, Double u) { @Test public void testOpClassDescription() { - OpClassInfo info = new OpClassInfo( // + DefaultOpClassInfo info = new DefaultOpClassInfo( // ClassOp.class, // "1.0", // "", // @@ -80,7 +80,7 @@ public static Double methodOp(Double in1, Double in2) { public void testOpMethodDescription() throws NoSuchMethodException { Method method = OpDescriptionTest.class.getMethod("methodOp", Double.class, Double.class); - OpMethodInfo info = new OpMethodInfo( // + DefaultOpMethodInfo info = new DefaultOpMethodInfo( // method, // BiFunction.class, // "1.0", // @@ -103,7 +103,7 @@ public void testOpMethodDescription() throws NoSuchMethodException { @Test public void testOpFieldDescription() throws NoSuchFieldException { Field field = OpDescriptionTest.class.getDeclaredField("fieldOp"); - OpFieldInfo info = new OpFieldInfo( // + DefaultOpFieldInfo info = new DefaultOpFieldInfo( // this, // field, // "", // @@ -123,7 +123,7 @@ public void testOpFieldDescription() throws NoSuchFieldException { @Test public void testReducedDescription() { - OpClassInfo info = new OpClassInfo( // + DefaultOpClassInfo info = new DefaultOpClassInfo( // ClassOp.class, // "1.0", // "", // diff --git a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpMethodInfoTest.java b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpMethodInfoTest.java index 5de6f1021..ed3e0c5dc 100644 --- a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpMethodInfoTest.java +++ b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/impl/OpMethodInfoTest.java @@ -50,7 +50,7 @@ private static Boolean privateOp() { public void testPrivateMethod() throws NoSuchMethodException { var m = this.getClass().getDeclaredMethod("privateOp"); Assertions.assertThrows(PrivateOpException.class, // - () -> new OpMethodInfo( // + () -> new DefaultOpMethodInfo( // m, // Producer.class, // "1.0", // @@ -68,7 +68,7 @@ public Boolean instanceOp() { public void testInstanceMethod() throws NoSuchMethodException { var m = this.getClass().getDeclaredMethod("instanceOp"); Assertions.assertThrows(InstanceOpMethodException.class, // - () -> new OpMethodInfo( // + () -> new DefaultOpMethodInfo( // m, // Producer.class, // "1.0", // @@ -86,7 +86,7 @@ public static Boolean staticOp() { public void testNonFuncIFace() throws NoSuchMethodException { var m = this.getClass().getDeclaredMethod("staticOp"); Assertions.assertThrows(FunctionalTypeOpException.class, - () -> new OpMethodInfo( // + () -> new DefaultOpMethodInfo( // m, // getClass(), // "1.0", // @@ -100,7 +100,7 @@ public void testNonFuncIFace() throws NoSuchMethodException { public void testWrongOpType() throws NoSuchMethodException { var m = this.getClass().getDeclaredMethod("staticOp"); Assertions.assertThrows(FunctionalTypeOpException.class, - () -> new OpMethodInfo( // + () -> new DefaultOpMethodInfo( // m, // Function.class, // "1.0", // @@ -117,7 +117,7 @@ public void testImmutableOutput() throws NoSuchMethodException { var m = this.getClass().getDeclaredMethod("mutateDouble", Double.class, Double.class); Assertions.assertThrows(FunctionalTypeOpException.class, - () -> new OpMethodInfo( // + () -> new DefaultOpMethodInfo( // m, // Computers.Arity1.class, // "1.0", // diff --git a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/reduce/NullableArgumentsFromIFaceTest.java b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/reduce/NullableArgumentsFromIFaceTest.java index c0dd0ecc9..68d866a16 100644 --- a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/reduce/NullableArgumentsFromIFaceTest.java +++ b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/reduce/NullableArgumentsFromIFaceTest.java @@ -37,8 +37,8 @@ import org.scijava.ops.engine.AbstractTestEnvironment; import org.scijava.ops.engine.exceptions.InvalidOpException; import org.scijava.ops.engine.exceptions.impl.NullablesOnMultipleMethodsException; -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.spi.Nullable; import org.scijava.ops.spi.OpCollection; import org.scijava.ops.spi.OpField; @@ -146,7 +146,7 @@ public void testNullablesOnOpMethodAndIFace() throws NoSuchMethodException { ); // Try to create an OpMethodInfo Assertions.assertThrows(NullablesOnMultipleMethodsException.class, - () -> new OpMethodInfo(// + () -> new DefaultOpMethodInfo(// m, // BiFunctionWithNullable.class, // "1.0", // @@ -178,7 +178,7 @@ public void testNullablesOnOpFieldAndIFace() throws NoSuchFieldException { var f = this.getClass().getDeclaredField("foo"); // Try to create an OpMethodInfo Assertions.assertThrows(NullablesOnMultipleMethodsException.class, - () -> new OpFieldInfo(// + () -> new DefaultOpFieldInfo(// this, // f, // Versions.getVersion(this.getClass()), // diff --git a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/YAMLOpTest.java b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/YAMLOpTest.java index f0b9fd4a9..6878ffe63 100644 --- a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/YAMLOpTest.java +++ b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/YAMLOpTest.java @@ -75,6 +75,12 @@ public void testYAMLMethodFunction() { Assertions.assertEquals(-1., sum, 1e-6); } + @Test + public void testYAMLMethodFunctionNullable() { + Double sum = ops.op("example.sub").input(2.).outType(Double.class).apply(); + Assertions.assertEquals(2., sum, 1e-6); + } + @Test public void testYAMLMethodInplaceShortType() { List l1 = Arrays.asList(1); diff --git a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/ops/YAMLFieldOp.java b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/ops/YAMLFieldOp.java index 03690fee1..ff72eed04 100644 --- a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/ops/YAMLFieldOp.java +++ b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/ops/YAMLFieldOp.java @@ -37,7 +37,7 @@ * * @author Gabriel Selzer */ -public class YAMLFieldOp { +public class YAMLFieldOp> { /** * A Field Op diff --git a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/ops/YAMLMethodOp.java b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/ops/YAMLMethodOp.java index 1080e4994..66560bbaa 100644 --- a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/ops/YAMLMethodOp.java +++ b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/yaml/impl/ops/YAMLMethodOp.java @@ -42,13 +42,16 @@ public class YAMLMethodOp { /** * An example Op, implemented by a {@link Method} * - * @implNote op name=example.sub, type=Function + * @implNote op name=example.sub * @param aDouble the first double - * @param aDouble2 the second double + * @param aDouble2 the second double (nullable) * @return the difference */ - public static Double subtract(Double aDouble, Double aDouble2) { - return aDouble - aDouble2; + public static Double subtract(N aDouble, N aDouble2) { + if (aDouble2 == null) { + return aDouble.doubleValue(); + } + return aDouble.doubleValue() - aDouble2.doubleValue(); } /** @@ -57,7 +60,6 @@ public static Double subtract(Double aDouble, Double aDouble2) { * @implNote op name=example.xor, type=Inplace1 * @param aList the first integer {@link List} * @param aList2 the second integer {@link List} - * @return the xor */ public static void xor(List aList, List aList2) { for (int i = 0; i < aList.size(); i++) { @@ -87,7 +89,7 @@ public static void and(List aList, List aList2, * * @implNote op name=example.or, type=Computer2 * @param aList the first integer {@link List} - * @param out the logical and of the two integer {@link List}s + * @param out the logical and of the two integer * @param aList2 the second integer {@link List} */ public static void or(List aList, List out, diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/LiftComputersToRAI.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/LiftComputersToRAI.java index 6191cb6f5..28bb7465a 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/LiftComputersToRAI.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/LiftComputersToRAI.java @@ -1,4 +1,4 @@ -/* +/*- * #%L * Image processing operations for SciJava Ops. * %% @@ -65,6 +65,8 @@ private LiftComputersToRAI() { } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAIO extends RandomAccessibleInterval> @@ -77,6 +79,8 @@ Computers.Arity1 lift11(Computers.Arity1 computer) } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAIO extends RandomAccessibleInterval> @@ -90,6 +94,8 @@ Computers.Arity2 lift21( } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAII2 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -103,6 +109,8 @@ Computers.Arity2 lift22( } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAIO extends RandomAccessibleInterval> @@ -116,6 +124,8 @@ Computers.Arity3 lift31( } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAII2 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -129,6 +139,8 @@ Computers.Arity3 lift32( } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAII2 extends RandomAccessibleInterval, RAII3 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -143,6 +155,8 @@ Computers.Arity3 lift33( } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAIO extends RandomAccessibleInterval> @@ -156,6 +170,8 @@ Computers.Arity4 lift41( } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAII2 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -169,6 +185,8 @@ Computers.Arity4 lift42( } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAII2 extends RandomAccessibleInterval, RAII3 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -184,6 +202,8 @@ Computers.Arity4 lift43( } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAII2 extends RandomAccessibleInterval, RAII3 extends RandomAccessibleInterval, RAII4 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -199,6 +219,8 @@ Computers.Arity4 lift44( } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAIO extends RandomAccessibleInterval> @@ -213,6 +235,8 @@ Computers.Arity5 lift51( } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAII2 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -227,6 +251,8 @@ Computers.Arity5 lift52( } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAII2 extends RandomAccessibleInterval, RAII3 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -242,6 +268,8 @@ Computers.Arity5 lift53( } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAII2 extends RandomAccessibleInterval, RAII3 extends RandomAccessibleInterval, RAII4 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -257,6 +285,8 @@ Computers.Arity5 lift54( } /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static , RAII2 extends RandomAccessibleInterval, RAII3 extends RandomAccessibleInterval, RAII4 extends RandomAccessibleInterval, RAII5 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/LiftFunctionsToRAI.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/LiftFunctionsToRAI.java index bd8fb9643..cdd1412dd 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/LiftFunctionsToRAI.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/LiftFunctionsToRAI.java @@ -68,6 +68,11 @@ private LiftFunctionsToRAI() { } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -86,6 +91,11 @@ Function lift11(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -104,6 +114,11 @@ BiFunction lift21(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAII2 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -123,6 +138,11 @@ BiFunction lift22(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -141,6 +161,11 @@ Functions.Arity3 lift31(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAII2 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -160,6 +185,11 @@ Functions.Arity3 lift32(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAII2 extends RandomAccessibleInterval, RAII3 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -180,6 +210,11 @@ Functions.Arity3 lift33(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -198,6 +233,11 @@ Functions.Arity4 lift41(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAII2 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -217,6 +257,11 @@ Functions.Arity4 lift42(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAII2 extends RandomAccessibleInterval, RAII3 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -237,6 +282,11 @@ Functions.Arity4 lift43(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAII2 extends RandomAccessibleInterval, RAII3 extends RandomAccessibleInterval, RAII4 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -258,6 +308,11 @@ Functions.Arity4 lift44(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -276,6 +331,11 @@ Functions.Arity5 lift51(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAII2 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -295,6 +355,11 @@ Functions.Arity5 lift52(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAII2 extends RandomAccessibleInterval, RAII3 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -316,6 +381,11 @@ Functions.Arity5 lift53(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAII2 extends RandomAccessibleInterval, RAII3 extends RandomAccessibleInterval, RAII4 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> @@ -338,6 +408,11 @@ Functions.Arity5 lift54(@OpDependency( } /** + * @param imgCreator an Op that can create the output + * {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} + * elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static , RAII1 extends RandomAccessibleInterval, RAII2 extends RandomAccessibleInterval, RAII3 extends RandomAccessibleInterval, RAII4 extends RandomAccessibleInterval, RAII5 extends RandomAccessibleInterval, RAIO extends RandomAccessibleInterval> diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/LiftNeighborhoodComputersToRAI.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/LiftNeighborhoodComputersToRAI.java index e130ff695..d94bd26ac 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/LiftNeighborhoodComputersToRAI.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/LiftNeighborhoodComputersToRAI.java @@ -1,4 +1,4 @@ -/*- +/* * #%L * Image processing operations for SciJava Ops. * %% @@ -45,6 +45,8 @@ private LiftNeighborhoodComputersToRAI() { } /** + * @param op the original Op, operating on {@link Neighborhood}s + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.', type=Function */ public static @@ -63,6 +65,8 @@ private LiftNeighborhoodComputersToRAI() { } /** + * @param op the original Op, operating on {@link Neighborhood}s + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.', type=Function */ public static , G extends F> @@ -78,6 +82,8 @@ private LiftNeighborhoodComputersToRAI() { } /** + * @param op the original Op, operating on {@link Neighborhood}s + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.', type=Function */ public static @@ -97,6 +103,8 @@ private LiftNeighborhoodComputersToRAI() { } /** + * @param op the original Op, operating on {@link Neighborhood}s + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.', type=Function */ public static > @@ -113,6 +121,8 @@ private LiftNeighborhoodComputersToRAI() { } /** + * @param op the original Op, operating on {@link Neighborhood}s + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.', type=Function */ public static @@ -132,6 +142,8 @@ private LiftNeighborhoodComputersToRAI() { } /** + * @param op the original Op, operating on {@link Neighborhood}s + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.', type=Function */ public static > diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/RAIToIIOps.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/RAIToIIOps.java index ddf61b5ad..3e5c4fef6 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/RAIToIIOps.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/RAIToIIOps.java @@ -38,6 +38,10 @@ public class RAIToIIOps { /** + * @input a {@link Function} that operates on an {@link Iterable}, producing + * some output value + * @output a {@link Function} that operates on a + * {@link RandomAccessibleInterval}, producing some output value * @implNote op names='engine.adapt' */ public final Function, U>, Function, U>> func = @@ -46,6 +50,10 @@ public class RAIToIIOps { }; /** + * @input a {@link BiFunction} that operates on {@link Iterable}s, producing + * some output value + * @output a {@link BiFunction} that operates on + * {@link RandomAccessibleInterval}s, producing some output value * @implNote op names='engine.adapt' */ public final Function, Iterable, V>, BiFunction, RandomAccessibleInterval, V>> biFunc = diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/complexLift/LiftNeighborhoodComputersToFunctionsOnImgs.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/complexLift/LiftNeighborhoodComputersToFunctionsOnImgs.java index e5b7ae8bb..a40d88a71 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/complexLift/LiftNeighborhoodComputersToFunctionsOnImgs.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/adapt/complexLift/LiftNeighborhoodComputersToFunctionsOnImgs.java @@ -58,6 +58,9 @@ private LiftNeighborhoodComputersToFunctionsOnImgs() { } /** + * @param creator an Op that can create the output image + * @param op the original Op, operating on {@link Neighborhood}s + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.', * type='java.util.function.Function' */ @@ -80,6 +83,9 @@ private LiftNeighborhoodComputersToFunctionsOnImgs() { } /** + * @param creator an Op that can create the output image + * @param op the original Op, operating on {@link Neighborhood}s + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.', * type='java.util.function.Function' */ diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/AdaptiveSmoothedKendallTau.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/AdaptiveSmoothedKendallTau.java index 23a333f6b..0df9b24db 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/AdaptiveSmoothedKendallTau.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/AdaptiveSmoothedKendallTau.java @@ -1,8 +1,8 @@ /* * #%L - * ImageJ2 software for multidimensional image processing and analysis. + * Image processing operations for SciJava Ops. * %% - * Copyright (C) 2014 - 2024 ImageJ2 developers. + * Copyright (C) 2014 - 2024 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/SACAHeatmapZScore.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/SACAHeatmapZScore.java index 19c50c67e..98c98a4ef 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/SACAHeatmapZScore.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/SACAHeatmapZScore.java @@ -1,8 +1,8 @@ /*- * #%L - * ImageJ software for multidimensional image processing and analysis. + * Image processing operations for SciJava Ops. * %% - * Copyright (C) 2014 - 2024 ImageJ developers. + * Copyright (C) 2014 - 2024 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/SACASigMask.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/SACASigMask.java index 1e95cd655..d09e4ed9a 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/SACASigMask.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/SACASigMask.java @@ -1,8 +1,8 @@ /*- * #%L - * ImageJ software for multidimensional image processing and analysis. + * Image processing operations for SciJava Ops. * %% - * Copyright (C) 2014 - 2024 ImageJ developers. + * Copyright (C) 2014 - 2024 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/WtKendallTau.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/WtKendallTau.java index 42fc384ca..3a21574b8 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/WtKendallTau.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/WtKendallTau.java @@ -1,8 +1,8 @@ /*- * #%L - * ImageJ2 software for multidimensional image processing and analysis. + * Image processing operations for SciJava Ops. * %% - * Copyright (C) 2014 - 2024 ImageJ2 developers. + * Copyright (C) 2014 - 2024 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/create/Creators.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/create/Creators.java index 5886a0922..b6b2f8f64 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/create/Creators.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/create/Creators.java @@ -321,11 +321,9 @@ public class Creators, L, I extends IntegerType, T ex /** * @input sigmas * @output gaussKernelRAI - */ - // TODO do we want to support this and if so is this the right way to do it? - /** * @implNote op names='create.kernelGauss' */ + // TODO do we want to support this and if so is this the right way to do it? public final Function> kernelGaussDoubleType = (sigmas) -> (RandomAccessibleInterval) kernelGauss.apply(sigmas, (C) new DoubleType()); diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/deconvolve/AccelerationState.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/deconvolve/AccelerationState.java index d515f1d56..573503a6b 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/deconvolve/AccelerationState.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/deconvolve/AccelerationState.java @@ -1,8 +1,8 @@ /* * #%L - * ImageJ2 software for multidimensional image processing and analysis. + * Image processing operations for SciJava Ops. * %% - * Copyright (C) 2014 - 2023 ImageJ2 developers. + * Copyright (C) 2014 - 2024 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/features/hog/HistogramOfOrientedGradients2D.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/features/hog/HistogramOfOrientedGradients2D.java index c121dd504..2361ee86a 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/features/hog/HistogramOfOrientedGradients2D.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/features/hog/HistogramOfOrientedGradients2D.java @@ -90,17 +90,16 @@ public class HistogramOfOrientedGradients2D> implements private Converter, FloatType> converterGetMax; - @SuppressWarnings("unchecked") /** * TODO * - * @param input + * @param in * @param numOrientations * @param spanOfNeighborhood - * @param executorService - * @param output + * @param out */ @Override + @SuppressWarnings("unchecked") public void compute(RandomAccessibleInterval in, Integer numOrientations, Integer spanOfNeighborhood, RandomAccessibleInterval out) { diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/features/tamura2d/DefaultCoarsenessFeature.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/features/tamura2d/DefaultCoarsenessFeature.java index a7d1f4813..7912135f1 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/features/tamura2d/DefaultCoarsenessFeature.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/features/tamura2d/DefaultCoarsenessFeature.java @@ -66,7 +66,6 @@ public class DefaultCoarsenessFeature, O extends RealType< private Computers.Arity3, Shape, // OutOfBoundsFactory>, RandomAccessibleInterval> meanOp; - @SuppressWarnings("unchecked") /** * TODO * @@ -74,6 +73,7 @@ public class DefaultCoarsenessFeature, O extends RealType< * @param output */ @Override + @SuppressWarnings("unchecked") public void compute(final RandomAccessibleInterval input, final O output) { if (input.numDimensions() != 2) throw new IllegalArgumentException( "Only 2 dimensional images allowed!"); diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/features/tamura2d/DefaultDirectionalityFeature.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/features/tamura2d/DefaultDirectionalityFeature.java index 4b8dac74d..b5acb05a5 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/features/tamura2d/DefaultDirectionalityFeature.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/features/tamura2d/DefaultDirectionalityFeature.java @@ -67,7 +67,6 @@ public class DefaultDirectionalityFeature, O extends RealT @OpDependency(name = "create.img") private BiFunction> imgCreator; - @SuppressWarnings("unchecked") /** * TODO * @@ -76,6 +75,7 @@ public class DefaultDirectionalityFeature, O extends RealT * @param output */ @Override + @SuppressWarnings("unchecked") public void compute(final RandomAccessibleInterval input, @Nullable Integer histogramSize, final O output) { diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/derivativeGauss/DefaultDerivativeGauss.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/derivativeGauss/DefaultDerivativeGauss.java index e0404ed49..a5a7bf742 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/derivativeGauss/DefaultDerivativeGauss.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/derivativeGauss/DefaultDerivativeGauss.java @@ -258,7 +258,6 @@ private > void convolve_n( } } - @SuppressWarnings("unchecked") /** * TODO * @@ -269,6 +268,7 @@ private > void convolve_n( * @param output */ @Override + @SuppressWarnings("unchecked") public void compute(final RandomAccessibleInterval input, final double[] sigma, final int[] derivatives, final RandomAccessibleInterval output) diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PadInput.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PadInput.java index 5e90de5da..1d4aab256 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PadInput.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PadInput.java @@ -61,16 +61,16 @@ public class PadInput, I extends RandomAccessibleInterv @OpDependency(name = "filter.padIntervalCentered") private BiFunction paddingIntervalCentered; - @Override - @SuppressWarnings("unchecked") /** * TODO * * @param input * @param paddedDimensions - * @param outOfBoundsFactory The OutOfBoundsFactory used to extend the image + * @param obf The OutOfBoundsFactory used to extend the image * @return the output */ + @Override + @SuppressWarnings("unchecked") public O apply(final I input, final Dimensions paddedDimensions, @Nullable OutOfBoundsFactory> obf) { diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PadInputFFT.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PadInputFFT.java index 2aabfc460..0d5479073 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PadInputFFT.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PadInputFFT.java @@ -65,6 +65,15 @@ public abstract class PadInputFFT, I extends RandomAcce private Function fftSizeOp; + /** + * TODO + * + * @param input function argument 1 + * @param paddedDimensions function argument 2 + * @param fast function argument 3 + * @param obf function argument 4 + * @return + */ @Override @SuppressWarnings("unchecked") public O apply( // diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PadShiftKernel.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PadShiftKernel.java index 660a5eb74..231a6f7b5 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PadShiftKernel.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PadShiftKernel.java @@ -61,8 +61,6 @@ public class PadShiftKernel, I extends RandomAccessible @OpDependency(name = "filter.padIntervalOrigin") private BiFunction paddingIntervalOrigin; - @Override - @SuppressWarnings("unchecked") /** * TODO * @@ -70,6 +68,8 @@ public class PadShiftKernel, I extends RandomAccessible * @param paddedDimensions * @return the output */ + @Override + @SuppressWarnings("unchecked") public O apply(final I kernel, final Dimensions paddedDimensions) { Dimensions paddedFFTInputDimensions; diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PaddingIntervalCentered.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PaddingIntervalCentered.java index 9d5f00474..9f2ca331d 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PaddingIntervalCentered.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PaddingIntervalCentered.java @@ -52,8 +52,6 @@ public class PaddingIntervalCentered, I extends RandomA implements BiFunction { - @Override - @SuppressWarnings("unchecked") /** * TODO * @@ -61,6 +59,8 @@ public class PaddingIntervalCentered, I extends RandomA * @param paddedDimensions * @return the output */ + @Override + @SuppressWarnings("unchecked") public O apply(final I input, final Dimensions paddedDimensions) { final long[] paddedSize = new long[paddedDimensions.numDimensions()]; diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PaddingIntervalOrigin.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PaddingIntervalOrigin.java index 8a1bc5fae..078b4f386 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PaddingIntervalOrigin.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/pad/PaddingIntervalOrigin.java @@ -50,15 +50,15 @@ public class PaddingIntervalOrigin, I extends RandomAcc implements BiFunction { - @Override - @SuppressWarnings("unchecked") /** * TODO * * @param input - * @param interval + * @param centeredInterval * @return the output */ + @Override + @SuppressWarnings("unchecked") public O apply(final I input, final Interval centeredInterval) { int numDimensions = input.numDimensions(); diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/tubeness/DefaultTubeness.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/tubeness/DefaultTubeness.java index 04289cebb..65d1cd72b 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/tubeness/DefaultTubeness.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/tubeness/DefaultTubeness.java @@ -101,6 +101,14 @@ public class DefaultTubeness> implements @OpDependency(name = "transform.project") private Computers.Arity3, Computers.Arity1, DoubleType>, Integer, IterableInterval> projector; + /** + * TODO + * + * @param input + * @param sigma + * @param calibration + * @param tubeness + */ @Override public void compute(final RandomAccessibleInterval input, final Double sigma, final double[] calibration, @@ -238,6 +246,13 @@ class DefaultTubenessWithoutCalibration> implements @OpDependency(name = "filter.tubeness") Computers.Arity3, Double, double[], IterableInterval> tubenessOp; + /** + * TODO Can this be removed? + * + * @param in1 + * @param in3 + * @param out + */ @Override public void compute(RandomAccessibleInterval in1, Double in3, IterableInterval out) diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractBoundarySizeConvexHull.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractBoundarySizeConvexHull.java index 6a7f94ab9..8f859a5ce 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractBoundarySizeConvexHull.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractBoundarySizeConvexHull.java @@ -52,6 +52,12 @@ public abstract class AbstractBoundarySizeConvexHull implements @OpDependency(name = "geom.boundarySize") private Function perimeterFunc; + /** + * TODO + * + * @param input + * @param output + */ @Override public void compute(I input, DoubleType output) { output.set(perimeterFunc.apply(convexHullFunc.apply(input))); diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractBoxivity.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractBoxivity.java index 9e1c248a6..cc021d844 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractBoxivity.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractBoxivity.java @@ -54,6 +54,12 @@ public abstract class AbstractBoxivity implements @OpDependency(name = "geom.smallestEnclosingBoundingBox") private Function smallestEnclosingRectangleFunc; + /** + * TODO + * + * @param input + * @param output + */ @Override public void compute(I input, DoubleType output) { output.set(areaFunc.apply(input).getRealDouble() / areaFunc.apply( diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractConvexity.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractConvexity.java index 69e8a4d9d..94a66569d 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractConvexity.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractConvexity.java @@ -53,6 +53,12 @@ public abstract class AbstractConvexity implements @OpDependency(name = "geom.boundarySizeConvexHull") private Function boundarySizeConvexHull; + /** + * TODO + * + * @param input + * @param output + */ @Override public void compute(final I input, final DoubleType output) { output.set(boundarySizeConvexHull.apply(input).get() / boundarySize.apply( diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractSizeConvexHull.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractSizeConvexHull.java index b4d64eace..07e5e520f 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractSizeConvexHull.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractSizeConvexHull.java @@ -52,6 +52,12 @@ public abstract class AbstractSizeConvexHull implements @OpDependency(name = "geom.size") private Function sizeFunc; + /** + * TODO + * + * @param input + * @param output + */ @Override public void compute(final I input, final DoubleType output) { output.set(sizeFunc.apply(convexHullFunc.apply(input))); diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractSolidity.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractSolidity.java index fd98727ab..fcfcfeed0 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractSolidity.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/AbstractSolidity.java @@ -53,6 +53,12 @@ public abstract class AbstractSolidity implements @OpDependency(name = "geom.sizeConvexHull") private Function convexHullVolume; + /** + * TODO + * + * @param input + * @param output + */ @Override public void compute(final I input, final DoubleType output) { output.set(volume.apply(input).get() / convexHullVolume.apply(input).get()); diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/geom3d/DefaultMarchingCubes.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/geom3d/DefaultMarchingCubes.java index 8ede4362e..9623d1a49 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/geom3d/DefaultMarchingCubes.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/geom/geom3d/DefaultMarchingCubes.java @@ -58,7 +58,6 @@ public class DefaultMarchingCubes> implements private double isolevel; private VertexInterpolator interpolatorClass; - @SuppressWarnings({ "unchecked" }) /** * TODO * @@ -68,6 +67,7 @@ public class DefaultMarchingCubes> implements * @return the output */ @Override + @SuppressWarnings({ "unchecked" }) public Mesh apply(final RandomAccessibleInterval input, @Nullable Double isolevel, @Nullable VertexInterpolator interpolatorClass) { diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/image/integral/AbstractIntegralImg.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/image/integral/AbstractIntegralImg.java index cd7846afd..b65953aa2 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/image/integral/AbstractIntegralImg.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/image/integral/AbstractIntegralImg.java @@ -49,6 +49,12 @@ public abstract class AbstractIntegralImg, O extends RealT Computers.Arity1, RandomAccessibleInterval> { + /** + * TODO + * + * @param input + * @param output + */ @Override public void compute(final RandomAccessibleInterval input, final RandomAccessibleInterval output) diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/image/watershed/WatershedSeeded.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/image/watershed/WatershedSeeded.java index 89e9a8827..96c0dee59 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/image/watershed/WatershedSeeded.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/image/watershed/WatershedSeeded.java @@ -123,18 +123,18 @@ public class WatershedSeeded, B extends BooleanType> /** Used by {@link WatershedSeeded.WatershedVoxel} */ private static final AtomicLong seq = new AtomicLong(); - @SuppressWarnings("unchecked") /** * TODO * - * @param input + * @param in * @param seeds * @param useEightConnectivity * @param drawWatersheds - * @param mask - * @param output + * @param maskInput + * @param out */ @Override + @SuppressWarnings("unchecked") public void compute( // final RandomAccessibleInterval in, // final ImgLabeling seeds, // diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/imagemoments/AbstractImageMomentOp.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/imagemoments/AbstractImageMomentOp.java index 1da7f4764..5342167e0 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/imagemoments/AbstractImageMomentOp.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/imagemoments/AbstractImageMomentOp.java @@ -49,6 +49,12 @@ public interface AbstractImageMomentOp, O extends RealType public void computeMoment(RandomAccessibleInterval input, O output); + /** + * TODO + * + * @param input + * @param output + */ @Override default void compute(RandomAccessibleInterval input, O output) { if (input.numDimensions() != 2) throw new IllegalArgumentException( diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/labeling/MergeLabeling.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/labeling/MergeLabeling.java index c30c68290..fb2f33891 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/labeling/MergeLabeling.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/labeling/MergeLabeling.java @@ -68,16 +68,17 @@ public class MergeLabeling, B extends BooleanType @OpDependency(name = "engine.adapt") private Function, LabelingType, LabelingType>, Computers.Arity2>, Iterable>, Iterable>>> adaptor; - @SuppressWarnings({ "unchecked", "rawtypes", "hiding" }) /** * TODO * - * @param labeling1 - * @param labeling2 + * @param input1 + * @param input1 * @param mask - * @param combinedLabeling + * @return an {@link ImgLabeling} that combines the labels of {@code input1} + * and {@code input2} */ @Override + @SuppressWarnings({ "unchecked", "rawtypes", "hiding" }) public ImgLabeling apply( // final ImgLabeling input1, // final ImgLabeling input2, // diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/labeling/cca/DefaultCCA.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/labeling/cca/DefaultCCA.java index 571a78b49..8d68f2a0d 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/labeling/cca/DefaultCCA.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/labeling/cca/DefaultCCA.java @@ -61,17 +61,16 @@ public class DefaultCCA, L, I extends IntegerType> @OpDependency(name = "create.imgLabeling") private BiFunction> imgLabelingCreator; - @SuppressWarnings("unchecked") /** * TODO * * @param input - * @param executorService - * @param structuringElement + * @param se * @param labelGenerator - * @param labeling + * @return the {@link ImgLabeling} */ @Override + @SuppressWarnings("unchecked") public ImgLabeling apply( // final RandomAccessibleInterval input, // final StructuringElement se, // diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/logic/BooleanTypeLogic.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/logic/BooleanTypeLogic.java index 3182119c1..a9322be51 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/logic/BooleanTypeLogic.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/logic/BooleanTypeLogic.java @@ -43,6 +43,9 @@ public class BooleanTypeLogic, C extends Comparable> /** * Performs logical and ({@literal &&}) between two {@link BooleanType}s. * + * @input in1 the first {@link BooleanType} + * @input in2 the second {@link BooleanType} + * @container out the preallocated result container * @implNote op names='logic.and' */ public final Computers.Arity2 ander = (in1, in2, out) -> { @@ -51,30 +54,44 @@ public class BooleanTypeLogic, C extends Comparable> }; /** + * @input in1 the first {@link Comparable} + * @input in2 the second {@link Comparable} + * @container out the preallocated result container * @implNote op names='logic.greaterThan' */ public final Computers.Arity2 greaterThan = (in1, in2, out) -> out .set(in1.compareTo(in2) > 0); /** + * @input in1 the first {@link Comparable} + * @input in2 the second {@link Comparable} + * @container out the preallocated result container * @implNote op names='logic.greaterThanOrEqual' */ public final Computers.Arity2 greaterThanOrEqual = (in1, in2, out) -> out.set(in1.compareTo(in2) >= 0); /** + * @input in1 the first {@link Comparable} + * @input in2 the second {@link Comparable} + * @container out the preallocated result container * @implNote op names='logic.lessThan' */ public final Computers.Arity2 lessThan = (in1, in2, out) -> out.set( in1.compareTo(in2) < 0); /** + * @input in1 the first {@link Comparable} + * @input in2 the second {@link Comparable} + * @container out the preallocated result container * @implNote op names='logic.lessThanOrEqual' */ public final Computers.Arity2 lessThanOrEqual = (in1, in2, out) -> out.set(in1.compareTo(in2) <= 0); /** + * @input in1 some {@link BooleanType} + * @container out the preallocated result container * @implNote op names='logic.not' */ public final Computers.Arity1 not = (in, out) -> { @@ -83,18 +100,29 @@ public class BooleanTypeLogic, C extends Comparable> }; /** + * @input in1 the first {@link Comparable} + * @input in2 the second {@link Comparable} + * @container out the preallocated result container * @implNote op names='logic.equal' */ public final Computers.Arity2 equals = (in1, in2, out) -> out.set(in1 .equals(in2)); /** + * @input in1 the first {@link Comparable} + * @input in2 the second {@link Comparable} + * @container out the preallocated result container * @implNote op names='logic.notEqual' */ public final Computers.Arity2 notEquals = (in1, in2, out) -> out.set( !in1.equals(in2)); /** + * Performs logical or ({@literal ||}) between two {@link BooleanType}s. + * + * @input in1 the first {@link BooleanType} + * @input in2 the second {@link BooleanType} + * @container out the preallocated result container * @implNote op names='logic.or' */ public final Computers.Arity2 or = (in1, in2, out) -> { @@ -103,6 +131,11 @@ public class BooleanTypeLogic, C extends Comparable> }; /** + * Performs logical xor ({@literal ^}) between two {@link BooleanType}s. + * + * @input in1 the first {@link BooleanType} + * @input in2 the second {@link BooleanType} + * @container out the preallocated result container * @implNote op names='logic.xor' */ public final Computers.Arity2 xor = (in1, in2, out) -> { diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/morphology/BlackTopHats.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/morphology/BlackTopHats.java index 1aa3c5222..1bdfb0b7f 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/morphology/BlackTopHats.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/morphology/BlackTopHats.java @@ -56,55 +56,127 @@ public class BlackTopHats & Comparable, R extends RealType> { /** + * Iteratively performs Black Top Hats with each passed {@link Shape} in + * order. + * + * @input img input data + * @input shapes a {@link List} containing the {@link Shape}s to use + * for each Black Top Hat neighborhood + * @input numThreads the number of threads to use in the execution + * @output img output data * @implNote op names='morphology.BlackTopHat' */ @SuppressWarnings("unchecked") public final Functions.Arity3, List, Integer, Img> BlackTopHatImgList = - (in1, in2, in3) -> BlackTopHat.blackTopHat(in1, (List) in2, in3); + BlackTopHat::blackTopHat; /** + * Performs a Black Top Hat. + * + * @input img input data + * @input shape a {@link Shape} to use for the neighborhood + * @input numThreads the number of threads to use in the execution + * @output img output data * @implNote op names='morphology.BlackTopHat' */ public final Functions.Arity3, Shape, Integer, Img> BlackTopHatImgSingle = BlackTopHat::blackTopHat; /** + * Iteratively performs Black Top Hats with each passed {@link Shape} in + * order. + * + * @input img input data + * @input shapes a {@link List} containing the {@link Shape}s to use + * for each Black Top Hat neighborhood + * @input numThreads the number of threads to use in the execution + * @input minValue a {@link T} that is smaller than all values in the input + * image. Used to speed computation. + * @input maxValue a {@link T} that is larger than all values in the input + * image. Used to speed computation. + * @output img output data * @implNote op names='morphology.BlackTopHat' */ @SuppressWarnings("unchecked") public final Functions.Arity5, List, T, T, Integer, Img> BlackTopHatImgListMinMax = - (in1, in2, in3, in4, in5) -> BlackTopHat.blackTopHat(in1, (List) in2, - in3, in4, in5); + BlackTopHat::blackTopHat; /** + * Performs a Black Top Hat. + * + * @input img input data + * @input shape a {@link Shape} to use for the neighborhood + * @input numThreads the number of threads to use in the execution + * @input minValue a {@link T} that is smaller than all values in the input + * image. Used to speed computation. + * @input maxValue a {@link T} that is larger than all values in the input + * image. Used to speed computation. + * @output img output data * @implNote op names='morphology.BlackTopHat' */ public final Functions.Arity5, Shape, T, T, Integer, Img> BlackTopHatImgSingleMinMax = BlackTopHat::blackTopHat; /** + * Iteratively performs Black Top Hats with each passed {@link Shape} in + * order, placing the result into the provided preallocated output buffer. + * + * @input img input data + * @input shapes a {@link List} containing the {@link Shape}s to use + * for each Black Top Hat neighborhood + * @input numThreads the number of threads to use in the execution + * @container img output data * @implNote op names='morphology.BlackTopHat' */ @SuppressWarnings("unchecked") public final Computers.Arity3, List, Integer, IterableInterval> BlackTopHatImgListComputer = - (in1, in2, in3, out) -> BlackTopHat.blackTopHat(in1, out, (List) in2, - in3); + (in1, in2, in3, out) -> BlackTopHat.blackTopHat(in1, out, in2, in3); /** + * Iteratively performs Black Top Hats with each passed {@link Shape} in + * order, placing the result into the provided preallocated output buffer. + * + * @input img input data + * @input shapes a {@link List} containing the {@link Shape}s to use + * for each Black Top Hat neighborhood + * @input numThreads the number of threads to use in the execution + * @input minValue a {@link T} that is smaller than all values in the input + * image. Used to speed computation. + * @input maxValue a {@link T} that is larger than all values in the input + * image. Used to speed computation. + * @container img output data * @implNote op names='morphology.BlackTopHat' */ @SuppressWarnings("unchecked") public final Computers.Arity5, List, T, T, Integer, IterableInterval> BlackTopHatImgListMinMaxComputer = - (in1, in2, in3, in4, in5, out) -> BlackTopHat.blackTopHat(in1, out, - (List) in2, in3, in4, in5); + (in1, in2, in3, in4, in5, out) -> BlackTopHat.blackTopHat(in1, out, in2, + in3, in4, in5); /** + * Performs a Black Top Hat, placing the result into the provided preallocated + * output buffer. + * + * @input img input data + * @input shape a {@link Shape} to use for the neighborhood + * @input numThreads the number of threads to use in the execution + * @container img output data * @implNote op names='morphology.BlackTopHat' */ public final Computers.Arity3, Shape, Integer, IterableInterval> BlackTopHatImgComputer = (in1, in2, in3, out) -> BlackTopHat.blackTopHat(in1, out, in2, in3); /** + * Performs a Black Top Hat, placing the result into the provided preallocated + * output buffer. + * + * @input img input data + * @input shape a {@link Shape} to use for the neighborhood + * @input numThreads the number of threads to use in the execution + * @input minValue a {@link T} that is smaller than all values in the input + * image. Used to speed computation. + * @input maxValue a {@link T} that is larger than all values in the input + * image. Used to speed computation. + * @container img output data * @implNote op names='morphology.BlackTopHat' */ public final Computers.Arity5, Shape, T, T, Integer, IterableInterval> BlackTopHatImgMinMaxComputer = @@ -112,6 +184,15 @@ public class BlackTopHats & Comparable, R extends RealT in3, in4, in5); /** + * Iteratively performs Black Top Hats within the provided + * {@link Interval} with each passed {@link Shape} in order, overwriting + * the provided input buffer with the results + * + * @mutable img input data + * @input interval the {@link Interval} restricting the bounds of computation. + * @input shapes a {@link List} containing the {@link Shape}s to use + * for each Black Top Hat neighborhood + * @input numThreads the number of threads to use in the execution * @implNote op names='morphology.BlackTopHat' */ @SuppressWarnings("unchecked") @@ -120,6 +201,19 @@ public class BlackTopHats & Comparable, R extends RealT (List) in3, in4); /** + * Iteratively performs Black Top Hats within the provided + * {@link Interval} with each passed {@link Shape} in order, overwriting + * the provided input buffer with the results + * + * @mutable img input data + * @input interval the {@link Interval} restricting the bounds of computation. + * @input shapes a {@link List} containing the {@link Shape}s to use + * for each Black Top Hat neighborhood + * @input numThreads the number of threads to use in the execution + * @input minValue a {@link T} that is smaller than all values in the input + * image. Used to speed computation. + * @input maxValue a {@link T} that is larger than all values in the input + * image. Used to speed computation. * @implNote op names='morphology.BlackTopHat' */ @SuppressWarnings("unchecked") @@ -128,12 +222,30 @@ public class BlackTopHats & Comparable, R extends RealT (List) in3, in4, in5, in6); /** + * Performs a Black Top Hat within the provided {@link Interval}, + * overwriting the provided input buffer with the results + * + * @mutable img input data + * @input interval the {@link Interval} restricting the bounds of computation. + * @input shape a {@link Shape} to use for the neighborhood + * @input numThreads the number of threads to use in the execution * @implNote op names='morphology.BlackTopHat' */ public final Inplaces.Arity4_1, Interval, Shape, Integer> BlackTopHatImgSingleInPlace = BlackTopHat::blackTopHatInPlace; /** + * Performs a Black Top Hat within the provided {@link Interval}, + * overwriting the provided input buffer with the results + * + * @mutable img input data + * @input interval the {@link Interval} restricting the bounds of computation. + * @input shape a {@link Shape} to use for the neighborhood + * @input numThreads the number of threads to use in the execution + * @input minValue a {@link T} that is smaller than all values in the input + * image. Used to speed computation. + * @input maxValue a {@link T} that is larger than all values in the input + * image. Used to speed computation. * @implNote op names='morphology.BlackTopHat' */ public final Inplaces.Arity6_1, Interval, Shape, T, T, Integer> BlackTopHatImgSingleMinMaxInplace = diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/stats/DefaultPNorm.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/stats/DefaultPNorm.java index b1c8d794e..e190c281c 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/stats/DefaultPNorm.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/stats/DefaultPNorm.java @@ -1,8 +1,8 @@ /*- * #%L - * ImageJ2 software for multidimensional image processing and analysis. + * Image processing operations for SciJava Ops. * %% - * Copyright (C) 2014 - 2024 ImageJ2 developers. + * Copyright (C) 2014 - 2024 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/stats/DefaultQNorm.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/stats/DefaultQNorm.java index 67d41787d..2cfa2aa5d 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/stats/DefaultQNorm.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/stats/DefaultQNorm.java @@ -1,8 +1,8 @@ /*- * #%L - * ImageJ2 software for multidimensional image processing and analysis. + * Image processing operations for SciJava Ops. * %% - * Copyright (C) 2014 - 2024 ImageJ2 developers. + * Copyright (C) 2014 - 2024 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/threshold/AbstractComputeThresholdHistogram.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/threshold/AbstractComputeThresholdHistogram.java index 49bbc1c3a..13881f644 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/threshold/AbstractComputeThresholdHistogram.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/threshold/AbstractComputeThresholdHistogram.java @@ -43,6 +43,12 @@ public abstract class AbstractComputeThresholdHistogram> implements Computers.Arity1, T> { + /** + * TODO + * + * @param input + * @param output + */ @Override public void compute(final Histogram1d input, final T output) { final long binPos = computeBin(input); diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/threshold/ApplyLocalThresholdIntegral.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/threshold/ApplyLocalThresholdIntegral.java index 150ad7d3f..076e41f8c 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/threshold/ApplyLocalThresholdIntegral.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/threshold/ApplyLocalThresholdIntegral.java @@ -94,6 +94,16 @@ else throw new OpExecutionException( ". There is no op available to do that (available orders are: 1, 2)."); } + /** + * TODO + * + * @param input + * @param inputNeighborhoodShape + * @param outOfBoundsFactory + * @param integralImageOps + * @param thresholdOp + * @param output + */ protected void compute(final RandomAccessibleInterval input, RectangleShape inputNeighborhoodShape, OutOfBoundsFactory> outOfBoundsFactory, diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/threshold/localPhansalkar/LocalPhansalkarThreshold.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/threshold/localPhansalkar/LocalPhansalkarThreshold.java index a8a1a5d67..b753c9b42 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/threshold/localPhansalkar/LocalPhansalkarThreshold.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/threshold/localPhansalkar/LocalPhansalkarThreshold.java @@ -73,8 +73,8 @@ public class LocalPhansalkarThreshold> extends * @param input * @param inputNeighborhoodShape * @param k - * @Param r - * @Param outOfBoundsFactory + * @param r + * @param outOfBoundsFactory * @param output */ @Override diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/types/maxValue/MaxValueRealTypes.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/types/maxValue/MaxValueRealTypes.java index cadb89b66..99485ef28 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/types/maxValue/MaxValueRealTypes.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/types/maxValue/MaxValueRealTypes.java @@ -64,6 +64,8 @@ public class MaxValueRealTypes { final BitType maxBit = new BitType(true); /** + * @input in some {@link BitType} + * @output maxValue a {@link BitType} containing the maximum value of a bit * @implNote op names='types.maxValue' */ public final Function maxBitType = in -> { @@ -73,6 +75,9 @@ public class MaxValueRealTypes { final BoolType maxBool = new BoolType(true); /** + * @input in some {@link BoolType} + * @output maxValue a {@link BoolType} containing the maximum value of a + * boolean * @implNote op names='types.maxValue' */ public final Function maxBoolType = in -> { @@ -82,6 +87,9 @@ public class MaxValueRealTypes { final NativeBoolType maxNativeBool = new NativeBoolType(true); /** + * @input in some {@link NativeBoolType} + * @output maxValue a {@link NativeBoolType} containing the maximum value of a + * boolean * @implNote op names='types.maxValue' */ public final Function maxNativeBoolType = @@ -92,6 +100,8 @@ public class MaxValueRealTypes { final ByteType maxByte = new ByteType(Byte.MAX_VALUE); /** + * @input in some {@link ByteType} + * @output maxValue a {@link ByteType} containing the maximum value of a byte * @implNote op names='types.maxValue' */ public final Function maxByteType = in -> { @@ -102,6 +112,9 @@ public class MaxValueRealTypes { -Byte.MIN_VALUE + Byte.MAX_VALUE); /** + * @input in some {@link UnsignedByteType} + * @output maxValue a {@link UnsignedByteType} containing the maximum value of + * an unsigned byte * @implNote op names='types.maxValue' */ public final Function maxUnsignedByteType = @@ -112,6 +125,8 @@ public class MaxValueRealTypes { final IntType maxInt = new IntType(Integer.MAX_VALUE); /** + * @input in some {@link IntType} + * @output maxValue a {@link IntType} containing the maximum value of an int * @implNote op names='types.maxValue' */ public final Function maxIntType = in -> { @@ -121,6 +136,9 @@ public class MaxValueRealTypes { final UnsignedIntType maxUnsignedInt = new UnsignedIntType(0xffffffffL); /** + * @input in some {@link UnsignedIntType} + * @output maxValue a {@link UnsignedIntType} containing the maximum value of + * an unsigned int * @implNote op names='types.maxValue' */ public final Function maxUnsignedIntType = @@ -131,6 +149,8 @@ public class MaxValueRealTypes { final LongType maxLong = new LongType(Long.MAX_VALUE); /** + * @input in some {@link LongType} + * @output maxValue a {@link LongType} containing the maximum value of a long * @implNote op names='types.maxValue' */ public final Function maxLongType = in -> { @@ -141,6 +161,9 @@ public class MaxValueRealTypes { new UnsignedLongType().getMaxBigIntegerValue()); /** + * @input in some {@link UnsignedLongType} + * @output maxValue a {@link UnsignedLongType} containing the maximum value of + * an unsigned long * @implNote op names='types.maxValue' */ public final Function maxUnsignedLongType = @@ -151,6 +174,9 @@ public class MaxValueRealTypes { final ShortType maxShort = new ShortType(Short.MAX_VALUE); /** + * @input in some {@link ShortType} + * @output maxValue a {@link ShortType} containing the maximum value of a + * short * @implNote op names='types.maxValue' */ public final Function maxShortType = in -> { @@ -161,6 +187,9 @@ public class MaxValueRealTypes { -Short.MIN_VALUE + Short.MAX_VALUE); /** + * @input in some {@link UnsignedShortType} + * @output maxValue a {@link UnsignedShortType} containing the maximum value + * of an unsigned short * @implNote op names='types.maxValue' */ public final Function maxUnsignedShortType = @@ -171,6 +200,9 @@ public class MaxValueRealTypes { final FloatType maxFloat = new FloatType(Float.MAX_VALUE); /** + * @input in some {@link FloatType} + * @output maxValue a {@link FloatType} containing the maximum value of a + * 32-bit floating point value * @implNote op names='types.maxValue' */ public final Function maxFloatType = in -> { @@ -180,6 +212,9 @@ public class MaxValueRealTypes { final DoubleType maxDouble = new DoubleType(Double.MAX_VALUE); /** + * @input in some {@link DoubleType} + * @output maxValue a {@link DoubleType} containing the maximum value of a + * 64-bit floating point value * @implNote op names='types.maxValue' */ public final Function maxDoubleType = in -> { @@ -189,6 +224,9 @@ public class MaxValueRealTypes { final Unsigned2BitType max2Bit = new Unsigned2BitType(3); /** + * @input in some {@link Unsigned2BitType} + * @output maxValue a {@link Unsigned2BitType} containing the maximum value of + * a 2-bit data structure * @implNote op names='types.maxValue' */ public final Function max2BitType = @@ -199,6 +237,9 @@ public class MaxValueRealTypes { final Unsigned4BitType max4Bit = new Unsigned4BitType(15); /** + * @input in some {@link Unsigned4BitType} + * @output maxValue a {@link Unsigned4BitType} containing the maximum value of + * a 4-bit data structure * @implNote op names='types.maxValue' */ public final Function max4BitType = @@ -209,6 +250,9 @@ public class MaxValueRealTypes { final Unsigned12BitType max12Bit = new Unsigned12BitType(4095); /** + * @input in some {@link Unsigned12BitType} + * @output maxValue a {@link Unsigned12BitType} containing the maximum value + * of a 12-bit data structure * @implNote op names='types.maxValue' */ public final Function max12BitType = @@ -220,6 +264,9 @@ public class MaxValueRealTypes { new Unsigned128BitType().getMaxBigIntegerValue()); /** + * @input in some {@link Unsigned128BitType} + * @output maxValue a {@link Unsigned128BitType} containing the maximum value + * of a 128-bit data structure * @implNote op names='types.maxValue' */ public final Function max128BitType = @@ -239,6 +286,9 @@ public class MaxValueRealTypes { * unsupported). TODO: Is there some way we could cache the values? Is that * worth it?? * + * @input in some {@link UnsignedVariableBitLengthType} + * @output maxValue a {@link UnsignedVariableBitLengthType} containing the + * maximum value storable in {@code in} * @implNote op names='types.maxValue' */ public final Function maxVarLengthType = diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/types/minValue/MinValueRealTypes.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/types/minValue/MinValueRealTypes.java index 0085ea466..cae457be0 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/types/minValue/MinValueRealTypes.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/types/minValue/MinValueRealTypes.java @@ -64,6 +64,8 @@ public class MinValueRealTypes { final BitType minBit = new BitType(false); /** + * @input in some {@link BitType} + * @output minValue a {@link BitType} containing the minimum value of a bit * @implNote op names='types.minValue' */ public final Function minBitType = in -> { @@ -73,6 +75,9 @@ public class MinValueRealTypes { final BoolType minBool = new BoolType(false); /** + * @input in some {@link BoolType} + * @output minValue a {@link BoolType} containing the minimum value of a + * boolean * @implNote op names='types.minValue' */ public final Function minBoolType = in -> { @@ -82,6 +87,9 @@ public class MinValueRealTypes { final NativeBoolType minNativeBool = new NativeBoolType(false); /** + * @input in some {@link NativeBoolType} + * @output minValue a {@link NativeBoolType} containing the minimum value of a + * boolean * @implNote op names='types.minValue' */ public final Function minNativeBoolType = @@ -92,6 +100,8 @@ public class MinValueRealTypes { final ByteType minByte = new ByteType(Byte.MIN_VALUE); /** + * @input in some {@link ByteType} + * @output minValue a {@link ByteType} containing the minimum value of a byte * @implNote op names='types.minValue' */ public final Function minByteType = in -> { @@ -101,6 +111,9 @@ public class MinValueRealTypes { final UnsignedByteType minUnsignedByte = new UnsignedByteType(0); /** + * @input in some {@link UnsignedByteType} + * @output minValue a {@link UnsignedByteType} containing the minimum value of + * an unsigned byte * @implNote op names='types.minValue' */ public final Function minUnsignedByteType = @@ -111,6 +124,8 @@ public class MinValueRealTypes { final IntType minInt = new IntType(Integer.MIN_VALUE); /** + * @input in some {@link IntType} + * @output minValue a {@link IntType} containing the minimum value of an int * @implNote op names='types.minValue' */ public final Function minIntType = in -> { @@ -120,6 +135,9 @@ public class MinValueRealTypes { final UnsignedIntType minUnsignedInt = new UnsignedIntType(0); /** + * @input in some {@link UnsignedIntType} + * @output minValue a {@link UnsignedIntType} containing the minimum value of + * an unsigned int * @implNote op names='types.minValue' */ public final Function minUnsignedIntType = @@ -130,6 +148,8 @@ public class MinValueRealTypes { final LongType minLong = new LongType(Long.MIN_VALUE); /** + * @input in some {@link LongType} + * @output minValue a {@link LongType} containing the minimum value of a long * @implNote op names='types.minValue' */ public final Function minLongType = in -> { @@ -139,6 +159,9 @@ public class MinValueRealTypes { final UnsignedLongType minUnsignedLong = new UnsignedLongType(0); /** + * @input in some {@link UnsignedLongType} + * @output minValue a {@link UnsignedLongType} containing the minimum value of + * an unsigned long * @implNote op names='types.minValue' */ public final Function minUnsignedLongType = @@ -149,6 +172,9 @@ public class MinValueRealTypes { final ShortType minShort = new ShortType(Short.MIN_VALUE); /** + * @input in some {@link ShortType} + * @output minValue a {@link ShortType} containing the minimum value of a + * short * @implNote op names='types.minValue' */ public final Function minShortType = in -> { @@ -158,6 +184,9 @@ public class MinValueRealTypes { final UnsignedShortType minUnsignedShort = new UnsignedShortType(0); /** + * @input in some {@link UnsignedShortType} + * @output minValue a {@link UnsignedShortType} containing the minimum value + * of an unsigned short * @implNote op names='types.minValue' */ public final Function minUnsignedShortType = @@ -168,6 +197,9 @@ public class MinValueRealTypes { final FloatType minFloat = new FloatType(Float.MIN_VALUE); /** + * @input in some {@link FloatType} + * @output minValue a {@link FloatType} containing the minimum value of a + * 32-bit floating point value * @implNote op names='types.minValue' */ public final Function minFloatType = in -> { @@ -177,6 +209,9 @@ public class MinValueRealTypes { final DoubleType minDouble = new DoubleType(Double.MIN_VALUE); /** + * @input in some {@link DoubleType} + * @output minValue a {@link DoubleType} containing the minimum value of a + * 64-bit floating point value * @implNote op names='types.minValue' */ public final Function minDoubleType = in -> { @@ -186,6 +221,9 @@ public class MinValueRealTypes { final Unsigned2BitType min2Bit = new Unsigned2BitType(0); /** + * @input in some {@link Unsigned2BitType} + * @output minValue a {@link Unsigned2BitType} containing the minimum value of + * a 2-bit data structure * @implNote op names='types.minValue' */ public final Function min2BitType = @@ -196,6 +234,9 @@ public class MinValueRealTypes { final Unsigned4BitType min4Bit = new Unsigned4BitType(0); /** + * @input in some {@link Unsigned4BitType} + * @output minValue a {@link Unsigned4BitType} containing the minimum value of + * a 4-bit data structure * @implNote op names='types.minValue' */ public final Function min4BitType = @@ -206,6 +247,9 @@ public class MinValueRealTypes { final Unsigned12BitType min12Bit = new Unsigned12BitType(0); /** + * @input in some {@link Unsigned12BitType} + * @output minValue a {@link Unsigned12BitType} containing the minimum value + * of a 12-bit data structure * @implNote op names='types.minValue' */ public final Function min12BitType = @@ -216,6 +260,9 @@ public class MinValueRealTypes { final Unsigned128BitType min128Bit = new Unsigned128BitType(BigInteger.ZERO); /** + * @input in some {@link Unsigned128BitType} + * @output minValue a {@link Unsigned128BitType} containing the minimum value + * of a 128-bit data structure * @implNote op names='types.minValue' */ public final Function min128BitType = @@ -230,6 +277,9 @@ public class MinValueRealTypes { * value. The best we can do is quickly compute the answer. TODO: Is there * some way we could cache the values? Is that worth it?? * + * @input in some {@link UnsignedVariableBitLengthType} + * @output minValue a {@link UnsignedVariableBitLengthType} containing the + * minimum value that can be stored in {@code in} * @implNote op names='types.minValue' */ public final Function minVarLengthType = diff --git a/scijava-ops-image/src/test/java/org/scijava/ops/image/adapt/LiftFunctionsToImgTest.java b/scijava-ops-image/src/test/java/org/scijava/ops/image/adapt/LiftFunctionsToImgTest.java index 5ed93eac7..64da94c1e 100644 --- a/scijava-ops-image/src/test/java/org/scijava/ops/image/adapt/LiftFunctionsToImgTest.java +++ b/scijava-ops-image/src/test/java/org/scijava/ops/image/adapt/LiftFunctionsToImgTest.java @@ -47,6 +47,8 @@ public class LiftFunctionsToImgTest extends AbstractOpTest implements { /** + * @input in + * @output out * @implNote op names="test.liftFunctionToImg" */ public final Function inc = // diff --git a/scijava-ops-image/src/test/java/org/scijava/ops/image/describe/ImgLib2DescriptorsTest.java b/scijava-ops-image/src/test/java/org/scijava/ops/image/describe/ImgLib2DescriptorsTest.java index 416eb4fa0..ca1a3f387 100644 --- a/scijava-ops-image/src/test/java/org/scijava/ops/image/describe/ImgLib2DescriptorsTest.java +++ b/scijava-ops-image/src/test/java/org/scijava/ops/image/describe/ImgLib2DescriptorsTest.java @@ -54,7 +54,8 @@ public class ImgLib2DescriptorsTest extends AbstractOpTest { /** - * @implNote op name=test.describeRealType, type=Inplace + * @param in + * @implNote op name=test.describeRealType, type=Inplace1 */ public static > void realType(T in) { in.mul(in); @@ -68,7 +69,8 @@ public void testRealTypeDescription() { } /** - * @implNote op name=test.describeComplexType, type=Inplace + * @param in + * @implNote op name=test.describeComplexType, type=Inplace1 */ public static > void complexType(T in) { in.mul(in); @@ -83,7 +85,8 @@ public void testComplexTypeDescription() { } /** - * @implNote op name=test.RAImutator, type=Inplace + * @param in + * @implNote op name=test.RAImutator, type=Inplace1 */ public static > void randomAccessibleInterval( RandomAccessibleInterval in) @@ -99,7 +102,8 @@ public void testRAIDescription() { } /** - * @implNote op name=test.IIMutator, type=Inplace + * @param in + * @implNote op name=test.IIMutator, type=Inplace1 */ public static > void iterableInterval( IterableInterval in) @@ -119,7 +123,8 @@ public void testIIDescription() { } /** - * @implNote op name=test.ImgLabelingMutator, type=Inplace + * @param in + * @implNote op name=test.ImgLabelingMutator, type=Inplace1 */ public static > void imgLabeling( ImgLabeling in) @@ -134,7 +139,8 @@ public void testImgLabelingDescription() { } /** - * @implNote op name=test.ArrayImgMutator, type=Inplace + * @param in + * @implNote op name=test.ArrayImgMutator, type=Inplace1 */ public static , A extends DataAccess> void arrayImg( ArrayImg in) diff --git a/scijava-ops-image/src/test/java/org/scijava/ops/image/filter/gauss/GaussTest.java b/scijava-ops-image/src/test/java/org/scijava/ops/image/filter/gauss/GaussTest.java index 6ab397bd9..590c38771 100644 --- a/scijava-ops-image/src/test/java/org/scijava/ops/image/filter/gauss/GaussTest.java +++ b/scijava-ops-image/src/test/java/org/scijava/ops/image/filter/gauss/GaussTest.java @@ -29,8 +29,6 @@ package org.scijava.ops.image.filter.gauss; -import org.scijava.ops.image.AbstractOpTest; -import org.scijava.ops.image.util.TestImgGeneration; import net.imglib2.Cursor; import net.imglib2.algorithm.gauss3.Gauss3; import net.imglib2.exception.IncompatibleTypeException; @@ -38,9 +36,10 @@ import net.imglib2.type.numeric.integer.ByteType; import net.imglib2.util.Util; import net.imglib2.view.Views; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.scijava.ops.image.AbstractOpTest; +import org.scijava.ops.image.util.TestImgGeneration; import org.scijava.types.Nil; /** diff --git a/scijava-ops-image/src/test/java/org/scijava/ops/image/types/TypeExtractorTests.java b/scijava-ops-image/src/test/java/org/scijava/ops/image/types/TypeExtractorTests.java index 23f218aed..1a8a3b0a8 100644 --- a/scijava-ops-image/src/test/java/org/scijava/ops/image/types/TypeExtractorTests.java +++ b/scijava-ops-image/src/test/java/org/scijava/ops/image/types/TypeExtractorTests.java @@ -52,6 +52,8 @@ public class TypeExtractorTests extends AbstractOpTest { /** + * @input oobf the {@link OutOfBoundsConstantValueFactory} + * @output some {@link String} * @implNote op names='test.oobcvfTypeExtractor' */ public final Function>, String> func = @@ -72,6 +74,9 @@ public void testOutOfBoundsConstantValueFactoryTypeExtractors() { // Test Op returns a string different from the one above /** + * @input oobf the {@link OutOfBoundsRandomValueFactory} + * @input rai the {@link RandomAccessibleInterval} + * @output some {@link String} * @implNote op names='test.oobrvfTypeExtractor' */ public final BiFunction>, RandomAccessibleInterval, String> funcRandom = diff --git a/scijava-ops-image/src/test/java/org/scijava/ops/image/types/adapt/LiftComputersToRAITest.java b/scijava-ops-image/src/test/java/org/scijava/ops/image/types/adapt/LiftComputersToRAITest.java index ba087f1dc..c90e5a085 100644 --- a/scijava-ops-image/src/test/java/org/scijava/ops/image/types/adapt/LiftComputersToRAITest.java +++ b/scijava-ops-image/src/test/java/org/scijava/ops/image/types/adapt/LiftComputersToRAITest.java @@ -47,29 +47,49 @@ public class LiftComputersToRAITest, O extends RealType { /** + * @input in + * @container out * @implNote op names='test.computer.liftImg' */ public final Computers.Arity1 testOp = (in, out) -> out.setReal(10.); /** + * @input in1 + * @input in2 + * @container out * @implNote op names='test.computer.liftImg' */ public final Computers.Arity2 testOp2 = (in1, in2, out) -> out .setReal(20.); /** + * @input in1 + * @input in2 + * @input in3 + * @container out * @implNote op names='test.computer.liftImg' */ public final Computers.Arity3 testOp3 = (in1, in2, in3, out) -> out.setReal(30.); /** + * @input in1 + * @input in2 + * @input in3 + * @input in4 + * @container out * @implNote op names='test.computer.liftImg' */ public final Computers.Arity4 testOp4 = (in1, in2, in3, in4, out) -> out.setReal(40.); /** + * @input in1 + * @input in2 + * @input in3 + * @input in4 + * @input in5 + * @container out * @implNote op names='test.computer.liftImg' */ public final Computers.Arity5 testOp5 = (in1, in2, in3, in4, diff --git a/scijava-ops-image/src/test/java/org/scijava/ops/image/types/adapt/LiftFunctionsToRAITest.java b/scijava-ops-image/src/test/java/org/scijava/ops/image/types/adapt/LiftFunctionsToRAITest.java index 483bf3bad..ecb2fecce 100644 --- a/scijava-ops-image/src/test/java/org/scijava/ops/image/types/adapt/LiftFunctionsToRAITest.java +++ b/scijava-ops-image/src/test/java/org/scijava/ops/image/types/adapt/LiftFunctionsToRAITest.java @@ -50,29 +50,49 @@ public class LiftFunctionsToRAITest> extends { /** + * @input in + * @output doubleType * @implNote op names='test.function.liftImg' */ public final Function testOp = (in) -> new DoubleType(10d); /** + * @input in1 + * @input in2 + * @output doubleType * @implNote op names='test.function.liftImg' */ public final BiFunction testOp2 = (in1, in2) -> new DoubleType(20d); /** + * @input in1 + * @input in2 + * @input in3 + * @output doubleType * @implNote op names='test.function.liftImg' */ public final Functions.Arity3 testOp3 = (in1, in2, in3) -> new DoubleType(30d); /** + * @input in1 + * @input in2 + * @input in3 + * @input in4 + * @output doubleType * @implNote op names='test.function.liftImg' */ public final Functions.Arity4 testOp4 = (in1, in2, in3, in4) -> new DoubleType(40d); /** + * @input in1 + * @input in2 + * @input in3 + * @input in4 + * @input in5 + * @output doubleType * @implNote op names='test.function.liftImg' */ public final Functions.Arity5 testOp5 = (in1, in2, diff --git a/scijava-ops-image/src/test/java/org/scijava/ops/image/types/adapt/LiftNeighborhoodComputersToRAITest.java b/scijava-ops-image/src/test/java/org/scijava/ops/image/types/adapt/LiftNeighborhoodComputersToRAITest.java index bf6c67136..43b5ec829 100644 --- a/scijava-ops-image/src/test/java/org/scijava/ops/image/types/adapt/LiftNeighborhoodComputersToRAITest.java +++ b/scijava-ops-image/src/test/java/org/scijava/ops/image/types/adapt/LiftNeighborhoodComputersToRAITest.java @@ -50,6 +50,8 @@ public class LiftNeighborhoodComputersToRAITest extends AbstractOpTest { /** + * @input in + * @container out * @implNote op names='test.liftImg' */ public final Computers.Arity1, UnsignedByteType> testOp = diff --git a/scijava-ops-image/templates/main/java/org/scijava/ops/image/adapt/LiftComputersToRAI.vm b/scijava-ops-image/templates/main/java/org/scijava/ops/image/adapt/LiftComputersToRAI.vm index ccdb033e1..7916ad119 100644 --- a/scijava-ops-image/templates/main/java/org/scijava/ops/image/adapt/LiftComputersToRAI.vm +++ b/scijava-ops-image/templates/main/java/org/scijava/ops/image/adapt/LiftComputersToRAI.vm @@ -39,6 +39,8 @@ public final class LiftComputersToRAI { #foreach($rais in [1..$arity]) /** + * @param originalOp the original Op + * @return the adapted Op * @implNote op names='engine.adapt', priority='100.' */ public static <#foreach($a in [1..$arity])I$a, #{end}O, #foreach($a in [1..$rais])RAII$a extends RandomAccessibleInterval, #{end}RAIO extends RandomAccessibleInterval> Computers.Arity$arity<#foreach($a in [1..$arity])#if($a <= $rais)RAI#{end}I$a, #{end}RAIO> lift$arity$rais(Computers.Arity$arity<#foreach($a in [1..$arity])I$a, #{end}O> computer) { diff --git a/scijava-ops-image/templates/main/java/org/scijava/ops/image/adapt/LiftFunctionsToRAI.vm b/scijava-ops-image/templates/main/java/org/scijava/ops/image/adapt/LiftFunctionsToRAI.vm index 2187067a3..485049019 100644 --- a/scijava-ops-image/templates/main/java/org/scijava/ops/image/adapt/LiftFunctionsToRAI.vm +++ b/scijava-ops-image/templates/main/java/org/scijava/ops/image/adapt/LiftFunctionsToRAI.vm @@ -42,6 +42,9 @@ public final class LiftFunctionsToRAI { #foreach($rais in [1..$arity]) /** + * @param imgCreator an Op that can create the output {@link RandomAccessibleInterval} + * @param func the original Op, operating on {@link RandomAccessibleInterval} elements + * @return {@code op} lifted to operate on {@link RandomAccessibleInterval}s. * @implNote op names='engine.adapt', priority='100.' */ public static <#foreach($a in [1..$arity])I$a, #{end}O extends Type, #foreach($a in [1..$rais])RAII$a extends RandomAccessibleInterval, #{end}RAIO extends RandomAccessibleInterval> $functionNames.call($arity)<#foreach($a in [1..$arity])#if($a <= $rais)RAI#{end}I$a, #{end}RAIO> lift$arity$rais( diff --git a/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpClassImplData.java b/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpClassImplData.java index 73f93cc81..49da28ee4 100644 --- a/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpClassImplData.java +++ b/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpClassImplData.java @@ -29,9 +29,6 @@ package org.scijava.ops.indexer; -import static org.scijava.ops.indexer.ProcessingUtils.blockSeparator; -import static org.scijava.ops.indexer.ProcessingUtils.tagElementSeparator; - import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.List; @@ -42,6 +39,9 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.NoType; +import javax.tools.Diagnostic; + +import static org.scijava.ops.indexer.ProcessingUtils.*; /** * {@link OpImplData} implementation handling {@link Class}es annotated with @@ -68,7 +68,11 @@ public OpClassImplData(TypeElement source, ExecutableElement fMethod, private void parseFunctionalMethod(ExecutableElement fMethod, String fMethodDoc) { - if (fMethodDoc == null || fMethodDoc.isEmpty()) return; + if (fMethodDoc == null || fMethodDoc.isEmpty()) { + printError(fMethod.getEnclosingElement(), + " has a functional method without javadoc!"); + return; + } String[] sections = blockSeparator.split(fMethodDoc); var paramTypes = fMethod.getParameters().iterator(); @@ -85,9 +89,10 @@ private void parseFunctionalMethod(ExecutableElement fMethod, String name = foo[0]; String description = foo[1]; if (paramTypes.hasNext()) { - String type = paramTypes.next().asType().toString(); - params.add(new OpParameter(name, type, OpParameter.IO_TYPE.INPUT, - description)); + var pType = paramTypes.next(); + String type = pType.asType().toString(); + params.add(new OpParameter(name, type, ProcessingUtils.ioType( + description), description, isNullable(pType, description))); } else { throw new IllegalArgumentException("Op " + this.source + " has " + @@ -102,7 +107,7 @@ private void parseFunctionalMethod(ExecutableElement fMethod, String description = elements[1]; String type = fMethod.getReturnType().toString(); params.add(new OpParameter(name, type, OpParameter.IO_TYPE.OUTPUT, - description)); + description, false)); break; } case "@author": @@ -144,4 +149,8 @@ protected String formulateSource(Element source) { return "javaClass:/" + URLEncoder.encode(srcString, StandardCharsets.UTF_8); } + private void printError(Element source, String msg) { + env.getMessager().printMessage(Diagnostic.Kind.ERROR, source + msg); + } + } diff --git a/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpFieldImplData.java b/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpFieldImplData.java index dd0da280f..d3ca94dfe 100644 --- a/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpFieldImplData.java +++ b/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpFieldImplData.java @@ -29,15 +29,18 @@ package org.scijava.ops.indexer; +import static org.scijava.ops.indexer.ProcessingUtils.isNullable; import static org.scijava.ops.indexer.ProcessingUtils.tagElementSeparator; import java.lang.reflect.Field; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.util.List; +import java.util.*; +import java.util.regex.Pattern; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.NoType; @@ -51,6 +54,15 @@ */ public class OpFieldImplData extends OpImplData { + // Regex to match Op types, coming from SciJava Function, who describe all + // of their parameters in order within their type parameters + private static final Pattern SJF_PATTERN = Pattern.compile( + "^org\\.scijava\\.function\\.(Computers|Functions|Inplaces|Producer).*"); + // Regex to match Op types, coming from vanilla Java, who describe all of + // their parameters in order within their type parameters + private static final Pattern JAVA_PATTERN = Pattern.compile( + "^java\\.util\\.function\\.(Function|BiFunction).*"); + public OpFieldImplData(Element source, String doc, ProcessingEnvironment env) { @@ -65,29 +77,34 @@ public OpFieldImplData(Element source, String doc, */ @Override void parseAdditionalTags(Element source, List additionalTags) { + // Get the types of each Op parameter. + Iterator itr = getParamTypes(source, additionalTags).iterator(); // Create the list of Op parameters by checking for @input, @container, // @mutable, @output tags for (String[] tag : additionalTags) { + // In the case where parameter types cannot be determined, describe the + // types as "UNKNOWN" + String pType = itr.hasNext() ? itr.next() : "UNKNOWN"; switch (tag[0]) { case "@input": String[] inData = tagElementSeparator.split(tag[1], 2); - params.add(new OpParameter(inData[0], null, OpParameter.IO_TYPE.INPUT, - inData[1])); + params.add(new OpParameter(inData[0], pType, + OpParameter.IO_TYPE.INPUT, inData[1], isNullable(inData[1]))); break; case "@output": // NB outputs generally don't have names - params.add(new OpParameter("output", null, OpParameter.IO_TYPE.OUTPUT, - tag[1])); + params.add(new OpParameter("output", pType, + OpParameter.IO_TYPE.OUTPUT, tag[1], false)); break; case "@container": String[] containerData = tagElementSeparator.split(tag[1], 2); - params.add(new OpParameter(containerData[0], null, - OpParameter.IO_TYPE.CONTAINER, containerData[1])); + params.add(new OpParameter(containerData[0], pType, + OpParameter.IO_TYPE.CONTAINER, containerData[1], false)); break; case "@mutable": String[] mutableData = tagElementSeparator.split(tag[1], 2); - params.add(new OpParameter(mutableData[0], null, - OpParameter.IO_TYPE.MUTABLE, mutableData[1])); + params.add(new OpParameter(mutableData[0], pType, + OpParameter.IO_TYPE.MUTABLE, mutableData[1], false)); break; } @@ -128,6 +145,101 @@ void parseAdditionalTags(Element source, List additionalTags) { } } + /** + * Helper method that provides a best attempt at finding the parameter types + * of an Op written as a Java field. Because we haven't yet compiled the + * field, we cannot introspect the class hierarchy to determine the parameter + * types of the functional method. This function uses pre-existing knowledge + * that a set of common functional interfaces describe all of their parameter + * types via their type parameters, and parses out those type variables to + * provide a list of Op parameter types. If the Op field does not conform to + * one of those specified functional interfaces, this method returns an empty + * list. + * + * @param source the Op field {@link Element} + * @param additionalTags the Javadoc belonging to {@code source}, split by + * Javadoc tag. Used to ensure that the method returns the correct + * number of parameter types. + * @return a {@link List} containing a string representing the type of each Op + * parameter, or an empty {@link List} if that cannot be done. + */ + private List getParamTypes(Element source, + List additionalTags) + { + var fieldStr = source.asType().toString(); + if ( // + !SJF_PATTERN.matcher(fieldStr).find() && // + !JAVA_PATTERN.matcher(fieldStr).find() // + ) { + env.getMessager().printMessage(Diagnostic.Kind.WARNING, "Op Field " + + source + " has type" + fieldStr + + " - we cannot infer parameter types from this type!"); + return Collections.emptyList(); + } + + // Find the enclosing class, so we can grab all the type variables + Element enclosing = source.getEnclosingElement(); + while (enclosing.getKind() != ElementKind.CLASS) { + enclosing = enclosing.getEnclosingElement(); + } + + // Replace all instances of each type variable in the field's type + // string. + for (var e : ((TypeElement) enclosing).getTypeParameters()) { + // Convert the type variable into a string representation + StringBuilder tpString = new StringBuilder(e.toString()).append( + " extends "); + var bounds = e.getBounds(); + for (int i = 0; i < bounds.size(); i++) { + tpString.append(bounds.get(i).toString()); + if (i < bounds.size() - 1) { + tpString.append(" & "); + } + } + // Replace each instance of the type variable with the stringification. + var regex = "(?" -> "T, U" + var ParamsStr = fieldStr.substring( // + fieldStr.indexOf('<') + 1, // + fieldStr.length() - 1 // + ); + // Split the type parameters by comma, taking care to avoid nested commas + List paramTypes = new ArrayList<>(); + StringBuilder tmp = new StringBuilder(); + int nestCount = 0; + for (int i = 0; i < ParamsStr.length(); i++) { + if (ParamsStr.charAt(i) == '<') { + tmp.append(ParamsStr.charAt(i)); + nestCount++; + } + else if (ParamsStr.charAt(i) == '>') { + tmp.append(ParamsStr.charAt(i)); + nestCount--; + } + else if (ParamsStr.charAt(i) == ',' && nestCount == 0) { + paramTypes.add(tmp.toString()); + tmp = new StringBuilder(); + } + else { + tmp.append(ParamsStr.charAt(i)); + } + } + paramTypes.add(tmp.toString()); + + // Finally, a sanity check - ensure correct number of types. + if (paramTypes.size() != additionalTags.size()) { + env.getMessager().printMessage(Diagnostic.Kind.WARNING, + "Could not infer parameter types from Field type."); + return Collections.emptyList(); + } + + return paramTypes; + } + @Override String formulateSource(Element source) { return "javaField:/" + URLEncoder.encode(source.getEnclosingElement() + diff --git a/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpImplData.java b/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpImplData.java index 5286e51ff..a5941dc84 100644 --- a/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpImplData.java +++ b/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpImplData.java @@ -107,9 +107,7 @@ public OpImplData(Element source, String doc, ProcessingEnvironment env) { .map(section -> tagElementSeparator.split(section, 2)) // .collect(Collectors.toList()); List remaining = parseUniversalTags(tags); - if (!remaining.isEmpty()) { - parseAdditionalTags(source, remaining); - } + parseAdditionalTags(source, remaining); validateOpImpl(); } diff --git a/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpMethodImplData.java b/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpMethodImplData.java index 97bfd2bb7..52d8e55dd 100644 --- a/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpMethodImplData.java +++ b/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpMethodImplData.java @@ -35,12 +35,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; +import javax.lang.model.element.*; import javax.lang.model.type.NoType; import javax.tools.Diagnostic; @@ -52,10 +51,58 @@ */ public class OpMethodImplData extends OpImplData { + // Regex matching org.scijava.function.Computers type hints + private static final Pattern COMPUTER_TYPE = Pattern.compile( + "[cC]omputer(\\d*)$"); + // Regex matching org.scijava.function.Inplaces type hints + private static final Pattern INPLACE_TYPE = Pattern.compile( + "[iI]nplace(\\d+)$"); + public OpMethodImplData(ExecutableElement source, String doc, ProcessingEnvironment env) { super(source, doc, env); + validateMethod(source); + } + + /** + * Catches some Op errors early, to prevent confusing errors at runtime. + * + * @param source the {@link ExecutableElement} referring to an Op described as + * a method. + */ + private void validateMethod(ExecutableElement source) { + // Allow only public methods + if (!source.getModifiers().contains(Modifier.PUBLIC)) { + printError(source, " should be a public method!"); + } + // Allow only static methods + if (!source.getModifiers().contains(Modifier.STATIC)) { + printError(source, " should be a static method!"); + } + // All Op dependencies must come before other parameters + int lastOpDependency = -1; + var params = source.getParameters(); + for (int i = 0; i < params.size(); i++) { + if (isDependency(params.get(i))) { + if (i != lastOpDependency + 1) { + printError(source, + " declares Op dependencies after it declares parameters - all Op dependencies must come first!"); + break; + } + lastOpDependency++; + } + + } + + } + + private boolean isDependency(VariableElement e) { + // HACK A dependency on SciJava Ops SPI is really tricky - creates a + // circular dependency so this is the easiest way to check for an + // OpDependency + return e.getAnnotationMirrors().stream() // + .anyMatch(a -> a.toString().contains("OpDependency")); } /** @@ -68,9 +115,10 @@ public OpMethodImplData(ExecutableElement source, String doc, @Override void parseAdditionalTags(Element source, List additionalTags) { ExecutableElement exSource = (ExecutableElement) source; - // First, parse parameters + // First, parse @param tags List opDependencies = new ArrayList<>(); var paramItr = exSource.getParameters().iterator(); + for (String[] tag : additionalTags) { if (!"@param".equals(tag[0])) continue; if (paramIsTypeVariable(tag[1])) { @@ -78,12 +126,9 @@ void parseAdditionalTags(Element source, List additionalTags) { continue; } VariableElement param = paramItr.next(); - // HACK A dependency on SciJava Ops SPI is really tricky - creates a - // circular dependency so this is the easiest way to check for an - // OpDependency - boolean isOpDep = param.getAnnotationMirrors().stream() // - .anyMatch(a -> a.toString().contains("OpDependency")); - if (isOpDep) opDependencies.add(param); + if (isDependency(param)) { + opDependencies.add(param); + } else { // Coerce @param tag + VariableElement into an OpParameter String name = param.getSimpleName().toString(); @@ -96,21 +141,27 @@ void parseAdditionalTags(Element source, List additionalTags) { else { description = ""; } - params.add(new OpParameter(name, type, OpParameter.IO_TYPE.INPUT, - description)); + params.add(new OpParameter( // + name, // + type, // + ProcessingUtils.ioType(description), // + description, // + ProcessingUtils.isNullable(param, description) // + )); } } + + // We also leave the option to specify the Op type using the type tag - + // check for it, and apply it if available. + if (tags.containsKey("type")) { + editIOIndex((String) tags.get("type"), params); + } // Validate number of inputs if (opDependencies.size() + params.size() != exSource.getParameters() .size()) { - var clsElement = exSource.getEnclosingElement(); - while (clsElement.getKind() != ElementKind.CLASS) { - clsElement = clsElement.getEnclosingElement(); - } - env.getMessager().printMessage(Diagnostic.Kind.ERROR, clsElement + " - " + - "The number of @param tags on " + exSource + - " does not match the number of parameters!"); + printError(exSource, + " does not have a matching @param tag for each of its parameters!"); } // Finally, parse the return @@ -124,18 +175,72 @@ void parseAdditionalTags(Element source, List additionalTags) { "output", // returnType, // OpParameter.IO_TYPE.OUTPUT, // - totalTag // + totalTag, // + false // )); } + + // Validate 0 or 1 outputs + int totalOutputs = 0; + for (var p : params) { + if (p.ioType != OpParameter.IO_TYPE.INPUT) { + totalOutputs++; + } + } + if (totalOutputs > 1) { + printError(exSource, + " is only allowed to have 0 or 1 parameter outputs!"); + } + // Validate number of outputs if (!(exSource.getReturnType() instanceof NoType) && returnTag.isEmpty()) { - var clsElement = exSource.getEnclosingElement(); - while (clsElement.getKind() != ElementKind.CLASS) { - clsElement = clsElement.getEnclosingElement(); - } - env.getMessager().printMessage(Diagnostic.Kind.ERROR, clsElement + " - " + - exSource + " has a return, but no @return parameter"); + printError(exSource, " has a return, but no @return parameter"); + } + } + + /** + * Sometimes, Op developers will choose to specify the functional type of the + * Op, instead of appending some tag to the I/O parameter. In this case, it's + * easiest to edit the appropriate {@link OpParameter} after we've made + * all of them. + * + * @param type the type hint specified in the {@code @implNote} Javadoc tag + * @param params the list of {@link OpParameter}s. + */ + private void editIOIndex(String type, List params) { + // NB the parameter index will be the discovered int, minus one. + // e.g. "Inplace1" means to edit the first parameter + Matcher m = COMPUTER_TYPE.matcher(type); + if (m.find()) { + var idx = m.group(1); + int ioIndex = idx.isEmpty() ? params.size() - 1 : Integer.parseInt(idx) - + 1; + params.get(ioIndex).ioType = OpParameter.IO_TYPE.CONTAINER; + return; + } + m = INPLACE_TYPE.matcher(type); + if (m.find()) { + var idx = m.group(1); + // Unlike for computers, Inplaces MUST have a suffix + int ioIndex = Integer.parseInt(idx) - 1; + params.get(ioIndex).ioType = OpParameter.IO_TYPE.MUTABLE; + } + } + + /** + * Helper function to print issues with {@code exSource} in a uniform manner. + * + * @param exSource some {@link ExecutableElement} referring to an Op written + * as a method. + * @param msg a {@link String} describing the issue with the Op method. + */ + private void printError(ExecutableElement exSource, String msg) { + var clsElement = exSource.getEnclosingElement(); + while (clsElement.getKind() != ElementKind.CLASS) { + clsElement = clsElement.getEnclosingElement(); } + env.getMessager().printMessage(Diagnostic.Kind.ERROR, clsElement + " - " + + exSource + msg); } /** diff --git a/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpParameter.java b/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpParameter.java index aec985b9a..a710eb6db 100644 --- a/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpParameter.java +++ b/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/OpParameter.java @@ -45,11 +45,12 @@ public enum IO_TYPE { INPUT, OUTPUT, MUTABLE, CONTAINER } - protected final IO_TYPE ioType; + protected IO_TYPE ioType; protected final String name; protected final String type; protected final String desc; + protected final boolean nullable; /** * Default constructor @@ -63,13 +64,14 @@ public enum IO_TYPE { * @param description a description of the parameter */ public OpParameter(String name, String type, IO_TYPE ioType, - String description) + String description, final boolean nullable) { // Assign io this.name = name; this.type = type; this.ioType = ioType; this.desc = description; + this.nullable = nullable; } /** @@ -82,6 +84,7 @@ public Map data() { map.put("name", name); map.put("parameter type", ioType.toString()); map.put("description", desc); + map.put("nullable", nullable); if (type != null) { map.put("type", type); } diff --git a/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/ProcessingUtils.java b/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/ProcessingUtils.java index 4dde2f35e..7f1f8333a 100644 --- a/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/ProcessingUtils.java +++ b/scijava-ops-indexer/src/main/java/org/scijava/ops/indexer/ProcessingUtils.java @@ -29,19 +29,15 @@ package org.scijava.ops.indexer; -import static javax.lang.model.element.ElementKind.METHOD; - +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.*; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; import java.io.PrintWriter; import java.io.StringWriter; import java.util.regex.Pattern; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; -import javax.tools.Diagnostic; +import static javax.lang.model.element.ElementKind.METHOD; /** * A set of static utilities useful for processing Ops @@ -75,6 +71,54 @@ private ProcessingUtils() { throw new AssertionError("not instantiable"); } + /** + * Determines the {@link OpParameter.IO_TYPE} from a parameter description + * + * @param paramDesc the {@link String} following a Javadoc tag (and optionally + * a parameter name) + * @return the {@link OpParameter.IO_TYPE} from the description + */ + public static OpParameter.IO_TYPE ioType(final String paramDesc) { + if (paramDesc.trim().endsWith("(container)")) { + return OpParameter.IO_TYPE.CONTAINER; + } + if (paramDesc.trim().endsWith("(mutable)")) { + return OpParameter.IO_TYPE.MUTABLE; + } + return OpParameter.IO_TYPE.INPUT; + } + + /** + * Determines whether {@code element} is a nullable (i.e. {@code null} can be + * given) parameter, with additional clues from {@code paramDesc}. + * + * @param element the {@link VariableElement} corresponding to the parameter + * of some method {@link ExecutableElement}. + * @param paramDesc the description following the Javadoc tag associated with + * {@code element} + * @return true iff {@code null} can be safely provided to {@code element} + */ + public static boolean isNullable(final VariableElement element, + final String paramDesc) + { + boolean elementIsNullable = element.getAnnotationMirrors().stream() + .anyMatch(a -> a.toString().contains("Nullable")); + boolean descIsNullable = isNullable(paramDesc); + return elementIsNullable || descIsNullable; + } + + /** + * Determines whether {@code paramDesc} is associated with a nullable (i.e. + * {@code null} can be given) parameter + * + * @param paramDesc the description following some Javadoc tag + * @return true iff {@code null} can be safely provided to the parameter + * associated with {@code paramDesc} + */ + public static boolean isNullable(final String paramDesc) { + return paramDesc.trim().endsWith("(nullable)"); + } + /** * Logs a {@link Throwable} parsing an {@link Element} * @@ -90,7 +134,7 @@ public static void printProcessingException(Element source, Throwable t, PrintWriter pw = new PrintWriter(sw); t.printStackTrace(pw); env.getMessager().printMessage(Diagnostic.Kind.ERROR, - "Exception parsing source + " + source + ": " + sw); + "Exception parsing source " + source + ": " + sw); } /** @@ -114,8 +158,19 @@ public static ExecutableElement findFunctionalMethod( // method if (fMethod != null) { for (Element e : env.getElementUtils().getAllMembers(source)) { - if (e.getSimpleName().equals(fMethod.getSimpleName())) { - return (ExecutableElement) e; + if (e.getKind().equals(METHOD)) { + ExecutableElement ex = (ExecutableElement) e; + boolean isFIFace = source.getAnnotationsByType( + FunctionalInterface.class).length > 0; + if ( // + // The functional method will have an @Override + // annotation UNLESS source is a functional interface itself. + (isFIFace || (ex.getAnnotation(Override.class) != null)) && // + ex.getSimpleName().equals(fMethod.getSimpleName())) // + { + return (ExecutableElement) e; + } + } } } @@ -128,37 +183,39 @@ private static ExecutableElement findAbstractFunctionalMethod( // TypeElement source // ) { // First check source itself for the abstract method - int abstractMethodCount = 0; - ExecutableElement firstAbstractMethod = null; - for (Element e : source.getEnclosedElements()) { - if (e.getKind() == METHOD && e.getModifiers().contains( - Modifier.ABSTRACT)) - { - firstAbstractMethod = (ExecutableElement) e; - abstractMethodCount++; + if (source.getAnnotationMirrors().stream().anyMatch(a -> a.toString() + .contains("FunctionalInterface"))) + { + int abstractMethodCount = 0; + ExecutableElement firstAbstractMethod = null; + for (Element e : source.getEnclosedElements()) { + if (e.getKind() == METHOD && e.getModifiers().contains( + Modifier.ABSTRACT)) + { + firstAbstractMethod = (ExecutableElement) e; + abstractMethodCount++; + } + } + if (abstractMethodCount == 1) { + return firstAbstractMethod; } - } - if (abstractMethodCount == 1) { - return firstAbstractMethod; } // Otherwise, check up the class hierarchy - else { - // First, check the interfaces - for (TypeMirror e : source.getInterfaces()) { - Element iFace = env.getTypeUtils().asElement(e); - if (iFace instanceof TypeElement) { - ExecutableElement fMethod = findAbstractFunctionalMethod(env, - (TypeElement) iFace); - if (fMethod != null) return fMethod; - } + // First, check the interfaces + for (TypeMirror e : source.getInterfaces()) { + Element iFace = env.getTypeUtils().asElement(e); + if (iFace instanceof TypeElement) { + ExecutableElement fMethod = findAbstractFunctionalMethod(env, + (TypeElement) iFace); + if (fMethod != null) return fMethod; } - // Then, check the superclass - Element superCls = env.getTypeUtils().asElement(source.getSuperclass()); - if (superCls instanceof TypeElement) { - return findAbstractFunctionalMethod(env, (TypeElement) superCls); - } - return null; } + // Then, check the superclass + Element superCls = env.getTypeUtils().asElement(source.getSuperclass()); + if (superCls instanceof TypeElement) { + return findAbstractFunctionalMethod(env, (TypeElement) superCls); + } + return null; } } diff --git a/scijava-ops-opencv/src/test/java/org/scijava/ops/opencv/TestOpenCV.java b/scijava-ops-opencv/src/test/java/org/scijava/ops/opencv/TestOpenCV.java index bb259fd71..bd024a814 100644 --- a/scijava-ops-opencv/src/test/java/org/scijava/ops/opencv/TestOpenCV.java +++ b/scijava-ops-opencv/src/test/java/org/scijava/ops/opencv/TestOpenCV.java @@ -62,13 +62,13 @@ public class TestOpenCV { + "\t- org.bytedeco.opencv.global.opencv_imgproc.GaussianBlur(org.bytedeco.opencv.opencv_core.GpuMat,org.bytedeco.opencv.opencv_core.GpuMat,org.bytedeco.opencv.opencv_core.Size,double)\n" // + "\t\t> arg0 : org.bytedeco.opencv.opencv_core.GpuMat\n" // - + "\t\t> container1 : @CONTAINER org.bytedeco.opencv.opencv_core.GpuMat\n" // + + "\t\t> arg1 : @CONTAINER org.bytedeco.opencv.opencv_core.GpuMat\n" // + "\t\t> arg2 : org.bytedeco.opencv.opencv_core.Size\n" // + "\t\t> arg3 : java.lang.Double\n" // + "\t- org.bytedeco.opencv.global.opencv_imgproc.GaussianBlur(org.bytedeco.opencv.opencv_core.GpuMat,org.bytedeco.opencv.opencv_core.GpuMat,org.bytedeco.opencv.opencv_core.Size,double,double,int)\n" // + "\t\t> arg0 : org.bytedeco.opencv.opencv_core.GpuMat\n" // - + "\t\t> container1 : @CONTAINER org.bytedeco.opencv.opencv_core.GpuMat\n" // + + "\t\t> arg1 : @CONTAINER org.bytedeco.opencv.opencv_core.GpuMat\n" // + "\t\t> arg2 : org.bytedeco.opencv.opencv_core.Size\n" // + "\t\t> arg3 : java.lang.Double\n" // + "\t\t> arg4 : java.lang.Double\n" // @@ -76,13 +76,13 @@ public class TestOpenCV { + "\t- org.bytedeco.opencv.global.opencv_imgproc.GaussianBlur(org.bytedeco.opencv.opencv_core.Mat,org.bytedeco.opencv.opencv_core.Mat,org.bytedeco.opencv.opencv_core.Size,double)\n" // + "\t\t> arg0 : org.bytedeco.opencv.opencv_core.Mat\n" // - + "\t\t> container1 : @CONTAINER org.bytedeco.opencv.opencv_core.Mat\n" // + + "\t\t> arg1 : @CONTAINER org.bytedeco.opencv.opencv_core.Mat\n" // + "\t\t> arg2 : org.bytedeco.opencv.opencv_core.Size\n" // + "\t\t> arg3 : java.lang.Double\n" // + "\t- org.bytedeco.opencv.global.opencv_imgproc.GaussianBlur(org.bytedeco.opencv.opencv_core.Mat,org.bytedeco.opencv.opencv_core.Mat,org.bytedeco.opencv.opencv_core.Size,double,double,int)\n" // + "\t\t> arg0 : org.bytedeco.opencv.opencv_core.Mat\n" // - + "\t\t> container1 : @CONTAINER org.bytedeco.opencv.opencv_core.Mat\n" // + + "\t\t> arg1 : @CONTAINER org.bytedeco.opencv.opencv_core.Mat\n" // + "\t\t> arg2 : org.bytedeco.opencv.opencv_core.Size\n" // + "\t\t> arg3 : java.lang.Double\n" // + "\t\t> arg4 : java.lang.Double\n" // @@ -90,13 +90,13 @@ public class TestOpenCV { + "\t- org.bytedeco.opencv.global.opencv_imgproc.GaussianBlur(org.bytedeco.opencv.opencv_core.UMat,org.bytedeco.opencv.opencv_core.UMat,org.bytedeco.opencv.opencv_core.Size,double)\n" // + "\t\t> arg0 : org.bytedeco.opencv.opencv_core.UMat\n" // - + "\t\t> container1 : @CONTAINER org.bytedeco.opencv.opencv_core.UMat\n" // + + "\t\t> arg1 : @CONTAINER org.bytedeco.opencv.opencv_core.UMat\n" // + "\t\t> arg2 : org.bytedeco.opencv.opencv_core.Size\n" // + "\t\t> arg3 : java.lang.Double\n" // + "\t- org.bytedeco.opencv.global.opencv_imgproc.GaussianBlur(org.bytedeco.opencv.opencv_core.UMat,org.bytedeco.opencv.opencv_core.UMat,org.bytedeco.opencv.opencv_core.Size,double,double,int)\n" // + "\t\t> arg0 : org.bytedeco.opencv.opencv_core.UMat\n" // - + "\t\t> container1 : @CONTAINER org.bytedeco.opencv.opencv_core.UMat\n" // + + "\t\t> arg1 : @CONTAINER org.bytedeco.opencv.opencv_core.UMat\n" // + "\t\t> arg2 : org.bytedeco.opencv.opencv_core.Size\n" // + "\t\t> arg3 : java.lang.Double\n" // + "\t\t> arg4 : java.lang.Double\n" // @@ -111,19 +111,19 @@ public class TestOpenCV { + "\t- org.bytedeco.opencv.global.opencv_imgproc.GaussianBlur(org.bytedeco.opencv.opencv_core.GpuMat,org.bytedeco.opencv.opencv_core.GpuMat,org.bytedeco.opencv.opencv_core.Size,double)\n" // + "\t\t> arg0 : org.bytedeco.opencv.opencv_core.GpuMat\n" // - + "\t\t> container1 : @CONTAINER org.bytedeco.opencv.opencv_core.GpuMat\n" // + + "\t\t> arg1 : @CONTAINER org.bytedeco.opencv.opencv_core.GpuMat\n" // + "\t\t> arg2 : org.bytedeco.opencv.opencv_core.Size\n" // + "\t\t> arg3 : java.lang.Double\n" // + "\t- org.bytedeco.opencv.global.opencv_imgproc.GaussianBlur(org.bytedeco.opencv.opencv_core.Mat,org.bytedeco.opencv.opencv_core.Mat,org.bytedeco.opencv.opencv_core.Size,double)\n" // + "\t\t> arg0 : org.bytedeco.opencv.opencv_core.Mat\n" // - + "\t\t> container1 : @CONTAINER org.bytedeco.opencv.opencv_core.Mat\n" // + + "\t\t> arg1 : @CONTAINER org.bytedeco.opencv.opencv_core.Mat\n" // + "\t\t> arg2 : org.bytedeco.opencv.opencv_core.Size\n" // + "\t\t> arg3 : java.lang.Double\n" // + "\t- org.bytedeco.opencv.global.opencv_imgproc.GaussianBlur(org.bytedeco.opencv.opencv_core.UMat,org.bytedeco.opencv.opencv_core.UMat,org.bytedeco.opencv.opencv_core.Size,double)\n" // + "\t\t> arg0 : org.bytedeco.opencv.opencv_core.UMat\n" // - + "\t\t> container1 : @CONTAINER org.bytedeco.opencv.opencv_core.UMat\n" // + + "\t\t> arg1 : @CONTAINER org.bytedeco.opencv.opencv_core.UMat\n" // + "\t\t> arg2 : org.bytedeco.opencv.opencv_core.Size\n" // + "\t\t> arg3 : java.lang.Double"; diff --git a/scijava-progress/src/main/java/org/scijava/progress/Progress.java b/scijava-progress/src/main/java/org/scijava/progress/Progress.java index e42dc9837..bbb4e8036 100644 --- a/scijava-progress/src/main/java/org/scijava/progress/Progress.java +++ b/scijava-progress/src/main/java/org/scijava/progress/Progress.java @@ -210,7 +210,7 @@ private static void pingListeners(Task task) { Collections.emptyList() // ); synchronized (list) { - for(var l: list) + for (var l : list) l.accept(task); } // Ping global listeners