Merge branch 'dev' into feature_plantList_M2

This commit is contained in:
schrom01
2022-10-24 12:51:52 +02:00
15 changed files with 583 additions and 19 deletions
@@ -0,0 +1,7 @@
package ch.zhaw.gartenverwaltung.io;
public class HardinessZoneNotSetException extends Exception {
public HardinessZoneNotSetException() {
super("HardinessZone must be set to retrieve plants!");
}
}
@@ -0,0 +1,95 @@
package ch.zhaw.gartenverwaltung.io;
import ch.zhaw.gartenverwaltung.types.HardinessZone;
import ch.zhaw.gartenverwaltung.types.Plant;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.MonthDayDeserializer;
import java.io.IOException;
import java.net.URL;
import java.time.MonthDay;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* Implements the {@link PlantDatabase} interface for loading {@link Plant} objects
* from a JSON file.
* The reads are cached to minimize file-io operations.
*/
public class JsonPlantDatabase implements PlantDatabase {
private final URL dataSource = getClass().getResource("plantdb.json");
private HardinessZone currentZone;
private Map<Long, Plant> plantMap = Collections.emptyMap();
/**
* Creating constant objects required to deserialize the {@link MonthDay} classes
*/
private final static JavaTimeModule timeModule = new JavaTimeModule();
static {
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM-dd");
MonthDayDeserializer dateDeserializer = new MonthDayDeserializer(dateFormat);
timeModule.addDeserializer(MonthDay.class, dateDeserializer);
}
/**
* If no data is currently loaded, or the specified zone differs
* from the {@link #currentZone}, data is loaded from {@link #dataSource}.
* In any case, the values of {@link #plantMap} are returned.
*
* @see PlantDatabase#getPlantList(HardinessZone)
*/
@Override
public List<Plant> getPlantList(HardinessZone zone) throws IOException, HardinessZoneNotSetException {
if (plantMap.isEmpty() || zone != currentZone) {
loadPlantList(zone);
}
return plantMap.values().stream().toList();
}
/**
* @see PlantDatabase#getPlantById(long)
*/
@Override
public Optional<Plant> getPlantById(long id) throws HardinessZoneNotSetException, IOException {
if (plantMap.isEmpty()) {
loadPlantList(currentZone);
}
return Optional.ofNullable(plantMap.get(id));
}
/**
* Loads the database from {@link #dataSource} and updates the cached data.
*
* @param zone The {@link HardinessZone} for which data is to be loaded
* @throws IOException If the database cannot be accessed
*/
private void loadPlantList(HardinessZone zone) throws IOException, HardinessZoneNotSetException {
if (zone == null) {
throw new HardinessZoneNotSetException();
}
if (dataSource != null) {
currentZone = zone;
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(timeModule);
List<Plant> result;
result = mapper.readerForListOf(Plant.class).readValue(dataSource);
for (Plant plant : result) {
plant.inZone(currentZone);
}
// Turn list into a HashMap with structure id => Plant
plantMap = result.stream()
.collect(HashMap::new,
(res, plant) -> res.put(plant.id(), plant),
(existing, replacement) -> { });
}
}
}
@@ -3,10 +3,32 @@ package ch.zhaw.gartenverwaltung.io;
import ch.zhaw.gartenverwaltung.types.Plant;
import ch.zhaw.gartenverwaltung.types.HardinessZone;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
/**
* A database of {@link Plant}s.
* The interface specifies the minimal required operations.
*/
public interface PlantDatabase {
List<Plant> getPlantList(HardinessZone zone);
Optional<Plant> getPlantById(long id);
/**
* Yields a list of all {@link Plant}s in the database with only data relevant to the specfied {@link HardinessZone}
*
* @param zone The zone for which data should be fetched
* @return A list of {@link Plant}s with data for the specified zone
* @throws IOException If the database cannot be accessed
* @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
*/
List<Plant> getPlantList(HardinessZone zone) throws IOException, HardinessZoneNotSetException;
/**
* Attempts to retrieve the {@link Plant} with the specified id.
*
* @param id The {@link Plant#id()} to look for
* @return {@link Optional} of the found {@link Plant}, {@link Optional#empty()} if no entry matched the criteria
* @throws IOException If the database cannot be accessed
* @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified
*/
Optional<Plant> getPlantById(long id) throws IOException, HardinessZoneNotSetException;
}
@@ -0,0 +1,28 @@
package ch.zhaw.gartenverwaltung.json;
import ch.zhaw.gartenverwaltung.types.GrowthPhaseType;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
public class GrowthPhaseTypeDeserializer extends StdDeserializer<GrowthPhaseType> {
public GrowthPhaseTypeDeserializer(Class<?> vc) {
super(vc);
}
public GrowthPhaseTypeDeserializer() { this(null); }
@Override
public GrowthPhaseType deserialize(JsonParser parser, DeserializationContext context) throws IOException {
GrowthPhaseType result = null;
try {
result = GrowthPhaseType.valueOf(parser.getText().toUpperCase());
} catch (IllegalArgumentException e) {
// TODO: Log
System.err.println("bad growth phase type");
}
return result;
}
}
@@ -0,0 +1,29 @@
package ch.zhaw.gartenverwaltung.json;
import ch.zhaw.gartenverwaltung.types.HardinessZone;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
public class HardinessZoneDeserializer extends StdDeserializer<HardinessZone> {
public HardinessZoneDeserializer(Class<?> vc) {
super(vc);
}
public HardinessZoneDeserializer() {
this(null);
}
@Override
public HardinessZone deserialize(JsonParser parser, DeserializationContext context) throws IOException {
HardinessZone result = null;
try {
result = HardinessZone.valueOf(parser.getText().toUpperCase());
} catch (IllegalArgumentException e) {
// TODO: Log
System.err.println("bad growth phase type");
}
return result;
}
}
@@ -1,9 +1,19 @@
package ch.zhaw.gartenverwaltung.types;
import java.util.Date;
import ch.zhaw.gartenverwaltung.json.GrowthPhaseTypeDeserializer;
import ch.zhaw.gartenverwaltung.json.HardinessZoneDeserializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
public record GrowthPhase(Date startDate,
Date endDate,
GrowthPhaseType type,
HardinessZone zone) {
import java.time.MonthDay;
import java.util.List;
public record GrowthPhase(
MonthDay startDate,
MonthDay endDate,
int group,
WateringCycle wateringCycle,
@JsonDeserialize(using = GrowthPhaseTypeDeserializer.class) GrowthPhaseType type,
@JsonDeserialize(using = HardinessZoneDeserializer.class) HardinessZone zone,
List<TaskTemplate> taskTemplates) {
}
@@ -1,5 +1,5 @@
package ch.zhaw.gartenverwaltung.types;
public enum GrowthPhaseType {
SOW, PLANT, HARVEST
SOW, PLANT, REPLANT, HARVEST
}
@@ -0,0 +1,4 @@
package ch.zhaw.gartenverwaltung.types;
public record Pest(String name, String description, String measures) {
}
@@ -1,11 +1,46 @@
package ch.zhaw.gartenverwaltung.types;
import java.time.LocalDate;
import java.util.List;
import static java.time.temporal.ChronoUnit.DAYS;
public record Plant(
long id,
String name,
String description,
int spacing,
String spacing,
int light,
String soil,
List<Pest> pests,
List<GrowthPhase> lifecycle) {
public void inZone(HardinessZone zone) {
lifecycle.removeIf(growthPhase -> !growthPhase.zone().equals(zone));
}
public List<GrowthPhase> lifecycleForGroup(int group) {
return lifecycle.stream()
.filter(growthPhase -> growthPhase.group() != group)
.toList();
}
public LocalDate sowDateFromHarvestDate(LocalDate harvestDate, int group) {
return harvestDate.minusDays(timeToHarvest(group));
}
public int timeToHarvest(int group) {
List<GrowthPhase> activeLifecycle = lifecycleForGroup(group);
GrowthPhase sow = activeLifecycle.stream()
.filter(growthPhase -> !growthPhase.type().equals(GrowthPhaseType.SOW))
.findFirst()
.orElseThrow();
GrowthPhase harvest = activeLifecycle.stream()
.filter(growthPhase -> !growthPhase.type().equals(GrowthPhaseType.HARVEST))
.findFirst()
.orElseThrow();
int currentYear = LocalDate.now().getYear();
return (int) DAYS.between(harvest.startDate().atYear(currentYear), sow.startDate().atYear(currentYear));
}
}
@@ -1,6 +1,7 @@
package ch.zhaw.gartenverwaltung.types;
import java.util.Date;
import java.time.LocalDate;
import java.util.Optional;
/**
@@ -11,14 +12,21 @@ public class Task {
private long id;
private final String name;
private final String description;
private final Date startDate;
private final LocalDate startDate;
private Integer interval;
private Date endDate;
private LocalDate endDate;
public Task(String name, String description, Date startDate) {
public Task(String name, String description, LocalDate startDate) {
this.name = name;
this.description = description;
this.startDate = endDate;
this.startDate = startDate;
}
public Task(String name, String description, LocalDate startDate, LocalDate endDate, int interval) {
this.name = name;
this.description = description;
this.startDate = startDate;
this.endDate = endDate;
this.interval = interval;
}
// Builder-pattern-style setters
@@ -30,7 +38,7 @@ public class Task {
this.interval = interval;
return this;
}
public Task withEndDate(Date endDate) {
public Task withEndDate(LocalDate endDate) {
this.endDate = endDate;
return this;
}
@@ -39,12 +47,12 @@ public class Task {
public long getId() { return id; }
public String getName() { return name; }
public String getDescription() { return description; }
public Date getStartDate() { return startDate; }
public LocalDate getStartDate() { return startDate; }
public Optional<Integer> getInterval() {
return Optional.ofNullable(interval);
}
public Optional<Date> getEndDate() {
public Optional<LocalDate> getEndDate() {
return Optional.ofNullable(endDate);
}
}
@@ -0,0 +1,57 @@
package ch.zhaw.gartenverwaltung.types;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.LocalDate;
public class TaskTemplate {
@JsonProperty
private final String name;
@JsonProperty
private final String description;
@JsonProperty
private final int relativeStartDate;
@JsonProperty
private Integer relativeEndDate;
@JsonProperty
private Integer interval;
// TODO: reconsider if we need this
@JsonProperty
private boolean isOptional = false;
/**
* Default constructor
* (Used by deserializer)
*/
public TaskTemplate() {
this.name = "";
this.description = "";
this.relativeStartDate = 0;
}
// Setters
public void setRelativeEndDate(Integer relativeEndDate) {
this.relativeEndDate = relativeEndDate;
}
public void setInterval(Integer interval) {
this.interval = interval;
}
public TaskTemplate(String name, String description, int relativeStartDate) {
this.name = name;
this.description = description;
this.relativeStartDate = relativeStartDate;
}
public Task generateTask(LocalDate realStartDate) {
Task task = new Task(name, description, realStartDate.plusDays(relativeStartDate));
if (relativeEndDate != null) {
task.withEndDate(realStartDate.plusDays(relativeEndDate));
}
if (interval != null) {
task.withInterval(interval);
}
return task;
}
}
@@ -0,0 +1,8 @@
package ch.zhaw.gartenverwaltung.types;
public record WateringCycle(
int litersPerSqM,
int interval,
String[] notes
) {
}
+6 -1
View File
@@ -1,8 +1,13 @@
module ch.zhaw.gartenverwaltung {
requires javafx.controls;
requires javafx.fxml;
requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.datatype.jsr310;
opens ch.zhaw.gartenverwaltung to javafx.fxml;
opens ch.zhaw.gartenverwaltung.types to com.fasterxml.jackson.databind;
// opens ch.zhaw.gartenverwaltung.types to com.fasterxml.jackson.databind;
exports ch.zhaw.gartenverwaltung;
exports ch.zhaw.gartenverwaltung.types;
exports ch.zhaw.gartenverwaltung.json;
}