1

So, I am trying to create a collection of Builders which can be applied to a collection of Specifications to build Stages. I am having a problem with adding my generic builders to a collection so that they can later be used in the building process.

It has something to do with how generics are implemented in Java. I spent a few days trying different approaches, but so far have not come up with a solution to this problem. It feels like it should be possible, but I am missing something basic.

Full code here: https://github.com/apara/templateTrouble/blob/master/src/Main.java)

Here are the basic interfaces which are used in the code:

public interface Specifications {}

public interface Stage {}

public interface StageBuilder<SPEC extends Specifications, STAGE extends Stage> {
    STAGE build(SPEC specifications);
    boolean canBuild(SPEC specs);
}

After creating these basic interfaces, I have created some trivial implementations:

public class FilterSpecifications implements Specifications {}

public class GroupSpecifications implements Specifications {}

public class FilterStage implements Stage {}

public class GroupStage implements Stage {}

Finally, I implemented some trivial builders:

public abstract class AbstractStageBuilder<SPEC extends Specifications, STAGE extends Stage> implements StageBuilder<SPEC, STAGE> {
    private Class<? extends SPEC>
        specClass;

    public AbstractStageBuilder(final Class<? extends SPEC> specClass) {
        this.specClass = specClass;
    }

    @Override
    public boolean canBuild(final SPEC specs) {
        return
            specClass.isAssignableFrom(specs.getClass());
    }
}

public class FilterStageBuilder extends AbstractStageBuilder<FilterSpecifications, FilterStage> {

    public FilterStageBuilder() {
        super(FilterSpecifications.class);
    }

    @Override
    public FilterStage build(final FilterSpecifications specifications) {
        return
            new FilterStage();
    }
}

public class GroupStageBuilder extends AbstractStageBuilder<GroupSpecifications, GroupStage> {

    public GroupStageBuilder() {
        super(GroupSpecifications.class);
    }

    @Override
    public GroupStage build(final GroupSpecifications specifications) {
        return
            new GroupStage();
    }
}

The problem seems to be is that I am not sure what to do to build a collection of builders to pass into the build method:

public class Main {

    public static void main(String[] args) {

        //Create the builder list
        //
        final Collection<StageBuilder<Specifications,?>>
            builders =
                new LinkedList<>();

        //*** THESE TWO LINES DO NOT COMPILE, cannot add specific builders to collection ***
        //
        //builders.add(new FilterStageBuilder());
        //builders.add(new GroupStageBuilder());

        final Collection<Stage>
            result =
                build(
                    builders,
                    Arrays.asList(
                        new FilterSpecifications(),
                        new GroupSpecifications()
                    )
                );

        System.out.println("Created stages: " + result.size());
    }


    static Collection<Stage> build(final Collection<StageBuilder<Specifications,?>> builders,  final Collection <Specifications> specifications) {
        return
            specifications
                .stream()
                .map(
                    spec ->
                        builders
                            .stream()
                            .filter(
                                builder ->
                                    builder
                                        .canBuild(spec)
                            )
                            .findFirst()
                            .orElseThrow(
                                () ->
                                    new RuntimeException(
                                        "Builder not found for " + spec
                                    )
                            )
                            .build(
                                spec
                            )
                )
                .collect(
                    Collectors.toList()
                );
    }
}

Thanks for any help.

PS: I don't necessarily want to cast, I am hoping ideally this could work with Java generics only.

EDIT 1:

I updated the list to the following specification:

final Collection<StageBuilder<? extends Specifications,? extends Stage>>
    builders =
       new LinkedList<>();

And now I am able to add the two builders, however, when sending the list to the build function:

static Collection<Stage> build(final Collection<StageBuilder<? extends Specifications,? extends Stage>> builders,  final Collection <? extends Specifications> specifications) {...}

I get the following error in trying to invoke the builder.canBuild(spec) method:

