-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bug] record interface can't be cast to object #940
Comments
Maybe it's because the record has generic parameters, I'll try that out and see if I can come up with a better error message. |
My initial thought was incorrect; In the example below, EqualsVerifier can handle the generics just fine: import java.util.Objects;
import org.junit.jupiter.api.Test;
public class ScratchTest {
@Test
void equalsverifier() {
EqualsVerifier.forClass(Aggregate.class).verify();
}
public static final class Aggregate<E extends EntityIdentifiable> {
private final DomainEvent<E> event;
public Aggregate(DomainEvent<E> event) {
this.event = event;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Aggregate)) {
return false;
}
Aggregate<E> other = (Aggregate<E>) obj;
return Objects.equals(event, other.event);
}
@Override
public int hashCode() {
return Objects.hash(event);
}
}
public abstract static class EntityIdentifiable {}
public static class ConcreteEntityIdentifiable extends EntityIdentifiable {}
public static record DomainEvent<E extends EntityIdentifiable>(E e) {}
} So I tried to copy the code you provided into my editor, and unfortunately it lit up like a christmas tree with errors. I was able to resolve some (by adding a Jakarta dependency, redirecting the So please, if you want me to look into this, give me some code that's both minimal and complete. |
I'll try to get back to this in a day or two. That class is here (domainEvents aren't). Sorry, I keep hoping the cause of these things is more obvious at a glance. I expect people to be me, and am surprised when they aren't, sorry. I was hoping your assumption was right. An educated guess... adding this code to the abstract aggregate linked in the issue above. private final @Transient List<DomainEvent<?, ID, SELF, ?>> domainEvents = new ArrayList<>();
protected void registerEvent( DomainEvent<?, ID, SELF, ?> event) {
this.domainEvents.add(event);
this.markDirty();
}
protected void clearDomainEvents() {
this.domainEvents.clear();
}
protected @NonNull Collection<DomainEvent<?, ID, SELF, ?>> domainEvents() {
return Collections.unmodifiableList(this.domainEvents);
} otherwise, yeah, I'll give it a stab tomorrow or something. |
Yeah, sometimes I'm able to see what the problem is by just looking at the code provided, but even then, it's helpful for me if there's as little code as possible, so I don't get distracted by irrelevant things. But in this case, the issue isn't obvious, unfortunately. I'm not sure what your guess is guessing at? Anyway, let me know if you managed to reduce this code, and I'll be happy to take a look. |
ok, cool, uh, I finally got back to a point where I can actually try to reduce this, as it's the last error before merge. |
I have no idea what a minimal reproduction of this looks like... I added code, and added more, and it still passed, kind of looks like I have to repro the full hierarchy to do this... I'm certain there's stuff in there that's not needed, but I can't begin to guess at what. |
I'm sure this can be reduced farther but... I'm not entirely certain how. package com.xenoterracide.jpa;
import java.io.Serial;
import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;
import org.junit.jupiter.api.Test;
public class EvTest {
@Test
void tev() {
EqualsVerifier.forClass(FooAggregate.class)
.withRedefinedSuperclass()
.withPrefabValues(BarEntity.Id.class, BarEntity.Id.create(), BarEntity.Id.create())
.withPrefabValues(FooAggregate.Id.class, FooAggregate.Id.create(), FooAggregate.Id.create())
.withPrefabValues(BarEntity.class, BarEntity.create(null, "bar"), BarEntity.create(null, "baz"))
.suppress(Warning.SURROGATE_OR_BUSINESS_KEY)
.withOnlyTheseFields("id")
.verify();
}
public interface EntityId<ID extends Serializable, E> {
ID id();
Class<E> type();
}
public record DEvent<
EVENTID extends Serializable,
AID extends AbstractIdentity<?>,
AGG extends AbstractEntity<AID>,
PAYLOAD extends EntityId<?, ?>
>(EVENTID id, ZonedDateTime occurredOn, EntityId<AID, AGG> aggregate, PAYLOAD payload) {}
public abstract static class AbstractEntity<ID extends AbstractIdentity<? extends Serializable>> {
private final List<DEvent<?, ID, ?, ?>> domainEvents = new ArrayList<>();
private ID id;
protected AbstractEntity() {}
protected AbstractEntity(ID id) {
this.id = id;
}
public ID getId() {
return this.id;
}
void setId(ID id) {
this.id = id;
}
@Override
public final int hashCode() {
return Objects.hash(this.id);
}
protected abstract boolean canEqual(AbstractEntity<?> that);
@Override
public final boolean equals(Object other) {
if (other instanceof AbstractEntity<?> that) {
// CHECKSTYLE.OFF: UnnecessaryParentheses
return (that.canEqual(this) && Objects.equals(this.id, that.id));
// CHECKSTYLE.ON: UnnecessaryParentheses
}
return false;
}
}
public abstract static class AbstractIdentity<ID extends Serializable> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private ID id;
protected AbstractIdentity() {}
protected AbstractIdentity(ID id) {
this.id = Objects.requireNonNull(id);
}
@Override
public final int hashCode() {
return Objects.hashCode(this.id);
}
protected abstract boolean canEqual(AbstractIdentity<?> that);
@Override
public final boolean equals(Object other) {
if (other instanceof AbstractIdentity<?> that) {
return that.canEqual(this) && Objects.equals(this.id, that.id);
}
return false;
}
@Override
public String toString() {
return String.valueOf(this.id);
}
}
public static class BarEntity extends AbstractEntity<BarEntity.Id> {
private String name;
private FooAggregate foo;
BarEntity(BarEntity.Id id, FooAggregate foo, String name) {
super(id);
this.foo = foo;
this.name = name;
}
public BarEntity() {}
static BarEntity create(FooAggregate foo, String name) {
return new BarEntity(BarEntity.Id.create(), foo, name);
}
public FooAggregate getFoo() {
return foo;
}
void setFoo(FooAggregate foo) {
this.foo = foo;
}
@Override
protected boolean canEqual(AbstractEntity<?> that) {
return that instanceof BarEntity;
}
public String getName() {
return name;
}
void setName(String name) {
this.name = name;
}
public static class Id extends AbstractIdentity<UUID> {
@Serial
private static final long serialVersionUID = 1L;
protected Id() {}
private Id(UUID id) {
super(id);
}
public static Id create() {
return new Id(UUID.randomUUID());
}
@Override
protected boolean canEqual(AbstractIdentity<?> that) {
return that instanceof Id;
}
}
}
public static class FooAggregate extends AbstractEntity<FooAggregate.Id> {
private String name;
private Set<BarEntity> bars = new HashSet<>();
protected FooAggregate() {}
public FooAggregate(FooAggregate.Id id, String name) {
super(id);
this.name = name;
}
static FooAggregate create(String name) {
return new FooAggregate(FooAggregate.Id.create(), name);
}
public BarEntity addBar(String name) {
var bar = BarEntity.create(this, name);
this.bars.add(bar);
return bar;
}
Set<BarEntity> getBars() {
return this.bars;
}
void setBars(Set<BarEntity> bars) {
this.bars = bars;
}
public String getName() {
return name;
}
void setName(String name) {
this.name = name;
}
@Override
protected boolean canEqual(AbstractEntity<?> that) {
return that instanceof FooAggregate;
}
public static class Id extends AbstractIdentity<UUID> {
@Serial
private static final long serialVersionUID = 1L;
protected Id() {}
private Id(UUID id) {
super(id);
}
public static Id create() {
return new Id(UUID.randomUUID());
}
@Override
protected boolean canEqual(AbstractIdentity<?> that) {
return that instanceof Id;
}
}
}
} |
It doesn't need to be perfect. The point is that I was able to copy/paste this into my editor and reproduce the issue within seconds, so thanks for that. I'll take a closer look in the coming days. |
I think as a workaround you should be able to add prefab values for |
This is the smallest I can get it: package com.xenoterracide.jpa;
import java.io.Serializable;
import java.util.Objects;
import nl.jqno.equalsverifier.EqualsVerifier;
import org.junit.jupiter.api.Test;
public class EvTest {
@Test
void tev() {
EqualsVerifier.forClass(FooAggregate.class).verify();
}
public record DEvent<EVENTID extends Serializable>(EVENTID id) {}
public static class FooAggregate {
private DEvent<?> domainEvents;
private String id;
@Override
public final boolean equals(Object other) {
if (other instanceof FooAggregate that) {
return Objects.equals(this.id, that.id);
}
return false;
}
}
} If you want to know how I did it; I added a little piece to the website the other day: https://jqno.nl/equalsverifier/submit-an-issue/ (I'm also linking to it from the issue template now) Haven't had time to look into why it fails yet, or why my earlier hunch was incorrect, because it looks suspiciously similar. To be continued. |
Huh, Yeah I feel like I had something similar to that at some point too... And it wasn't causing the problem... I honestly did try to cut it down from the big thing, It just didn't trigger the issue... |
TLDR; sorry, I'm old cynical and salty. firstly, sorry for being such a bad bug reporter. I've gotten rather used to being abused as a bug reporter in the java community; or having to abuse to get someone to take action. I've been dealing with things that are far more painful and critical than this project. Gradle has been a daily war. One thing that has ALWAYS (for ~20 years) bugged me, though. An open source project developer assuming we aren't working on our own projects with our own time. That's usually a complaint I've had with "patches welcome" vs what you're asking for though. I've even contributed small patches to the java world, only to have my code "stolen" (that's what I call taking my code doing a slight modification and committing it without retaining my authorship). What you're asking for is reasonable in request, but (and not your fault) less reasonable in practice due to hurdles created by the Java world. Basically, creating a minimal example is often a lot of work. I've had a thought on how to remediate this given our current tools. 1, github provides repository templates. 80% of the pain of a minimal example is setting up the build. some projects (checkstyle) expect your minimal example to be compiled in /var/tmp (no seriously, read their issue template and ask, is this not nutty?). Unfortunately we can't all have what prettier has, a server where we can paste code and have the sample produce. Too bad that doesn't have a github way to be integrated. So, solution part 1, use githubs repository template so people can simply clone and own. This repo needs to contain a build structure, and at least 1 modifiable source file that's as complete as can be to doing the thing. In your case? probably a test that passes equalsverifier. Next step, your issue template needs to provide full instructions on how to do it. My current thought is basically an inline copy and paste-able shell script.
This is an idea I've been having, you don't have to, setting this up and maintaining it is it's own amount of work. again, sorry, I've not been a great reporter, I'm just burned out. I don't want to be reporting problems anymore. I just want to build the thing I'm building. You've been great to work with, hopefully I can give you a break after this issue. |
Hey, I'm sorry to hear you've had bad experiences in the past. People being rude (or worse) is, unfortunately, a thing that exists in the open source world. I get that you have other work to get done too, but so do I. We have to meet in the middle somewhere. I do feel I have to defend Checkstyle a little, though. I looked at their template, and I honestly don't believe you really need to run their steps in On a much smaller scale, it's the same with me. I had half an hour for EqualsVerifier last Thursday, and I spent it reducing your code sample. That's fine, but it also means that I couldn't spend that same time actually looking for a fix. Then the Easter weekend came, and this week will be a busy one. I don't know when I'll next have time to look at your ticket. Again, I don't mind, but if you're actually waiting for a fix, here's how you could have accelerated it. I'll keep you posted about a potential fix. |
I was just giving you some context on what I'm saying. Their template could be better. The wrote it like they expect you to copy/paste it, and it's not viable. I honestly think copy-paste is what we all need (to improve minimal viable), that's just a good example of how not to do it. If I ever get people using my java stuff like I used to (a decade ago) for Perl, I'll probably try to dogfood this idea.
yeah, I suck. If it helps I spent more getting it that far :/ I don't know how you distilled it that much (I applaud you). I know I had tried simpler generic scenarios, but they didn't pan out. Admittedly I kind of threw my hands up in the air once I realized I was getting closer to reproducing the whole thing trying to find it. fortunately it's not impeding me. Although I think I've disabled the test for now, because IIRC I had trouble with prefabs in a more production code scenario, or something. Error looked the same, and I couldn't be bothered. |
No you don't. What you did here was extremely helpful. If you'd opened with that one, I would have been very happy :) |
Found it. I managed to reduce even further (it was a fun puzzle, got me nerd-sniped for a moment 😅) record DEvent<T extends Serializable>(T t)
record FooAggregate(DEvent<?> e) {} The problem was that You can workaround by changing the field to Or you can simply update to version 3.16.1, which is now syncing with Maven Central 🙂 |
🥃 Cheers. It's generics all the way down 😜 |
Haha, indeed 😄 |
Describe the bug
So, I'm playing around with the events being records... I'm not certain what the bug here is, feels like the error message is wrong or could be better. Using prefabs worked, but I had to use them on DomainEvent which is not the thing that the error was complaining about
Steps to reproduce
No response
Error message and version number
Code: EqualsVerifier invocation
Code: class under test
Additional context
looks like adding a prefab works
The text was updated successfully, but these errors were encountered: