summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Ferrari <69283684+Sowgro@users.noreply.github.com>2025-11-16 02:16:38 -0500
committerGitHub <noreply@github.com>2025-11-16 02:16:38 -0500
commit73dfd2dd419483c7d1060ef2fb40e328a0209e02 (patch)
tree456ed56e629a6324e5993b7ce094705c72e0b922
parentb5d46c7701716bcb2dd6127aeb97f8fcdb7774fc (diff)
parent6ffc6b4cbd9e0c5ce2dc82a7c77f39b3adf849b6 (diff)
downloaddesignproject-design-6-73dfd2dd419483c7d1060ef2fb40e328a0209e02.tar.gz
designproject-design-6-73dfd2dd419483c7d1060ef2fb40e328a0209e02.tar.bz2
designproject-design-6-73dfd2dd419483c7d1060ef2fb40e328a0209e02.zip
Merge pull request #21 from RIT-SWEN-262/league-model
League model
Diffstat (limited to '')
-rw-r--r--pom.xml9
-rw-r--r--src/main/java/design/controller/userinput/Menu.java2
-rw-r--r--src/main/java/design/controller/userinput/menus/ImportExportMenu.java81
-rw-r--r--src/main/java/design/controller/userinput/menus/MainMenu.java1
-rw-r--r--src/main/java/design/controller/userinput/menus/SelectLeague.java2
-rw-r--r--src/main/java/design/model/Golfer.java29
-rw-r--r--src/main/java/design/model/League.java29
-rw-r--r--src/main/java/design/model/ScrambleLeague.java49
-rw-r--r--src/main/java/design/model/StrokeLeague.java42
-rw-r--r--src/main/java/design/model/Team.java31
-rw-r--r--src/main/java/design/persistence/JSONLeagueDatabase.java53
-rw-r--r--src/main/java/design/persistence/JSONPersonalDatabase.java56
-rw-r--r--src/main/java/design/persistence/LeagueDatabase.java9
-rw-r--r--src/main/java/design/persistence/PersonalDatabase.java10
-rw-r--r--src/main/java/design/persistence/Serializers.java25
-rw-r--r--src/main/java/design/persistence/importexport/DataHandler.java11
-rw-r--r--src/main/java/design/persistence/importexport/DataSource.java16
-rw-r--r--src/main/java/design/persistence/importexport/JSONHandler.java35
-rw-r--r--src/main/java/design/persistence/importexport/XMLHandler.java38
-rw-r--r--src/test/java/design/model/GolferTest.java3
-rw-r--r--test.json160
-rw-r--r--test.xml231
22 files changed, 750 insertions, 172 deletions
diff --git a/pom.xml b/pom.xml
index 6e37b12..2333fd8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,6 +42,11 @@
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.20.0</version>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-xml</artifactId>
+ <version>2.20.0</version>
+ </dependency>
</dependencies>
<build>
<plugins>
@@ -67,8 +72,8 @@
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<configuration>
- <destfile>/target/coverage-reports/jacoco-unit.exec</destfile>
- <datafile>/target/coverage-reports/jacoco-unit.exec</datafile>
+ <destFile>/target/coverage-reports/jacoco-unit.exec</destFile>
+ <destFile>/target/coverage-reports/jacoco-unit.exec</destFile>
</configuration>
<executions>
<execution>
diff --git a/src/main/java/design/controller/userinput/Menu.java b/src/main/java/design/controller/userinput/Menu.java
index ab633e1..01e80a9 100644
--- a/src/main/java/design/controller/userinput/Menu.java
+++ b/src/main/java/design/controller/userinput/Menu.java
@@ -23,7 +23,7 @@ public abstract class Menu {
try {
int i = Integer.parseInt(line) - 1;
menuOptions.get(i).onAction();
- } catch (IndexOutOfBoundsException ex) {
+ } catch (IndexOutOfBoundsException | NumberFormatException ex) {
System.err.printf("Invalid option \"%s\"\n", line);
present();
}
diff --git a/src/main/java/design/controller/userinput/menus/ImportExportMenu.java b/src/main/java/design/controller/userinput/menus/ImportExportMenu.java
new file mode 100644
index 0000000..21ffa6c
--- /dev/null
+++ b/src/main/java/design/controller/userinput/menus/ImportExportMenu.java
@@ -0,0 +1,81 @@
+package design.controller.userinput.menus;
+
+import design.controller.userinput.Menu;
+import design.controller.userinput.MenuOption;
+import design.persistence.*;
+import design.persistence.importexport.DataHandler;
+import design.persistence.importexport.DataSource;
+import design.persistence.importexport.JSONHandler;
+import design.persistence.importexport.XMLHandler;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Scanner;
+
+public class ImportExportMenu extends Menu {
+
+ @Override
+ public String getTitle() {
+ return "import export menu";
+ }
+
+ @Override
+ public List<MenuOption> getMenuOptions() {
+ List<MenuOption> opts = new java.util.ArrayList<>();
+
+
+ opts.add(new MenuOption("return to main menu", () -> new MainMenu().present()));
+
+ opts.add(new MenuOption("import leagues...", () -> promptForPath(true, false)));
+ opts.add(new MenuOption("export leagues...", () -> promptForPath(false, false)));
+ opts.add(new MenuOption("import profiles...", () -> promptForPath(true, true)));
+ opts.add(new MenuOption("export profiles...", () -> promptForPath(false, true)));
+
+ return opts;
+ }
+
+ private void promptForPath(boolean isImporting, boolean isPersonalProfile)
+ {
+ System.out.print("Enter file path: ");
+ Scanner sc = new Scanner(System.in);
+ String filePath = sc.nextLine();
+ File file = new File(filePath);
+
+ DataSource source;
+ if (isPersonalProfile) {
+ source = PersonalDatabase.instance();
+ } else {
+ source = LeagueDatabase.instance();
+ }
+
+ DataHandler handler;
+ String ext = getExtension(filePath);
+ switch (ext) {
+ case "json" -> handler = new JSONHandler(source);
+ case "xml" -> handler = new XMLHandler(source);
+ default -> {
+ System.out.println("Unsupported file type: " + ext);
+ this.present();
+ return;
+ }
+ }
+
+ try {
+ if (isImporting) {
+ handler.importData(file);
+ } else {
+ handler.exportData(file);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ this.present();
+ }
+
+ private static String getExtension(String fileName) {
+ int i = fileName.lastIndexOf('.');
+ return (i >= 0) ? fileName.substring(i + 1).toLowerCase() : "unknown";
+ }
+}
diff --git a/src/main/java/design/controller/userinput/menus/MainMenu.java b/src/main/java/design/controller/userinput/menus/MainMenu.java
index 4331e56..3f520e4 100644
--- a/src/main/java/design/controller/userinput/menus/MainMenu.java
+++ b/src/main/java/design/controller/userinput/menus/MainMenu.java
@@ -27,6 +27,7 @@ public class MainMenu extends Menu {
options.add(new MenuOption("statistics...", () -> new StatisticsMenu().present()));
options.add(new MenuOption("log round...", () -> new HolePlayMenu().present()));
options.add(new MenuOption("league play...", () -> new SelectLeague().present()));
+ options.add(new MenuOption("manage data...", () -> new ImportExportMenu().present()));
}
return options;
}
diff --git a/src/main/java/design/controller/userinput/menus/SelectLeague.java b/src/main/java/design/controller/userinput/menus/SelectLeague.java
index 08f6c91..e1675e9 100644
--- a/src/main/java/design/controller/userinput/menus/SelectLeague.java
+++ b/src/main/java/design/controller/userinput/menus/SelectLeague.java
@@ -26,7 +26,7 @@ public class SelectLeague extends Menu {
public List<MenuOption> getMenuOptions() {
List<MenuOption> options = new ArrayList<>();
for (League l : LeagueDatabase.instance().getLeagues()) {
- options.add(new MenuOption(l.getName(), () -> {
+ options.add(new MenuOption(l.toString(), () -> {
if (l instanceof ScrambleLeague sl && sl.locateTeam(golfer) == null) {
Scanner sc = new Scanner(System.in);
System.out.print("You are not a member of a team for this league. Would you like to create one (Y/n): ");
diff --git a/src/main/java/design/model/Golfer.java b/src/main/java/design/model/Golfer.java
index 6ea9d32..4fb521e 100644
--- a/src/main/java/design/model/Golfer.java
+++ b/src/main/java/design/model/Golfer.java
@@ -1,6 +1,7 @@
package design.model;
import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import java.util.ArrayList;
@@ -18,10 +19,14 @@ public class Golfer implements Originator {
private final List<Round> rounds;
private final List<Club> clubs; // Keep track of golfer's clubs
private int nextClubId;
+ private final List<Invite> invites;
+ private Team joinedTeam;
+
+
@JsonCreator
private Golfer(String username, int passwordHash, String fullName, List<Course> courses, List<Round> rounds,
- List<Club> clubs) {
+ List<Club> clubs, List<Invite> invites, Team joinedTeam) {
this.username = username;
this.passwordHash = passwordHash;
this.fullName = fullName;
@@ -29,6 +34,8 @@ public class Golfer implements Originator {
this.rounds = rounds;
this.clubs = clubs;
this.nextClubId = this.clubs.stream().mapToInt(Club::getId).max().orElse(0) + 1;
+ this.invites = invites;
+ this.joinedTeam = joinedTeam;
}
public Golfer(String fullName, String username, String password) {
@@ -39,6 +46,8 @@ public class Golfer implements Originator {
this.rounds = new ArrayList<>();
this.clubs = new ArrayList<>();
this.nextClubId = 1;
+ this.invites = new ArrayList<>();
+ this.joinedTeam = null;
}
public String getUsername() {
@@ -160,5 +169,21 @@ public class Golfer implements Originator {
this.clubs.addAll(gm.clubs);
this.nextClubId = gm.nextClubId;
- }
+ }
+
+ public boolean joinTeam(Team team){
+ for(Invite invite : invites){
+ if(invite.getTeam().equals(team)){
+ this.joinedTeam = team;
+ team.addMember(this);
+ invites.remove(invite);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Team getTeam(){
+ return joinedTeam;
+ }
}
diff --git a/src/main/java/design/model/League.java b/src/main/java/design/model/League.java
index 881e25f..8793c86 100644
--- a/src/main/java/design/model/League.java
+++ b/src/main/java/design/model/League.java
@@ -25,6 +25,7 @@ public abstract class League {
private final Date endDate;
private final Golfer owner;
private final List<Match> schedule;
+ private boolean completed;
@JsonCreator
protected League(int id, String name, Date registrationDate, Date startDate, Date endDate, Golfer owner, List<Match> schedule) {
@@ -35,6 +36,7 @@ public abstract class League {
this.endDate = endDate;
this.owner = owner;
this.schedule = schedule != null ? schedule : new ArrayList<>();
+ this.completed = false;
}
public League(String name, Date registrationDate, Date startDate, Date endDate, Golfer owner) {
@@ -45,6 +47,7 @@ public abstract class League {
this.endDate = endDate;
this.owner = owner;
this.schedule = new ArrayList<>();
+ this.completed = false;
}
public int getId() {
@@ -75,9 +78,12 @@ public abstract class League {
return schedule.toArray(Match[]::new);
}
+ public boolean isCompleted() {
+ return completed;
+ }
+
public void addMatchToSchedule(Match match) {
- Date date = match.getDateScheduled();
- if(date.after(endDate)){
+ if(match.getDateScheduled().after(endDate)){
throw new IllegalArgumentException("Cannot create match after league has ended");
}
schedule.add(match);
@@ -87,4 +93,23 @@ public abstract class League {
assert this.id == -1;
this.id = id;
}
+
+ public abstract String getType();
+
+ @Override
+ public String toString() {
+ return String.format("%s - %s", name, getType());
+ }
+ public boolean isPlayable() {
+ Date now = new Date();
+ return now.after(startDate) && now.before(endDate);
+ }
+
+ public void markCompleted(){
+ this.completed = true;
+ }
+
+ public abstract void recordPlay(Golfer player, Match match, Round round);
+
+ public abstract void finalizeLeague();
}
diff --git a/src/main/java/design/model/ScrambleLeague.java b/src/main/java/design/model/ScrambleLeague.java
index b0d65f8..1fb8f1d 100644
--- a/src/main/java/design/model/ScrambleLeague.java
+++ b/src/main/java/design/model/ScrambleLeague.java
@@ -4,30 +4,41 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@JsonTypeName("scramble")
public class ScrambleLeague extends League {
- private final List<Team> participants;
+ private List<Team> participants;
+ private Map<Team, Integer> totalTeamScores;
@JsonCreator
private ScrambleLeague(int id, String name, Date registrationDate, Date startDate, Date endDate, Golfer owner, List<Team> participants, List<Match> schedule) {
super(id, name, registrationDate, startDate, endDate, owner, schedule);
this.participants = participants;
+ this.totalTeamScores = new HashMap<>();
+ participants.forEach(t -> totalTeamScores.putIfAbsent(t, 0));
}
public ScrambleLeague(String name, Date registrationDate, Date startDate, Date endDate, Golfer owner) {
super(name, registrationDate, startDate, endDate, owner);
this.participants = new ArrayList<>();
+ this.totalTeamScores = new HashMap<>();
}
- public boolean addParticipants(Team e) {
- return participants.add(e);
+ public boolean addParticipants(Team t) {
+ boolean added = participants.add(t);
+ if(added) totalTeamScores.putIfAbsent(t, 0);
+ return added;
}
- public boolean removeParticipants(Team o) {
- return participants.remove(o);
+ public boolean removeParticipants(Team t) {
+ totalTeamScores.remove(t);
+ return participants.remove(t);
}
public Team[] getParticipants() {
@@ -42,4 +53,32 @@ public class ScrambleLeague extends League {
}
return null;
}
+
+ @Override
+ public String getType() {
+ return "scramble";
+ }
+ @Override
+ public void recordPlay(Golfer player, Match match, Round round){
+ if(!isPlayable()) return;
+ Team team = player.getTeam();
+ if (team == null) return;
+ int strokes = round.getTotalSwings();
+ team.addMemberRound(player, strokes);
+ match.addRound(round);
+ }
+
+ @Override
+ public void finalizeLeague(){
+ markCompleted();
+ for(Team team : participants){
+ int score = team.computeTotalBestScore();
+ totalTeamScores.put(team, score);
+ }
+ participants.sort(Comparator.comparingInt(totalTeamScores::get));
+ }
+
+ public Map<Team, Integer> getTotalTeamScores(){
+ return Collections.unmodifiableMap(totalTeamScores);
+ }
}
diff --git a/src/main/java/design/model/StrokeLeague.java b/src/main/java/design/model/StrokeLeague.java
index 2b787b5..d051fd0 100644
--- a/src/main/java/design/model/StrokeLeague.java
+++ b/src/main/java/design/model/StrokeLeague.java
@@ -4,33 +4,65 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@JsonTypeName("stroke")
public class StrokeLeague extends League {
- private final List<Golfer> participants;
+ private List<Golfer> participants;
+ private Map<Golfer, Integer> totalStrokes;
@JsonCreator
private StrokeLeague(int id, String name, Date registrationDate, Date startDate, Date endDate, Golfer owner, List<Golfer> participants, List<Match> schedule) {
super(id, name, registrationDate, startDate, endDate, owner, schedule);
this.participants = participants;
+ this.totalStrokes = new HashMap<>();
+ participants.forEach(p -> totalStrokes.putIfAbsent(p, 0));
}
public StrokeLeague(String name, Date registrationDate, Date startDate, Date endDate, Golfer owner) {
super(name, registrationDate, startDate, endDate, owner);
this.participants = new ArrayList<>();
+ this.totalStrokes = new HashMap<>();
}
- public boolean addParticipants(Golfer e) {
- return participants.add(e);
+ public boolean addParticipants(Golfer g) {
+ boolean added = participants.add(g);
+ if(added) totalStrokes.putIfAbsent(g, 0);
+ return added;
}
- public boolean removeParticipants(Golfer o) {
- return participants.remove(o);
+ public boolean removeParticipants(Golfer g) {
+ totalStrokes.remove(g);
+ return participants.remove(g);
}
public Golfer[] getParticipants() {
return participants.toArray(Golfer[]::new);
}
+
+ @Override
+ public String getType() {
+ return "stroke";
+ }
+ public void recordPlay(Golfer player, Match match, Round round){
+ if(!isPlayable() || !participants.contains(player)) return;
+ int strokes = round.getTotalSwings();
+ totalStrokes.merge(player, strokes, Integer::sum);
+ match.addRound(round);
+ }
+
+ @Override
+ public void finalizeLeague(){
+ markCompleted();
+ participants.sort(Comparator.comparingInt(totalStrokes::get));
+ }
+
+ public Map<Golfer, Integer> getTotalStrokes(){
+ return Collections.unmodifiableMap(totalStrokes);
+ }
}
diff --git a/src/main/java/design/model/Team.java b/src/main/java/design/model/Team.java
index 53b276e..8f30468 100644
--- a/src/main/java/design/model/Team.java
+++ b/src/main/java/design/model/Team.java
@@ -1,17 +1,22 @@
package design.model;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
public class Team {
private String name;
private final List<Golfer> members;
+ private final Map<Golfer, Integer> memberBestRounds;
private final Golfer owner;
public Team(String name, Golfer owner) {
this.name = name;
this.owner = owner;
this.members = new ArrayList<>();
+ this.memberBestRounds = new HashMap<>();
}
public String getName() {
@@ -27,14 +32,40 @@ public class Team {
}
public boolean addMember(Golfer golfer) {
+ if(members.size() >= 4){
+ throw new IllegalArgumentException("Team size limit reached!");
+ }
return members.add(golfer);
}
public boolean removeMember(Golfer g) {
+ memberBestRounds.remove(g);
return members.remove(g);
}
public Golfer getOwner() {
return owner;
}
+
+ public void addMemberRound(Golfer g, int strokes){
+ memberBestRounds.merge(g, strokes, Math::min);
+ }
+
+ public int computeTotalBestScore(){
+ if(memberBestRounds.isEmpty()) return 100; // Bad Score Penalty for not playing
+ return memberBestRounds.values().stream().min(Integer::compareTo).orElse(100);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ Team other = (Team) obj;
+ return Objects.equals(name, other.name) && Objects.equals(owner, other.owner);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, owner);
+ }
}
diff --git a/src/main/java/design/persistence/JSONLeagueDatabase.java b/src/main/java/design/persistence/JSONLeagueDatabase.java
index 42fa000..b5f4bc3 100644
--- a/src/main/java/design/persistence/JSONLeagueDatabase.java
+++ b/src/main/java/design/persistence/JSONLeagueDatabase.java
@@ -1,14 +1,7 @@
package design.persistence;
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.core.StreamReadFeature;
import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
-import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import design.model.Course;
import design.model.Golfer;
import design.model.League;
@@ -44,18 +37,10 @@ public class JSONLeagueDatabase implements LeagueDatabase {
public JSONLeagueDatabase(String filename) {
this.file = new File(filename);
this.cache = new HashMap<>();
- this.mapper = JsonMapper.builder().enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION).build();
+ this.mapper = new ObjectMapper();
- SimpleModule module = new SimpleModule();
- module.addDeserializer(Course.class, new Serializers.CourseIdDeserializer());
- module.addSerializer(Course.class, new Serializers.CourseIdSerializer());
- module.addDeserializer(Golfer.class, new Serializers.GolferUsernameDeserializer());
- module.addSerializer(Golfer.class, new Serializers.GolferUsernameSerializer());
- mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
- mapper.registerModule(module);
- mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
- mapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
- mapper.registerModule(new JavaTimeModule());
+ Serializers.configureMapper(mapper);
+ mapper.registerModule(this.getJacksonModule());
try {
load();
@@ -105,4 +90,36 @@ public class JSONLeagueDatabase implements LeagueDatabase {
cache.put(league.getId(), league);
save();
}
+
+ @Override
+ public void importData(Object rawData) throws IOException {
+ if (!(rawData instanceof League[] data)) {
+ throw new ClassCastException();
+ }
+ cache.clear();
+ for (League league : data) {
+ cache.put(league.getId(), league);
+ }
+ save();
+ }
+
+ @Override
+ public League[] exportData() {
+ return getLeagues();
+ }
+
+ @Override
+ public SimpleModule getJacksonModule() {
+ SimpleModule module = new SimpleModule();
+ module.addDeserializer(Course.class, new Serializers.CourseIdDeserializer());
+ module.addSerializer(Course.class, new Serializers.CourseIdSerializer());
+ module.addDeserializer(Golfer.class, new Serializers.GolferUsernameDeserializer());
+ module.addSerializer(Golfer.class, new Serializers.GolferUsernameSerializer());
+ return module;
+ }
+
+ @Override
+ public Class<?> getTargetClass() {
+ return League[].class;
+ }
}
diff --git a/src/main/java/design/persistence/JSONPersonalDatabase.java b/src/main/java/design/persistence/JSONPersonalDatabase.java
index 7f55f78..e035ae4 100644
--- a/src/main/java/design/persistence/JSONPersonalDatabase.java
+++ b/src/main/java/design/persistence/JSONPersonalDatabase.java
@@ -1,14 +1,7 @@
package design.persistence;
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.core.StreamReadFeature;
import com.fasterxml.jackson.databind.*;
-import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
-import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import design.model.Course;
import design.model.Golfer;
import design.model.League;
@@ -42,21 +35,10 @@ public class JSONPersonalDatabase implements PersonalDatabase {
private JSONPersonalDatabase(String filename) {
this.file = new File(filename);
this.cache = new HashMap<>();
- this.mapper = JsonMapper.builder().enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION).build();;
+ this.mapper = new ObjectMapper();
- // TODO: Once the saved JSON matches the model, consider removing.
- // TEMP: tolerate unknown props while the model stabilizes
- mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
-
- SimpleModule module = new SimpleModule();
- module.addDeserializer(Course.class, new Serializers.CourseIdDeserializer());
- module.addSerializer(Course.class, new Serializers.CourseIdSerializer());
- module.addSerializer(League.class, new Serializers.LeagueIDSerializer());
- module.addDeserializer(League.class, new Serializers.LeagueIDDeserializer());
- mapper.registerModule(module);
- mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
- mapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
- mapper.registerModule(new JavaTimeModule());
+ Serializers.configureMapper(mapper);
+ mapper.registerModule(this.getJacksonModule());
try {
load();
@@ -106,4 +88,36 @@ public class JSONPersonalDatabase implements PersonalDatabase {
cache.put(golfer.getUsername(), golfer);
save();
}
+
+ @Override
+ public void importData(Object rawData) throws IOException {
+ if (!(rawData instanceof Golfer[] data)) {
+ throw new ClassCastException();
+ }
+ cache.clear();
+ for (Golfer golfer : data) {
+ cache.put(golfer.getUsername(), golfer);
+ }
+ save();
+ }
+
+ @Override
+ public Golfer[] exportData() {
+ return getGolfers();
+ }
+
+ @Override
+ public SimpleModule getJacksonModule() {
+ SimpleModule module = new SimpleModule();
+ module.addDeserializer(Course.class, new Serializers.CourseIdDeserializer());
+ module.addSerializer(Course.class, new Serializers.CourseIdSerializer());
+ module.addSerializer(League.class, new Serializers.LeagueIDSerializer());
+ module.addDeserializer(League.class, new Serializers.LeagueIDDeserializer());
+ return module;
+ }
+
+ @Override
+ public Class<?> getTargetClass() {
+ return Golfer[].class;
+ }
}
diff --git a/src/main/java/design/persistence/LeagueDatabase.java b/src/main/java/design/persistence/LeagueDatabase.java
index 953624d..ed6ddae 100644
--- a/src/main/java/design/persistence/LeagueDatabase.java
+++ b/src/main/java/design/persistence/LeagueDatabase.java
@@ -1,10 +1,11 @@
package design.persistence;
import design.model.League;
+import design.persistence.importexport.DataSource;
import java.io.IOException;
-public interface LeagueDatabase {
+public interface LeagueDatabase extends DataSource {
static LeagueDatabase instance() {
return JSONLeagueDatabase.instance();
}
@@ -18,4 +19,10 @@ public interface LeagueDatabase {
void removeLeague(League league) throws IOException;
void updateLeague(League league) throws IOException;
+
+ @Override
+ void importData(Object data) throws IOException;
+
+ @Override
+ Object exportData() throws IOException;
}
diff --git a/src/main/java/design/persistence/PersonalDatabase.java b/src/main/java/design/persistence/PersonalDatabase.java
index adb865d..70dd37d 100644
--- a/src/main/java/design/persistence/PersonalDatabase.java
+++ b/src/main/java/design/persistence/PersonalDatabase.java
@@ -1,9 +1,11 @@
package design.persistence;
import design.model.Golfer;
+import design.persistence.importexport.DataSource;
+
import java.io.IOException;
-public interface PersonalDatabase {
+public interface PersonalDatabase extends DataSource {
static PersonalDatabase instance() {
return JSONPersonalDatabase.instance();
@@ -18,4 +20,10 @@ public interface PersonalDatabase {
void removeGolfer(Golfer golfer) throws IOException;
void updateGolfer(Golfer golfer) throws IOException;
+
+ @Override
+ void importData(Object data) throws IOException;
+
+ @Override
+ Object exportData() throws IOException;
}
diff --git a/src/main/java/design/persistence/Serializers.java b/src/main/java/design/persistence/Serializers.java
index 3940b44..246dbf1 100644
--- a/src/main/java/design/persistence/Serializers.java
+++ b/src/main/java/design/persistence/Serializers.java
@@ -1,18 +1,20 @@
package design.persistence;
+import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import design.model.Course;
import design.model.Golfer;
import design.model.League;
import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.List;
public class Serializers {
public static class CustomPrettyPrinter extends DefaultPrettyPrinter {
@@ -76,4 +78,19 @@ public class Serializers {
return personalDB.getGolfer(username);
}
}
+
+ public static class DateTimeStringSerializer extends JsonSerializer<LocalDateTime> {
+ @Override
+ public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ gen.writeString(value.toString());
+ }
+ }
+
+ public static void configureMapper(ObjectMapper mapper) {
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
+ mapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
+ mapper.registerModule(new JavaTimeModule());
+ mapper.configOverride(List.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
+ }
}
diff --git a/src/main/java/design/persistence/importexport/DataHandler.java b/src/main/java/design/persistence/importexport/DataHandler.java
new file mode 100644
index 0000000..59df9be
--- /dev/null
+++ b/src/main/java/design/persistence/importexport/DataHandler.java
@@ -0,0 +1,11 @@
+package design.persistence.importexport;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface DataHandler {
+
+ void importData(File file) throws IOException;
+
+ void exportData(File file) throws IOException;
+}
diff --git a/src/main/java/design/persistence/importexport/DataSource.java b/src/main/java/design/persistence/importexport/DataSource.java
new file mode 100644
index 0000000..e735c7e
--- /dev/null
+++ b/src/main/java/design/persistence/importexport/DataSource.java
@@ -0,0 +1,16 @@
+package design.persistence.importexport;
+
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+import java.io.IOException;
+
+public interface DataSource {
+
+ void importData(Object data) throws IOException;
+
+ Object exportData() throws IOException;
+
+ SimpleModule getJacksonModule();
+
+ Class<?> getTargetClass();
+} \ No newline at end of file
diff --git a/src/main/java/design/persistence/importexport/JSONHandler.java b/src/main/java/design/persistence/importexport/JSONHandler.java
new file mode 100644
index 0000000..9c04281
--- /dev/null
+++ b/src/main/java/design/persistence/importexport/JSONHandler.java
@@ -0,0 +1,35 @@
+package design.persistence.importexport;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import design.persistence.Serializers;
+
+import java.io.File;
+import java.io.IOException;
+
+public class JSONHandler implements DataHandler {
+
+ private final DataSource dataSource;
+ private final ObjectMapper jsonMapper = new JsonMapper();
+
+ public JSONHandler(DataSource dataSource) {
+ this.dataSource = dataSource;
+
+ Serializers.configureMapper(jsonMapper);
+ jsonMapper.registerModule(dataSource.getJacksonModule());
+ }
+
+ @Override
+ public void importData(File file) throws IOException {
+ Object data = jsonMapper.readValue(file, dataSource.getTargetClass());
+ dataSource.importData(data);
+ }
+
+ @Override
+ public void exportData(File file) throws IOException{
+ Object data = dataSource.exportData();
+ jsonMapper.writer(new Serializers.CustomPrettyPrinter())
+ .writeValue(file, data);
+ }
+}
+
+
diff --git a/src/main/java/design/persistence/importexport/XMLHandler.java b/src/main/java/design/persistence/importexport/XMLHandler.java
new file mode 100644
index 0000000..0a07d6e
--- /dev/null
+++ b/src/main/java/design/persistence/importexport/XMLHandler.java
@@ -0,0 +1,38 @@
+package design.persistence.importexport;
+import java.io.File;
+import java.io.IOException;
+import java.time.LocalDateTime;
+
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import design.persistence.Serializers;
+
+public class XMLHandler implements DataHandler {
+
+ private final DataSource dataSource;
+ private final XmlMapper xmlMapper = new XmlMapper();
+
+ public XMLHandler(DataSource dataSource) {
+ this.dataSource = dataSource;
+
+ Serializers.configureMapper(xmlMapper);
+ SimpleModule module = dataSource.getJacksonModule();
+ module.addSerializer(LocalDateTime.class, new Serializers.DateTimeStringSerializer());
+ xmlMapper.registerModule(module);
+ }
+
+ @Override
+ public void importData(File file) throws IOException {
+ Object data = xmlMapper.readValue(file, dataSource.getTargetClass());
+ dataSource.importData(data);
+ }
+
+ @Override
+ public void exportData(File file) throws IOException {
+ Object data = dataSource.exportData();
+ xmlMapper.writerWithDefaultPrettyPrinter()
+ .writeValue(file, data);
+ }
+}
+
+
diff --git a/src/test/java/design/model/GolferTest.java b/src/test/java/design/model/GolferTest.java
index 5f113ab..98dad20 100644
--- a/src/test/java/design/model/GolferTest.java
+++ b/src/test/java/design/model/GolferTest.java
@@ -150,5 +150,4 @@ public class GolferTest {
}
-}
-
+} \ No newline at end of file
diff --git a/test.json b/test.json
new file mode 100644
index 0000000..4e42e0d
--- /dev/null
+++ b/test.json
@@ -0,0 +1,160 @@
+[
+ {
+ "clubs": [],
+ "nextClubId": 1,
+ "username": "test",
+ "passwordHash": 3556498,
+ "fullName": "test",
+ "courses": [],
+ "rounds": [],
+ "invites": []
+ },
+ {
+ "clubs": [],
+ "nextClubId": 1,
+ "username": "GUYHERE",
+ "passwordHash": 3556498,
+ "fullName": "GUYHERE",
+ "courses": [],
+ "rounds": [],
+ "invites": []
+ },
+ {
+ "clubs": [
+ {
+ "id": 1,
+ "manufacture": "Bobby",
+ "nickname": "swen 261",
+ "clubType": "DRIVER"
+ },
+ {
+ "id": 2,
+ "manufacture": "Bobby",
+ "nickname": "swen 262",
+ "clubType": "PUTTER"
+ },
+ {
+ "id": 3,
+ "manufacture": "sowclub",
+ "nickname": "man",
+ "clubType": "WOOD"
+ }
+ ],
+ "nextClubId": 4,
+ "username": "john_doe",
+ "passwordHash": 46792755,
+ "fullName": "John Doe",
+ "courses": [
+ 2,
+ 1
+ ],
+ "rounds": [
+ {
+ "course": 1,
+ "dateTime": [
+ 2025,
+ 10,
+ 7,
+ 13,
+ 54,
+ 37,
+ 429963500
+ ],
+ "startingHole": {
+ "number": 1,
+ "par": 5
+ },
+ "plays": [
+ {
+ "holeNumber": 1,
+ "swings": [
+ {
+ "distance": 100,
+ "clubUsed": 1
+ },
+ {
+ "distance": 5,
+ "clubUsed": 2
+ },
+ {
+ "distance": 1,
+ "clubUsed": 2
+ }
+ ],
+ "swingCount": 3,
+ "distance": 106
+ },
+ {
+ "holeNumber": 2,
+ "swings": [
+ {
+ "distance": 1000,
+ "clubUsed": 1
+ },
+ {
+ "distance": 2,
+ "clubUsed": 1
+ }
+ ],
+ "swingCount": 2,
+ "distance": 1002
+ }
+ ],
+ "currentHoleIndex": 2,
+ "totalSwings": 5,
+ "totalDistance": 1108.0,
+ "currentHole": {
+ "number": 3,
+ "par": 4
+ }
+ },
+ {
+ "course": 1,
+ "dateTime": [
+ 2025,
+ 10,
+ 8,
+ 20,
+ 19,
+ 38,
+ 968996400
+ ],
+ "startingHole": {
+ "number": 9,
+ "par": 3
+ },
+ "plays": [
+ {
+ "holeNumber": 9,
+ "swings": [
+ {
+ "distance": 204,
+ "clubUsed": 1
+ }
+ ],
+ "swingCount": 1,
+ "distance": 204
+ }
+ ],
+ "currentHoleIndex": 9,
+ "totalSwings": 1,
+ "totalDistance": 204.0,
+ "currentHole": {
+ "number": 10,
+ "par": 3
+ }
+ }
+ ],
+ "invites": []
+ },
+ {
+ "clubs": [],
+ "nextClubId": 1,
+ "username": "sowgro",
+ "passwordHash": -896456343,
+ "fullName": "sowgro",
+ "courses": [],
+ "rounds": [],
+ "invites": []
+ }
+] \ No newline at end of file
diff --git a/test.xml b/test.xml
index 6a85537..4701977 100644
--- a/test.xml
+++ b/test.xml
@@ -1,127 +1,144 @@
-<export>
- <items>
+<Golfers>
+ <item>
+ <clubs/>
<nextClubId>1</nextClubId>
<username>test</username>
<passwordHash>3556498</passwordHash>
<fullName>test</fullName>
- </items>
- <items>
+ <courses/>
+ <rounds/>
+ <invites/>
+ </item>
+ <item>
+ <clubs/>
<nextClubId>1</nextClubId>
<username>GUYHERE</username>
<passwordHash>3556498</passwordHash>
<fullName>GUYHERE</fullName>
- </items>
- <items>
+ <courses/>
+ <rounds/>
+ <invites/>
+ </item>
+ <item>
<clubs>
- <id>1</id>
- <manufacture>Bobby</manufacture>
- <nickname>swen 261</nickname>
- <clubType>DRIVER</clubType>
- </clubs>
- <clubs>
- <id>2</id>
- <manufacture>Bobby</manufacture>
- <nickname>swen 262</nickname>
- <clubType>PUTTER</clubType>
- </clubs>
- <clubs>
- <id>3</id>
- <manufacture>sowclub</manufacture>
- <nickname>man</nickname>
- <clubType>WOOD</clubType>
+ <clubs>
+ <id>1</id>
+ <manufacture>Bobby</manufacture>
+ <nickname>swen 261</nickname>
+ <clubType>DRIVER</clubType>
+ </clubs>
+ <clubs>
+ <id>2</id>
+ <manufacture>Bobby</manufacture>
+ <nickname>swen 262</nickname>
+ <clubType>PUTTER</clubType>
+ </clubs>
+ <clubs>
+ <id>3</id>
+ <manufacture>sowclub</manufacture>
+ <nickname>man</nickname>
+ <clubType>WOOD</clubType>
+ </clubs>
</clubs>
<nextClubId>4</nextClubId>
<username>john_doe</username>
<passwordHash>46792755</passwordHash>
<fullName>John Doe</fullName>
- <courses>2</courses>
- <courses>1</courses>
- <rounds>
- <course>1</course>
- <dateTime>2025</dateTime>
- <dateTime>10</dateTime>
- <dateTime>7</dateTime>
- <dateTime>13</dateTime>
- <dateTime>54</dateTime>
- <dateTime>37</dateTime>
- <dateTime>429963500</dateTime>
- <startingHole>
- <number>1</number>
- <par>5</par>
- </startingHole>
- <plays>
- <holeNumber>1</holeNumber>
- <swings>
- <distance>100</distance>
- <clubUsed>1</clubUsed>
- </swings>
- <swings>
- <distance>5</distance>
- <clubUsed>2</clubUsed>
- </swings>
- <swings>
- <distance>1</distance>
- <clubUsed>2</clubUsed>
- </swings>
- <swingCount>3</swingCount>
- <distance>106</distance>
- </plays>
- <plays>
- <holeNumber>2</holeNumber>
- <swings>
- <distance>1000</distance>
- <clubUsed>1</clubUsed>
- </swings>
- <swings>
- <distance>2</distance>
- <clubUsed>1</clubUsed>
- </swings>
- <swingCount>2</swingCount>
- <distance>1002</distance>
- </plays>
- <currentHoleIndex>2</currentHoleIndex>
- <totalDistance>1108.0</totalDistance>
- <currentHole>
- <number>3</number>
- <par>4</par>
- </currentHole>
- <totalSwings>5</totalSwings>
- </rounds>
+ <courses>
+ <courses>2</courses>
+ <courses>1</courses>
+ </courses>
<rounds>
- <course>1</course>
- <dateTime>2025</dateTime>
- <dateTime>10</dateTime>
- <dateTime>8</dateTime>
- <dateTime>20</dateTime>
- <dateTime>19</dateTime>
- <dateTime>38</dateTime>
- <dateTime>968996400</dateTime>
- <startingHole>
- <number>9</number>
- <par>3</par>
- </startingHole>
- <plays>
- <holeNumber>9</holeNumber>
- <swings>
- <distance>204</distance>
- <clubUsed>1</clubUsed>
- </swings>
- <swingCount>1</swingCount>
- <distance>204</distance>
- </plays>
- <currentHoleIndex>9</currentHoleIndex>
- <totalDistance>204.0</totalDistance>
- <currentHole>
- <number>10</number>
- <par>3</par>
- </currentHole>
- <totalSwings>1</totalSwings>
+ <rounds>
+ <course>1</course>
+ <dateTime>2025-10-07T13:54:37.429963500</dateTime>
+ <startingHole>
+ <number>1</number>
+ <par>5</par>
+ </startingHole>
+ <plays>
+ <plays>
+ <holeNumber>1</holeNumber>
+ <swings>
+ <swings>
+ <distance>100</distance>
+ <clubUsed>1</clubUsed>
+ </swings>
+ <swings>
+ <distance>5</distance>
+ <clubUsed>2</clubUsed>
+ </swings>
+ <swings>
+ <distance>1</distance>
+ <clubUsed>2</clubUsed>
+ </swings>
+ </swings>
+ <swingCount>3</swingCount>
+ <distance>106</distance>
+ </plays>
+ <plays>
+ <holeNumber>2</holeNumber>
+ <swings>
+ <swings>
+ <distance>1000</distance>
+ <clubUsed>1</clubUsed>
+ </swings>
+ <swings>
+ <distance>2</distance>
+ <clubUsed>1</clubUsed>
+ </swings>
+ </swings>
+ <swingCount>2</swingCount>
+ <distance>1002</distance>
+ </plays>
+ </plays>
+ <currentHoleIndex>2</currentHoleIndex>
+ <totalSwings>5</totalSwings>
+ <totalDistance>1108.0</totalDistance>
+ <currentHole>
+ <number>3</number>
+ <par>4</par>
+ </currentHole>
+ </rounds>
+ <rounds>
+ <course>1</course>
+ <dateTime>2025-10-08T20:19:38.968996400</dateTime>
+ <startingHole>
+ <number>9</number>
+ <par>3</par>
+ </startingHole>
+ <plays>
+ <plays>
+ <holeNumber>9</holeNumber>
+ <swings>
+ <swings>
+ <distance>204</distance>
+ <clubUsed>1</clubUsed>
+ </swings>
+ </swings>
+ <swingCount>1</swingCount>
+ <distance>204</distance>
+ </plays>
+ </plays>
+ <currentHoleIndex>9</currentHoleIndex>
+ <totalSwings>1</totalSwings>
+ <totalDistance>204.0</totalDistance>
+ <currentHole>
+ <number>10</number>
+ <par>3</par>
+ </currentHole>
+ </rounds>
</rounds>
- </items>
- <items>
+ <invites/>
+ </item>
+ <item>
+ <clubs/>
<nextClubId>1</nextClubId>
<username>sowgro</username>
<passwordHash>-896456343</passwordHash>
<fullName>sowgro</fullName>
- </items>
-</export>
+ <courses/>
+ <rounds/>
+ <invites/>
+ </item>
+</Golfers>