Main.java:52: error: method canBuild in interface StageBuilder<SPEC,STAGE> cannot be applied to given types;
                                        .canBuild(spec)   //--- << unable to invoke here
                                        ^
  required: CAP#1
  found: CAP#2
  reason: argument mismatch; Specifications cannot be converted to CAP#1
  where SPEC,STAGE are type-variables:
    SPEC extends Specifications declared in interface StageBuilder
    STAGE extends Stage declared in interface StageBuilder
  where CAP#1,CAP#2 are fresh type-variables:
    CAP#1 extends Specifications from capture of ? extends Specifications
    CAP#2 extends Specifications from capture of ? extends Specifications

So, it looks like it is "string wise" equivalent but both CAP#1 and CAP#2 while looking the same obviously are not. So, I need to somehow make them the same?

EDIT 2:

So, I got most of it working, but still cannot actually add the builder to a collection:

Builder collection is defined simply as:

final Collection<StageBuilder<Specification, Stage>>
    builders =
        new LinkedList<>();

The actual build function is defined as (no problems with it):

static
<STAGE extends Stage, SPEC extends Specification>
Collection<? extends Stage> build(final Collection<? extends StageBuilder<? super SPEC, ? super STAGE>> builders, final Collection <SPEC> specifications) {
    return
        specifications
            .stream()
            .map(
                specification ->
                    builders
                        .stream()
                        .filter(
                            builder ->
                                builder
                                    .canBuild(specification)
                        )
                        .findFirst()
                        .orElseThrow(
                            RuntimeException::new
                        )
                        .build(
                            specification
                        )
            )
            .collect(
                Collectors.toList()
            );
}

Here is where I am stuck. I created a helper function (as I seen done in some posts), and while it can be invoked, it fails to compile inside the function:

static
<SPEC extends Specification, STAGE extends Stage, BUILDER extends StageBuilder<?,?>>
void add(final Collection<? extends StageBuilder<? super SPEC, ? super STAGE>> builders, final BUILDER builder){ //StageBuilder<? super SPEC, ? super STAGE> builder) {
    builders
        .add(builder);  // <-- DOES NOT COMPILE
}

Still getting one error:

Main.java:81: error: method add in interface Collection<E> cannot be applied to given types;
            .add(builder);  // <-- DOES NOT COMPILE
            ^
  required: CAP#1
  found: BUILDER
  reason: argument mismatch; BUILDER cannot be converted to CAP#1
  where SPEC,STAGE,BUILDER,E are type-variables:
    SPEC extends Specification declared in method <SPEC,STAGE,BUILDER>add(Collection<? extends StageBuilder<? super SPEC,? super STAGE>>,BUILDER)
    STAGE extends Stage declared in method <SPEC,STAGE,BUILDER>add(Collection<? extends StageBuilder<? super SPEC,? super STAGE>>,BUILDER)
    BUILDER extends StageBuilder<?,?> declared in method <SPEC,STAGE,BUILDER>add(Collection<? extends StageBuilder<? super SPEC,? super STAGE>>,BUILDER)
    E extends Object declared in interface Collection
  where CAP#1 is a fresh type-variable:
    CAP#1 extends StageBuilder<? super SPEC,? super STAGE> from capture of ? extends StageBuilder<? super SPEC,? super STAGE>
1 error

Here is the link to the current code for full reference:

https://github.com/apara/templateTrouble/blob/7356c049ee5c2ea69f371d3b84d44dbe7a104aec/src/Main.java

EDIT 3

Here is the essence of the problem, slightly changing the signature of the add method switches the problem between either not being able to add or to not being able to invoke the method.

So, we have either this setup:

//Attempt 1
//
add(builders, filterStageBuilder); // <-- DOES NOT COMPILE
add(builders, groupStageBuilder); // <-- DOES NOT COMPILE

static
<SPEC extends Specification, STAGE extends Stage, BUILDER extends StageBuilder<? extends SPEC, ? extends STAGE>>
void add(final Collection<? super BUILDER>  builders, final BUILDER builder) {
    builders
        .add(builder);  // <-- COMPILES FINE
}

Or this setup:

//Attempt 2
//
add2(builders, filterStageBuilder);  // <-- COMPILES FINE
add2(builders, groupStageBuilder); // <-- COMPILES FINE 

static
<SPEC extends Specification, STAGE extends Stage, BUILDER extends StageBuilder<? extends SPEC, ? extends STAGE>>
void add2(final Collection<? extends BUILDER>  builders, final BUILDER builder) {
    builders
        .add(builder);  // <-- DOES NOT COMPILE
}

