diff --git a/pom.xml b/pom.xml
index ff4d6d8..21a46da 100644
--- a/pom.xml
+++ b/pom.xml
@@ -127,9 +127,9 @@
-
-
-
+
+
+
@@ -169,6 +169,10 @@
3.8.1
test
+
+ com.google.code.gson
+ gson
+
diff --git a/src/main/java/com/example/application/data/chesscom/ChessComArchive.java b/src/main/java/com/example/application/data/chesscom/ChessComArchive.java
new file mode 100644
index 0000000..c2ad97e
--- /dev/null
+++ b/src/main/java/com/example/application/data/chesscom/ChessComArchive.java
@@ -0,0 +1,15 @@
+package com.example.application.data.chesscom;
+
+import java.util.List;
+
+public class ChessComArchive {
+ private List games;
+
+ public List getGames() {
+ return games;
+ }
+
+ public void setGames(List games) {
+ this.games = games;
+ }
+}
diff --git a/src/main/java/com/example/application/data/chesscom/ChessComArchiveList.java b/src/main/java/com/example/application/data/chesscom/ChessComArchiveList.java
new file mode 100644
index 0000000..fb10c17
--- /dev/null
+++ b/src/main/java/com/example/application/data/chesscom/ChessComArchiveList.java
@@ -0,0 +1,15 @@
+package com.example.application.data.chesscom;
+
+import java.util.List;
+
+public class ChessComArchiveList {
+ private List archives;
+
+ public List getArchives() {
+ return archives;
+ }
+
+ public void setArchives(List archives) {
+ this.archives = archives;
+ }
+}
diff --git a/src/main/java/com/example/application/data/chesscom/ChessComGame.java b/src/main/java/com/example/application/data/chesscom/ChessComGame.java
new file mode 100644
index 0000000..f7f4262
--- /dev/null
+++ b/src/main/java/com/example/application/data/chesscom/ChessComGame.java
@@ -0,0 +1,134 @@
+package com.example.application.data.chesscom;
+
+public class ChessComGame {
+ private String url;
+ private String pgn;
+ private String timeControl;
+ private long endTime;
+ private boolean rated;
+ private String fen;
+ private String timeClass;
+ private String rules;
+ private ChessComGamePlayer white;
+ private ChessComGamePlayer black;
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getPgn() {
+ return pgn;
+ }
+
+ public void setPgn(String pgn) {
+ this.pgn = pgn;
+ }
+
+ public String getTimeControl() {
+ return timeControl;
+ }
+
+ public void setTimeControl(String timeControl) {
+ this.timeControl = timeControl;
+ }
+
+ public long getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(long endTime) {
+ this.endTime = endTime;
+ }
+
+ public boolean isRated() {
+ return rated;
+ }
+
+ public void setRated(boolean rated) {
+ this.rated = rated;
+ }
+
+ public String getFen() {
+ return fen;
+ }
+
+ public void setFen(String fen) {
+ this.fen = fen;
+ }
+
+ public String getTimeClass() {
+ return timeClass;
+ }
+
+ public void setTimeClass(String timeClass) {
+ this.timeClass = timeClass;
+ }
+
+ public String getRules() {
+ return rules;
+ }
+
+ public void setRules(String rules) {
+ this.rules = rules;
+ }
+
+ public ChessComGamePlayer getWhite() {
+ return white;
+ }
+
+ public void setWhite(ChessComGamePlayer white) {
+ this.white = white;
+ }
+
+ public ChessComGamePlayer getBlack() {
+ return black;
+ }
+
+ public void setBlack(ChessComGamePlayer black) {
+ this.black = black;
+ }
+
+ public static class ChessComGamePlayer {
+ private int rating;
+ private String result;
+ private String id;
+ private String username;
+
+ public int getRating() {
+ return rating;
+ }
+
+ public void setRating(int rating) {
+ this.rating = rating;
+ }
+
+ public String getResult() {
+ return result;
+ }
+
+ public void setResult(String result) {
+ this.result = result;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+ }
+
+}
diff --git a/src/main/java/com/example/application/data/entity/Season.java b/src/main/java/com/example/application/data/entity/Season.java
index 59a5200..ecaac56 100644
--- a/src/main/java/com/example/application/data/entity/Season.java
+++ b/src/main/java/com/example/application/data/entity/Season.java
@@ -69,7 +69,7 @@ public class Season {
public String toString() {
String s = yearStart.toString();
if (!yearEnd.equals(yearStart)) {
- s += "/" + yearEnd.toString();
+ s += "-" + yearEnd.toString();
}
return s;
}
diff --git a/src/main/java/com/example/application/data/service/ChessComService.java b/src/main/java/com/example/application/data/service/ChessComService.java
new file mode 100644
index 0000000..5268025
--- /dev/null
+++ b/src/main/java/com/example/application/data/service/ChessComService.java
@@ -0,0 +1,81 @@
+package com.example.application.data.service;
+
+import com.example.application.data.chesscom.ChessComArchive;
+import com.example.application.data.chesscom.ChessComArchiveList;
+import com.example.application.data.chesscom.ChessComGame;
+import com.example.application.data.entity.Game;
+import com.example.application.data.entity.GameInfo;
+import com.example.application.data.entity.Player;
+import com.example.application.utils.ChessComUtils;
+import com.example.application.utils.HttpUtils;
+import com.google.gson.Gson;
+import org.springframework.lang.NonNull;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+
+@Service
+public class ChessComService {
+ // TODO: make everything nullsafe
+
+ private static final Gson gson = new Gson();
+
+ public ChessComService() {
+ }
+
+ @NonNull
+ public List getLatestGamesBetweenPlayers(@NonNull Player player1, @NonNull Player player2, int minimum_amount_of_games) {
+ String url = String.format("https://api.chess.com/pub/player/%s/games/archives", ChessComUtils.getUrlPart(player1));
+
+ List archiveUrls = gson.fromJson(HttpUtils.getJson(url), ChessComArchiveList.class).getArchives();
+ archiveUrls.sort(Collections.reverseOrder());
+
+ if (archiveUrls.size() >= 2) {
+ archiveUrls = archiveUrls.subList(0, 2);
+ }
+
+ List list = new ArrayList<>();
+ for (String archiveUrl : archiveUrls) {
+ String unpreparedJson = HttpUtils.getJson(archiveUrl);
+ if (unpreparedJson == null) {
+ continue;
+ }
+ String preparedJson = ChessComUtils.getPreparedArchiveJson(unpreparedJson);
+ List games = gson.fromJson(preparedJson, ChessComArchive.class).getGames();
+
+ list.addAll(games.stream()
+ .sorted(Comparator.comparingLong(ChessComGame::getEndTime).reversed())
+ .filter(chessComGame -> ChessComUtils.isGameBetween(chessComGame, player1, player2))
+ .filter(ChessComUtils::hasValidTimeControl)
+ .map(chessComGame -> getGame(chessComGame, player1, player2))
+ .collect(Collectors.toList()));
+
+ if (list.size() >= minimum_amount_of_games) {
+ break;
+ }
+ }
+ return list;
+ } // TODO: find exactly two games of each time control
+
+ @NonNull
+ private Game getGame(@NonNull ChessComGame chessComGame, @NonNull Player player1, @NonNull Player player2) {
+ Game game = new Game();
+ GameInfo gameInfo = new GameInfo();
+
+ game.setPlayer1IsWhite(chessComGame.getWhite().getUsername().equals(player1.getNickname()));
+ game.setResult(ChessComUtils.getResult(chessComGame));
+ game.setGameInfo(gameInfo);
+
+ gameInfo.setUrl(chessComGame.getUrl());
+ gameInfo.setFormat(Integer.parseInt(chessComGame.getTimeControl())/60); // TODO: change gameinfo.format in database to reflect this!
+ gameInfo.setGame(game);
+
+ return game; // important: match is not set here!
+ }
+
+}
diff --git a/src/main/java/com/example/application/data/service/MatchdayService.java b/src/main/java/com/example/application/data/service/MatchdayService.java
index ce5b70b..9e1ead7 100644
--- a/src/main/java/com/example/application/data/service/MatchdayService.java
+++ b/src/main/java/com/example/application/data/service/MatchdayService.java
@@ -4,12 +4,12 @@ import com.example.application.data.entity.Matchday;
import com.example.application.data.entity.Season;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
-import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.vaadin.artur.helpers.CrudService;
import java.util.Comparator;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
@Service
@@ -34,6 +34,14 @@ public class MatchdayService extends CrudService {
.collect(Collectors.toList());
}
+ public Optional getFirstMatchdayForSeason(@NonNull Season season) {
+ List matchdays = getMatchdaysForSeasonSorted(season);
+ if (matchdays.isEmpty()) {
+ return Optional.empty();
+ }
+ return Optional.of(matchdays.get(0));
+ }
+
@NonNull
public List getMatchdaysWithActivityForSeasonSorted(@NonNull Season season) {
return repository.findAll().stream()
@@ -43,13 +51,12 @@ public class MatchdayService extends CrudService {
.collect(Collectors.toList());
}
- @Nullable
- public Matchday getLastMatchdayWithActivityForSeason(@NonNull Season season) {
+ public Optional getLastMatchdayWithActivityForSeason(@NonNull Season season) {
List matchdaysWithActivity = getMatchdaysWithActivityForSeasonSorted(season);
if (matchdaysWithActivity.isEmpty()) {
- return null;
+ return Optional.empty();
}
- return matchdaysWithActivity.get(matchdaysWithActivity.size()-1);
+ return Optional.of(matchdaysWithActivity.get(matchdaysWithActivity.size()-1));
}
public boolean hasActivity(@NonNull Matchday matchday) {
diff --git a/src/main/java/com/example/application/data/service/SeasonService.java b/src/main/java/com/example/application/data/service/SeasonService.java
index 574ee20..4b70688 100644
--- a/src/main/java/com/example/application/data/service/SeasonService.java
+++ b/src/main/java/com/example/application/data/service/SeasonService.java
@@ -2,12 +2,12 @@ package com.example.application.data.service;
import com.example.application.data.entity.Season;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.vaadin.artur.helpers.CrudService;
import java.util.Comparator;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
@Service
@@ -31,13 +31,18 @@ public class SeasonService extends CrudService {
.collect(Collectors.toList());
}
- @Nullable
- public Season getLatestSeason() {
+ public Optional getLatestSeason() {
List allSeasonsSorted = getAllSeasonsSorted();
if (allSeasonsSorted.isEmpty()) {
- return null;
+ return Optional.empty();
}
- return allSeasonsSorted.get(allSeasonsSorted.size() - 1);
+ return Optional.of(allSeasonsSorted.get(allSeasonsSorted.size() - 1));
+ }
+
+ public Optional getSeason(String name) {
+ return repository.findAll().stream()
+ .filter(season -> season.toString().equals(name))
+ .findFirst();
}
}
diff --git a/src/main/java/com/example/application/utils/ChessComUtils.java b/src/main/java/com/example/application/utils/ChessComUtils.java
new file mode 100644
index 0000000..2e027c4
--- /dev/null
+++ b/src/main/java/com/example/application/utils/ChessComUtils.java
@@ -0,0 +1,48 @@
+package com.example.application.utils;
+
+import com.example.application.data.chesscom.ChessComGame;
+import com.example.application.data.entity.Player;
+import org.springframework.lang.NonNull;
+
+public class ChessComUtils {
+ private ChessComUtils() {}
+
+ public static String getPreparedArchiveJson(String unpreparedArchiveJson) {
+ return unpreparedArchiveJson
+ .replace("@id", "id")
+ .replace("end_time", "endTime")
+ .replace("time_class", "timeClass")
+ .replace("time_control", "timeControl");
+ }
+
+ public static String getUrlPart(Player player) {
+ return player.getNickname().toLowerCase();
+ }
+
+ public static int getResult(ChessComGame chessComGame) {
+ if (chessComGame.getWhite().getResult().equals("win")) {
+ return 1;
+ }
+ if (chessComGame.getBlack().getResult().equals("win")) {
+ return -1;
+ }
+ return 0;
+ }
+
+ public static boolean isGameBetween(@NonNull ChessComGame game, @NonNull Player player1, @NonNull Player player2) {
+ String white = game.getWhite().getUsername();
+ String black = game.getBlack().getUsername();
+ String name1 = player1.getNickname();
+ String name2 = player2.getNickname();
+ return (white.equals(name1) || white.equals(name2)) && (black.equals(name1) || black.equals(name2));
+ }
+
+ public static boolean hasValidTimeControl(ChessComGame game) {
+ String timeControl = game.getTimeControl();
+ if (timeControl != null) {
+ return timeControl.equals("600") || timeControl.equals("300") || timeControl.equals("180");
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/com/example/application/utils/HttpUtils.java b/src/main/java/com/example/application/utils/HttpUtils.java
new file mode 100644
index 0000000..f66e2a2
--- /dev/null
+++ b/src/main/java/com/example/application/utils/HttpUtils.java
@@ -0,0 +1,41 @@
+package com.example.application.utils;
+
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Scanner;
+
+public class HttpUtils {
+ private HttpUtils() {
+ }
+
+ @Nullable
+ public static String getJson(@NonNull String url) {
+ try {
+ URL url_ = new URL(url);
+
+ HttpURLConnection conn = (HttpURLConnection) url_.openConnection();
+ conn.setRequestMethod("GET");
+ conn.connect();
+
+ int responsecode = conn.getResponseCode();
+
+ if (responsecode != 200) {
+ throw new RuntimeException("HttpResponseCode: " + responsecode);
+ }
+ StringBuilder jsonBuilder = new StringBuilder();
+ Scanner scanner = new Scanner(url_.openStream());
+ while (scanner.hasNext()) {
+ jsonBuilder.append(scanner.nextLine());
+ }
+ scanner.close();
+
+ return jsonBuilder.toString();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigationView.java b/src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigation.java
similarity index 51%
rename from src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigationView.java
rename to src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigation.java
index 50feb9b..267df6d 100644
--- a/src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigationView.java
+++ b/src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigation.java
@@ -4,69 +4,62 @@ import com.example.application.data.entity.Matchday;
import com.example.application.data.entity.Season;
import com.example.application.data.service.MatchdayService;
import com.example.application.data.service.SeasonService;
-import com.vaadin.flow.component.AbstractField;
-import com.vaadin.flow.component.Component;
-import com.vaadin.flow.component.HasValue;
-import com.vaadin.flow.component.UI;
-import com.vaadin.flow.component.html.Div;
+import com.vaadin.flow.component.*;
+import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Label;
+import com.vaadin.flow.component.icon.Icon;
+import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
-import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.select.Select;
-import com.vaadin.flow.router.BeforeEvent;
-import com.vaadin.flow.router.HasUrlParameter;
-import com.vaadin.flow.router.WildcardParameter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
-public abstract class SeasonAndMatchdayNavigationView extends Div implements HasUrlParameter {
+public class SeasonAndMatchdayNavigation {
// TODO: show dropdown menus also for invalid URLs (with content that fits the situation)
- protected final SeasonService seasonService;
- protected final MatchdayService matchdayService;
-
- protected String seasonParam;
- protected String matchdayParam;
+ private final SeasonService seasonService;
+ private final MatchdayService matchdayService;
private final Label invalidUrlLabel = new Label();
- protected final VerticalLayout outer = new VerticalLayout();
- protected final HorizontalLayout selectionLayout = new HorizontalLayout();
-
- protected final List seasonList = new ArrayList<>();
- protected final Select seasonSelect = new Select<>();
+ private final HorizontalLayout selectionLayout = new HorizontalLayout();
- protected final List matchdayList = new ArrayList<>();
- protected final Select matchdaySelect = new Select<>();
- protected Component contentLayout;
+ private final List seasonList = new ArrayList<>();
+ private final Select seasonSelect = new Select<>();
- public SeasonAndMatchdayNavigationView(@Autowired SeasonService seasonService, @Autowired MatchdayService matchdayService) {
- this.seasonService = seasonService;
- this.matchdayService = matchdayService;
+ private final List matchdayList = new ArrayList<>();
+ private final Select matchdaySelect = new Select<>();
+ private final String route;
+ private final boolean onlyMatchdaysWithActivity;
+ private Runnable runnableToBeRunAfterSelection;
+ private final Button prevButton = new Button(new Icon(VaadinIcon.ARROW_LEFT));
+ private final Button nextButton = new Button(new Icon(VaadinIcon.ARROW_RIGHT));
- addClassName("results-view");
+ private String seasonParam;
+ private String matchdayParam;
- configureOuterLayout();
- configureSelectionLayout();
- configureContentLayout();
+ public SeasonAndMatchdayNavigation(String route,
+ @Autowired SeasonService seasonService,
+ @Autowired MatchdayService matchdayService) {
+ this(route, seasonService, matchdayService, false);
}
- protected abstract String route();
-
- protected abstract void configureContentLayout();
-
- protected abstract void configureContent();
-
- protected abstract boolean showOnlyMatchdaysWithActivity();
+ public SeasonAndMatchdayNavigation(String route,
+ @Autowired SeasonService seasonService,
+ @Autowired MatchdayService matchdayService,
+ boolean onlyMatchdaysWithActivity) {
+ this.route = route;
+ this.seasonService = seasonService;
+ this.matchdayService = matchdayService;
+ this.onlyMatchdaysWithActivity = onlyMatchdaysWithActivity;
- private void configureOuterLayout() {
- add(outer);
- outer.setAlignItems(FlexComponent.Alignment.CENTER);
+ configureSelectionLayout();
}
private void configureSelectionLayout() {
@@ -83,19 +76,19 @@ public abstract class SeasonAndMatchdayNavigationView extends Div implements Has
}
private HasValue.ValueChangeListener super AbstractField.ComponentValueChangeEvent