From 57b0ac89b263e0f281527e655f0b1b25f77dd4db Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Tue, 20 Oct 2026 22:11:21 -0500 Subject: [PATCH 1/4] Fix generated class names for nested array types Reduced and converted op wrappers derive a class name from the parameter types, replacing "[]" with "_Arr". This was done only one level deep, so a parameter such as double[][] produced an invalid class name and the op could not be called with its optional parameters omitted. Co-Authored-By: Claude Opus 5.5 --- .../org/scijava/ops/engine/matcher/convert/Conversions.java | 2 +- .../org/scijava/ops/engine/matcher/reduce/ReductionUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/convert/Conversions.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/convert/Conversions.java index 9eeb24402..a011d0975 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/convert/Conversions.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/convert/Conversions.java @@ -479,7 +479,7 @@ static String getClassName(Type t) { var className = clazz.getSimpleName(); if (className.chars().allMatch(Character::isJavaIdentifierPart)) return className; - if (clazz.isArray()) return clazz.getComponentType().getSimpleName() + + if (clazz.isArray()) return getClassName(clazz.getComponentType()) + "_Arr"; return className; } diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/reduce/ReductionUtils.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/reduce/ReductionUtils.java index 6566073c8..4eecf1f50 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/reduce/ReductionUtils.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/reduce/ReductionUtils.java @@ -176,7 +176,7 @@ private static String getClassName(Class clazz) { var className = clazz.getSimpleName(); if (className.chars().allMatch(c -> Character.isJavaIdentifierPart(c))) return className; - if (clazz.isArray()) return clazz.getComponentType().getSimpleName() + + if (clazz.isArray()) return getClassName(clazz.getComponentType()) + "_Arr"; return className; } From 386d2fff934ab9c48f891398ce061aba7ecccea0 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 22 Oct 2026 09:52:20 -0500 Subject: [PATCH 2/4] Test ops with nested array parameters in the engine The fix for generated class names of nested array types (a14c733e9) was only exercised indirectly by color.deconvolveStains in scijava-ops-image. NestedArrayParameterTest calls ops with double[][] and double[][][] parameters with a @Nullable parameter omitted (reduced wrapper), and with a converted argument and output; without the fix, the reduced calls fail. The converted wrapper name is not compiled into a class on that path, so ConversionTest checks Conversions.getClassName directly. Co-Authored-By: Claude Opus 5.5 --- .../matcher/convert/ConversionTest.java | 9 ++ .../reduce/NestedArrayParameterTest.java | 129 ++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 scijava-ops-engine/src/test/java/org/scijava/ops/engine/reduce/NestedArrayParameterTest.java diff --git a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/convert/ConversionTest.java b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/convert/ConversionTest.java index b759bca8b..ae41e0ce4 100644 --- a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/convert/ConversionTest.java +++ b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/matcher/convert/ConversionTest.java @@ -259,4 +259,13 @@ public void testConversionDescription() { Assertions.assertTrue(info.toString().contains(TEST_DESC)); } + /** Generated names of nested array types must be valid identifiers. */ + @Test + public void testClassNameOfNestedArray() { + assertEquals("double", Conversions.getClassName(double.class)); + assertEquals("double_Arr", Conversions.getClassName(double[].class)); + assertEquals("double_Arr_Arr", Conversions.getClassName(double[][].class)); + assertEquals("Integer_Arr_Arr_Arr", Conversions.getClassName( + Integer[][][].class)); + } } diff --git a/scijava-ops-engine/src/test/java/org/scijava/ops/engine/reduce/NestedArrayParameterTest.java b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/reduce/NestedArrayParameterTest.java new file mode 100644 index 000000000..ac980dff6 --- /dev/null +++ b/scijava-ops-engine/src/test/java/org/scijava/ops/engine/reduce/NestedArrayParameterTest.java @@ -0,0 +1,129 @@ +/*- + * #%L + * Java implementation of the SciJava Ops matching engine. + * %% + * Copyright (C) 2016 - 2025 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.reduce; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.function.BiFunction; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.scijava.ops.engine.AbstractTestEnvironment; +import org.scijava.ops.engine.conversionLoss.impl.IdentityLossReporter; +import org.scijava.ops.engine.conversionLoss.impl.PrimitiveLossReporters; +import org.scijava.ops.engine.copy.CopyOpCollection; +import org.scijava.ops.engine.matcher.convert.IdentityCollection; +import org.scijava.ops.engine.matcher.convert.PrimitiveConverters; +import org.scijava.ops.engine.matcher.convert.UtilityConverters; +import org.scijava.ops.engine.matcher.impl.LossReporterWrapper; +import org.scijava.ops.spi.Nullable; +import org.scijava.ops.spi.OpCollection; +import org.scijava.ops.spi.OpMethod; + +/** + * Tests ops with a multi-dimensional array parameter, whose reduced and + * converted wrappers need a generated class name derived from the parameter + * types ({@code double[][]} must give a valid identifier such as + * {@code double_Arr_Arr}). + * + * @author Curtis Rueden + * @author Claude Opus 5.5 + */ +public class NestedArrayParameterTest extends AbstractTestEnvironment + implements OpCollection +{ + + @BeforeAll + public static void addNeededOps() { + ops.register( // + new CopyOpCollection<>(), // + new IdentityCollection<>(), // + new IdentityLossReporter<>(), // + new LossReporterWrapper<>(), // + new PrimitiveConverters<>(), // + new PrimitiveLossReporters(), // + new UtilityConverters(), // + new NestedArrayParameterTest() // + ); + } + + @OpMethod(names = "test.nestedArraySum", type = BiFunction.class) + public static Double nestedArraySum(final double[][] values, + @Nullable final Double scale) + { + double sum = 0; + for (final double[] row : values) + for (final double v : row) + sum += v; + return sum * (scale == null ? 1 : scale); + } + + @OpMethod(names = "test.nestedArrayCount", type = BiFunction.class) + public static Double nestedArrayCount(final double[][][] values, + @Nullable final Double scale) + { + long count = 0; + for (final double[][] plane : values) + for (final double[] row : plane) + count += row.length; + return count * (scale == null ? 1 : scale); + } + + private static final double[][] VALUES = { { 1, 2 }, { 3, 4 } }; + + @Test + public void testAllArguments() { + final Double sum = ops.op("test.nestedArraySum").input(VALUES, 2.0) + .outType(Double.class).apply(); + assertEquals(20, sum, 0); + } + + /** Omitting the optional parameter goes through a reduced wrapper. */ + @Test + public void testOptionalOmitted() { + final Double sum = ops.op("test.nestedArraySum").input(VALUES).outType( + Double.class).apply(); + assertEquals(10, sum, 0); + final Double count = ops.op("test.nestedArrayCount").input( + new double[2][3][4]).outType(Double.class).apply(); + assertEquals(24, count, 0); + } + + /** An Integer scale goes through a converted wrapper. */ + @Test + public void testConverted() { + final Double sum = ops.op("test.nestedArraySum").input(VALUES, 3) + .outType(Double.class).apply(); + assertEquals(30, sum, 0); + final Integer rounded = ops.op("test.nestedArraySum").input(VALUES) + .outType(Integer.class).apply(); + assertEquals(10, rounded); + } +} From c9e1168f4f28175670191e9248220259e46d14ba Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 22 Oct 2026 17:36:03 -0500 Subject: [PATCH 3/4] Add a changelog for SciJava Ops Engine Co-Authored-By: Claude Opus 5.5 --- scijava-ops-engine/CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 scijava-ops-engine/CHANGELOG.md diff --git a/scijava-ops-engine/CHANGELOG.md b/scijava-ops-engine/CHANGELOG.md new file mode 100644 index 000000000..814e8dcfd --- /dev/null +++ b/scijava-ops-engine/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to SciJava Ops Engine. + +## [Unreleased] + +### Fixed + +- Ops with multi-dimensional array parameters (e.g. `double[][]`) could not + be called with their optional parameters omitted, or with converted + arguments. From 93660f4b7b2c5a2e9bbc713b1ba16d7b87755702 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Tue, 27 Oct 2026 17:58:55 -0500 Subject: [PATCH 4/4] Simplify and comment --- .../org/scijava/ops/engine/matcher/convert/Conversions.java | 1 + .../org/scijava/ops/engine/matcher/reduce/ReductionUtils.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/convert/Conversions.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/convert/Conversions.java index a011d0975..b76dbf7fc 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/convert/Conversions.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/convert/Conversions.java @@ -479,6 +479,7 @@ static String getClassName(Type t) { var className = clazz.getSimpleName(); if (className.chars().allMatch(Character::isJavaIdentifierPart)) return className; + // Recurse to handle ND arrays if (clazz.isArray()) return getClassName(clazz.getComponentType()) + "_Arr"; return className; diff --git a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/reduce/ReductionUtils.java b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/reduce/ReductionUtils.java index 4eecf1f50..8acc08687 100644 --- a/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/reduce/ReductionUtils.java +++ b/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/reduce/ReductionUtils.java @@ -174,8 +174,9 @@ private static String memberNames(ReducedOpInfo reducedInfo) { */ private static String getClassName(Class clazz) { var className = clazz.getSimpleName(); - if (className.chars().allMatch(c -> Character.isJavaIdentifierPart(c))) + if (className.chars().allMatch(Character::isJavaIdentifierPart)) return className; + // Recurse to handle ND arrays if (clazz.isArray()) return getClassName(clazz.getComponentType()) + "_Arr"; return className;