package design.persistence; import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.databind.*; 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; import java.io.File; import java.io.IOException; import java.time.LocalDateTime; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class JSONLeagueDatabase implements LeagueDatabase { private static JSONLeagueDatabase INSTANCE; public static JSONLeagueDatabase instance() { if (INSTANCE == null) { INSTANCE = new JSONLeagueDatabase("data/leaguedb.json"); } return INSTANCE; } private final Map cache; private final ObjectMapper mapper; private final File file; private int nextLeagueID; public JSONLeagueDatabase(String filename) { this.file = new File(filename); this.cache = new HashMap<>(); 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()); module.addSerializer(LocalDateTime.class, new Serializers.DateTimeStringSerializer()); 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.registerModule(module); mapper.configOverride(List.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY)); try { load(); } catch (IOException ex) { throw new RuntimeException(ex); } this.nextLeagueID = cache.values().stream().mapToInt(League::getId).max().orElse(0) + 1; } private void load() throws IOException { League[] data = mapper.readValue(file, League[].class); cache.clear(); Arrays.stream(data).forEach(i -> cache.put(i.getId(), i)); } private void save() throws IOException { League[] data = cache.values().toArray(League[]::new); mapper.writer(new Serializers.CustomPrettyPrinter()).writeValue(file, data); } @Override public League getLeague(int id) { return cache.get(id); } @Override public League[] getLeagues() { return cache.values().toArray(League[]::new); } @Override public void addLeague(League league) throws IOException { league.setId(nextLeagueID++); cache.putIfAbsent(league.getId(), league); save(); } @Override public void removeLeague(League league) throws IOException { cache.remove(league.getId()); save(); } @Override public void updateLeague(League league) throws IOException { cache.put(league.getId(), league); save(); } @Override public void importData(JsonNode tree) throws IOException { League[] data = mapper.treeToValue(tree, League[].class); cache.clear(); for (League league : data) { cache.put(league.getId(), league); } save(); } @Override public JsonNode exportData() { Object[] data = cache.values().toArray(); return mapper.valueToTree(data); } }