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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 54 additions & 16 deletions scijava-common3/src/main/java/org/scijava/common3/Types.java
Original file line number Diff line number Diff line change
Expand Up @@ -1127,8 +1127,23 @@ private static boolean isAssignable(final Type type, final Any toType,
final Map<TypeVariable<?>, Type> typeVarAssigns)
{
if (type instanceof TypeVariable) {
// TODO: do we need to do here what we do with the ParameterizedType?
// If the type variable has an assignment already, we should check against that instead.
TypeVariable<?> typeVar = (TypeVariable<?>) type;
if (typeVarAssigns.containsKey(typeVar)) {{
return isAssignable(typeVarAssigns.get(typeVar), toType, typeVarAssigns);
}}
}

// For a type to be assignable to an Any, it must fit within the Any's upper and lower bounds.
for (var upperBound : toType.getUpperBounds()) {
if (!isAssignable(type, upperBound)) return false;
}

for (var lowerBound : toType.getLowerBounds()) {
if (!isAssignable(lowerBound, type)) return false;
}

// Now that we know the type is assignable to an Any, we can do some type variable mapping.
if (type instanceof ParameterizedType) {
// check if any of the type parameters are TypeVariables, and if so
// bind them to a new Any with the bounds of the TypeVariable.
Expand All @@ -1145,14 +1160,6 @@ private static boolean isAssignable(final Type type, final Any toType,
return true;
}

for (var upperBound : toType.getUpperBounds()) {
if (!isAssignable(type, upperBound)) return false;
}

for (var lowerBound : toType.getLowerBounds()) {
if (!isAssignable(lowerBound, type)) return false;
}

return true;
}

Expand Down Expand Up @@ -1367,10 +1374,29 @@ else if (!isAssignable(fromResolved == null ? fromTypeArg
typeVarAssigns.put(unbounded, fromResolved);
toResolved = fromResolved;
}
// bind unbounded to another type variable
// bind unbounded to a TypeVariable or Any
else {
typeVarAssigns.put((TypeVariable<?>) toTypeVarAssigns.get(var),
fromTypeVarAssigns.get(var));
TypeVariable<?> to = (TypeVariable<?>) toTypeVarAssigns.get(var);
Type from = fromTypeVarAssigns.get(var);
if (from == null) {
// IMPORTANT: Understanding the Any bounds are very important
// here for understanding type variable bounds ACROSS
// isAssignable calls. This happens often in SciJava Ops, to which
// the following description pertains:
//
// Suppose we ask for a
// Function<IntType, Any> , hoping to match some
// Computers.Arity1<I extends RealType<I>, O extends RealType<O>> via
// adaptation. Adapation requires (1) the underlying Computers.Arity1<I, O>
// Op, but also (2) some way to generate the output (in practice, either a
// Function<IntType, Any> or a Producer<Any>). If there are no bounds on
// the Any, then we can get incorrect matches, such as a Producer<Double>,
// which would produce an object unsuitable for the Computers.Arity1 op
// being adapted. Therefore it is necessary to attach bounds on the Any (as
// an example, an Any bounded by RealType) to ensure correct matching.
from = new Any(to.getBounds());
}
typeVarAssigns.put(to, from);
}
}

Expand All @@ -1379,8 +1405,20 @@ else if (!isAssignable(fromResolved == null ? fromTypeArg
// parameters of the target type.
if (fromResolved != null && !fromResolved.equals(toResolved)) {
// check for anys
if (Any.is(fromResolved) || Any.is(toResolved)) continue;
if (fromResolved instanceof ParameterizedType &&
if (Any.is(toResolved)) {
Any a = toResolved instanceof Any ? (Any) toResolved : new Any();
for (Type upper: a.getUpperBounds()) {
if (!Types.isAssignable(fromResolved, upper))
return false;
}
for (Type lower: a.getLowerBounds()) {
if (!Types.isAssignable(lower, fromResolved))
return false;
}
continue;
}
else if (Any.is(fromResolved)) continue;
else if (fromResolved instanceof ParameterizedType &&
toResolved instanceof ParameterizedType)
{
if (raw(fromResolved) != raw(toResolved)) {
Expand Down Expand Up @@ -1791,8 +1829,8 @@ private static boolean isAssignable(final Type type,
}

if (Any.is(type)) {
typeVarAssigns.put(toTypeVariable, new Any(toTypeVariable.getBounds()));
return true;
typeVarAssigns.putIfAbsent(toTypeVariable, new Any(toTypeVariable.getBounds()));
return isAssignable(typeVarAssigns.get(toTypeVariable), type);
}

throw new IllegalStateException("found an unhandled type: " + type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
import org.scijava.ops.engine.matcher.OpCandidate.StatusCode;
import org.scijava.ops.engine.matcher.OpMatcher;
import org.scijava.ops.engine.matcher.impl.DefaultOpRequest;
import org.scijava.ops.engine.struct.FunctionalMethodType;
import org.scijava.ops.engine.struct.FunctionalParameters;
import org.scijava.ops.engine.util.Infos;
import org.scijava.priority.Priority;
Expand Down Expand Up @@ -172,10 +171,11 @@ public OpCandidate findMatch(MatchingConditions conditions, OpMatcher matcher,
* @param candidate the {@link OpCandidate} matched for the adaptor input
* @param map the mapping
*/
private void captureTypeVarsFromCandidate(Type adaptorType, OpCandidate candidate,
Map<TypeVariable<?>, Type> map)
{

private void captureTypeVarsFromCandidate(
Type adaptorType,
OpCandidate candidate,
Map<TypeVariable<?>, Type> map
) {
// STEP 1: Base adaptor type variables
// For example, let's say we're operating on Computer<I, O>s.
// The adaptor might work on Computer<T, T>s. Which we need to resolve.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
*
* @author Alison Walter
*/
public final class ConvertTypes<C extends ComplexType<C>, T extends IntegerType<T>> {
public final class ConvertComplexTypes<C extends ComplexType<C>, T extends IntegerType<T>> {

/**
* @input input
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*-
* #%L
* Image processing operations for SciJava Ops.
* %%
* 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:
*
* 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.image.convert;

import net.imglib2.type.numeric.IntegerType;
import net.imglib2.type.numeric.RealType;
import net.imglib2.type.numeric.integer.*;
import net.imglib2.type.numeric.real.DoubleType;
import net.imglib2.type.numeric.real.FloatType;

import java.util.function.Function;

/**
* Converters for converting between {@link RealType}s and {@link Number}s
*
* @author Gabriel Selzer
*/
public class ConvertNumbersToRealTypes<N extends Number, T extends RealType<T>, I extends IntegerType<I>> {

// -- Numbers to RealTypes -- //

// NB Combo conversion uses these to convert to any RealType

/**
* @input num the {@link Number} to convert
* @output a {@link ByteType} containing the information in {@code num}
* @implNote op names='engine.convert, convert.int8'
*/
public final Function<N, ByteType> numberToByteType = //
num -> new ByteType(num.byteValue());

/**
* @input num the {@link Number} to convert
* @output a {@link UnsignedByteType} containing the information in {@code num}
* @implNote op names='engine.convert, convert.uint8'
*/
public final Function<N, UnsignedByteType> numberToUnsignedByteType = //
num -> new UnsignedByteType(num.shortValue());

/**
* @input num the {@link Number} to convert
* @output a {@link ShortType} containing the information in {@code num}
* @implNote op names='engine.convert, convert.int16'
*/
public final Function<N, ShortType> numberToShortType = //
num -> new ShortType(num.shortValue());

/**
* @input num the {@link Number} to convert
* @output a {@link UnsignedShortType} containing the information in {@code num}
* @implNote op names='engine.convert, convert.uint16'
*/
public final Function<N, UnsignedShortType> numberToUnsignedShortType = //
num -> new UnsignedShortType(num.intValue());

/**
* @input num the {@link Number} to convert
* @output a {@link IntType} containing the information in {@code num}
* @implNote op names='engine.convert, convert.int32'
*/
public final Function<N, IntType> numberToIntType = //
num -> new IntType(num.intValue());

/**
* @input num the {@link Number} to convert
* @output a {@link UnsignedIntType} containing the information in {@code num}
* @implNote op names='engine.convert, convert.uint32'
*/
public final Function<N, UnsignedIntType> numberToUnsignedIntType = //
num -> new UnsignedIntType(num.longValue());

/**
* @input num the {@link Number} to convert
* @output a {@link LongType} containing the information in {@code num}
* @implNote op names='engine.convert, convert.int64'
*/
public final Function<N, LongType> numberToLongType = //
num -> new LongType(num.longValue());

/**
* @input num the {@link Number} to convert
* @output a {@link FloatType} containing the information in {@code num}
* @implNote op names='engine.convert, convert.float32'
*/
public final Function<N, FloatType> numberToFloatType = //
num -> new FloatType(num.floatValue());

/**
* NB This converter wins against those above in requests for e.g.
* {@code Function<N, NativeType<T>>}
*
* @input num the {@link Number} to convert
* @output a {@link DoubleType} containing the information in {@code num}
* @implNote op names='engine.convert, convert.float64', priority=100
*/
public final Function<N, DoubleType> numberToDoubleType = //
num -> new DoubleType(num.doubleValue());

// -- RealTypes to Numbers -- //

/**
* @input integerType the {@link IntegerType} to convert
* @output the {@link Byte}, converted from {@code integerType}
* @implNote op names='engine.convert, convert.int8', priority="10"
*/
public final Function<I, Byte> integerTypeToByte = i -> (byte) i.getIntegerLong();

/**
* NB potentially lossy, so lower priority
*
* @input realType the {@link RealType} to convert
* @output the {@link Byte}, converted from {@code realType}
* @implNote op names='engine.convert, convert.int8'
*/
public final Function<T, Byte> realTypeToByte = i -> (byte) i.getRealDouble();

/**
* @input integerType the {@link IntegerType} to convert
* @output the {@link Short}, converted from {@code integerType}
* @implNote op names='engine.convert, convert.int16', priority="10"
*/
public final Function<I, Short> integerTypeToShort = i -> (short) i.getInteger();

/**
* NB potentially lossy, so lower priority
*
* @input realType the {@link RealType} to convert
* @output the {@link Short}, converted from {@code realType}
* @implNote op names='engine.convert, convert.int16'
*/
public final Function<T, Short> realTypeToShort = i -> (short) i.getRealDouble();

/**
* @input integerType the {@link IntegerType} to convert
* @output the {@link Integer}, converted from {@code integerType}
* @implNote op names='engine.convert, convert.int32', priority="10"
*/
public final Function<I, Integer> integerTypeToInteger = IntegerType::getInteger;

/**
* NB potentially lossy, so lower priority
*
* @input realType the {@link RealType} to convert
* @output the {@link Integer}, converted from {@code realType}
* @implNote op names='engine.convert, convert.int32'
*/
public final Function<T, Integer> realTypeToInteger = i -> (int) i.getRealDouble();

/**
* @input integerType the {@link IntegerType} to convert
* @output the {@link Long}, converted from {@code integerType}
* @implNote op names='engine.convert, convert.int64', priority="10"
*/
public final Function<I, Long> integerTypeToLong = IntegerType::getIntegerLong;

/**
* NB potentially lossy, so lower priority
*
* @input realType the {@link RealType} to convert
* @output the {@link Long}, converted from {@code realType}
* @implNote op names='engine.convert, convert.int64'
*/
public final Function<T, Long> realTypeToLong = i -> (long) i.getRealDouble();

/**
* @input realType the {@link RealType} to convert
* @output the {@link Float}, converted from {@code realType}
* @implNote op names='engine.convert, convert.float32'
*/
public final Function<T, Float> realTypeToFloat = RealType::getRealFloat;

/**
* @input realType the {@link RealType} to convert
* @output the {@link Double}, converted from {@code realType}
* @implNote op names='engine.convert, convert.float64'
*/
public final Function<T, Double> realTypeToDouble = RealType::getRealDouble;
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class OpRegressionTest {

@Test
public void testOpDiscoveryRegression() {
long expected = 1943;
long expected = 1964;
long actual = ops.infos().size();
assertEquals(expected, actual);
}
Expand Down
Loading