So with final Collection builders I cannot invoke the method, but with final Collection builders I cannot add to the method.

The solution seems close at hand, but I cannot put my finger on the proper type specification that would allow me to invoke the method and add to the collection. This feels like it should be implementable without hackery such as casts.

EDIT 4

Very nice solution and explanation of the problem by Tim. I have updated the source code with the working, compiling code. For future references, here is the link:

https://github.com/apara/templateTrouble

-AP_

Alex Paransky
  • 985
  • 2
  • 10
  • 21
  • 1
    The problem with your `build()` signature is that a `StageBuilder extends Specification, ?>` cannot have `build()` invoked. When the generics are realized, `StageBuilder.build()` becomes `build(?)` or effectively `build(null)` due to the `extends`. If you change your method to accept `Collection> builders` then that method will work properly. See also http://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java I don't know though how you can construct a collection of that type to use. – Paul Bilnoski Aug 08 '15 at 03:55
  • Exactly. If it must be `StageBuilder` as the first type parameter, by the same logic it must likewise be `StageBuilder`. – user207421 Aug 08 '15 at 05:02
  • I updated the code with EDIT 2. Now, the build function can be invoked and works without any issues (no warnings, no casts). However, I am still stuck in trying to add the builders to a collection. – Alex Paransky Aug 08 '15 at 21:55
  • Updated with EDIT 3. What I have now will either allow me to invoke the method "add" or to add to the collection inside the method, but not both. Seems like this should really be possible with Java. – Alex Paransky Aug 09 '15 at 04:59
  • Updated with EDIT 4 to implement Tim's solution and provide a link to the final version of the code. – Alex Paransky Aug 09 '15 at 15:00

2 Answers2

3

What you want to do is not possible given the type model you've chosen for StageBuilder

The core of your problem is essentially that StageBuilder takes a type parameter for its specification, that makes it difficult to treat your builders generically, because they (in effect) operate over different a different input domain.

You're telling the compiler that you want it to make sure that you never pass FilterSpecifications to a GroupStageBuilder. That is, you don't want this to compile:

Specifications spec = new FilterSpecifications();
GroupStageBuilder builder = new GroupStageBuilder();
builder.build(spec);

It makes sense that you want that, but what you're trying to do with your collection of builders is essentially the same thing:

Collection<Specifications> specs = Collections.singleton(new FilterSpecifications());
Collection<GroupStageBuilder> builders = Collections.singleton(new GroupStageBuilder());
specs.forEach( s -> builders.forEach( b-> b.build(s) ) );

You're using canBuild as a runtime protection for this problem, but that doesn't solve the compile-time issue that you have.

You have a collection of things that are (potentially) a mix of different Specifications types, and you want to pass them to a collection of builders that are only defined for a subset of those types.

There are a couple of solutions you could go with, but they'll all turn out to be quite similar.

The more straight-forward option is to change your StageBuilder interface so that it is defined for every Specifications type, because that is what your Main.build method is in fact expecting - it wants the compiler to let it pass any Specifications object to any StageBuilder, and do safety checks at runtime.

public interface StageBuilder<STAGE extends Stage> {
    STAGE build(Specifications specifications);
    boolean canBuild(Specifications specs);
}

public abstract class AbstractStageBuilder<SPEC extends Specifications, STAGE extends Stage> implements StageBuilder<STAGE> {
    private Class<? extends SPEC>
        specClass;

    public AbstractStageBuilder(final Class<? extends SPEC> specClass) {
        this.specClass = specClass;
    }

    @Override
    public boolean canBuild(final Specifications specs) {
        return specClass.isAssignableFrom(specs.getClass());
    }

    @Override
    public STAGE build(Specifications specifications) {
        SPEC spec = specClass.cast(specifications);
        doBuild(spec);
    }

    protected abstract STAGE doBuild(SPEC specifications);
}

If you want to put more safety around that cast, then you'd need to change the interface so that canBuild and build are effectively the same method.

As it stands (in my example above) it's theoretically possible to simply ignore canBuild and then pass the wrong type to build and get a ClassCastException.

