1

I have a method with generic parameters:

public static void addActionColumnAndSetSelectionListener(Grid<? extends UpdatableRecord<?>> grid,
                                                              EditDialog<? extends UpdatableRecord<?>> dialog,
                                                              Callback afterSave, Supplier<UpdatableRecord<?>> onNewRecord,
                                                              Consumer<? extends UpdatableRecord<?>> insteadOfDelete) {
        Button buttonAdd = new Button(grid.getTranslation("Add"));
        buttonAdd.addClickListener(event -> dialog.open(onNewRecord.get(), afterSave));

        grid.addComponentColumn(record -> {
            Button delete = new Button(grid.getTranslation("Delete"));
            delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
            delete.addClickListener(event -> {
                getBean(TransactionTemplate.class).executeWithoutResult(transactionStatus -> {
                    try {
                        if (insteadOfDelete != null) {
                            insteadOfDelete.accept(record);
                        } else {
                            getBean(DSLContext.class).attach(record);
                        }
                        record.delete();
                    } catch (DataAccessException e) {
                        Notification.show(e.getMessage());
                    }
                });
            });

            HorizontalLayout horizontalLayout = new HorizontalLayout(delete);
            horizontalLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
            return horizontalLayout;
        }).setTextAlign(ColumnTextAlign.END).setHeader(buttonAdd);

        grid.addSelectionListener(event -> event.getFirstSelectedItem().ifPresent(record -> dialog.open(record, afterSave)));
    }

The problem is that the line insteadOfDelete.accept(record); does not compile:

Error:(47, 52) java: incompatible types: org.jooq.UpdatableRecord<capture#1 of ?> cannot be converted to capture#2 of ? extends org.jooq.UpdatableRecord<?>

I don't understand the problem. If I change

Consumer<? extends UpdatableRecord<?>> insteadOfDelete

to

Consumer<UpdatableRecord<?>> insteadOfDelete

it compiles.

Simon Martinelli
  • 34,053
  • 5
  • 48
  • 82

1 Answers1

2

The joys of recursive generics... It could be seen as a design flaw of jOOQ to use them in the UpdatableRecord type hierarchy. You should capture the wild card in a generic type variable of your method. While it might work to some extent when using ? extends UpdatableRecord<?> or even just UpdatableRecord<?>, I think with an <R> type variable, you're going to get cleaner code.

This might work (I only changed the parameters and added the <R> type variable, nothing else):

public static <R extends UpdatableRecord<R>> void addActionColumnAndSetSelectionListener(
    Grid<R> grid,
    EditDialog<R> dialog,
    Callback afterSave, 
    Supplier<R> onNewRecord,
    Consumer<R> insteadOfDelete
) {
    Button buttonAdd = new Button(grid.getTranslation("Add"));
    buttonAdd.addClickListener(event -> dialog.open(onNewRecord.get(), afterSave));
    grid.addComponentColumn(record -> {
        Button delete = new Button(grid.getTranslation("Delete"));
        delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
        delete.addClickListener(event -> {
            getBean(TransactionTemplate.class).executeWithoutResult(transactionStatus -> {
                try {
                    if (insteadOfDelete != null) {
                        insteadOfDelete.accept(record);
                    } else {
                        getBean(DSLContext.class).attach(record);
                    }
                    record.delete();
                } catch (DataAccessException e) {
                    Notification.show(e.getMessage());
                }
            });
        });
        HorizontalLayout horizontalLayout = new HorizontalLayout(delete);
        horizontalLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
        return horizontalLayout;
    }).setTextAlign(ColumnTextAlign.END).setHeader(buttonAdd);
    grid.addSelectionListener(event -> event.getFirstSelectedItem().ifPresent(
        record -> dialog.open(record, afterSave)));
}

Also, I've removed covariance of your individual method parameters, as I think you probably don't need it. Otherwise, remember that Supplier is covariant and Consumer is contravariant. This answer here explains it nicely. This explains your observation that suddenly things compiled when you had an invariant consumer.

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509