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 8882e8e..5591acd 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,22 +37,59 @@ 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 (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 (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 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) -> { }); } - - 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 2a38e3b..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) 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; } 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 27b1944..13652a6 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 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/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 50b74f1..f513570 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.", + "measures": "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.", + "measures": "less water" + } + ] } ] \ No newline at end of file