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/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/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..7c21101 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/MainFXMLController.java @@ -0,0 +1,107 @@ +package ch.zhaw.gartenverwaltung; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Node; +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; + +public class MainFXMLController implements Initializable { + /** + * Caching the panes + */ + private final Map panes = new HashMap<>(); + + @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) { + e.printStackTrace(); + } + } +} + 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..e223e8b --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/MyPlantsController.java @@ -0,0 +1,58 @@ +package ch.zhaw.gartenverwaltung; + +import ch.zhaw.gartenverwaltung.types.Plant; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.layout.VBox; + +import java.io.IOException; +import java.net.URL; +import java.util.LinkedList; +import java.util.List; +import java.util.ResourceBundle; + +public class MyPlantsController implements Initializable { + MainFXMLController mainController; + + @FXML + private Button addPlant_button; + + @FXML + private VBox myPlants_vbox; + + @FXML + void addPlant(ActionEvent event) throws IOException { + mainController.loadPane("Plants.fxml"); + } + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + //ToDo + List myPlants = getMyPlants(); + createPlantView(myPlants); + } + + private void createPlantView(List myPlants) { + //ToDo + for(Plant plant : myPlants) { + createPlantView(); + } + } + + public void getMainController(MainFXMLController controller) { + mainController = controller; + } + + private List getMyPlants() { + //ToDo method to get myPlantList(scheduled) + //Method to call all Plants saved + List myPlantList = new LinkedList<>(); + return myPlantList; + } + + private void createPlantView() { + //ToDo FXML Panel with Plant data + } +} 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..b16445a --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/MyScheduleController.java @@ -0,0 +1,124 @@ +package ch.zhaw.gartenverwaltung; + +import ch.zhaw.gartenverwaltung.types.Plant; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +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 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 Plant selectedPlant = null; + + @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; + + @Override + public void initialize(URL location, ResourceBundle resources) { + List plantList = new LinkedList<>(); + fillListViewMyPlantsInSchedule(plantList); + getSelectedPlantTask(); + setDayLabels(); + information_label.setText(""); + } + + private void getSelectedPlantTask() { + scheduledPlants_listview.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Plant oldValue, Plant newValue) { + if(newValue != null) { + selectedPlant = newValue; + //ToDo update day_panel with task for the day + } else { + selectedPlant = null; + //ToDo update day_panel with task for the day (all plants) + } + } + }); + } + + 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 fillListViewMyPlantsInSchedule(List list) { + for (Plant plant : list) { + scheduledPlants_listview.getItems().add(plant); + } + scheduledPlants_listview.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()); + } + } + }); + } + + +} 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..5554d3d --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/PlantsController.java @@ -0,0 +1,258 @@ +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.application.Platform; +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.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.geometry.Bounds; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.VBox; + +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.ResourceBundle; + +public class PlantsController implements Initializable { + 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 saveToMyPlant_button; + + @FXML + private TextField search_plants; + + /** + * saves the current selected plant in new JSON database + * @param event event + */ + @FXML + void saveToMyPlant(ActionEvent event) { + //ToDo model save selectedPlant to mySelectedPlant(IO) + } + + /** + * 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(""); + saveToMyPlant_button.setDisable(true); + + createFilterSeasons(); + createFilterHardinessZone(); + lookForSelectedListEntry(); + try { + viewFilteredListBySearch(); + } catch (HardinessZoneNotSetException | IOException e) { + e.printStackTrace(); + } + } + + /** + * 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 | IOException e) { + e.printStackTrace(); + } + } + }); + } + + /** + * 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 | IOException e) { + e.printStackTrace(); + } + } + + /** + * 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(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Boolean oldValue, Boolean 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.name()); + radioButton.setToggleGroup(seasonGroup); + radioButton.setPadding(new Insets(0,0,10,0)); + if (season.equals(Seasons.AllSEASONS)) { + radioButton.setSelected(true); + } + radioButton.selectedProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { + if (season.equals(Seasons.AllSEASONS)) { + fillPlantListWithHardinessZone(); + } else { + try { + viewFilteredListBySeason(season); + } catch (HardinessZoneNotSetException | IOException e) { + e.printStackTrace(); + } + } + + } + }); + seasons.getChildren().add(radioButton); + } + } + + /** + * observes changes in the selectedProperty of ListView and updates: + * the description label + * image of the plant + */ + private void lookForSelectedListEntry() { + list_plants.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Plant oldValue, Plant newValue) { + if(newValue != null) { + selectedPlant = newValue; + description_plant.setText(selectedPlant.description()); + saveToMyPlant_button.setDisable(false); + Image img; + if(selectedPlant.image() != null) { + img = selectedPlant.image(); + } else { + img = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png"))); + } + img_plant.setImage(img); + + + } else { + selectedPlant = null; + description_plant.setText(""); + saveToMyPlant_button.setDisable(true); + Image img = new Image(String.valueOf(PlantsController.class.getResource("placeholder.png"))); + img_plant.setImage(img); + } + } + }); + } + + + /** + * clears the ListView of entries + */ + private void clearListView() { + plantListProperty.clear(); + } +} 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..24757bd --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/gardenplan/Gardenplanmodel.java @@ -0,0 +1,8 @@ +package ch.zhaw.gartenverwaltung.gardenplan; + +public class Gardenplanmodel { +// private JasonGardenplan gardenplan; + // liste von crops + //task generieren + //plant holen, task template mit tasktemplateklasse tasks erstellen +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java index 3f84f23..f09d90e 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonPlantDatabase.java @@ -1,10 +1,13 @@ 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; @@ -31,11 +34,14 @@ public class JsonPlantDatabase implements PlantDatabase { * 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()); } /** @@ -76,8 +82,9 @@ public class JsonPlantDatabase implements PlantDatabase { } if (dataSource != null) { currentZone = zone; - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(timeModule); + ObjectMapper mapper = new ObjectMapper() + .registerModule(timeModule) + .registerModule(imageModule); List result; result = mapper.readerForListOf(Plant.class).readValue(dataSource); 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..e16a74d --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/JsonTaskDatabase.java @@ -0,0 +1,126 @@ +package ch.zhaw.gartenverwaltung.io; + +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 java.io.File; +import java.io.IOException; +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 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); + timeModule.addDeserializer(LocalDate.class, dateDeserializer); + } + + /** + * 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(); + } + + /** + * 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 { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(timeModule) + .registerModule(new Jdk8Module()); + + if(dataSource != null) { + mapper.writeValue(new File(dataSource.getFile()), taskMap); + } + } + + /** + * 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); + + List result; + result = mapper.readerForListOf(Task.class).readValue(dataSource); + + taskMap = result.stream() + .collect(HashMap::new, + (res, task) -> res.put(task.getId(), task), + (existing, replacement) -> {}); + } + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/io/TaskDatabase.java b/src/main/java/ch/zhaw/gartenverwaltung/io/TaskDatabase.java index 73cf8e0..19ad778 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/io/TaskDatabase.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/io/TaskDatabase.java @@ -1,13 +1,42 @@ package ch.zhaw.gartenverwaltung.io; +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; + + /** + * 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 index f3bb60f..285536a 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/json/GrowthPhaseTypeDeserializer.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/json/GrowthPhaseTypeDeserializer.java @@ -17,11 +17,12 @@ public class GrowthPhaseTypeDeserializer extends StdDeserializer { @Override public HardinessZone deserialize(JsonParser parser, DeserializationContext context) throws IOException { HardinessZone result = null; + String token = parser.getText(); try { - result = HardinessZone.valueOf(parser.getText().toUpperCase()); + result = HardinessZone.valueOf(token.toUpperCase()); } catch (IllegalArgumentException e) { // TODO: Log - System.err.println("bad growth phase type"); + System.err.printf("Unknown Hardiness Zone \"%s\"\n", token); } 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..f22cb35 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/json/PlantImageDeserializer.java @@ -0,0 +1,33 @@ +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) { + // TODO: Log + e.printStackTrace(); + System.err.printf("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 index bd12a28..4bfc9b1 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/plantList/PlantListModel.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/plantList/PlantListModel.java @@ -3,14 +3,17 @@ 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; @@ -19,8 +22,8 @@ public class PlantListModel { /** * Comparators to create sorted Plant List */ - static final Comparator sortByName = (Plant o1, Plant o2) -> o1.name().compareTo(o2.name()); - static final Comparator SortById = (Plant o1, Plant o2) -> Long.compare(o1.id(), o2.id()); + static final Comparator sortByName = Comparator.comparing(Plant::name); + static final Comparator SortById = Comparator.comparingLong(Plant::id); /** * Constructor to create Database Object. @@ -30,12 +33,12 @@ public class PlantListModel { setDefaultZone(); } - public PlantListModel(PlantDatabase plantDatabase){ + public PlantListModel(PlantDatabase plantDatabase) { this.plantDatabase = plantDatabase; setDefaultZone(); } - private void setDefaultZone(){ + private void setDefaultZone() { currentZone = HardinessZone.ZONE_8A; // TODO: get Default Zone from Config } @@ -49,6 +52,7 @@ public class PlantListModel { /** * Method to get actual Plant List in alphabetic Order + * * @return actual Plant List in alphabetic Order */ public List getPlantList(HardinessZone zone) throws HardinessZoneNotSetException, IOException { @@ -59,36 +63,39 @@ public class PlantListModel { /** * Method to get the actual Plant list in custom Order - * @param zone selected hardiness zone + * + * @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 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).toList(); + 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 + * @param zone selected hardiness zone * @return filterd list with plants in the hardinness zone - * @throws IOException If the database cannot be accessed + * @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).toList(); + 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 + * @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 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 { @@ -97,4 +104,80 @@ public class PlantListModel { 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) == '#') { + try { + return getFilteredPlantListById(zone, Long.parseLong(searchString.substring(1))); + } catch (NumberFormatException e) { + 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))); + } } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java b/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java index 13652a6..3250b4f 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/Plant.java @@ -1,7 +1,10 @@ package ch.zhaw.gartenverwaltung.types; +import javafx.scene.image.Image; + import java.time.LocalDate; import java.util.List; +import java.util.stream.Collectors; import static java.time.temporal.ChronoUnit.DAYS; @@ -9,6 +12,7 @@ public record Plant( long id, String name, String description, + Image image, String spacing, int light, String soil, @@ -22,7 +26,7 @@ public record Plant( public List lifecycleForGroup(int group) { return lifecycle.stream() .filter(growthPhase -> growthPhase.group() != group) - .toList(); + .collect(Collectors.toList()); } public LocalDate sowDateFromHarvestDate(LocalDate harvestDate, int group) { 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..5e907a4 --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/Seasons.java @@ -0,0 +1,26 @@ +package ch.zhaw.gartenverwaltung.types; + +import java.time.MonthDay; + +public enum Seasons { + AllSEASONS("--01-01", "--12-31"), + SPRING("--03-01", "--05-30"), + SOMMER("--06-01", "--08-30"), + AUTUM("--09-01", "--11-30"), + WINTER("--12-01", "--02-28"); + + public final String startDate; + public final String endDate; + + Seasons(String startDate, String endDate) { + this.startDate = startDate; + this.endDate = endDate; + } + + public MonthDay getStartDate() { + return MonthDay.parse(this.startDate); + } + public MonthDay getEndDate() { + return MonthDay.parse(this.endDate); + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/types/Task.java b/src/main/java/ch/zhaw/gartenverwaltung/types/Task.java index 90e00f4..3b26745 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/types/Task.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/types/Task.java @@ -16,11 +16,22 @@ public class Task { private Integer interval; private LocalDate endDate; + /** + * default constructor + * (used by Json deserializer) + */ + public Task(){ + name= ""; + description= ""; + startDate = LocalDate.now(); + } + public Task(String name, String description, LocalDate startDate) { this.name = name; this.description = description; this.startDate = startDate; } + public Task(String name, String description, LocalDate startDate, LocalDate endDate, int interval) { this.name = name; this.description = description; @@ -43,6 +54,13 @@ public class Task { return this; } + public boolean isInTimePeriode(LocalDate searchStartDate, LocalDate searchEndDate){ + if(startDate.isAfter(searchStartDate) &&startDate.isBefore(searchEndDate)){ + return true; + } + return false; + } + // Getters public long getId() { return id; } public String getName() { return name; } diff --git a/src/main/resources/ch/zhaw/gartenverwaltung/Home.fxml b/src/main/resources/ch/zhaw/gartenverwaltung/Home.fxml new file mode 100644 index 0000000..2bd24b3 --- /dev/null +++ b/src/main/resources/ch/zhaw/gartenverwaltung/Home.fxml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ch/zhaw/gartenverwaltung/MainFXML.fxml b/src/main/resources/ch/zhaw/gartenverwaltung/MainFXML.fxml new file mode 100644 index 0000000..569a89d --- /dev/null +++ b/src/main/resources/ch/zhaw/gartenverwaltung/MainFXML.fxml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + 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..4fbb89b --- /dev/null +++ b/src/main/resources/ch/zhaw/gartenverwaltung/Plants.fxml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +