Skip to content

Commit 104b60e

Browse files
Merge pull request #677 from softwaremagico/612-allow-to-customize-a-championship-tree
612 allow to customize a championship tree
2 parents 840fbd1 + 3cc9e38 commit 104b60e

File tree

37 files changed

+768
-117
lines changed

37 files changed

+768
-117
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
[![GitHub last commit](https://img.shields.io/github/last-commit/softwaremagico/KendoTournamentManager)](https://github.com/softwaremagico/KendoTournamentManager)
1010
[![Issues](https://img.shields.io/github/issues/softwaremagico/KendoTournamentManager.svg)](https://github.com/softwaremagico/KendoTournamentManager/issues)
1111
[![CircleCI](https://circleci.com/gh/softwaremagico/KendoTournamentManager.svg?style=shield)](https://circleci.com/gh/softwaremagico/KendoTournamentManager)
12-
[![Time](https://img.shields.io/badge/development-824h-blueviolet.svg)]()
12+
[![Time](https://img.shields.io/badge/development-829h-blueviolet.svg)]()
1313

1414
[![Powered by](https://img.shields.io/badge/powered%20by%20java-orange.svg?logo=OpenJDK&logoColor=white)]()
1515
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=kendo-tournament-backend&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=kendo-tournament-backend)

backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/controller/CsvController.java

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,39 @@
2222
*/
2323

2424
import com.softwaremagico.kt.core.controller.models.ClubDTO;
25+
import com.softwaremagico.kt.core.controller.models.GroupLinkDTO;
2526
import com.softwaremagico.kt.core.controller.models.ParticipantDTO;
2627
import com.softwaremagico.kt.core.controller.models.TeamDTO;
2728
import com.softwaremagico.kt.core.converters.ClubConverter;
29+
import com.softwaremagico.kt.core.converters.GroupLinkConverter;
2830
import com.softwaremagico.kt.core.converters.ParticipantConverter;
2931
import com.softwaremagico.kt.core.converters.TeamConverter;
3032
import com.softwaremagico.kt.core.converters.models.ClubConverterRequest;
33+
import com.softwaremagico.kt.core.converters.models.GroupLinkConverterRequest;
3134
import com.softwaremagico.kt.core.converters.models.ParticipantConverterRequest;
3235
import com.softwaremagico.kt.core.converters.models.TeamConverterRequest;
3336
import com.softwaremagico.kt.core.csv.ClubCsv;
37+
import com.softwaremagico.kt.core.csv.GroupLinkCsv;
3438
import com.softwaremagico.kt.core.csv.ParticipantCsv;
3539
import com.softwaremagico.kt.core.csv.TeamCsv;
3640
import com.softwaremagico.kt.core.exceptions.InvalidCsvFieldException;
41+
import com.softwaremagico.kt.core.exceptions.TournamentNotFoundException;
3742
import com.softwaremagico.kt.core.providers.ClubProvider;
43+
import com.softwaremagico.kt.core.providers.GroupLinkProvider;
44+
import com.softwaremagico.kt.core.providers.GroupProvider;
3845
import com.softwaremagico.kt.core.providers.ParticipantProvider;
3946
import com.softwaremagico.kt.core.providers.RoleProvider;
4047
import com.softwaremagico.kt.core.providers.TeamProvider;
48+
import com.softwaremagico.kt.core.providers.TournamentProvider;
49+
import com.softwaremagico.kt.logger.ExceptionType;
4150
import com.softwaremagico.kt.logger.KendoTournamentLogger;
4251
import com.softwaremagico.kt.persistence.entities.Club;
52+
import com.softwaremagico.kt.persistence.entities.Group;
53+
import com.softwaremagico.kt.persistence.entities.GroupLink;
4354
import com.softwaremagico.kt.persistence.entities.Participant;
4455
import com.softwaremagico.kt.persistence.entities.Role;
4556
import com.softwaremagico.kt.persistence.entities.Team;
57+
import com.softwaremagico.kt.persistence.entities.Tournament;
4658
import com.softwaremagico.kt.persistence.values.RoleType;
4759
import org.springframework.stereotype.Controller;
4860

@@ -65,11 +77,19 @@ public class CsvController {
6577
private final TeamProvider teamProvider;
6678
private final TeamConverter teamConverter;
6779

80+
private final GroupLinkCsv groupLinkCsv;
81+
private final GroupLinkProvider groupLinkProvider;
82+
private final GroupLinkConverter groupLinkConverter;
83+
6884
private final RoleProvider roleProvider;
85+
private final TournamentProvider tournamentProvider;
86+
private final GroupProvider groupProvider;
6987

7088
public CsvController(ClubCsv clubCsv, ClubProvider clubProvider, ClubConverter clubConverter,
7189
ParticipantCsv participantCsv, ParticipantProvider participantProvider, ParticipantConverter participantConverter,
72-
TeamCsv teamCsv, TeamProvider teamProvider, TeamConverter teamConverter, RoleProvider roleProvider) {
90+
TeamCsv teamCsv, TeamProvider teamProvider, TeamConverter teamConverter, GroupLinkCsv groupLinkCsv,
91+
GroupLinkProvider groupLinkProvider, GroupLinkConverter groupLinkConverter, RoleProvider roleProvider,
92+
TournamentProvider tournamentProvider, GroupProvider groupProvider) {
7393
this.clubCsv = clubCsv;
7494
this.clubProvider = clubProvider;
7595
this.clubConverter = clubConverter;
@@ -79,7 +99,12 @@ public CsvController(ClubCsv clubCsv, ClubProvider clubProvider, ClubConverter c
7999
this.teamCsv = teamCsv;
80100
this.teamProvider = teamProvider;
81101
this.teamConverter = teamConverter;
102+
this.groupLinkCsv = groupLinkCsv;
103+
this.groupLinkProvider = groupLinkProvider;
104+
this.groupLinkConverter = groupLinkConverter;
82105
this.roleProvider = roleProvider;
106+
this.tournamentProvider = tournamentProvider;
107+
this.groupProvider = groupProvider;
83108
}
84109

85110

@@ -183,4 +208,87 @@ private void setTeamMemberRoles(Team team) {
183208
}
184209
});
185210
}
211+
212+
public List<GroupLinkDTO> addGroupLinks(Integer tournamentId, String csvContent, String uploadedBy) {
213+
final Tournament tournament = tournamentProvider.get(tournamentId)
214+
.orElseThrow(() -> new TournamentNotFoundException(getClass(), "No tournament found with id '" + tournamentId + "',",
215+
ExceptionType.INFO));
216+
217+
//Define groups of the tournament.
218+
final int totalSourceGroups = groupLinkCsv.getSourceGroupSize(csvContent);
219+
final int totalDestinationGroups = groupLinkCsv.getDestinationGroupSize(csvContent);
220+
final int levels = generateCustomGroupTree(tournament, totalSourceGroups, totalDestinationGroups);
221+
222+
//Assign the links
223+
groupLinkProvider.deleteByTournament(tournament);
224+
final List<GroupLink> groupLinks = groupLinkCsv.readCSV(tournament, csvContent);
225+
final List<GroupLinkDTO> failedGroupLinks = new ArrayList<>();
226+
227+
//Set the links.
228+
for (GroupLink groupLink : groupLinks) {
229+
groupLink.setTournament(tournament);
230+
groupLink.setUpdatedBy(uploadedBy);
231+
try {
232+
groupLinkProvider.save(groupLink);
233+
} catch (Exception e) {
234+
KendoTournamentLogger.errorMessage(this.getClass(), e);
235+
failedGroupLinks.add(groupLinkConverter.convert(new GroupLinkConverterRequest(groupLink)));
236+
}
237+
}
238+
239+
generateInnerLevelLinks(tournament, levels);
240+
241+
return failedGroupLinks;
242+
}
243+
244+
private List<GroupLink> generateInnerLevelLinks(Tournament tournament, int totalLevels) {
245+
//Update other levels links.
246+
final List<Group> groupsOfTournament = groupProvider.getGroups(tournament);
247+
return groupLinkProvider.save(groupLinkProvider.generateLinks(
248+
groupsOfTournament,
249+
1, totalLevels, 1));
250+
}
251+
252+
253+
private int generateCustomGroupTree(Tournament tournament, int levelZeroSize, int levelOneSize) {
254+
groupProvider.delete(tournament);
255+
256+
//Define Level 0
257+
for (int i = 0; i < levelZeroSize; i++) {
258+
final Group levelGroup = new Group(tournament, 0, i);
259+
groupProvider.addGroup(tournament, levelGroup);
260+
}
261+
262+
//Define Level 1
263+
for (int i = 0; i < levelOneSize; i++) {
264+
final Group levelGroup = new Group(tournament, 1, i);
265+
groupProvider.addGroup(tournament, levelGroup);
266+
}
267+
268+
//Define other levels groups.
269+
int previousLevelSize = levelOneSize;
270+
int newLevel = 2;
271+
while (previousLevelSize > 1) {
272+
final List<Group> generatedGroups = generateGroupsOnLevel(tournament, previousLevelSize, newLevel);
273+
previousLevelSize = generatedGroups.size();
274+
newLevel++;
275+
}
276+
return newLevel - 1;
277+
}
278+
279+
private List<Group> generateGroupsOnLevel(Tournament tournament, int lastLevelSize, int currentLevel) {
280+
if (lastLevelSize <= 1) {
281+
return List.of();
282+
}
283+
284+
//On inner tree, always one winner by group.
285+
final int groupSize = (lastLevelSize + 1) / 2;
286+
287+
final List<Group> groups = new ArrayList<>();
288+
for (int i = 0; i < groupSize; i++) {
289+
final Group levelGroup = new Group(tournament, currentLevel, i);
290+
groups.add(groupProvider.addGroup(tournament, levelGroup));
291+
}
292+
return groups;
293+
}
186294
}

backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/controller/GroupLinkController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public List<GroupLinkDTO> getLinks(TournamentDTO tournamentDTO) {
7171
}
7272

7373
public List<GroupLinkDTO> getLinks(Tournament tournament) {
74-
return convertAll(groupLinkProvider.generateLinks(tournament));
74+
return convertAll(groupLinkProvider.getGroupLinks(tournament));
7575
}
7676

7777
private List<GroupLinkDTO> convertAll(Collection<GroupLink> entities) {

backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/controller/models/GroupLinkDTO.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public class GroupLinkDTO extends ElementDTO {
3636
@NotNull
3737
private GroupDTO destination;
3838

39+
private TournamentDTO tournament;
40+
3941
private int winner = 0;
4042

4143
public GroupDTO getSource() {
@@ -62,6 +64,14 @@ public void setWinner(int winner) {
6264
this.winner = winner;
6365
}
6466

67+
public TournamentDTO getTournament() {
68+
return tournament;
69+
}
70+
71+
public void setTournament(TournamentDTO tournament) {
72+
this.tournament = tournament;
73+
}
74+
6575
@Override
6676
public String toString() {
6777
return "GroupLink{"

backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/converters/GroupLinkConverter.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,19 @@
2424
import com.softwaremagico.kt.core.controller.models.GroupLinkDTO;
2525
import com.softwaremagico.kt.core.converters.models.GroupConverterRequest;
2626
import com.softwaremagico.kt.core.converters.models.GroupLinkConverterRequest;
27+
import com.softwaremagico.kt.core.converters.models.TournamentConverterRequest;
2728
import com.softwaremagico.kt.persistence.entities.GroupLink;
2829
import org.springframework.beans.BeanUtils;
2930
import org.springframework.stereotype.Component;
3031

3132
@Component
3233
public class GroupLinkConverter extends ElementConverter<GroupLink, GroupLinkDTO, GroupLinkConverterRequest> {
3334
private final GroupConverter groupConverter;
35+
private final TournamentConverter tournamentConverter;
3436

35-
public GroupLinkConverter(GroupConverter groupConverter) {
37+
public GroupLinkConverter(GroupConverter groupConverter, TournamentConverter tournamentConverter) {
3638
this.groupConverter = groupConverter;
39+
this.tournamentConverter = tournamentConverter;
3740
}
3841

3942
@Override
@@ -42,6 +45,7 @@ protected GroupLinkDTO convertElement(GroupLinkConverterRequest from) {
4245
BeanUtils.copyProperties(from.getEntity(), groupLinkDTO);
4346
groupLinkDTO.setSource(groupConverter.convert(new GroupConverterRequest(from.getEntity().getSource())));
4447
groupLinkDTO.setDestination(groupConverter.convert(new GroupConverterRequest(from.getEntity().getDestination())));
48+
groupLinkDTO.setTournament(tournamentConverter.convert(new TournamentConverterRequest(from.getEntity().getTournament())));
4549
return groupLinkDTO;
4650
}
4751

@@ -51,6 +55,7 @@ public GroupLink reverse(GroupLinkDTO to) {
5155
BeanUtils.copyProperties(to, groupLink);
5256
groupLink.setSource(groupConverter.reverse(to.getSource()));
5357
groupLink.setDestination(groupConverter.reverse(to.getDestination()));
58+
groupLink.setTournament(tournamentConverter.reverse(to.getTournament()));
5459
return groupLink;
5560
}
5661
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package com.softwaremagico.kt.core.csv;
2+
3+
/*-
4+
* #%L
5+
* Kendo Tournament Manager (Core)
6+
* %%
7+
* Copyright (C) 2021 - 2026 SoftwareMagico
8+
* %%
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Affero General Public License as published by
11+
* the Free Software Foundation, either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Affero General Public License
20+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
* #L%
22+
*/
23+
24+
import com.softwaremagico.kt.core.exceptions.InvalidCsvFieldException;
25+
import com.softwaremagico.kt.core.providers.GroupProvider;
26+
import com.softwaremagico.kt.logger.KendoTournamentLogger;
27+
import com.softwaremagico.kt.persistence.entities.Group;
28+
import com.softwaremagico.kt.persistence.entities.GroupLink;
29+
import com.softwaremagico.kt.persistence.entities.Tournament;
30+
import org.springframework.stereotype.Component;
31+
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
35+
@Component
36+
public class GroupLinkCsv extends CsvReader<GroupLink> {
37+
private static final String SOURCE_GROUP_INDEX = "source";
38+
private static final String WINNER = "winner";
39+
private static final String DESTINATION_GROUP_INDEX = "destination";
40+
private static final int MAX_ALLOWED_WINNERS = 2;
41+
private final GroupProvider groupProvider;
42+
43+
public GroupLinkCsv(GroupProvider groupProvider) {
44+
this.groupProvider = groupProvider;
45+
}
46+
47+
@Override
48+
public List<GroupLink> readCSV(String csvContent) {
49+
return List.of();
50+
}
51+
52+
53+
public List<GroupLink> readCSV(Tournament tournament, String csvContent) {
54+
final String[] headers = getHeaders(csvContent);
55+
checkHeaders(headers, SOURCE_GROUP_INDEX, WINNER, DESTINATION_GROUP_INDEX);
56+
final String[] content = getContent(csvContent);
57+
final List<GroupLink> groupLinks = new ArrayList<>();
58+
59+
final int sourceGroupIndex = getHeaderIndex(headers, SOURCE_GROUP_INDEX);
60+
final int winnerIndex = getHeaderIndex(headers, WINNER);
61+
final int destinationGroupIndex = getHeaderIndex(headers, DESTINATION_GROUP_INDEX);
62+
63+
final List<Group> sourceGroups = groupProvider.getGroups(tournament, 0);
64+
final List<Group> destinationGroups = groupProvider.getGroups(tournament, 1);
65+
66+
for (String groupLinkLine : content) {
67+
final GroupLink groupLink = new GroupLink();
68+
69+
try {
70+
groupLink.setSource(sourceGroups.get(Integer.parseInt(getField(groupLinkLine, sourceGroupIndex))));
71+
} catch (NumberFormatException | IndexOutOfBoundsException e) {
72+
KendoTournamentLogger.errorMessage(this.getClass(), e);
73+
throw new InvalidCsvFieldException(this.getClass(), "Group source is incorrect on line '" + groupLinkLine + "'!", null);
74+
}
75+
76+
try {
77+
groupLink.setWinner(Integer.valueOf(getField(groupLinkLine, winnerIndex)));
78+
if (groupLink.getWinner() >= MAX_ALLOWED_WINNERS) {
79+
throw new InvalidCsvFieldException(this.getClass(), "Winner is incorrect on line '" + groupLinkLine
80+
+ "'! Only two winners are allowed", null);
81+
}
82+
} catch (NumberFormatException e) {
83+
KendoTournamentLogger.errorMessage(this.getClass(), e);
84+
throw new InvalidCsvFieldException(this.getClass(), "Winner is incorrect on line '" + groupLinkLine + "'!", null);
85+
}
86+
87+
try {
88+
groupLink.setDestination(destinationGroups.get(Integer.parseInt(getField(groupLinkLine, destinationGroupIndex))));
89+
} catch (NumberFormatException | IndexOutOfBoundsException e) {
90+
KendoTournamentLogger.errorMessage(this.getClass(), e);
91+
throw new InvalidCsvFieldException(this.getClass(), "Group destination is incorrect on line '" + groupLinkLine + "'!", null);
92+
}
93+
groupLinks.add(groupLink);
94+
}
95+
return groupLinks;
96+
}
97+
98+
public int getSourceGroupSize(String csvContent) {
99+
final String[] content = getContent(csvContent);
100+
101+
final String[] headers = getHeaders(csvContent);
102+
checkHeaders(headers, SOURCE_GROUP_INDEX, WINNER, DESTINATION_GROUP_INDEX);
103+
final int sourceGroupIndex = getHeaderIndex(headers, SOURCE_GROUP_INDEX);
104+
105+
int maxGroupIndex = -1;
106+
for (String groupLinkLine : content) {
107+
try {
108+
final int groupIndex = Integer.parseInt(getField(groupLinkLine, sourceGroupIndex));
109+
if (groupIndex > maxGroupIndex) {
110+
maxGroupIndex = groupIndex;
111+
}
112+
} catch (NumberFormatException e) {
113+
KendoTournamentLogger.errorMessage(this.getClass(), e);
114+
throw new InvalidCsvFieldException(this.getClass(), "Group source is incorrect on line '" + groupLinkLine + "'!", null);
115+
}
116+
}
117+
return maxGroupIndex + 1;
118+
}
119+
120+
public int getDestinationGroupSize(String csvContent) {
121+
final String[] content = getContent(csvContent);
122+
123+
final String[] headers = getHeaders(csvContent);
124+
checkHeaders(headers, SOURCE_GROUP_INDEX, WINNER, DESTINATION_GROUP_INDEX);
125+
final int destinationGroupIndex = getHeaderIndex(headers, DESTINATION_GROUP_INDEX);
126+
127+
int maxGroupIndex = -1;
128+
for (String groupLinkLine : content) {
129+
try {
130+
final int groupIndex = Integer.parseInt(getField(groupLinkLine, destinationGroupIndex));
131+
if (groupIndex > maxGroupIndex) {
132+
maxGroupIndex = groupIndex;
133+
}
134+
} catch (NumberFormatException e) {
135+
KendoTournamentLogger.errorMessage(this.getClass(), e);
136+
throw new InvalidCsvFieldException(this.getClass(), "Group destination is incorrect on line '" + groupLinkLine + "'!", null);
137+
}
138+
}
139+
return maxGroupIndex + 1;
140+
}
141+
}

0 commit comments

Comments
 (0)