Skip to content
Closed
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
83 changes: 46 additions & 37 deletions src/main/java/cat/udl/eps/softarch/fll/domain/CompetitionTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,88 +9,97 @@
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Size;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Table(name = "competition_tables")
@Getter
@Setter
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true)
public class CompetitionTable extends UriEntity<String> {

@Id
@EqualsAndHashCode.Include
private String id;

@OneToMany(mappedBy = "competitionTable", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "competitionTable", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JsonManagedReference("table-matches")
@Setter(lombok.AccessLevel.NONE)
private List<Match> matches = new ArrayList<>();

@OneToMany(mappedBy = "supervisesTable")
@Size(max = 3, message = "A table can have a maximum of 3 referees")
@JsonManagedReference("table-referees")
@Setter(lombok.AccessLevel.NONE)
private List<Referee> referees = new ArrayList<>();


@Override
public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public List<Match> getMatches() {
return matches;
}

public void setMatches(List<Match> matches) {
new ArrayList<>(this.matches).forEach(this::removeMatch);
if (matches != null) {
matches.forEach(this::addMatch);
}
}

public List<Referee> getReferees() {
return referees;
}

public void setReferees(List<Referee> referees) {
new ArrayList<>(this.referees).forEach(this::removeReferee);
if (referees != null) {
referees.forEach(this::addReferee);
public void addMatch(Match match) {
if (match == null || matches.contains(match)) {
return;
}
Comment on lines +47 to 50
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addMatch relies on matches.contains(match) for de-duplication, but Match.equals/hashCode is based solely on a generated id. This makes all transient matches (id == null) appear equal, so adding multiple new matches to a table can be silently skipped and removeMatch may remove an unintended instance. Use an identity-based duplicate check for transient entities or adjust Match.equals/hashCode to avoid null-id equality.

Copilot uses AI. Check for mistakes.
}

public void addMatch(Match match) {
CompetitionTable previousTable = match.getCompetitionTable();
if (previousTable != null && previousTable != this) {
previousTable.removeMatch(match);
}
if (!matches.contains(match)) {
matches.add(match);
}

matches.add(match);
match.setCompetitionTable(this);
}

public void removeMatch(Match match) {
matches.remove(match);
match.setCompetitionTable(null);
if (match == null) {
return;
}

if (matches.remove(match)) {
match.setCompetitionTable(null);
}
}

public void addReferee(Referee referee) {
CompetitionTable previousTable = referee.getSupervisesTable();
if (previousTable != null && previousTable != this) {
previousTable.removeReferee(referee);
public void setReferees(List<Referee> referees) {
new ArrayList<>(this.referees).forEach(this::removeReferee);
if (referees != null) {
referees.forEach(this::addReferee);
}
if (referees.contains(referee)) {
}

public void addReferee(Referee referee) {
if (referee == null || referees.contains(referee)) {
Comment on lines +78 to +79
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addReferee uses referees.contains(referee) to prevent duplicates. Since Referee equality comes from Volunteer and is based on a generated id, two new Referee() instances (with id == null) will compare equal and the second one will be dropped. If you need duplicate prevention, consider checking duplicates by object identity when id is null (or only by id when non-null).

Suggested change
public void addReferee(Referee referee) {
if (referee == null || referees.contains(referee)) {
private boolean isRefereeAlreadyAssigned(Referee referee) {
if (referee == null) {
return false;
}
Object refereeId = referee.getId();
if (refereeId == null) {
for (Referee existingReferee : referees) {
if (existingReferee == referee) {
return true;
}
}
return false;
}
for (Referee existingReferee : referees) {
if (refereeId.equals(existingReferee.getId())) {
return true;
}
}
return false;
}
public void addReferee(Referee referee) {
if (referee == null || isRefereeAlreadyAssigned(referee)) {

Copilot uses AI. Check for mistakes.
return;
}

if (referees.size() >= 3) {
throw new IllegalStateException("A table can have a maximum of 3 referees");
}

CompetitionTable previousTable = referee.getSupervisesTable();
if (previousTable != null && previousTable != this) {
previousTable.removeReferee(referee);
}

referees.add(referee);
referee.setSupervisesTable(this);
}

public void removeReferee(Referee referee) {
referees.remove(referee);
referee.setSupervisesTable(null);
if (referee == null) {
return;
}

if (referees.remove(referee)) {
referee.setSupervisesTable(null);
}
}
}
57 changes: 11 additions & 46 deletions src/main/java/cat/udl/eps/softarch/fll/domain/Match.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,69 +10,34 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "matches")
@Getter
@Setter
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true)
public class Match extends UriEntity<Long> {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@EqualsAndHashCode.Include
private Long id;
Comment on lines +20 to 29
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@EqualsAndHashCode includes only the generated id. When id is null (common before persistence), different Match instances will compare equal and share the same hash code. This can break collection operations (e.g., List.contains/remove) and is currently observable via Round.addMatch/CompetitionTable.addMatch duplicate checks. Consider implementing an equality strategy that does not treat two transient entities as equal (e.g., only compare ids when both are non-null).

Copilot uses AI. Check for mistakes.

private LocalTime startTime;

private LocalTime endTime;

@JsonBackReference("round-matches")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "round_id")
@JsonBackReference("round-matches")
private Round round;

@JsonBackReference("table-matches")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "table_id")
@JsonBackReference("table-matches")
private CompetitionTable competitionTable;

public Match() {}

@Override
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public LocalTime getStartTime() {
return startTime;
}

public void setStartTime(LocalTime startTime) {
this.startTime = startTime;
}

public LocalTime getEndTime() {
return endTime;
}

public void setEndTime(LocalTime endTime) {
this.endTime = endTime;
}

public Round getRound() {
return round;
}

public void setRound(Round round) {
this.round = round;
}

public CompetitionTable getCompetitionTable() {
return competitionTable;
}

public void setCompetitionTable(CompetitionTable competitionTable) {
this.competitionTable = competitionTable;
}
}
25 changes: 8 additions & 17 deletions src/main/java/cat/udl/eps/softarch/fll/domain/Referee.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;


@Entity
@Table(name = "referees")
@Getter
@Setter
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
public class Referee extends Volunteer {

private boolean expert;
Expand All @@ -18,21 +26,4 @@ public class Referee extends Volunteer {
@JoinColumn(name = "supervises_table_id")
@JsonBackReference("table-referees")
private CompetitionTable supervisesTable;


public boolean isExpert() {
return expert;
}

public void setExpert(boolean expert) {
this.expert = expert;
}

public CompetitionTable getSupervisesTable() {
return supervisesTable;
}

public void setSupervisesTable(CompetitionTable supervisesTable) {
this.supervisesTable = supervisesTable;
}
}
55 changes: 30 additions & 25 deletions src/main/java/cat/udl/eps/softarch/fll/domain/Round.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,32 @@
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "rounds")
@Getter
@Setter
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true)
public class Round extends UriEntity<Long> {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@EqualsAndHashCode.Include
private Long id;

@Column(unique = true)
private int number;

@OneToMany(mappedBy = "round", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonManagedReference("round-matches")
@Setter(lombok.AccessLevel.NONE)
private List<Match> matches = new ArrayList<>();

public Round() {}

@Override
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public int getNumber() {
return number;
}

public void setNumber(int number) {
this.number = number;
}

public List<Match> getMatches() {
return matches;
}

public void setMatches(List<Match> matches) {
this.matches.clear();
if (matches != null) {
Expand All @@ -58,12 +45,30 @@ public void setMatches(List<Match> matches) {
}
Comment on lines 40 to 45
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setMatches clears this.matches without updating the owning side (Match.round). This can leave previously associated Match instances still pointing to this Round, breaking bidirectional consistency and potentially confusing JPA orphan removal. Prefer iterating over a copy and calling removeMatch for each existing match before adding the new ones.

Copilot uses AI. Check for mistakes.

public void addMatch(Match match) {
if (match == null) {
return;
}

if (matches.contains(match)) {
return;
}
Comment on lines 47 to +54
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addMatch uses matches.contains(match) to prevent duplicates, but Match now has Lombok equals/hashCode based only on the generated id. For transient matches (where id == null), all instances compare equal, so adding multiple new Match() objects will be incorrectly blocked (and removeMatch may remove the wrong instance). Consider changing the duplicate check to use object identity when id is null, or revising Match.equals/hashCode so entities with null ids are not considered equal.

Copilot uses AI. Check for mistakes.

Round previousRound = match.getRound();
if (previousRound != null && previousRound != this) {
previousRound.removeMatch(match);
}

matches.add(match);
match.setRound(this);
}

public void removeMatch(Match match) {
matches.remove(match);
match.setRound(null);
if (match == null) {
return;
}

if (matches.remove(match)) {
match.setRound(null);
}
}
}
Loading