The solution is to change it to something like:

public interface StageBuilder<STAGE extends Stage> {
    Optional<Supplier<STAGE>> supplier(Specifications specifications);
}

public abstract class AbstractStageBuilder<SPEC extends Specifications, STAGE extends Stage> implements StageBuilder<STAGE> {
    private Class<? extends SPEC>
        specClass;

    public AbstractStageBuilder(final Class<? extends SPEC> specClass) {
        this.specClass = specClass;
    }

    @Override
    public Optional<Supplier<Stage>> supplier(final Specifications specs) {
        if( specClass.isAssignableFrom(specs.getClass()) ) {
            return Optional.of( () -> this.build(specClass.cast(specs)) );
        } else {
            return Optional.empty();
        }
    }

    protected abstract STAGE build(SPEC specifications);
}

And then change Main.build to use map( builder -> builder.supplier(spec) ).filter(o -> o.isPresent() ) instead of the existing filter on canBuild

Tim
  • 6,406
  • 22
  • 34
0

I am not entirely certain what your trying to do here, but I was able to get it to compile. I changed

Collection<StageBuilder<? extends Specifications,? extends Stage>>

to just

Collection<StageBuilder>

on lines 19 and 41. (Your line-numbers may be slightly different, I got it from github but might have accidentally done some autoformating).

Here is a full paste of what I ended up with just in case:

import api.Specifications;
import api.Stage;
import api.StageBuilder;
import builder.FilterStageBuilder;
import builder.GroupStageBuilder;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.stream.Collectors;
import specification.FilterSpecifications;
import specification.GroupSpecifications;

public class Main {
public static void main(String[] args) {

    //Create the builder list
    //
    final Collection<StageBuilder>
        builders =
            new LinkedList<>();

    builders.add(new FilterStageBuilder());
    builders.add(new GroupStageBuilder());

    final Collection<Stage>
        result =
            build(
                builders,
                Arrays.asList(
                    new FilterSpecifications(),
                    new GroupSpecifications()
                )
            );

    System.out.println("Created stages: " + result.size());
}


static Collection<Stage> build(final Collection<StageBuilder> builders,  final Collection <? extends Specifications> specifications) {
    return
        specifications
            .stream()
            .map(
                spec ->
                    builders
                        .stream()
                        .filter(
                            builder ->
                                builder
                                    .canBuild(spec)
                        )
                        .findFirst()
                        .orElseThrow(
                            () ->
                                new RuntimeException(
                                    "Builder not found for " + spec
                                )
                        )
                        .build(
                            spec
                        )
            )
            .collect(
                Collectors.toList()
            );
    }
}
WillShackleford
  • 6,918
  • 2
  • 17
  • 33
  • Well it compiles but you lose most of the benefits of the generics. – user207421 Aug 08 '15 at 05:03
  • You are right, it does compile, but you get a big checked warning about using unchecked: Main.java:99: warning: [unchecked] unchecked call to canBuild(SPEC) as a member of the raw type StageBuilder .canBuild(specification) ^ Main.java:105: warning: [unchecked] unchecked call to build(SPEC) as a member of the raw type StageBuilder .build( ^ So, this is not the desirable effect. – Alex Paransky Aug 08 '15 at 21:36
  • On the other hand, a warning is A LOT better than having to use a cast to get the code to compile. So, at the end, if I am not able to get a more elegant answer simply changing the code as you have mentioned may be the best option. So far, I still have hope that there is a more elegant solution that I am just not seeing. – Alex Paransky Aug 09 '15 at 05:13
  • Perhaps also not very elegant but you can use @SuppressWarnings("unchecked") to disable the warning only on the functions it is needed. – WillShackleford Aug 09 '15 at 10:38
  • @AlexParansky, Why is a warning better than a cast? The warning is telling you that your code is losing the benefits of compile-type checking of generics. That's a pretty big deal. Obviously _"better"_ is a matter of opinion, but an appropriately placed cast is much _safer_ than simply turning off compiler type checks. – Tim Aug 09 '15 at 11:59
  • My scoring was based on if the code would compile or not. Purely my opinion. – Alex Paransky Aug 10 '15 at 12:59