From 5f53bb86c639e663aee6daba0f85a476da01d725 Mon Sep 17 00:00:00 2001 From: David Guler Date: Mon, 17 Oct 2022 17:01:50 +0200 Subject: [PATCH 1/7] #11 Very basic implementation with dummy data --- build.gradle | 3 +- .../gartenverwaltung/HelloApplication.java | 9 +++++ .../io/JsonPlantDatabase.java | 37 +++++++++++++++++++ .../gartenverwaltung/io/PlantDatabase.java | 5 ++- .../json/GrowthPhaseTypeDeserializer.java | 28 ++++++++++++++ .../json/HardinessZoneDeserializer.java | 29 +++++++++++++++ .../gartenverwaltung/types/GrowthPhase.java | 14 +++++-- .../ch/zhaw/gartenverwaltung/types/Plant.java | 5 +++ src/main/java/module-info.java | 5 +++ .../ch/zhaw/gartenverwaltung/io/plantdb.json | 31 ++++++++++++++++ 10 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java create mode 100644 src/main/java/ch/zhaw/gartenverwaltung/json/GrowthPhaseTypeDeserializer.java create mode 100644 src/main/java/ch/zhaw/gartenverwaltung/json/HardinessZoneDeserializer.java create mode 100644 src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json diff --git a/build.gradle b/build.gradle index 5268664..41f89ac 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'org.beryx.jlink' version '2.25.0' } -group 'ch.zhaw.pm3' +group 'ch.zhaw.gartenverwaltung' version '1.0-SNAPSHOT' repositories { @@ -37,6 +37,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}") + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.4' } test { diff --git a/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java b/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java index 0d70b44..7877221 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java @@ -1,5 +1,8 @@ package ch.zhaw.gartenverwaltung; +import ch.zhaw.gartenverwaltung.io.JsonPlantDatabase; +import ch.zhaw.gartenverwaltung.io.PlantDatabase; +import ch.zhaw.gartenverwaltung.types.HardinessZone; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; @@ -18,6 +21,12 @@ public class HelloApplication extends Application { } public static void main(String[] args) { + PlantDatabase db = new JsonPlantDatabase(); + try { + System.out.println(db.getPlantList(HardinessZone.ZONE_8A)); + } catch (IOException e) { + e.printStackTrace(); + } launch(); } } \ No newline at end of file diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java new file mode 100644 index 0000000..c889195 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java @@ -0,0 +1,37 @@ +package ch.zhaw.gartenverwaltung.io; + +import ch.zhaw.gartenverwaltung.types.HardinessZone; +import ch.zhaw.gartenverwaltung.types.Plant; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.net.URL; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class JsonPlantDatabase implements PlantDatabase { + private final URL dataSource = getClass().getResource("plantdb.json"); + + @Override + public List getPlantList(HardinessZone zone) throws IOException { + List result = Collections.emptyList(); + + if (dataSource != null) { + ObjectMapper mapper = new ObjectMapper(); + DateFormat dateFormat = new SimpleDateFormat("MM-dd"); + mapper.setDateFormat(dateFormat); + + result = mapper.readerForListOf(Plant.class).readValue(dataSource); + } + + return result; + } + + @Override + public Optional getPlantById(long id) { + return Optional.empty(); + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java b/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java index f0d9bf9..2a38e3b 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java @@ -3,10 +3,11 @@ 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; public interface PlantDatabase { - List getPlantList(HardinessZone zone); - Optional getPlantById(long id); + List getPlantList(HardinessZone zone) throws IOException; + Optional getPlantById(long id) throws IOException; } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/json/GrowthPhaseTypeDeserializer.java b/src/main/java/ch/zhaw/gartenverwaltung/json/GrowthPhaseTypeDeserializer.java new file mode 100644 index 0000000..f3bb60f --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/json/GrowthPhaseTypeDeserializer.java @@ -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 { + 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; + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/json/HardinessZoneDeserializer.java b/src/main/java/ch/zhaw/gartenverwaltung/json/HardinessZoneDeserializer.java new file mode 100644 index 0000000..cf89d6a --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/json/HardinessZoneDeserializer.java @@ -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 { + 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; + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java b/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java index 665b895..da33b2a 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java @@ -1,9 +1,15 @@ package ch.zhaw.gartenverwaltung.types; +import ch.zhaw.gartenverwaltung.json.GrowthPhaseTypeDeserializer; +import ch.zhaw.gartenverwaltung.json.HardinessZoneDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + import java.util.Date; -public record GrowthPhase(Date startDate, - Date endDate, - GrowthPhaseType type, - HardinessZone zone) { + +public record GrowthPhase( + Date startDate, + Date endDate, + @JsonDeserialize(using = GrowthPhaseTypeDeserializer.class) GrowthPhaseType type, + @JsonDeserialize(using = HardinessZoneDeserializer.class) HardinessZone zone) { } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java b/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java index 99336e7..612679c 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java @@ -7,5 +7,10 @@ public record Plant( String name, String description, int spacing, + Object water, + int light, + List maintenance, + List specialTasks, + List pests, List lifecycle) { } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 4bffe36..2bb0935 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,8 +1,13 @@ module ch.zhaw.gartenverwaltung { requires javafx.controls; requires javafx.fxml; + requires com.fasterxml.jackson.databind; 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; } \ No newline at end of file diff --git a/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json b/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json new file mode 100644 index 0000000..7eb3366 --- /dev/null +++ b/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json @@ -0,0 +1,31 @@ +[ + { + "name": "Potato", + "description": "Tasty tubers.", + "water": { + "amount": "27-55", + "interval": 7 + }, + "light": 6, + "maintenance": [], + "specialTasks": [], + "lifecycle": [ + { + "startDate": "04-01", + "endDate": "05-31", + "type": "SOW", + "zone": "ZONE_8A" + }, + { + "startDate": "07-01", + "endDate": "08-31", + "type": "HARVEST", + "zone": "ZONE_8A" + } + ], + "spacing": 35, + "pests": [ + "Potato beetle" + ] + } +] \ No newline at end of file From 6d13bede7a5d91b00716a90cb415aaf7c0df4065 Mon Sep 17 00:00:00 2001 From: David Guler Date: Thu, 20 Oct 2022 21:46:00 +0200 Subject: [PATCH 2/7] Minimum viable deserialization Added TaskTemplate type Added dummy data with (probably) usable format Used the java.time classes instead of the legacy util.Date --- build.gradle | 1 + .../io/JsonPlantDatabase.java | 16 +++- .../gartenverwaltung/types/GrowthPhase.java | 12 ++- .../types/GrowthPhaseType.java | 2 +- .../ch/zhaw/gartenverwaltung/types/Plant.java | 6 +- .../ch/zhaw/gartenverwaltung/types/Task.java | 24 +++-- .../gartenverwaltung/types/TaskTemplate.java | 57 +++++++++++ src/main/java/module-info.java | 2 +- .../ch/zhaw/gartenverwaltung/io/plantdb.json | 96 +++++++++++++++---- 9 files changed, 173 insertions(+), 43 deletions(-) create mode 100644 src/main/java/ch/zhaw/gartenverwaltung/types/TaskTemplate.java diff --git a/build.gradle b/build.gradle index 41f89ac..122bba0 100644 --- a/build.gradle +++ b/build.gradle @@ -38,6 +38,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}") implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.4' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.4' } test { diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java index c889195..8882e8e 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java @@ -3,11 +3,13 @@ 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.text.DateFormat; -import java.text.SimpleDateFormat; +import java.time.MonthDay; +import java.time.format.DateTimeFormatter; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -15,14 +17,20 @@ import java.util.Optional; public class JsonPlantDatabase implements PlantDatabase { private final URL dataSource = getClass().getResource("plantdb.json"); + private final static JavaTimeModule timeModule = new JavaTimeModule(); + static { + DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM-dd"); + MonthDayDeserializer dateDeserializer = new MonthDayDeserializer(dateFormat); + timeModule.addDeserializer(MonthDay.class, dateDeserializer); + } + @Override public List getPlantList(HardinessZone zone) throws IOException { List result = Collections.emptyList(); if (dataSource != null) { ObjectMapper mapper = new ObjectMapper(); - DateFormat dateFormat = new SimpleDateFormat("MM-dd"); - mapper.setDateFormat(dateFormat); + mapper.registerModule(timeModule); result = mapper.readerForListOf(Plant.class).readValue(dataSource); } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java b/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java index da33b2a..71b4705 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java @@ -4,12 +4,16 @@ import ch.zhaw.gartenverwaltung.json.GrowthPhaseTypeDeserializer; import ch.zhaw.gartenverwaltung.json.HardinessZoneDeserializer; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.Date; +import java.time.MonthDay; +import java.util.List; public record GrowthPhase( - Date startDate, - Date endDate, + MonthDay startDate, + MonthDay endDate, + int group, + Object wateringCycle, @JsonDeserialize(using = GrowthPhaseTypeDeserializer.class) GrowthPhaseType type, - @JsonDeserialize(using = HardinessZoneDeserializer.class) HardinessZone zone) { + @JsonDeserialize(using = HardinessZoneDeserializer.class) HardinessZone zone, + List taskTemplates) { } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhaseType.java b/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhaseType.java index 99e5d2b..96609cc 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhaseType.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhaseType.java @@ -1,5 +1,5 @@ package ch.zhaw.gartenverwaltung.types; public enum GrowthPhaseType { - SOW, PLANT, HARVEST + SOW, PLANT, REPLANT, HARVEST } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java b/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java index 612679c..27b1944 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java @@ -7,10 +7,8 @@ public record Plant( String name, String description, int spacing, - Object water, int light, - List maintenance, - List specialTasks, - List pests, + String soil, + List pests, List lifecycle) { } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/Task.java b/src/main/java/ch/zhaw/gartenverwaltung/types/Task.java index d85435b..90e00f4 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/Task.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/Task.java @@ -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 getInterval() { return Optional.ofNullable(interval); } - public Optional getEndDate() { + public Optional getEndDate() { return Optional.ofNullable(endDate); } } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/TaskTemplate.java b/src/main/java/ch/zhaw/gartenverwaltung/types/TaskTemplate.java new file mode 100644 index 0000000..290175e --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/TaskTemplate.java @@ -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; + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 2bb0935..8e02fc7 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -2,7 +2,7 @@ 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; diff --git a/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json b/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json index 7eb3366..50b74f1 100644 --- a/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json +++ b/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json @@ -1,31 +1,85 @@ [ { + "id": 0, "name": "Potato", - "description": "Tasty tubers.", - "water": { - "amount": "27-55", - "interval": 7 - }, + "description": "The potato is a tuber, round or oval, with small white roots called 'eyes', that are growth buds. The size varies depending on the variety; the colour of the skin can be white, yellow or even purple.", "light": 6, - "maintenance": [], - "specialTasks": [], - "lifecycle": [ + "spacing": "35", + "soil": "sandy", + "pests": [ { - "startDate": "04-01", - "endDate": "05-31", - "type": "SOW", - "zone": "ZONE_8A" - }, - { - "startDate": "07-01", - "endDate": "08-31", - "type": "HARVEST", - "zone": "ZONE_8A" + "name": "Rot", + "description": "Rot, any of several plant diseases, caused by any of hundreds of species of soil-borne bacteria, fungi, and funguslike organisms (Oomycota). Rot diseases are characterized by plant decomposition and putrefaction. The decay may be hard, dry, spongy, watery, mushy, or slimy and may affect any plant part.", + "measures": "Less water." } ], - "spacing": 35, - "pests": [ - "Potato beetle" + "lifecycle": [ + { + "startDate": "03-10", + "endDate": "04-10", + "type": "SOW", + "zone": "ZONE_8A", + "group": 0, + "wateringCycle": { + "litersPerSqM": 0, + "interval": null, + "notes": [] + }, + "taskTemplates": [ + { + "name": "Germinate", + "relativeStartDate": -14, + "relativeEndDate": null, + "description": "\"Take an egg carton and fill it with soil. Put the seedling deep enaugh so its half covered with soil. Keep it in 10-15 * Celsius with lots of light.\"", + "interval": null, + "isOptional": false + } + ] + }, + { + "startDate": "04-10", + "endDate": "07-10", + "type": "PLANT", + "zone": "ZONE_8A", + "group": 0, + "wateringCycle": { + "litersPerSqM": 25, + "interval": 7, + "notes": [] + }, + "taskTemplates": [ + { + "name": "hilling", + "relativeStartDate": 0, + "relativeEndDate": null, + "description": "\"When the plants are 20 cm tall, begin hilling the potatoes by gently mounding the soil from the center of your rows around the stems of the plant. Mound up the soil around the plant until just the top few leaves show above the soil. Two weeks later, hill up the soil again when the plants grow another 20 cm.\"", + "interval": 21, + "isOptional": false + } + ] + }, + { + "startDate": "06-10", + "endDate": "08-10", + "type": "HARVEST", + "zone": "ZONE_8A", + "group": 0, + "wateringCycle": { + "litersPerSqM": 0, + "interval": null, + "notes": [] + }, + "taskTemplates": [ + { + "name": "Harvest", + "relativeStartDate": 0, + "relativeEndDate": null, + "description": "Once the foliage has wilted and dried completely, harvest on a dry day. Store in a dark and cool location.", + "interval": null, + "isOptional": false + } + ] + } ] } ] \ No newline at end of file From bf4d56e7593cb0c0c2f05a54abf7961e8a3b24c2 Mon Sep 17 00:00:00 2001 From: David Guler Date: Fri, 21 Oct 2022 12:16:15 +0200 Subject: [PATCH 3/7] removed test code --- .../java/ch/zhaw/gartenverwaltung/HelloApplication.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java b/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java index 7877221..0d70b44 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java @@ -1,8 +1,5 @@ package ch.zhaw.gartenverwaltung; -import ch.zhaw.gartenverwaltung.io.JsonPlantDatabase; -import ch.zhaw.gartenverwaltung.io.PlantDatabase; -import ch.zhaw.gartenverwaltung.types.HardinessZone; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; @@ -21,12 +18,6 @@ public class HelloApplication extends Application { } public static void main(String[] args) { - PlantDatabase db = new JsonPlantDatabase(); - try { - System.out.println(db.getPlantList(HardinessZone.ZONE_8A)); - } catch (IOException e) { - e.printStackTrace(); - } launch(); } } \ No newline at end of file From 429ac16d98b52cf95f266d9ae329a03bcdbd342e Mon Sep 17 00:00:00 2001 From: David Guler Date: Sun, 23 Oct 2022 09:49:22 +0200 Subject: [PATCH 4/7] filtering lifecycle based on hardiness-zone - Added 2 more plants to test db - Db filters plant lifecycles based on hardiness-zones - getPlantById implemented - Added methods to Plant for calculating sowDates and accounting for multiple lifecycles --- .../io/JsonPlantDatabase.java | 9 +- .../gartenverwaltung/io/PlantDatabase.java | 2 +- .../ch/zhaw/gartenverwaltung/types/Plant.java | 34 +++- .../ch/zhaw/gartenverwaltung/io/plantdb.json | 169 ++++++++++++++++++ 4 files changed, 210 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java index 8882e8e..e589bc8 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java @@ -34,12 +34,17 @@ public class JsonPlantDatabase implements PlantDatabase { result = mapper.readerForListOf(Plant.class).readValue(dataSource); } + for (Plant plant : result) { + plant.inZone(zone); + } return result; } @Override - public Optional getPlantById(long id) { - return Optional.empty(); + public Optional getPlantById(long id, HardinessZone zone) throws IOException { + return getPlantList(zone).stream() + .filter(plant -> plant.id() != id) + .findFirst(); } } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java b/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java index 2a38e3b..4b8fe85 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java @@ -9,5 +9,5 @@ import java.util.Optional; public interface PlantDatabase { List getPlantList(HardinessZone zone) throws IOException; - Optional getPlantById(long id) throws IOException; + Optional getPlantById(long id, HardinessZone zone) throws IOException; } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java b/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java index 27b1944..46ff155 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java @@ -1,14 +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 pests, List lifecycle) { + + public void inZone(HardinessZone zone) { + lifecycle.removeIf(growthPhase -> !growthPhase.zone().equals(zone)); + } + + public List 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 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)); + } } diff --git a/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json b/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json index 50b74f1..8ec3394 100644 --- a/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json +++ b/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json @@ -81,5 +81,174 @@ ] } ] + }, + { + "id": 1, + "name": "Early Carrot", + "description": "Carrot, (Daucus carota), herbaceous, generally biennial plant of the Apiaceae family that produces an edible taproot. Among common varieties root shapes range from globular to long, with lower ends blunt to pointed. Besides the orange-coloured roots, white-, yellow-, and purple-fleshed varieties are known.", + "lifecycle": [ + { + "startDate": "02-20", + "endDate": "03-10", + "zone": "ZONE_8A", + "type": "SOW", + "wateringCycle": { + "litersPerSqM": 15, + "interval": 3, + "notes": [] + }, + "taskTemplates": [ + { + "name": "hilling", + "relativeStartDate": 0, + "relativeEndDate": 0, + "description": "Mound up the soil around the plant until just the top few leaves show above the soil. ", + "interval": null, + "isOptional": false + } + ] + }, + { + "startDate": "03-10", + "endDate": "05-10", + "zone": "ZONE_8A", + "type": "PLANT", + "wateringCycle": { + "litersPerSqM": 25, + "interval": 3, + "notes": [ + "Be careful not to pour water over the leaves, as this will lead to sunburn." + ] + }, + "taskTemplates": [ + { + "name": "hilling", + "relativeStartDate": 0, + "relativeEndDate": null, + "description": "Mound up the soil around the plant until just the top few leaves show above the soil. ", + "interval": 15, + "isOptional": true + } + ] + }, + { + "startDate": "05-10", + "endDate": "05-20", + "zone": "ZONE_8A", + "type": "HARVEST", + "wateringCycle": { + "litersPerSqM": 0, + "interval": null, + "notes": [] + }, + "taskTemplates": [ + { + "name": "Harvesting", + "relativeStartDate": 0, + "relativeEndDate": 14, + "description": "When the leaves turn to a yellowish brown. Do not harvest earlier. The plant will show when it's ready.", + "interval": null, + "isOptional": false + } + ] + } + ], + "soil": "sandy to loamy, loose soil, free of stones", + "spacing": "5,35,2.5", + "pests": [ + { + "name": "Rot", + "description": "rot, any of several plant diseases, caused by any of hundreds of species of soil-borne bacteria, fungi, and funguslike organisms (Oomycota). Rot diseases are characterized by plant decomposition and putrefaction. The decay may be hard, dry, spongy, watery, mushy, or slimy and may affect any plant part.", + "measurement": "less water" + } + ] + }, + { + "id": 2, + "name": "summertime onion", + "description": "Onion, (Allium cepa), herbaceous biennial plant in the amaryllis family (Amaryllidaceae) grown for its edible bulb. The onion is likely native to southwestern Asia but is now grown throughout the world, chiefly in the temperate zones. Onions are low in nutrients but are valued for their flavour and are used widely in cooking. They add flavour to such dishes as stews, roasts, soups, and salads and are also served as a cooked vegetable.", + "lifecycle": [ + { + "startDate": "03-15", + "endDate": "04-10", + "type": "SOW", + "zone": "ZONE_8A", + "group": 0, + "wateringCycle": { + "litersPerSqM": 15, + "interval": 4, + "notes": [ + + ] + }, + "taskTemplates": [ + { + "name": "hilling", + "relativeStartDate": 0, + "relativeEndDate": 0, + "description": "Mound up the soil around the plant until just the top few leaves show above the soil. ", + "interval": null, + "isOptional": false + } + ] + }, + { + "startDate": "04-10", + "endDate": "07-10", + "type": "PLANT", + "zone": "ZONE_8A", + "group": 0, + "wateringCycle": { + "litersPerSqM": 25, + "interval": 3, + "notes": [ + "" + ] + }, + "taskTemplates": [ + { + "name": "hilling", + "relativeStartDate": 0, + "relativeEndDate": null, + "description": "Mound up the soil around the plant until just the top few leaves show above the soil. ", + "interval": 15, + "isOptional": true + } + ] + }, + { + "startDate": "07-10", + "endDate": "09-20", + "type": "HARVEST", + "zone": "ZONE_8A", + "group": 0, + "wateringCycle": { + "litersPerSqM": 0, + "interval": null, + "notes": [ + + ] + }, + "taskTemplates": [ + { + "name": "Harvesting", + "relativeStartDate": 0, + "relativeEndDate": 14, + "description": "When ready for harvest, the leaves on your onion plants will start to flop over. This happens at the \"neck\" of the onion and it signals that the plant has stopped growing and is ready for storage. Onions should be harvested soon thereafter", + "interval": null, + "isOptional": false + } + ] + } + ], + "soil": "sandy to loamy, loose soil, free of stones", + "spacing": "15,30,2", + "pests": [ + { + "name": "Rot", + "description": "rot, any of several plant diseases, caused by any of hundreds of species of soil-borne bacteria, fungi, and funguslike organisms (Oomycota). Rot diseases are characterized by plant decomposition and putrefaction. The decay may be hard, dry, spongy, watery, mushy, or slimy and may affect any plant part.", + "measurement": "less water" + } + ] } ] \ No newline at end of file From 7355ce563f2771355e06c4e1c7e4b653fc6b88f9 Mon Sep 17 00:00:00 2001 From: David Guler Date: Sun, 23 Oct 2022 09:54:56 +0200 Subject: [PATCH 5/7] Added WateringCycle and Pest types --- .../java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java | 2 +- src/main/java/ch/zhaw/gartenverwaltung/types/Pest.java | 4 ++++ src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java | 2 +- .../ch/zhaw/gartenverwaltung/types/WateringCycle.java | 8 ++++++++ .../resources/ch/zhaw/gartenverwaltung/io/plantdb.json | 4 ++-- 5 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ch/zhaw/gartenverwaltung/types/Pest.java create mode 100644 src/main/java/ch/zhaw/gartenverwaltung/types/WateringCycle.java diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java b/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java index 71b4705..9348f2b 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java @@ -12,7 +12,7 @@ public record GrowthPhase( MonthDay startDate, MonthDay endDate, int group, - Object wateringCycle, + WateringCycle wateringCycle, @JsonDeserialize(using = GrowthPhaseTypeDeserializer.class) GrowthPhaseType type, @JsonDeserialize(using = HardinessZoneDeserializer.class) HardinessZone zone, List taskTemplates) { diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/Pest.java b/src/main/java/ch/zhaw/gartenverwaltung/types/Pest.java new file mode 100644 index 0000000..cfbcb81 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/Pest.java @@ -0,0 +1,4 @@ +package ch.zhaw.gartenverwaltung.types; + +public record Pest(String name, String description, String measures) { +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java b/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java index 46ff155..13652a6 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java @@ -12,7 +12,7 @@ public record Plant( String spacing, int light, String soil, - List pests, + List pests, List lifecycle) { public void inZone(HardinessZone zone) { diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/WateringCycle.java b/src/main/java/ch/zhaw/gartenverwaltung/types/WateringCycle.java new file mode 100644 index 0000000..9d7c7d0 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/WateringCycle.java @@ -0,0 +1,8 @@ +package ch.zhaw.gartenverwaltung.types; + +public record WateringCycle( + int litersPerSqM, + int interval, + String[] notes +) { +} diff --git a/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json b/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json index 8ec3394..f513570 100644 --- a/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json +++ b/src/main/resources/ch/zhaw/gartenverwaltung/io/plantdb.json @@ -159,7 +159,7 @@ { "name": "Rot", "description": "rot, any of several plant diseases, caused by any of hundreds of species of soil-borne bacteria, fungi, and funguslike organisms (Oomycota). Rot diseases are characterized by plant decomposition and putrefaction. The decay may be hard, dry, spongy, watery, mushy, or slimy and may affect any plant part.", - "measurement": "less water" + "measures": "less water" } ] }, @@ -247,7 +247,7 @@ { "name": "Rot", "description": "rot, any of several plant diseases, caused by any of hundreds of species of soil-borne bacteria, fungi, and funguslike organisms (Oomycota). Rot diseases are characterized by plant decomposition and putrefaction. The decay may be hard, dry, spongy, watery, mushy, or slimy and may affect any plant part.", - "measurement": "less water" + "measures": "less water" } ] } From 2c61cd33930e35fcb01618a0ba9cc11d70041c52 Mon Sep 17 00:00:00 2001 From: David Guler Date: Sun, 23 Oct 2022 11:11:52 +0200 Subject: [PATCH 6/7] Implemented simple caching for JsonPlantDatabase Also added Javadoc for the PlantDatabase interface --- .../io/HardinessZoneNotSetException.java | 7 ++ .../io/JsonPlantDatabase.java | 74 +++++++++++++++---- .../gartenverwaltung/io/PlantDatabase.java | 25 ++++++- 3 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ch/zhaw/gartenverwaltung/io/HardinessZoneNotSetException.java diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/HardinessZoneNotSetException.java b/src/main/java/ch/zhaw/gartenverwaltung/io/HardinessZoneNotSetException.java new file mode 100644 index 0000000..cbb7015 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/HardinessZoneNotSetException.java @@ -0,0 +1,7 @@ +package ch.zhaw.gartenverwaltung.io; + +public class HardinessZoneNotSetException extends Exception { + public HardinessZoneNotSetException() { + super("HardinessZone must be set to retrieve plants!"); + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java index e589bc8..4ae2435 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java @@ -11,12 +11,25 @@ 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 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"); @@ -24,27 +37,62 @@ public class JsonPlantDatabase implements PlantDatabase { 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 getPlantList(HardinessZone zone) throws IOException { - List result = Collections.emptyList(); + public List getPlantList(HardinessZone zone) throws IOException, HardinessZoneNotSetException { + if (zone == null) { + throw new HardinessZoneNotSetException(); + } + if (plantMap.isEmpty() || zone != currentZone) { + loadPlantList(zone); + } + return plantMap.values().stream().toList(); + } + /** + * @see PlantDatabase#getPlantById(long) + */ + @Override + public Optional getPlantById(long id) throws HardinessZoneNotSetException, IOException { + if (currentZone == null) { + throw new HardinessZoneNotSetException(); + } + 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 { if (dataSource != null) { + currentZone = zone; ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(timeModule); + List result; result = mapper.readerForListOf(Plant.class).readValue(dataSource); - } - for (Plant plant : result) { - plant.inZone(zone); - } - return result; - } + for (Plant plant : result) { + plant.inZone(currentZone); + } - @Override - public Optional getPlantById(long id, HardinessZone zone) throws IOException { - return getPlantList(zone).stream() - .filter(plant -> plant.id() != id) - .findFirst(); + // Turn list into a HashMap with structure id => Plant + plantMap = result.stream() + .collect(HashMap::new, + (res, plant) -> res.put(plant.id(), plant), + (existing, replacement) -> { }); + } } } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java b/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java index 4b8fe85..0b6908d 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java @@ -7,7 +7,28 @@ 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 getPlantList(HardinessZone zone) throws IOException; - Optional getPlantById(long id, HardinessZone zone) throws IOException; + /** + * 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 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 getPlantById(long id) throws IOException, HardinessZoneNotSetException; } From 0d24bcc2adb58723f367a16dacdf12cfe7bfd73c Mon Sep 17 00:00:00 2001 From: David Guler Date: Mon, 24 Oct 2022 12:32:28 +0200 Subject: [PATCH 7/7] Moved null-check to loading method --- .../zhaw/gartenverwaltung/io/JsonPlantDatabase.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java index 4ae2435..5591acd 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java @@ -46,9 +46,6 @@ public class JsonPlantDatabase implements PlantDatabase { */ @Override public List getPlantList(HardinessZone zone) throws IOException, HardinessZoneNotSetException { - if (zone == null) { - throw new HardinessZoneNotSetException(); - } if (plantMap.isEmpty() || zone != currentZone) { loadPlantList(zone); } @@ -60,9 +57,6 @@ public class JsonPlantDatabase implements PlantDatabase { */ @Override public Optional getPlantById(long id) throws HardinessZoneNotSetException, IOException { - if (currentZone == null) { - throw new HardinessZoneNotSetException(); - } if (plantMap.isEmpty()) { loadPlantList(currentZone); } @@ -75,7 +69,10 @@ public class JsonPlantDatabase implements PlantDatabase { * @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 { + private void loadPlantList(HardinessZone zone) throws IOException, HardinessZoneNotSetException { + if (zone == null) { + throw new HardinessZoneNotSetException(); + } if (dataSource != null) { currentZone = zone; ObjectMapper mapper = new ObjectMapper();