diff --git a/README.md b/README.md index 5b7f091..8e56120 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,6 @@ These branches are for bugfixes. These branches are for javadoc and project documentation (such as the readme, class diagrams etc.). + +## User Manual +- Search Plant List: if first Char is '#': only exact match in ID. diff --git a/build.gradle b/build.gradle index 5268664..88a7a2b 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,10 @@ 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' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.4' + testImplementation 'org.mockito:mockito-core:4.3.+' } test { diff --git a/doc/Classdiagramm/layer-diagram.png b/doc/Classdiagramm/layer-diagram.png new file mode 100644 index 0000000..6e6fdfe Binary files /dev/null and b/doc/Classdiagramm/layer-diagram.png differ diff --git a/doc/Classdiagramm/layer-diagram.uxf b/doc/Classdiagramm/layer-diagram.uxf new file mode 100644 index 0000000..6330af5 --- /dev/null +++ b/doc/Classdiagramm/layer-diagram.uxf @@ -0,0 +1,7 @@ +10UMLPackage39060340100UIUMLPackage390180460120DomainUMLPackage390330460120Technical ServicesUMLPackage4009010060Views (JFX)UMLPackage40022010060IOUMLPackage40037010060JacksonUMLPackage51022010060TypesUMLPackage62022010060ModelsUMLPackage5109011060Controllers (JFX)UMLPackage51037010060LoggingUMLPackage62037010060JavaFXUMLPackage73022011060ServicesUMLPackage73037011060HTTP/APIRelation5701509070lt=.> +70;10;10;50Relation57029011080lt=.> +90;10;10;60Relation340120350400lt=.> +60;10;10;10;10;380;330;380;330;310Relation77027030120lt=.> +10;10;10;100Relation36025060180lt=.> +40;10;10;10;10;160;40;160Relation61012070120lt=.> +10;10;50;10;50;100 \ No newline at end of file diff --git a/doc/Classdiagramm/pm3-dcd.png b/doc/Classdiagramm/pm3-dcd.png new file mode 100644 index 0000000..a94ebe3 Binary files /dev/null and b/doc/Classdiagramm/pm3-dcd.png differ diff --git a/doc/Classdiagramm/pm3-dcd.uxf b/doc/Classdiagramm/pm3-dcd.uxf new file mode 100644 index 0000000..5d662bd --- /dev/null +++ b/doc/Classdiagramm/pm3-dcd.uxf @@ -0,0 +1,144 @@ +10UMLClass48236010030MainUMLClass1082380370100<<Interface>> +PlantDatabase +-- ++ getPlantList(zone: HardinessZone): List<Plant> ++ getPlantById(zone: HardinessZone id: long): Optional<Plant>UMLClass67271022040TaskListControllerUMLClass1132110300180<<Record>> +Plant +-- ++ id: long ++ name: String ++ description: String ++ spacing: int ++ lifecycle: List<GrowthPhase> +-- ++ calculateStartDate(harvestDate: Date): Date ++ generateTasks() +UMLClass942710210120TaskListModel +-- +- tasks: ListProperty<Task> +- taskDb: TaskDatabase +-- ++ getTask(id: long): Optional<Task> ++ saveTask(task: Task) ++ removeTask(task: Task)UMLClass322690250120GardenPlanModel +-- +- tasks: ListProperty<Crop> +- gardenPlan: GardenPlan +-- ++ plantAsCrop(planting: UserPlanting) ++ removePlanting(planting: UserPlanting)UMLClass7268018080GardenPlanControllerUMLClass1362830490240Task +-- ++ id: long ++ name: String {readOnly} ++ description: String {readOnly} ++ startDate: Date {readOnly} ++ isOptional: boolean {readOnly} +- interval: int +- endDate: Date +-- ++ Task(name: String, description: String, startDate: String, isReadOnly: boolean): Task ++ withInterval(interval: int): Task ++ withEndDate(endDate: Date): Task ++ withId(id: long): Task +-- ++ getInterval(): Optional<int> ++ getEndDate(): Optional<Date>UMLClass902930280140<<Interface>> +TaskDatabase +-- ++ getTaskList(start: Date, end: Date): List<Task> ++ saveTask(Task task) throws ??Exception ++ removeTask(Task task) throws ??ExceptionUMLClass44244017080MainWindowControllerUMLClass962131021070NotificationService +-- +- taskDb: TaskDatabase +-- ++ tick()UMLClass1412131024090<<Interface>> +WeatherProvider +-- ++ getWeatherForecast: WeatherForecastUMLClass13921150400100WeatherService +-- +- weatherPovider: WeatherProvider +- taskDb: TaskDatabase +-- ++ WeatherService(provider: WeatherProvider, taskDb: TaskDatabase) +- updateTasks()UMLClass1502110260150<<Record>> +GrowthPhase +-- ++ startDate: MonthDay ++ endDate: MonthDay ++ type: GrowthPhaseType + +Relation142218010040lt=<->>>>> +m1=* +m2=180;10;10;10Text15213019070Note: +-- +{final, readOnly} omitted on public data fields in <<Record>> Entities for clarity +style=wordwrapRelation117297021050lt=<. +m1=* +m2=1 +returns >190;20;10;20Relation1032820140130lt=<- +m1=1 external database +m2=1 internal list +accesses10;110;10;10UMLClass3221120240150Crop +-- +- cropId: Long ++ plantId: long {final, readOnly} ++ startDate: LocalDate {final, readOnly} ++ area: Double +-- ++ withId(long): Crop ++ withArea(double): Crop +-- ++ getCropId(): Optional<Long>Relation126228070120lt=<. +m1=* +m2=1 +returns10;10;10;100UMLClass1632290130100<<Enumeration>> +GrowthPhaseType +-- +SOW +PLANT +HARVESTRelation158225070120lt=<->>>> +m1=1 +m2=*50;90;10;90;10;10Relation8827208030lt=<->>>>60;10;10;10Relation4321020110120lt=<. +m1=* +m2=1 +returns / saves10;100;10;10Relation24271010030lt=<->>>>80;10;10;10UMLClass302890280140<<Interface>> +GardenPlan +-- ++ getPlantings(): List<UserPlanting> ++ addPlanting(plantId: long, startDate) ++ savePlanting(planting: UserPlanting) +Relation42280080110lt=<. +m1=1 +m2=1 +accesses10;90;10;10Relation152212403090lt=<.10;70;10;10Relation1062106080270lt=<- +m1=1 +m2=1 +accesses10;10;10;250Relation11421060270170lt=<- +m1=1 +m2=1 +adds Tasks10;10;10;140;250;140UMLClass822114020050JsonTaskDatabaseRelation912106030100lt=<<-10;10;10;80UMLClass156243020050JsonPlantDatabaseRelation144244014030lt=<<-10;10;120;10UMLClass3293020050JsonGardenPlanRelation22294010030lt=<<-80;10;10;10Relation582510220220lt=<.200;200;10;10Relation172510340190lt=<.10;170;320;10UMLClass58225016060PlantListControllerRelation5223803080lt=<.10;60;10;10Relation60230080210lt=<.60;10;10;190UMLClass73239016060PlantListModelRelation73228090130lt=<.70;110;10;10Relation88240022070lt=<- +m1=1\nexternal\ndatabase +m2=1\ninternal\nlist +accesses >200;20;10;20Relation582019050lt=- +m1=0..n +m2=0..1 +teaches to >10;20;170;20UMLClass1412530420200TaskTemplate +-- ++ name: String {readOnly} ++ description: String {readOnly} ++ relativeStartDate: int {readOnly} +- interval: Integer +- relativeEndDate: Integer +- isOptional: boolean +-- ++ Task(name: String, description: String, startDate: String): TaskTemplate ++ setRelativeEndDate(relativeEndDate: int) ++ setInterval(interval: Integer) ++ generateTask(realStartDate: LocalDate): Task +Relation1752180130490lt=<->>>>> +m1=* +m2=180;460;110;460;110;10;10;10Relation160272090130lt=<. +m1=* +m2=1 +generates10;110;10;10Relation56277040040lt=<- +delegates generating Tasks380;20;10;20 \ No newline at end of file diff --git a/src/main/java/ch/zhaw/gartenverwaltung/Config.java b/src/main/java/ch/zhaw/gartenverwaltung/Config.java new file mode 100644 index 0000000..2fa0117 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/Config.java @@ -0,0 +1,19 @@ +package ch.zhaw.gartenverwaltung; + +import ch.zhaw.gartenverwaltung.types.HardinessZone; + +public class Config { + private static HardinessZone currentHardinessZone; + + static { + currentHardinessZone = HardinessZone.ZONE_8A; + } + + public static HardinessZone getCurrentHardinessZone() { + return currentHardinessZone; + } + + public static void setCurrentHardinessZone(HardinessZone currentHardinessZone) { + Config.currentHardinessZone = currentHardinessZone; + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/CropDetailController.java b/src/main/java/ch/zhaw/gartenverwaltung/CropDetailController.java new file mode 100644 index 0000000..6e5d792 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/CropDetailController.java @@ -0,0 +1,132 @@ +package ch.zhaw.gartenverwaltung; + +import ch.zhaw.gartenverwaltung.gardenplan.Gardenplanmodel; +import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; +import ch.zhaw.gartenverwaltung.plantList.PlantListModel; +import ch.zhaw.gartenverwaltung.taskList.TaskListModel; +import ch.zhaw.gartenverwaltung.types.Crop; +import ch.zhaw.gartenverwaltung.types.Pest; +import ch.zhaw.gartenverwaltung.types.Plant; +import ch.zhaw.gartenverwaltung.types.Task; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +import java.io.IOException; +import java.util.List; + +public class CropDetailController { + private Crop crop = null; + private final PlantListModel plantListModel = new PlantListModel(); + private final TaskListModel taskListModel = new TaskListModel(); + private final Gardenplanmodel gardenplanmodel = new Gardenplanmodel(taskListModel); + + @FXML + private ImageView imageView; + + @FXML + private Button area_button; + + @FXML + private Label area_label; + + @FXML + private Label cropName_label; + + @FXML + private Label description_label; + + @FXML + private VBox growthPhases_vbox; + + @FXML + private Label location_label; + + @FXML + private Label light_label; + + @FXML + private Button location_button; + + @FXML + private VBox pests_vbox; + + @FXML + private Label soil_label; + + @FXML + private Label spacing_label; + + public CropDetailController() throws IOException { + } + + @FXML + void editTaskList(ActionEvent event) { + + } + + @FXML + void goBack(ActionEvent event) { + Stage stage = (Stage) imageView.getScene().getWindow(); + stage.close(); + } + + @FXML + void setArea(ActionEvent event) { + + } + + @FXML + void setLocation(ActionEvent event) { + + } + + public void setPlantFromCrop(Crop crop) throws HardinessZoneNotSetException, IOException { + this.crop = crop; + Plant plant = plantListModel.getFilteredPlantListById(Config.getCurrentHardinessZone(), crop.getPlantId()).get(0); + cropName_label.setText(plant.name()); + description_label.setText(plant.description()); + light_label.setText(String.valueOf(plant.light())); + soil_label.setText(plant.soil()); + spacing_label.setText(plant.spacing()); + if (plant.image() != null) { + imageView.setImage(plant.image()); + } + area_label.setText(""); + location_label.setText(""); + createTaskLists(crop); + createPestList(plant); + } + + private void createTaskLists(Crop crop) throws IOException { + List taskList = taskListModel.getTaskListForCrop(crop.getCropId().get()); + for (Task task : taskList) { + Label label = new Label(task.getDescription()); + growthPhases_vbox.getChildren().add(label); + } + } + + private void createPestList(Plant plant) { + List pests = plant.pests(); + for (Pest pest : pests) { + Label label = new Label(pest.name() + ":"); + label.setStyle("-fx-font-weight: bold"); + HBox hBox = new HBox(); + hBox.fillHeightProperty(); + Label label1 = new Label(pest.description()); + label1.setAlignment(Pos.TOP_LEFT); + label1.setWrapText(true); + label1.setMaxWidth(600); + label1.setMaxHeight(100); + Button button = new Button("Get Counter Measures"); + hBox.getChildren().addAll(label1, button); + pests_vbox.getChildren().addAll(label, hBox); + } + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java b/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java index 0d70b44..fd31017 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java @@ -10,9 +10,9 @@ import java.io.IOException; public class HelloApplication extends Application { @Override public void start(Stage stage) throws IOException { - FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml")); - Scene scene = new Scene(fxmlLoader.load(), 320, 240); - stage.setTitle("Hello!"); + FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("MainFXML.fxml")); + Scene scene = new Scene(fxmlLoader.load()); + stage.setTitle("Gartenverwaltung"); stage.setScene(scene); stage.show(); } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/HomeController.java b/src/main/java/ch/zhaw/gartenverwaltung/HomeController.java new file mode 100644 index 0000000..7df4661 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/HomeController.java @@ -0,0 +1,5 @@ +package ch.zhaw.gartenverwaltung; + +public class HomeController +{ +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/MainFXMLController.java b/src/main/java/ch/zhaw/gartenverwaltung/MainFXMLController.java new file mode 100644 index 0000000..032bdb8 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/MainFXMLController.java @@ -0,0 +1,109 @@ +package ch.zhaw.gartenverwaltung; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.layout.AnchorPane; + +import java.io.IOException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class MainFXMLController implements Initializable { + /** + * Caching the panes + */ + private final Map panes = new HashMap<>(); + private static final Logger LOG = Logger.getLogger(MainFXMLController.class.getName()); + + @FXML + private Button home_button; + + @FXML + private AnchorPane mainPane; + + @FXML + private Button myPlants_button; + + @FXML + private Button mySchedule_button; + + @FXML + private Button plants_button; + + @FXML + void goToHome(ActionEvent event) throws IOException { + loadPane("Home.fxml"); + styleChangeButton(home_button); + } + + @FXML + void goToMyPlants(ActionEvent event) throws IOException { + loadPane("MyPlants.fxml"); + styleChangeButton(myPlants_button); + } + + @FXML + void goToMySchedule(ActionEvent event) throws IOException { + loadPane("MySchedule.fxml"); + styleChangeButton(mySchedule_button); + } + + @FXML + void goToPlants(ActionEvent event) throws IOException { + loadPane("Plants.fxml"); + styleChangeButton(plants_button); + } + + /** + * Updates the mainPane with the selected fxml file. + * set HGrow and VGrow to parent AnchorPane. + * Sends MainController to other Controllers. + * @param fxmlFile string of fxml file + * @throws IOException exception when file does not exist + */ + public void loadPane(String fxmlFile) throws IOException { + + AnchorPane anchorPane = panes.get(fxmlFile); + if (anchorPane == null) { + FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile))); + anchorPane = loader.load(); + panes.put(fxmlFile, anchorPane); + + if(fxmlFile.equals("MyPlants.fxml")) { + MyPlantsController myPlantsController = loader.getController(); + myPlantsController.getMainController(this); + } + } + mainPane.getChildren().setAll(anchorPane); + anchorPane.prefWidthProperty().bind(mainPane.widthProperty()); + anchorPane.prefHeightProperty().bind(mainPane.heightProperty()); + + } + + private void styleChangeButton(Button button) { + //ToDo changeStyle of the menu buttons + } + + /** + * loads the default FXML File + * {@inheritDoc} + */ + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + try { + loadPane("Home.fxml"); + styleChangeButton(home_button); + } catch (IOException e) { + LOG.log(Level.SEVERE, "Failed to load FXML-Pane!", e); + } + } +} + diff --git a/src/main/java/ch/zhaw/gartenverwaltung/MyPlantsController.java b/src/main/java/ch/zhaw/gartenverwaltung/MyPlantsController.java new file mode 100644 index 0000000..3d9d82c --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/MyPlantsController.java @@ -0,0 +1,160 @@ +package ch.zhaw.gartenverwaltung; + +import ch.zhaw.gartenverwaltung.gardenplan.Gardenplanmodel; +import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; +import ch.zhaw.gartenverwaltung.plantList.PlantListModel; +import ch.zhaw.gartenverwaltung.taskList.TaskListModel; +import ch.zhaw.gartenverwaltung.types.Crop; +import ch.zhaw.gartenverwaltung.types.Plant; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.stage.Modality; +import javafx.stage.Stage; + +import java.io.IOException; +import java.net.URL; +import java.util.*; + +public class MyPlantsController implements Initializable { + MainFXMLController mainController; + private final TaskListModel taskListModel = new TaskListModel(); + private final Gardenplanmodel gardenplanmodel = new Gardenplanmodel(taskListModel); + private final PlantListModel plantListModel = new PlantListModel(); + + @FXML + private VBox myPlants_vbox; + + public MyPlantsController() throws IOException { + } + + @FXML + void addPlant(ActionEvent event) throws IOException { + mainController.loadPane("Plants.fxml"); + } + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + //ToDo update, when new crops are added + try { + loadCropList(); + } catch (HardinessZoneNotSetException | IOException e) { + e.printStackTrace(); + } + } + + private void loadCropList() throws HardinessZoneNotSetException, IOException { + List cropList = new LinkedList<>(); + try { + cropList = getCropList(); + } catch (IOException e) { + e.printStackTrace(); + } + createPlantView(cropList); + } + + private void createPlantView(List crops) throws HardinessZoneNotSetException, IOException { + myPlants_vbox.getChildren().clear(); + for(Crop crop : crops) { + HBox hBox = createPlantView(crop); + myPlants_vbox.getChildren().add(hBox); + } + } + + public void getMainController(MainFXMLController controller) { + mainController = controller; + } + + private List getCropList() throws IOException { + List cropList; + cropList = gardenplanmodel.getCrops(); + return cropList; + } + + private HBox createPlantView(Crop crop) throws HardinessZoneNotSetException, IOException { + //ToDo add better design + Plant plant = plantListModel.getFilteredPlantListById(Config.getCurrentHardinessZone(), crop.getPlantId()).get(0); + HBox hBox = new HBox(10); + ImageView imageView = new ImageView(); + imageView.setPreserveRatio(false); + imageView.setFitHeight(100); + imageView.setFitWidth(100); + imageView.maxHeight(100); + if (plant.image() != null) { + imageView.setImage(plant.image()); + } + hBox.setMinHeight(100); + Label label = new Label(plant.name()); + label.setMaxWidth(2000); + HBox.setHgrow(label, Priority.ALWAYS); + Button details = new Button("Details"); + Button delete = new Button("delete"); + details.setOnAction(getGoToCropDetailEvent(crop)); + delete.setOnAction(getDeleteCropEvent(crop)); + hBox.getChildren().addAll(imageView, label, details, delete); + return hBox; + } + + private EventHandler getGoToCropDetailEvent(Crop crop) { + EventHandler event = new EventHandler() { + @Override + public void handle(ActionEvent event) { + Parent root; + FXMLLoader fxmlLoader = new FXMLLoader(Objects.requireNonNull(getClass().getResource("CropDetail.fxml"))); + try { + root = fxmlLoader.load(); + CropDetailController controller = fxmlLoader.getController(); + controller.setPlantFromCrop(crop); + Stage stage = new Stage(); + stage.setScene(new Scene(root)); + stage.initModality(Modality.APPLICATION_MODAL); + stage.setResizable(true); + stage.showAndWait(); + } catch (IOException | HardinessZoneNotSetException e) { + e.printStackTrace(); + } + } + }; + return event; + } + + private EventHandler getDeleteCropEvent(Crop crop) { + EventHandler event = new EventHandler() { + @Override + public void handle(ActionEvent event) { + try { + showConfirmation(crop); + } catch (IOException | HardinessZoneNotSetException e) { + e.printStackTrace(); + } + } + }; + return event; + } + + private void showConfirmation(Crop crop) throws IOException, HardinessZoneNotSetException { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setTitle("Delete Crop"); + alert.setHeaderText("Are you sure want to delete this Crop?"); + alert.setContentText("placeholder"); + + Optional option = alert.showAndWait(); + + if (option.get() == ButtonType.OK) { + gardenplanmodel.removeCrop(crop); + loadCropList(); + } + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/MyScheduleController.java b/src/main/java/ch/zhaw/gartenverwaltung/MyScheduleController.java new file mode 100644 index 0000000..a73052c --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/MyScheduleController.java @@ -0,0 +1,182 @@ +package ch.zhaw.gartenverwaltung; + +import ch.zhaw.gartenverwaltung.gardenplan.Gardenplanmodel; +import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; +import ch.zhaw.gartenverwaltung.plantList.PlantListModel; +import ch.zhaw.gartenverwaltung.taskList.TaskListModel; +import ch.zhaw.gartenverwaltung.types.Crop; +import ch.zhaw.gartenverwaltung.types.Task; +import javafx.beans.property.ListProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; + +import java.io.IOException; +import java.net.URL; +import java.time.LocalDate; +import java.util.LinkedList; +import java.util.List; +import java.util.ResourceBundle; + +public class MyScheduleController implements Initializable { + private Crop selectedCrop = null; + private final TaskListModel taskListModel = new TaskListModel(); + private final Gardenplanmodel gardenplanmodel = new Gardenplanmodel(taskListModel); + private final PlantListModel plantListModel = new PlantListModel(); + + private final ListProperty cropListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + + @FXML + private Label day1_label; + + @FXML + private Pane day1_pane; + + @FXML + private Label day2_label; + + @FXML + private Pane day2_pane; + + @FXML + private Label day3_label; + + @FXML + private Pane day3_pane; + + @FXML + private Label day4_label; + + @FXML + private Pane day4_pane; + + @FXML + private Label day5_label; + + @FXML + private Pane day5_pane; + + @FXML + private Label day6_label; + + @FXML + private Pane day6_pane; + + @FXML + private Label day7_label; + + @FXML + private Pane day7_pane; + + @FXML + private Label information_label; + + @FXML + private ListView scheduledPlants_listview; + + public MyScheduleController() throws IOException { + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + List cropList; + try { + cropList = gardenplanmodel.getCrops(); + cropListProperty.addAll(cropList); + } catch (IOException e) { + e.printStackTrace(); + } + setCellFactoryListView(); + scheduledPlants_listview.itemsProperty().bind(cropListProperty); + lookForSelectedListEntries(); + setDayLabels(); + information_label.setText(""); + try { + loadTaskList(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void lookForSelectedListEntries() { + scheduledPlants_listview.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Crop oldValue, Crop newValue) { + selectedCrop = newValue; + try { + loadTaskList(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + + private void setDayLabels() { + LocalDate today = LocalDate.now(); + day1_label.setText(today.getDayOfWeek().toString()); + day2_label.setText(today.plusDays(1).getDayOfWeek().toString()); + day3_label.setText(today.plusDays(2).getDayOfWeek().toString()); + day4_label.setText(today.plusDays(3).getDayOfWeek().toString()); + day5_label.setText(today.plusDays(4).getDayOfWeek().toString()); + day6_label.setText(today.plusDays(5).getDayOfWeek().toString()); + day7_label.setText(today.plusDays(6).getDayOfWeek().toString()); + } + + private void setCellFactoryListView() { + scheduledPlants_listview.setCellFactory(param -> new ListCell() { + @Override + protected void updateItem(Crop crop, boolean empty) { + super.updateItem(crop, empty); + + if (empty || crop == null) { + setText(null); + } else { + try { + setText(plantListModel.getFilteredPlantListById(Config.getCurrentHardinessZone(), crop.getPlantId()).get(0).name()); + } catch (HardinessZoneNotSetException | IOException e) { + e.printStackTrace(); + } + } + } + }); + } + + private void loadTaskList() throws IOException { + List> taskLists = new LinkedList<>(); + if (selectedCrop != null) { + taskLists = taskListModel.getTasksUpcomingWeekForCrop(selectedCrop.getCropId().get()); + } else { + taskLists = taskListModel.getTasksUpcomingWeek(); + } + if (!taskLists.isEmpty()) { + viewTaskListOfDay(day1_pane, taskLists.get(0)); + viewTaskListOfDay(day2_pane, taskLists.get(1)); + viewTaskListOfDay(day3_pane, taskLists.get(2)); + viewTaskListOfDay(day4_pane, taskLists.get(3)); + viewTaskListOfDay(day5_pane, taskLists.get(4)); + viewTaskListOfDay(day6_pane, taskLists.get(5)); + viewTaskListOfDay(day7_pane, taskLists.get(6)); + } + } + + private void viewTaskListOfDay(Pane pane, List tasks) { + //ToDo update pane with task list + VBox vBox = new VBox(); + for (Task task : tasks) { + Label label = new Label(task.getDescription()); + vBox.getChildren().add(label); + } + pane.getChildren().add(vBox); + } + + +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/PlantsController.java b/src/main/java/ch/zhaw/gartenverwaltung/PlantsController.java new file mode 100644 index 0000000..c8ff333 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/PlantsController.java @@ -0,0 +1,272 @@ +package ch.zhaw.gartenverwaltung; + +import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; +import ch.zhaw.gartenverwaltung.plantList.PlantListModel; +import ch.zhaw.gartenverwaltung.types.HardinessZone; +import ch.zhaw.gartenverwaltung.types.Plant; +import ch.zhaw.gartenverwaltung.types.Seasons; +import javafx.beans.property.ListProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.collections.FXCollections; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.VBox; +import javafx.stage.Modality; +import javafx.stage.Stage; + +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class PlantsController implements Initializable { + private static final Logger LOG = Logger.getLogger(PlantsController.class.getName()); + private final PlantListModel plantListModel = new PlantListModel(); + private Plant selectedPlant = null; + private final HardinessZone DEFAULT_HARDINESS_ZONE = HardinessZone.ZONE_8A; + + // TODO: move to model + private final ListProperty plantListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + + @FXML + private VBox seasons; + + @FXML + private VBox climate_zones; + + @FXML + private Label description_plant; + + @FXML + private ImageView img_plant; + + @FXML + private ListView list_plants; + + @FXML + private Button selectSowDay_button; + + @FXML + private TextField search_plants; + + /** + * open new window to select sow or harvest day to save the crop + * @param event event + */ + @FXML + void selectSowDate(ActionEvent event) throws IOException { + Parent root; + FXMLLoader fxmlLoader = new FXMLLoader(Objects.requireNonNull(getClass().getResource("SelectSowDay.fxml"))); + root = fxmlLoader.load(); + SelectSowDayController controller = fxmlLoader.getController(); + controller.getSelectedPlant(selectedPlant); + Stage stage = new Stage(); + stage.setScene(new Scene(root)); + stage.initModality(Modality.APPLICATION_MODAL); + stage.setResizable(false); + stage.showAndWait(); + } + + /** + * fill list view with current hardiness zone + * set default values + * create filter of season and hardiness zone + * create event listener for selected list entry and search by query + * {@inheritDoc} + */ + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + setListCellFactory(); + fillPlantListWithHardinessZone(); + list_plants.itemsProperty().bind(plantListProperty); + + description_plant.setText(""); + selectSowDay_button.setDisable(true); + + createFilterSeasons(); + createFilterHardinessZone(); + lookForSelectedListEntry(); + try { + viewFilteredListBySearch(); + } catch (HardinessZoneNotSetException e) { + LOG.log(Level.WARNING, "Hardiness Zone not set!"); + } catch (IOException e) { + LOG.log(Level.WARNING, "Could not retrieve data!", e); + } + } + + /** + * set text of list view to plant name + */ + private void setListCellFactory() { + list_plants.setCellFactory(param -> new ListCell<>() { + @Override + protected void updateItem(Plant plant, boolean empty) { + super.updateItem(plant, empty); + + if (empty || plant == null || plant.name() == null) { + setText(null); + } else { + setText(plant.name()); + } + } + }); + } + + /** + * get plant list according to param season and hardiness zone + * fill list view with plant list + * @param season enum of seasons + * @throws HardinessZoneNotSetException throws exception + * @throws IOException throws exception + */ + private void viewFilteredListBySeason(Seasons season) throws HardinessZoneNotSetException, IOException { + clearListView(); + plantListProperty.addAll(plantListModel.getFilteredPlantListBySaisonWithoutGrowthPhase(plantListModel.getCurrentZone(), season.getStartDate(), season.getEndDate())); + } + + /** + * get plant list filtered by search plant entry and hardiness zone + * fill list view with plant list + * @throws HardinessZoneNotSetException throws exception when no hardiness zone is defined + * @throws IOException throws exception + */ + private void viewFilteredListBySearch() throws HardinessZoneNotSetException, IOException { + search_plants.textProperty().addListener((observable, oldValue, newValue) -> { + if (newValue.isEmpty()) { + fillPlantListWithHardinessZone(); + } else { + try { + List filteredPlants = plantListModel.getFilteredPlantListByString(DEFAULT_HARDINESS_ZONE, newValue); + clearListView(); + plantListProperty.addAll(filteredPlants); + } catch (HardinessZoneNotSetException e) { + LOG.log(Level.WARNING, "Hardiness Zone not set!"); + } catch (IOException e) { + LOG.log(Level.WARNING, "Could not retrieve data!", e); + } + } + }); + } + + /** + * get plant list of current hardiness zone + * fill list view with plant list + */ + private void fillPlantListWithHardinessZone() { + try { + clearListView(); + plantListProperty.addAll(plantListModel.getPlantList(plantListModel.getCurrentZone())); + } catch (HardinessZoneNotSetException e) { + LOG.log(Level.WARNING, "Hardiness Zone not set!"); + } catch (IOException e) { + LOG.log(Level.WARNING, "Could not retrieve data!", e); + } + } + + /** + * creates radio buttons for the hardiness zones defined in enum HardinessZone + * defines default value as selected + * when selected filter viewList according to hardiness zone + */ + private void createFilterHardinessZone() { + ToggleGroup hardinessGroup = new ToggleGroup(); + for (HardinessZone zone : HardinessZone.values()) { + RadioButton radioButton = new RadioButton(zone.name()); + radioButton.setToggleGroup(hardinessGroup); + radioButton.setPadding(new Insets(0,0,10,0)); + if (zone.equals(DEFAULT_HARDINESS_ZONE)) { + radioButton.setSelected(true); + } + radioButton.selectedProperty().addListener((observable, oldValue, newValue) -> { + plantListModel.setCurrentZone(zone); + fillPlantListWithHardinessZone(); + }); + climate_zones.getChildren().add(radioButton); + } + } + + /** + * creates radio buttons for the seasons defined in enum Seasons + * defines default value as selected + * when selected filter viewList according to seasons + */ + private void createFilterSeasons() { + ToggleGroup seasonGroup = new ToggleGroup(); + for (Seasons season : Seasons.values()) { + RadioButton radioButton = new RadioButton(season.getName()); + radioButton.setToggleGroup(seasonGroup); + radioButton.setPadding(new Insets(0,0,10,0)); + if (season.equals(Seasons.AllSEASONS)) { + radioButton.setSelected(true); + } + radioButton.selectedProperty().addListener((observable, oldValue, newValue) -> { + if (season.equals(Seasons.AllSEASONS)) { + fillPlantListWithHardinessZone(); + } else { + try { + viewFilteredListBySeason(season); + } catch (HardinessZoneNotSetException e) { + LOG.log(Level.WARNING, "Hardiness Zone not set!"); + } catch (IOException e) { + LOG.log(Level.WARNING, "Could not retrieve data!", e); + } + } + + }); + seasons.getChildren().add(radioButton); + } + } + + /** + * observes changes in the selectedProperty of ListView and updates: + * the description label + * image of the plant + */ + private void lookForSelectedListEntry() { + selectedPlant = null; + description_plant.setText(""); + selectSowDay_button.setDisable(true); + Image img = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png"))); + img_plant.setImage(img); + list_plants.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if(newValue != null) { + selectedPlant = newValue; + description_plant.setText(selectedPlant.description()); + selectSowDay_button.setDisable(false); + Image img1; + if(selectedPlant.image() != null) { + img1 = selectedPlant.image(); + } else { + img1 = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png"))); + } + img_plant.setImage(img1); + } else { + selectedPlant = null; + description_plant.setText(""); + selectSowDay_button.setDisable(true); + Image img1 = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png"))); + img_plant.setImage(img1); + } + }); + } + + + /** + * clears the ListView of entries + */ + private void clearListView() { + plantListProperty.clear(); + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/SelectSowDayController.java b/src/main/java/ch/zhaw/gartenverwaltung/SelectSowDayController.java new file mode 100644 index 0000000..c5445e1 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/SelectSowDayController.java @@ -0,0 +1,174 @@ +package ch.zhaw.gartenverwaltung; + +import ch.zhaw.gartenverwaltung.gardenplan.Gardenplanmodel; +import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; +import ch.zhaw.gartenverwaltung.taskList.PlantNotFoundException; +import ch.zhaw.gartenverwaltung.taskList.TaskListModel; +import ch.zhaw.gartenverwaltung.types.GrowthPhaseType; +import ch.zhaw.gartenverwaltung.types.Plant; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.*; +import javafx.stage.Stage; +import javafx.util.Callback; + +import java.io.IOException; +import java.net.URL; +import java.time.LocalDate; +import java.util.List; +import java.util.ResourceBundle; + +public class SelectSowDayController implements Initializable { + private Plant selectedPlant = null; + private final TaskListModel taskListModel = new TaskListModel(); + private final Gardenplanmodel gardenplanmodel = new Gardenplanmodel(taskListModel); + + @FXML + private DatePicker datepicker; + + @FXML + private Label popup_label; + + @FXML + private Button save_button; + + @FXML + private RadioButton sow_radio; + + public SelectSowDayController() throws IOException {} + + /** + * close the date selector window + * @param event event + */ + @FXML + void cancel(ActionEvent event) { + closeWindow(); + } + + /** + * get sow date from datePicker or calculate sow date from harvest date + * save selected plant and sow date + * @param event event + */ + @FXML + void save(ActionEvent event) throws HardinessZoneNotSetException, IOException, PlantNotFoundException { + LocalDate sowDate; + if (sow_radio.isSelected()) { + sowDate = datepicker.getValue(); + } else { + //ToDo method to get current lifecycle group in plant + sowDate = selectedPlant.sowDateFromHarvestDate(datepicker.getValue(), 0); + } + gardenplanmodel.plantAsCrop(selectedPlant, sowDate); + closeWindow(); + } + + /** + * save the plant which will be planted and update label + * @param plant Plant + */ + public void getSelectedPlant(Plant plant) { + selectedPlant = plant; + popup_label.setText("Select Harvest/Sow Date for" + selectedPlant.name()); + } + + /** + * add listener and set default values + * {@inheritDoc} + * @param location location + * @param resources resources + */ + @Override + public void initialize(URL location, ResourceBundle resources) { + clearDatePickerEntries(); + + Callback dayCellFactory= getDayCellFactory(); + datepicker.setDayCellFactory(dayCellFactory); + datepicker.getEditor().setEditable(false); + + enableDisableSaveButton(); + } + + /** + * clear date picker editor when radio button is changed + */ + private void clearDatePickerEntries() { + sow_radio.selectedProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Boolean oldValue, Boolean isNowSelected) { + datepicker.getEditor().clear(); + } + }); + } + + /** + * date picker disable/enable dates according to selected plant: sow or harvest day + * @return cellFactory of datePicker + */ + private Callback getDayCellFactory() { + + final Callback dayCellFactory = new Callback() { + + @Override + public DateCell call(final DatePicker datePicker) { + return new DateCell() { + @Override + public void updateItem(LocalDate item, boolean empty) { + super.updateItem(item, empty); + setDisable(true); + setStyle("-fx-background-color: #ffc0cb;"); + List dates; + LocalDate today = LocalDate.now(); + if (sow_radio.isSelected()) { + dates = selectedPlant.getDateListOfGrowthPhase(GrowthPhaseType.SOW); + } else { + dates = selectedPlant.getDateListOfGrowthPhase(GrowthPhaseType.HARVEST); + } + for (LocalDate date : dates) { + if (item.getMonth() == date.getMonth() + && item.getDayOfMonth() == date.getDayOfMonth() + && item.compareTo(today) > 0) { + setDisable(false); + setStyle("-fx-background-color: #32CD32;"); + } + } + if ((!sow_radio.isSelected() && selectedPlant.sowDateFromHarvestDate(item, 0).compareTo(today) < 0)) { + setDisable(true); + setStyle("-fx-background-color: #ffc0cb;"); + } + } + }; + } + }; + return dayCellFactory; + } + + /** + * close date picker window + */ + private void closeWindow() { + Stage stage = (Stage) save_button.getScene().getWindow(); + stage.close(); + } + + /** + * disable save button, when there is no date selected in date picker + */ + private void enableDisableSaveButton() { + save_button.setDisable(true); + datepicker.getEditor().textProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, String oldValue, String newValue) { + if (newValue == null || newValue.equals("")) { + save_button.setDisable(true); + } else { + save_button.setDisable(false); + } + } + }); + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/gardenplan/Gardenplanmodel.java b/src/main/java/ch/zhaw/gartenverwaltung/gardenplan/Gardenplanmodel.java new file mode 100644 index 0000000..00ebd44 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/gardenplan/Gardenplanmodel.java @@ -0,0 +1,92 @@ +package ch.zhaw.gartenverwaltung.gardenplan; + +import ch.zhaw.gartenverwaltung.io.GardenPlan; +import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; +import ch.zhaw.gartenverwaltung.io.JsonGardenPlan; +import ch.zhaw.gartenverwaltung.taskList.PlantNotFoundException; +import ch.zhaw.gartenverwaltung.taskList.TaskListModel; +import ch.zhaw.gartenverwaltung.types.Crop; +import ch.zhaw.gartenverwaltung.types.Plant; +import ch.zhaw.gartenverwaltung.types.Task; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + + +/** + * The Gardenplan model manages the crops in the gardenplan. + */ +public class Gardenplanmodel { + private GardenPlan gardenPlan; + private List cropList; + private TaskListModel taskListModel; + private Object IllegalArgumentException; + + /** + * Constructor of Gardenplan model + * + * @param taskListModel holds a reference to the task list object. + */ + public Gardenplanmodel(TaskListModel taskListModel) throws IOException { + this.taskListModel = taskListModel; + gardenPlan = new JsonGardenPlan(); + cropList = new ArrayList<>(); + cropList = gardenPlan.getCrops(); + } + + /** + * Creates a Crop with a {@link Plant} and the planting date of the plant. Then let the Tasklistmodel create the + * gardening {@link Task} for the crop. Store the crop in the gardenplan file and the cache. + * + * @param plant The plant which is wnated to be planted + * @param plantingDate The date, when the plant is planted + * @throws IOException If the database cannot be accessed + * @throws HardinessZoneNotSetException If the hardinesszone could not be added + * @throws PlantNotFoundException If the plant is not found in the database. + */ + public void plantAsCrop(Plant plant, LocalDate plantingDate) throws IOException, HardinessZoneNotSetException, PlantNotFoundException { + Crop crop = new Crop(plant.id(), plantingDate); + //TODO Add Area to Plant + //crop.withArea(0); + gardenPlan.saveCrop(crop); + taskListModel.planTasksForCrop(crop); + cropList = gardenPlan.getCrops(); + } + + /** + * Removes a {@link Crop} from the file and the cache + * + * @param crop The plant which is wnated to be planted + * @throws IOException If the database cannot be accessed + */ + public void removeCrop(Crop crop) throws IOException { + gardenPlan.removeCrop(crop); + taskListModel.removeTasksForCrop(crop.getCropId().orElseThrow(IllegalArgumentException::new)); + cropList = gardenPlan.getCrops(); + } + /** + * Returns a list of {@link Crop}s which are currently in the gardenplan. + * + * @throws IOException If the database cannot be accessed + */ + public List getCrops() throws IOException { + if(!cropList.isEmpty()){ + cropList = gardenPlan.getCrops(); + } + return cropList; + } + + /** + * Returns an Optional of {@link Crop} if the crop is the gardenplan + * + * @param cropId The date, when the plant is planted + * @throws IOException If the database cannot be accessed + */ + public Optional getCrop(Long cropId) throws IOException { + return gardenPlan.getCropById(cropId); + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/GardenPlan.java b/src/main/java/ch/zhaw/gartenverwaltung/io/GardenPlan.java index 94e1346..38d0598 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/io/GardenPlan.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/GardenPlan.java @@ -1,12 +1,42 @@ package ch.zhaw.gartenverwaltung.io; -import ch.zhaw.gartenverwaltung.types.UserPlanting; +import ch.zhaw.gartenverwaltung.types.Crop; import java.io.IOException; import java.util.List; +import java.util.Optional; public interface GardenPlan { - List getPlantings(); - void savePlanting(UserPlanting planting) throws IOException; - void removePlanting(UserPlanting planting) throws IOException; + /** + * Yields a list of all {@link Crop}s in the database. + * + * @return A list of all {@link Crop}s in the database + * @throws IOException If there is a problem reading from or writing to the database + */ + List getCrops() throws IOException; + + /** + * Attempts to retrieve the {@link Crop} with the specified cropId. + * + * @param cropId The {@link Crop} to look for + * @return {@link Optional} of the found {@link Crop}, {@link Optional#empty()} if no entry matched the criteria + * @throws IOException If there is a problem reading from or writing to the database + */ + Optional getCropById(long cropId) throws IOException; + + /** + * Saves a Crop to the Database. + * + * @param crop The {@link Crop} to be saved + * @throws IOException If there is a problem reading from or writing to the database + */ + void saveCrop(Crop crop) throws IOException; + + /** + * Removes a Crop from the Database. + * + * @param crop The {@link Crop} to be removed + * @throws IOException If there is a problem reading from or writing to the database + */ + void removeCrop(Crop crop) throws IOException; } 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/IdProvider.java b/src/main/java/ch/zhaw/gartenverwaltung/io/IdProvider.java new file mode 100644 index 0000000..5842f0d --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/IdProvider.java @@ -0,0 +1,11 @@ +package ch.zhaw.gartenverwaltung.io; + +public class IdProvider { + private long currentId; + public IdProvider(long initialValue) { + currentId = initialValue; + } + public long incrementAndGet() { + return ++currentId; + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/InvalidJsonException.java b/src/main/java/ch/zhaw/gartenverwaltung/io/InvalidJsonException.java new file mode 100644 index 0000000..9c3b8dc --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/InvalidJsonException.java @@ -0,0 +1,9 @@ +package ch.zhaw.gartenverwaltung.io; + +import java.io.IOException; + +class InvalidJsonException extends IOException { + public InvalidJsonException(String reason) { + super(reason); + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/JsonGardenPlan.java b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonGardenPlan.java new file mode 100644 index 0000000..cfe2663 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonGardenPlan.java @@ -0,0 +1,153 @@ +package ch.zhaw.gartenverwaltung.io; + +import ch.zhaw.gartenverwaltung.types.Crop; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.time.LocalDate; +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; + +public class JsonGardenPlan implements GardenPlan { + private final URL dataSource; + + private IdProvider idProvider; + private Map cropMap = Collections.emptyMap(); + + /** + * Creating constant objects required to deserialize the {@link LocalDate} classes + */ + private final static JavaTimeModule timeModule = new JavaTimeModule(); + private final static String INVALID_DATASOURCE_MSG = "Invalid datasource specified!"; + static { + DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDateDeserializer dateDeserializer = new LocalDateDeserializer(dateFormat); + LocalDateSerializer dateSerializer = new LocalDateSerializer(dateFormat); + + timeModule.addDeserializer(LocalDate.class, dateDeserializer); + timeModule.addSerializer(LocalDate.class, dateSerializer); + } + + /** + * Default constructor + */ + public JsonGardenPlan() { + this.dataSource = getClass().getResource("user-crops.json"); + } + + /** + * Constructor to use a specified {@link URL} as a {@link #dataSource} + * @param dataSource A {@link URL} to the file to be used as a data source + */ + public JsonGardenPlan(URL dataSource) { + this.dataSource = dataSource; + } + /** + * {@inheritDoc} + */ + @Override + public List getCrops() throws IOException { + if (idProvider == null) { + loadCropList(); + } + return cropMap.values().stream().toList(); + } + + /** + * {@inheritDoc} + */ + @Override + public Optional getCropById(long id) throws IOException { + if (idProvider == null) { + loadCropList(); + } + return Optional.ofNullable(cropMap.get(id)); + } + + /** + * {@inheritDoc} + * + * Saves a crop to the database. + * If no {@link Crop#cropId} is set, one will be generated and + * the Crop is stored as a new entry. + */ + @Override + public void saveCrop(Crop crop) throws IOException { + if (idProvider == null) { + loadCropList(); + } + Long cropId = crop.getCropId().orElse(idProvider.incrementAndGet()); + cropMap.put(cropId, crop.withId(cropId)); + writeCropList(); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeCrop(Crop crop) throws IOException { + if (idProvider == null) { + loadCropList(); + } + Optional cropId = crop.getCropId(); + if (cropId.isPresent()) { + cropMap.remove(cropId.get()); + writeCropList(); + } + } + + /** + * Loads the database from {@link #dataSource} and updates the cached data. + * + * @throws IOException If the database cannot be accessed or invalid data was read. + */ + private void loadCropList() throws IOException { + if (dataSource != null) { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(timeModule); + + List result; + result = mapper.readerForListOf(Crop.class).readValue(dataSource); + + // Turn list into a HashMap with structure cropId => Crop + cropMap = new HashMap<>(result.size()); + for (Crop crop : result) { + Long id = crop.getCropId() + .orElseThrow(() -> new InvalidJsonException(String.format("Invalid value for \"cropId\" in %s!", crop))); + cropMap.put(id, crop); + } + Long maxId = cropMap.isEmpty() ? 0L : Collections.max(cropMap.keySet()); + idProvider = new IdProvider(maxId); + } + } + + /** + * Writes the values from {@link #cropMap} to the {@link #dataSource} + * + * @throws IOException If the database cannot be accessed + */ + private void writeCropList() throws IOException { + if (dataSource != null) { + try { + new ObjectMapper() + .registerModule(timeModule) + .registerModule(new Jdk8Module()) + .writeValue(new File(dataSource.toURI()), cropMap.values()); + } catch (URISyntaxException e) { + throw new IOException(INVALID_DATASOURCE_MSG, e); + } + } + } + +} 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..f09d90e --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java @@ -0,0 +1,104 @@ +package ch.zhaw.gartenverwaltung.io; + +import ch.zhaw.gartenverwaltung.json.PlantImageDeserializer; +import ch.zhaw.gartenverwaltung.types.HardinessZone; +import ch.zhaw.gartenverwaltung.types.Plant; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.MonthDayDeserializer; +import javafx.scene.image.Image; + +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 plantMap = Collections.emptyMap(); + + /** + * Creating constant objects required to deserialize the {@link MonthDay} classes + */ + private final static JavaTimeModule timeModule = new JavaTimeModule(); + private final static SimpleModule imageModule = new SimpleModule(); + + static { + DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM-dd"); + MonthDayDeserializer dateDeserializer = new MonthDayDeserializer(dateFormat); + timeModule.addDeserializer(MonthDay.class, dateDeserializer); + + imageModule.addDeserializer(Image.class, new PlantImageDeserializer()); + } + + /** + * 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, HardinessZoneNotSetException { + if (plantMap.isEmpty() || zone != currentZone) { + loadPlantList(zone); + } + return plantMap.values().stream().toList(); + } + + /** + * @see PlantDatabase#getPlantById(long) + */ + @Override + public Optional getPlantById(HardinessZone zone, long id) throws HardinessZoneNotSetException, IOException { + if (plantMap.isEmpty()) { + loadPlantList(zone); + } + 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() + .registerModule(timeModule) + .registerModule(imageModule); + + List result; + result = mapper.readerForListOf(Plant.class).readValue(dataSource); + + plantMap = result.stream() + // Remove plants not in the current zone + .filter(plant -> { + plant.inZone(currentZone); + return !plant.lifecycle().isEmpty(); + }) + // Create Hashmap from results + .collect(HashMap::new, + (res, plant) -> res.put(plant.id(), plant), + (existing, replacement) -> {}); + } + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/JsonTaskDatabase.java b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonTaskDatabase.java new file mode 100644 index 0000000..be0d2ab --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonTaskDatabase.java @@ -0,0 +1,166 @@ +package ch.zhaw.gartenverwaltung.io; + +import ch.zhaw.gartenverwaltung.types.Crop; +import ch.zhaw.gartenverwaltung.types.Task; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implements the {@link TaskDatabase} interface for loading and writing {@link Task} objects + * from and to a JSON file. + * The reads are cached to minimize file-io operations. + */ +public class JsonTaskDatabase implements TaskDatabase{ + IdProvider idProvider; + private final URL dataSource = getClass().getResource("taskdb.json"); + private final static String INVALID_DATASOURCE_MSG = "Invalid datasource specified!"; + + private Map taskMap = Collections.emptyMap(); + + /** + * Creating constant objects required to deserialize the {@link LocalDate} classes + */ + private final static JavaTimeModule timeModule = new JavaTimeModule(); + static { + DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDateDeserializer dateDeserializer = new LocalDateDeserializer(dateFormat); + LocalDateSerializer dateSerializer = new LocalDateSerializer(dateFormat); + + timeModule.addDeserializer(LocalDate.class, dateDeserializer); + timeModule.addSerializer(LocalDate.class, dateSerializer); + } + + /** + * If no data is currently loaded, data is loaded from {@link #dataSource}. + * In any case, the values of {@link #taskMap} are returned. + * + * @see TaskDatabase#getTaskList(LocalDate, LocalDate) + */ + @Override + public List getTaskList(LocalDate start, LocalDate end) throws IOException{ + if(taskMap.isEmpty()) { + loadTaskListFromFile(); + } + return taskMap.values().stream().filter(task -> task.isInTimePeriode(start, end)).toList(); + } + + /** + * Method get all Tasks for a specific Crop + * @param cropId the cropId + * @return List of Tasks for given Crop + */ + @Override + public List getTaskForCrop(long cropId) throws IOException { + if(taskMap.isEmpty()) { + loadTaskListFromFile(); + } + return taskMap.values().stream().filter(task -> task.getCropId() == cropId).toList(); + } + + /** + * Method remove all Tasks for a specific Crop + * @param cropId the crop + */ + @Override + public void removeTasksForCrop(long cropId) throws IOException { + if(taskMap.isEmpty()) { + loadTaskListFromFile(); + } + taskMap.values().removeIf(task -> task.getCropId() == cropId); + } + + /** + * If no data is currently loaded, data is loaded from {@link #dataSource}. + * If the {@link Task} has an id than the task is added to the {@link #taskMap} + * otherwise the id is generated with the {@link IdProvider} before adding + * it to the {@link #taskMap}. In any case, the {@link #taskMap} is written + * to the {@link #dataSource}. + * + * @see TaskDatabase#saveTask(Task) + */ + @Override + public void saveTask(Task task) throws IOException { + if(taskMap.isEmpty()) { + loadTaskListFromFile(); + } + if(task.getId() == 0) { + task.withId(idProvider.incrementAndGet()); + } + writeTaskListToFile(); + } + + /** + * If no data is currently loaded, data is loaded from {@link #dataSource}. + * If the {@link Task}s id is found in the {@link #taskMap}, the Task is removed + * from the {@link #taskMap}. Then the Task are written to the {@link #dataSource}. + * + * @see TaskDatabase#removeTask(Task) + */ + @Override + public void removeTask(Task task) throws IOException { + if(taskMap.isEmpty()) { + loadTaskListFromFile(); + } + if(taskMap.containsKey(task.getId())){ + taskMap.remove(task.getId()); + writeTaskListToFile(); + } + } + + /** + * Writes cached data to the {@link #dataSource}. + * + * @throws IOException If the database cannot be accessed + */ + private void writeTaskListToFile() throws IOException { + if(dataSource != null) { + try { + new ObjectMapper() + .registerModule(timeModule) + .registerModule(new Jdk8Module()) + .writeValue(new File(dataSource.toURI()), taskMap.values()); + + } catch (URISyntaxException e) { + throw new IOException(INVALID_DATASOURCE_MSG, e); + } + } + } + + /** + * Loads the database from {@link #dataSource} and updates the cached data. + * + * @throws IOException If the database cannot be accessed + */ + private void loadTaskListFromFile() throws IOException { + if (dataSource != null) { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(timeModule) + .registerModule(new Jdk8Module()); + + List result; + result = mapper.readerForListOf(Task.class).readValue(dataSource); + + taskMap = result.stream() + .collect(HashMap::new, + (res, task) -> res.put(task.getId(), task), + (existing, replacement) -> {}); + } + + Long maxId = taskMap.isEmpty() ? 0L : Collections.max(taskMap.keySet()); + idProvider = new IdProvider(maxId); + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java b/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java index f0d9bf9..455791e 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/PlantDatabase.java @@ -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 getPlantList(HardinessZone zone); - Optional 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 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(HardinessZone zone, long id) throws IOException, HardinessZoneNotSetException; } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/TaskDatabase.java b/src/main/java/ch/zhaw/gartenverwaltung/io/TaskDatabase.java index 73cf8e0..02bef7a 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/io/TaskDatabase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/TaskDatabase.java @@ -1,13 +1,58 @@ package ch.zhaw.gartenverwaltung.io; +import ch.zhaw.gartenverwaltung.types.Crop; +import ch.zhaw.gartenverwaltung.types.HardinessZone; +import ch.zhaw.gartenverwaltung.types.Plant; import ch.zhaw.gartenverwaltung.types.Task; import java.io.IOException; +import java.time.LocalDate; import java.util.Date; import java.util.List; +/** + * A database of {@link Task}s. + * The interface specifies the minimal required operations. + */ public interface TaskDatabase { - List getTaskList(Date start, Date end); + /** + * Yields a list of all {@link Task}s in the database with the start and end date of a period of time. + * + * @param start The start date of the wanted time period + * @param end The end date of the wanted time period + * @return A list of {@link Task}s planned in the specified time period + * @throws IOException If the database cannot be accessed + */ + List getTaskList(LocalDate start, LocalDate end) throws IOException; + + /** + * Method get all Tasks for a specific Crop + * @param cropId the cropId + * @return List of Tasks for given Crop + * @throws IOException If the database cannot be accessed + */ + List getTaskForCrop(long cropId) throws IOException; + + /** + * Method remove all Tasks for a specific Crop + * @param cropId the cropId + * @throws IOException If the database cannot be accessed + */ + void removeTasksForCrop(long cropId) throws IOException; + + /** + * Saves the {@link Task} in the Cache. + * + * @param task The {@link Task} which is wanted to be saved in the TaskList + * @throws IOException If the database cannot be accessed + */ void saveTask(Task task) throws IOException; + + /** + * Removes the {@link Task}from the Cache. + * + * @param task The {@link Task} which has to be removed from the {@link Task} list. + * @throws IOException If the database cannot be accessed + */ void removeTask(Task task) 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..35956a1 --- /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; + String token = parser.getText(); + try { + result = GrowthPhaseType.valueOf(token.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IOException(String.format("Bad growth phase type \"%s\"\n", token)); + } + 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..2a8ec1b --- /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; + String token = parser.getText(); + try { + result = HardinessZone.valueOf(token.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IOException(String.format("Unknown Hardiness Zone \"%s\"\n", token), e); + } + return result; + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/json/PlantImageDeserializer.java b/src/main/java/ch/zhaw/gartenverwaltung/json/PlantImageDeserializer.java new file mode 100644 index 0000000..14da20c --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/json/PlantImageDeserializer.java @@ -0,0 +1,31 @@ +package ch.zhaw.gartenverwaltung.json; + +import ch.zhaw.gartenverwaltung.io.PlantDatabase; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import javafx.scene.image.Image; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.net.URL; + +public class PlantImageDeserializer extends JsonDeserializer { + + @Override + public Image deserialize(JsonParser parser, DeserializationContext context) throws IOException { + Image result = null; + URL imageUrl = PlantDatabase.class.getResource(String.format("images/%s", parser.getText())); + if (imageUrl != null) { + try (InputStream is = new FileInputStream(new File(imageUrl.toURI()))) { + result = new Image(is); + } catch (IllegalArgumentException | URISyntaxException e) { + throw new IOException(String.format("Cannot find Image \"%s\"\n", imageUrl.getFile())); + } + } + return result; + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/plantList/PlantListModel.java b/src/main/java/ch/zhaw/gartenverwaltung/plantList/PlantListModel.java new file mode 100644 index 0000000..ffccb30 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/plantList/PlantListModel.java @@ -0,0 +1,193 @@ +package ch.zhaw.gartenverwaltung.plantList; + +import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; +import ch.zhaw.gartenverwaltung.io.JsonPlantDatabase; +import ch.zhaw.gartenverwaltung.io.PlantDatabase; +import ch.zhaw.gartenverwaltung.types.GrowthPhaseType; +import ch.zhaw.gartenverwaltung.types.HardinessZone; +import ch.zhaw.gartenverwaltung.types.Plant; + +import java.io.IOException; +import java.time.MonthDay; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class PlantListModel { + private PlantDatabase plantDatabase; + private HardinessZone currentZone; + + /** + * Comparators to create sorted Plant List + */ + static final Comparator sortByName = Comparator.comparing(Plant::name); + static final Comparator SortById = Comparator.comparingLong(Plant::id); + + /** + * Constructor to create Database Object. + */ + public PlantListModel() { + plantDatabase = new JsonPlantDatabase(); + setDefaultZone(); + } + + public PlantListModel(PlantDatabase plantDatabase) { + this.plantDatabase = plantDatabase; + setDefaultZone(); + } + + private void setDefaultZone() { + currentZone = HardinessZone.ZONE_8A; // TODO: get Default Zone from Config + } + + public void setCurrentZone(HardinessZone currentZone) { + this.currentZone = currentZone; + } + + public HardinessZone getCurrentZone() { + return currentZone; + } + + /** + * Method to get actual Plant List in alphabetic Order + * + * @return actual Plant List in alphabetic Order + */ + public List getPlantList(HardinessZone zone) throws HardinessZoneNotSetException, IOException { + setCurrentZone(zone); + return getSortedPlantList(zone, sortByName); + } + + + /** + * Method to get the actual Plant list in custom Order + * + * @param zone selected hardiness zone + * @param comparator comparator to sort the list + * @return sorted list with plants in the given hardiness zone + * @throws IOException If the database cannot be accessed + * @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified + */ + public List getSortedPlantList(HardinessZone zone, Comparator comparator) throws HardinessZoneNotSetException, IOException { + setCurrentZone(zone); + return plantDatabase.getPlantList(zone).stream().sorted(comparator).collect(Collectors.toList()); + } + + /** + * Method to get Filtered plant list + * + * @param predicate predicate to filter the list + * @param zone selected hardiness zone + * @return filterd list with plants in the hardinness zone + * @throws IOException If the database cannot be accessed + * @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified + */ + public List getFilteredPlantList(HardinessZone zone, Predicate predicate) throws HardinessZoneNotSetException, IOException { + setCurrentZone(zone); + return getPlantList(zone).stream().filter(predicate).collect(Collectors.toList()); + } + + /** + * Method to get Filtered plant list by id by exact match + * + * @param zone selected hardiness zone + * @param id id of plant + * @return if id doesn't exist: empty List, else list with 1 plant entry. + * @throws IOException If the database cannot be accessed + * @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified + */ + public List getFilteredPlantListById(HardinessZone zone, Long id) throws HardinessZoneNotSetException, IOException { + setCurrentZone(zone); + List plantList = new ArrayList<>(); + plantDatabase.getPlantById(zone, id).ifPresent(plantList::add); + return plantList; + } + + /** + * @param zone selected hardiness zone + * @param searchString the string to search plant List, set '#' as first char the search by id. + * @return List of plants found in Plant List which contain the search String in the name or description + * @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified + * @throws IOException If the database cannot be accessed + */ + public List getFilteredPlantListByString(HardinessZone zone, String searchString) throws HardinessZoneNotSetException, IOException { + if (searchString.length() == 0) { + return getPlantList(zone); + } else if (searchString.charAt(0) == '#') { + if (isPositiveIntegral(searchString.substring(1))) { + Long searchId = Long.parseLong(searchString.substring(1)); + return getFilteredPlantListById(zone, searchId); + } else { + return new ArrayList<>(); + } + } else { + String caseInsensitiveSearchString = searchString.toLowerCase(); + return getFilteredPlantList(zone, plant -> + plant.name().toLowerCase().contains(caseInsensitiveSearchString) || + plant.description().toLowerCase().contains(caseInsensitiveSearchString) + ); + } + } + + + /** + * @param type GrowPhaseType to filter + * @param zone selected hardiness zone + * @param from the earliest date to for the filter + * @param to the lastest date for the filter + * @return List of Plants with selected saison + * @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified + * @throws IOException If the database cannot be accessed + */ + private List getFilteredPlantListBySaison(GrowthPhaseType type, HardinessZone zone, MonthDay from, MonthDay to) throws HardinessZoneNotSetException, IOException { + return getFilteredPlantList(zone, plant -> plant.lifecycle().stream().anyMatch(growthPhase -> growthPhase.startDate().compareTo(from) >= 0 && (growthPhase.startDate().compareTo(to) <= 0) && growthPhase.type() == type)); + } + + /** + * @param zone selected hardiness zone + * @param from the earliest date to for the filter + * @param to the lastest date for the filter + * @return List of Plants with selected saison + * @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified + * @throws IOException If the database cannot be accessed + */ + public List getFilteredPlantListByPlantingSaison(HardinessZone zone, MonthDay from, MonthDay to) throws HardinessZoneNotSetException, IOException { + return getFilteredPlantListBySaison(GrowthPhaseType.PLANT, zone, from, to); + } + + /** + * @param zone selected hardiness zone + * @param from the earliest date to for the filter + * @param to the lastest date for the filter + * @return List of Plants with selected saison + * @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified + * @throws IOException If the database cannot be accessed + */ + public List getFilteredPlantListByHarvestSaison(HardinessZone zone, MonthDay from, MonthDay to) throws HardinessZoneNotSetException, IOException { + return getFilteredPlantListBySaison(GrowthPhaseType.HARVEST, zone, from, to); + } + + /** + * @param zone selected hardiness zone + * @param from the earliest date to for the filter + * @param to the lastest date for the filter + * @return List of Plants with selected saison + * @throws HardinessZoneNotSetException If no {@link HardinessZone} was specified + * @throws IOException If the database cannot be accessed + */ + public List getFilteredPlantListBySaisonWithoutGrowthPhase(HardinessZone zone, MonthDay from, MonthDay to) throws HardinessZoneNotSetException, IOException { + return getFilteredPlantList(zone, plant -> plant.lifecycle().stream().anyMatch(growthPhase -> growthPhase.startDate().compareTo(from) >= 0 && (growthPhase.startDate().compareTo(to) <= 0))); + } + + /** + * Check if a string can safely be parsed as a positive Integral value (short/int/long) + * + * @param subject The string to be tested + * @return Whether the string contains only digits + */ + private boolean isPositiveIntegral(String subject) { + return subject != null && subject.matches("[0-9]+"); + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/taskList/PlantNotFoundException.java b/src/main/java/ch/zhaw/gartenverwaltung/taskList/PlantNotFoundException.java new file mode 100644 index 0000000..149e1ef --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/taskList/PlantNotFoundException.java @@ -0,0 +1,7 @@ +package ch.zhaw.gartenverwaltung.taskList; + +public class PlantNotFoundException extends Exception { + public PlantNotFoundException() { + super("The selected Plant was not found in Database!"); + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/taskList/TaskListModel.java b/src/main/java/ch/zhaw/gartenverwaltung/taskList/TaskListModel.java new file mode 100644 index 0000000..af0ca00 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/taskList/TaskListModel.java @@ -0,0 +1,186 @@ +package ch.zhaw.gartenverwaltung.taskList; + +import ch.zhaw.gartenverwaltung.Config; +import ch.zhaw.gartenverwaltung.io.*; +import ch.zhaw.gartenverwaltung.types.*; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class TaskListModel { + private TaskDatabase taskDatabase; + private PlantDatabase plantDatabase; + + /** + * Comparators to create sorted Task List + */ + static final Comparator sortByStartDate = Comparator.comparing(Task::getStartDate); + + public TaskListModel(){ + taskDatabase = new JsonTaskDatabase(); + plantDatabase = new JsonPlantDatabase(); + } + + /** + * Constructor to create Database Objects. + */ + public TaskListModel(TaskDatabase taskDatabase, PlantDatabase plantDatabase) { + this.taskDatabase = taskDatabase; + this.plantDatabase = plantDatabase; + } + + /** + * Method to save a new Task to Task Database + * @param task the Task to save + * @throws IOException If the database cannot be accessed + */ + public void addTask(Task task) throws IOException { + taskDatabase.saveTask(task); + } + + /** + * Method to add all Tasks for a new crop + * @param crop the crop which is added + * @throws PlantNotFoundException if the plantId in the crop doesn't exist in Plant Database + * @throws HardinessZoneNotSetException If there is no Hardiness Zone Set in Plant Database + * @throws IOException If the database cannot be accessed + */ + public void planTasksForCrop(Crop crop) throws PlantNotFoundException, HardinessZoneNotSetException, IOException { + Plant plant = plantDatabase.getPlantById(Config.getCurrentHardinessZone(), crop.getPlantId()).orElseThrow(PlantNotFoundException::new); + for (GrowthPhase growthPhase : plant.lifecycle()) { + for (TaskTemplate taskTemplate : growthPhase.taskTemplates()) { + addTask(taskTemplate.generateTask(crop.getStartDate(), crop.getCropId().orElse(0L))); + } + } + } + + /** + * Method to remove all Tasks for a specific Crop + * @param cropId The crop which is removed + * @throws IOException If the database cannot be accessed + */ + public void removeTasksForCrop(long cropId) throws IOException { + taskDatabase.removeTasksForCrop(cropId); + } + + /** + * Method to remove a Task from Database + * @param task the Task to remove + * @throws IOException If the database cannot be accessed + */ + public void removeTask(Task task) throws IOException { + taskDatabase.removeTask(task); + } + + private List filterListByCrop(List taskList, Long cropId) { + return taskList.stream().filter(task -> task.getCropId() == cropId).collect(Collectors.toList()); + } + + /** + * Method to get all Tasks + * @return List of all Tasks + * @throws IOException If the database cannot be accessed + */ + public List getTaskList() throws IOException { + return getFilteredTaskList(LocalDate.MIN, LocalDate.MAX); + } + + /** + * Method to get all Tasks for specific Crop + * @return List of all Tasks for with given CropID + * @throws IOException If the database cannot be accessed + */ + public List getTaskListForCrop(Long cropId) throws IOException { + return filterListByCrop(getTaskList(), cropId); + } + + /** + * Method to get all Tasks which are today or in future + * @return List of Tasks + * @throws IOException If the database cannot be accessed + */ + public List getFutureTasks() throws IOException { + return getFilteredTaskList(LocalDate.now(), LocalDate.MAX); + } + + /** + * Method to get all Tasks which are today or in future for specific Crop + * @return List of Tasks with given crop ID + * @throws IOException If the database cannot be accessed + */ + public List getFutureTasksForCrop(Long cropId) throws IOException { + return filterListByCrop(getFutureTasks(), cropId); + } + + /** + * Method to get all Tasks which are today or in past + * @return List of Tasks + * @throws IOException If the database cannot be accessed + */ + public List getPastTasks() throws IOException { + return getFilteredTaskList(LocalDate.MIN, LocalDate.now()); + } + + /** + * Method to get all Tasks which are today or in past for specifc crop + * @return List of Tasks with given grop id + * @throws IOException If the database cannot be accessed + */ + public List getPastTasksForCrop(Long cropId) throws IOException { + return filterListByCrop(getPastTasks(), cropId); + } + + /** + * Method to get an List of 7 Tasklists for the next 7 days. Index 0 is Tasklist for Today. + * @return List with length 7 (List>) + * @throws IOException If the database cannot be accessed + */ + public List> getTasksUpcomingWeek() throws IOException { + List> dayTaskList = new ArrayList<>(); + for(int i = 0; i < 7; i++) { + LocalDate date = LocalDate.now().plusDays(i); + dayTaskList.add(taskDatabase.getTaskList(date, date)); + } + return dayTaskList; + } + + /** + * Method to get an List of 7 Tasklists for the next 7 days. (Filtered Index 0 is Tasklist for Today. + * @return List with length 7 (List>) + * @throws IOException If the database cannot be accessed + */ + public List> getTasksUpcomingWeekForCrop(Long cropId) throws IOException { + List> dayTaskList = new ArrayList<>(); + for(int i = 0; i < 7; i++) { + LocalDate date = LocalDate.now().plusDays(i); + dayTaskList.add(filterListByCrop(taskDatabase.getTaskList(date, date), cropId)); + } + return dayTaskList; + } + + /** + * Method to get Tasklist filtered by date. + * @param start the start date for the filter + * @param end the end date for the filter + * @return List of Tasks matched by the filter + * @throws IOException If the database cannot be accessed + */ + public List getFilteredTaskList(LocalDate start, LocalDate end) throws IOException { + return getSortedTaskList(taskDatabase.getTaskList(start, end), sortByStartDate); + } + + /** + * Method to sort a Tasklist by a given Comparator + * @param taskList The Tasklist to sort + * @param comparator the comparator to sort + * @return a sorted coppy of the given Tasklist + */ + private List getSortedTaskList(List taskList, Comparator comparator) { + return taskList.stream().sorted(comparator).collect(Collectors.toList()); + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/Crop.java b/src/main/java/ch/zhaw/gartenverwaltung/types/Crop.java new file mode 100644 index 0000000..e8f539f --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/Crop.java @@ -0,0 +1,71 @@ +package ch.zhaw.gartenverwaltung.types; + +import java.time.LocalDate; +import java.util.Objects; +import java.util.Optional; + +public class Crop { + private Long cropId = null; + private final long plantId; + private final LocalDate startDate; + private double area = 1; + + /** + * Default Constructor (needed for deserialization) + */ + public Crop() { + plantId = 0; + startDate = null; + } + + public Crop(long plantId, LocalDate startDate) { + this.plantId = plantId; + this.startDate = startDate; + } + + // Builder-Style setter + public Crop withId(long cropId) { + this.cropId = cropId; + return this; + } + public Crop withArea(double area) { + this.area = area; + return this; + } + + // Getters + public Optional getCropId() { + return Optional.ofNullable(cropId); + } + + public long getPlantId() { return plantId; } + public LocalDate getStartDate() { return startDate; } + public double getArea() { return area; } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (other instanceof Crop otherCrop) { + return Objects.equals(this.cropId, otherCrop.cropId) && + plantId == otherCrop.plantId && + startDate != null && startDate.equals(otherCrop.startDate) && + area == otherCrop.area; + } + return false; + } + + @Override + public int hashCode() { + int startCode = startDate != null ? startDate.hashCode() : 0; + return (int) plantId ^ (startCode << 16); + } + + @Override + public String toString() { + return String.format("Crop [ cropId: %d, plantId: %d, startDate: %s, area: %f ]", + cropId, + plantId, + startDate, + area); + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java b/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java index 665b895..9348f2b 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/GrowthPhase.java @@ -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 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/HardinessZone.java b/src/main/java/ch/zhaw/gartenverwaltung/types/HardinessZone.java index 5d0bc91..da3c3c8 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/HardinessZone.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/HardinessZone.java @@ -5,5 +5,6 @@ package ch.zhaw.gartenverwaltung.types; * (Subject to later expansion) */ public enum HardinessZone { + ZONE_1A, ZONE_8A } 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 99336e7..9cb3d08 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java @@ -1,11 +1,105 @@ package ch.zhaw.gartenverwaltung.types; +import javafx.scene.image.Image; + +import java.time.LocalDate; +import java.util.LinkedList; import java.util.List; +import java.util.stream.Collectors; + +import static java.time.temporal.ChronoUnit.DAYS; public record Plant( long id, String name, String description, - int spacing, + Image image, + String spacing, + int light, + String soil, + List pests, List lifecycle) { + + /** + * remove all growthPhase which do not belong to the hardiness zone + * @param zone hardiness zone + */ + public void inZone(HardinessZone zone) { + lifecycle.removeIf(growthPhase -> !growthPhase.zone().equals(zone)); + } + + /** + * get all growthPhases of lifecycle group + * @param group lifecycle group + * @return list of growthPhases + */ + public List lifecycleForGroup(int group) { + return lifecycle.stream() + .filter(growthPhase -> growthPhase.group() == group) + .collect(Collectors.toList()); + } + + /** + * get sow date from given harvest day from lifecycle group + * @param harvestDate date of the harvest + * @param group lifecycle group + * @return LocaleDate of sow date + */ + public LocalDate sowDateFromHarvestDate(LocalDate harvestDate, int group) { + return harvestDate.minusDays(timeToHarvest(group)); + } + + /** + * calculate the days between sow and harvest day for lifecycle group + * @param group the lifecycle group + * @return Integer number of dates between sow and harvest day + */ + 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)); + } + + /** + * filter out the given growthPhase out of the lifecycle + * create list of dates for this growthPhase and return it + * @param growthPhase the wanted growthPhase + * @return a list of dates of the current year + */ + public List getDateListOfGrowthPhase(GrowthPhaseType growthPhase) { + List dates = new LinkedList<>(); + for (GrowthPhase growth : lifecycle) { + if (growth.type().equals(growthPhase)) { + dates = addDatesFromMonthDay(growth); + } + } + return dates; + } + + /** + * transform monthDay value of the given growthPhase to localDate + * return a list of dates from start to end of growth phase + * @param growthPhase the current growthPhase + * @return a list of dates of the current year + */ + private List addDatesFromMonthDay(GrowthPhase growthPhase) { + List dates = new LinkedList<>(); + LocalDate today = LocalDate.now(); + LocalDate start = growthPhase.startDate().atYear(today.getYear()); + LocalDate end = growthPhase.endDate().atYear(today.getYear()); + while (!start.isAfter(end)) { + dates.add(start); + start = start.plusDays(1); + } + return dates; + } } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/Seasons.java b/src/main/java/ch/zhaw/gartenverwaltung/types/Seasons.java new file mode 100644 index 0000000..2a2d2d0 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/Seasons.java @@ -0,0 +1,31 @@ +package ch.zhaw.gartenverwaltung.types; + +import java.time.MonthDay; + +public enum Seasons { + AllSEASONS("--01-01", "--12-31", "All Seasons"), + SPRING("--03-01", "--05-30", "Spring"), + SUMMER("--06-01", "--08-30", "Summer"), + AUTUMN("--09-01", "--11-30", "Autumn"), + WINTER("--12-01", "--02-28", "Winter"); + + public final String startDate; + public final String endDate; + public final String name; + + Seasons(String startDate, String endDate, String name) { + this.startDate = startDate; + this.endDate = endDate; + this.name = name; + } + + public MonthDay getStartDate() { + return MonthDay.parse(this.startDate); + } + public MonthDay getEndDate() { + return MonthDay.parse(this.endDate); + } + public String getName() { + return this.name; + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/Task.java b/src/main/java/ch/zhaw/gartenverwaltung/types/Task.java index d85435b..16de6c3 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,35 @@ 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; + private long cropId; - public Task(String name, String description, Date startDate) { + /** + * default constructor + * (used by Json deserializer) + */ + public Task(){ + name= ""; + description= ""; + startDate = LocalDate.now(); + } + + public Task(String name, String description, LocalDate startDate, long cropId) { this.name = name; this.description = description; - this.startDate = endDate; + this.startDate = startDate; + this.cropId = cropId; + } + + public Task(String name, String description, LocalDate startDate, LocalDate endDate, int interval, long cropId) { + this.name = name; + this.description = description; + this.startDate = startDate; + this.endDate = endDate; + this.interval = interval; + this.cropId = cropId; } // Builder-pattern-style setters @@ -30,21 +52,26 @@ public class Task { this.interval = interval; return this; } - public Task withEndDate(Date endDate) { + public Task withEndDate(LocalDate endDate) { this.endDate = endDate; return this; } + public boolean isInTimePeriode(LocalDate searchStartDate, LocalDate searchEndDate){ + return startDate.isAfter(searchStartDate) && startDate.isBefore(searchEndDate); + } + // Getters 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 long getCropId() { return cropId; } 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..8ae7862 --- /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, long cropId) { + Task task = new Task(name, description, realStartDate.plusDays(relativeStartDate), cropId); + if (relativeEndDate != null) { + task.withEndDate(realStartDate.plusDays(relativeEndDate)); + } + if (interval != null) { + task.withInterval(interval); + } + return task; + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/UserPlanting.java b/src/main/java/ch/zhaw/gartenverwaltung/types/UserPlanting.java deleted file mode 100644 index 804d388..0000000 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/UserPlanting.java +++ /dev/null @@ -1,10 +0,0 @@ -package ch.zhaw.gartenverwaltung.types; - -import java.util.Date; - -public record UserPlanting( - long plantId, - Date startDate, - int area -) { -} 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/java/module-info.java b/src/main/java/module-info.java index 4bffe36..6900f7f 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,8 +1,14 @@ module ch.zhaw.gartenverwaltung { requires javafx.controls; requires javafx.fxml; - + requires com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.datatype.jsr310; + requires com.fasterxml.jackson.datatype.jdk8; + requires java.logging; opens ch.zhaw.gartenverwaltung to javafx.fxml; + 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/CropDetail.fxml b/src/main/resources/ch/zhaw/gartenverwaltung/CropDetail.fxml new file mode 100644 index 0000000..ed3a476 --- /dev/null +++ b/src/main/resources/ch/zhaw/gartenverwaltung/CropDetail.fxml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ch/zhaw/gartenverwaltung/MySchedule.fxml b/src/main/resources/ch/zhaw/gartenverwaltung/MySchedule.fxml new file mode 100644 index 0000000..b778d50 --- /dev/null +++ b/src/main/resources/ch/zhaw/gartenverwaltung/MySchedule.fxml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ch/zhaw/gartenverwaltung/Plants.fxml b/src/main/resources/ch/zhaw/gartenverwaltung/Plants.fxml new file mode 100644 index 0000000..18af41a --- /dev/null +++ b/src/main/resources/ch/zhaw/gartenverwaltung/Plants.fxml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +