Automatically creating Datasets based on their type

I’d like to create a number of different types of Datasets for testing purposes. Basically what I’d like to be able to do is:

@Test
public void testInvalidTypesFail() {
    final RealType[] invalidTypes = {new IntType(), new LongType(), new ShortType()};
    
    for (RealType invalidType : invalidTypes) {
        final Dataset dataset = datasetCreator.createDataset(invalidType);
        boolean result = (boolean) ij.op().run("myOp", dataset);
        assertFalse(result);
    }
}
...
public class DatasetCreator {
    public <T extends RealType<T> & NativeType<T>> Dataset createDataset(T type) {
        return datasetService.create(type, DIMENSIONS, "Dataset", AXES_TYPES);
    }
}

But when I try to compile this kind of code I get the error:

Error:(82, 27) java: no suitable method found for createDataset(net.imglib2.type.numeric.RealType)
method org.bonej.testUtil.DatasetCreator.createDataset(T) is not applicable
(inferred type does not conform to upper bound(s)
inferred: net.imglib2.type.numeric.RealType
upper bound(s): net.imglib2.type.NativeType<net.imglib2.type.numeric.RealType>,net.imglib2.type.numeric.RealType<net.imglib2.type.numeric.RealType>,java.lang.Object)

What would be the correct signature for the array, and the method? Is what I’m trying to achieve even possible?

Thanks & best,
Richard Domander

P.S. I have feeling that this’d be a relatively simple matter to google from StackOverflow, but I just can’t come up with the search terms…

I have a work around solution where I have an enum DatasetType, and then create Datasets like:

switch (type) {
    case BIT:
        return datasetService.create(new BitType(), dimensions, "Dataset", axesTypes);
    case BYTE:
        return datasetService.create(new ByteType(), dimensions, "Dataset", axesTypes);
...

but it has its problems. For example the constructor of VolatileRealType works differently, and would need more parameters.

There’s not much ImageJ2/ImgLib2-specific stuff on SO; there’s nothing wrong with asking on SO, but in this case the scope of the issue is narrow enough that posting here is very appropriate :slightly_smiling: now we just need a real rep system!

Looking at your later code snippets I think you realized this, but just wanted to reiterate that this class is unnecessary; genericizing the method to match the upper bounds of the datasetService.create method is an idea that sounds like it will work but unfortunately can not - it’s just pushing the type matching error down one level.

The real problem is here: you have a RealType array, so Java can not guarantee that elements are both RealType and NativeType, and thus items from this array can not be passed to the datasetService.create methods.

This is not a fault in your use; there simply isn’t a single interface that unifies NativeType and RealType. There isn’t even an interface to unify Short/Long/Int types since the NativeType inheritance is always introduced at the GenericXXXType level.

Off the top of my head I can’t think of a simpler way to do this… note that VolatileRealType is not a NativeType so I don’t think you need to/can test it here. If you have issues with other specific types please let us know.

@tpietzsch @axtimwalde - any thoughts about a higher level interface unifying RealType and NativeType?

1 Like

Thanks for your reply. I went trough the types a Dataset can have, and they work fine. I didn’t realise that the type T had to be an “intersection” of NativeType and RealType. I thought of it as an either-or situation.

There are no plans to establish an intersection interface of NativeType and RealType. There are various ways in Java to deal with the requirement to have both interfaces implemented by some parameters, either generic types in the method specification, casting, or multiple references (one per interface) on the same object. ‘Convenience’ interfaces that do nothing but combine arbitrary combinations of other interfaces quickly lead to non-generic code because it cannot be guaranteed that implementing classes are aware of all possible combinations of interfaces at all times. To make it worse, there are also some Java bugs with cross-interface inheritance. Generic methods should thus never assume that such interfaces are used and so nobody needs them.

@axtimwalde To preface, I appreciate why you would not want unified interfaces in ImgLib2, and don’t expect any changes as a result of these discussions. But I do think it is valuable to talk about this topic further, especially if it leads to any suggestions on how to improve the exposure of ImgLib2 types in ImageJ.

From my point of view, looking at the ImgLib2 types reveals a number of classes of high interest (IntType, ShortType, LongType, DoubleType, FloatType) that share a similar type hierarchy (implementing both NativeType and RealType - fundamentally useful types throughout ImgLib2) yet can not be consumed in a general way. It is not possible to create a Collection that mixes these types and safely exposes the fact that these types are both Native and Real.

It is possible to use case logic and casting to coerce an object to one type or or the other, but this places a high burden on the downstream developer. Furthermore, in this particular case we did use a generic type to unify Native and Real type, but that actually makes casting (or multiple references) worse because there is no interface to cast to or use as a reference.

I am very disappointed that @rimadoma had to rewrite method calls for each concrete type, but I do not know how to do better.

The existence of a RealNativeType interface would allow more general code to be written in this case, although it’s still not a perfect solution due to the recursive generics. Arrays or Collections of raw RealNativeType would be OK, but I believe adding a generic parameter (e.g. List<T extends RealNativeType<T>>) would still lock it into a single type.

Basically, I agree that a proliferation of arbitrary unions is not helpful: union interfaces should be derived from concrete use cases that emerge in ImgLib2’s architecture… e.g., four1,2,3,4 abstract and two1,2 concrete classes that independently unify Real and Native type.

Collections are easy if you have a generic parameter in the surrounding block (e.g. the method), this method creates an ArrayList<T> that conforms to the desired properties:

class Thing {
    static public <T extends RealType<T> & NativeType<T>> ArrayList<T> createList() {
      return new ArrayList<T>();
    }

    static public <T extends RealType<T> & NativeType<T>> void useList() {
      final ArrayList<T> list = createList();
    }
}

Can you point me to a concrete example where independent interfaces were the reason to do case logics?

@axtimwalde Well uh, I wrote this piece because I couldn’t figure out the correct generic signature for the method or collection. I’ll try again tomorrow with the examples in your post.

You are, of course, correct that this creates a Collection that can mix the concrete Types. My point was that you can not use this list in a way that is both safe and general. I have to choose one:

    static public <T extends RealType<T> & NativeType<T>> void useList() {
        final ArrayList<T> list = createList();
        list.add((T) new ByteType()); // unsafe cast
        list.add((T) new FloatType()); // unsafe cast
    }

This is flexible but uses unsafe casts. If anything consumes this method and narrows its generic parameter, the method will break.

    static public void addByteToList() {
        List<ByteType> byteList = createList();
        byteList.add(new ByteType());
    }

This is safe, but I am now locked into a type.

A raw list of course allows mixed types, but then loses the RealType and NativeType information:

    public static void main(String... args) {
        List byteList = createList();
        byteList.add(new ByteType());
        byteList.add(new FloatType());

        for (Object o : byteList) {
            doSomething(o);
        }
    }

    static public <T extends RealType<T> & NativeType<T>> void doSomething(T t) {
        System.out.println(t);
    }

This gives a compiler error on the doSomething, and there is no single cast that will make this work.

1 Like