From 31faa5aed481c6434cc971efe8efaa473cc9baaa Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Fri, 31 Jan 2025 15:56:44 +0100 Subject: [PATCH 01/18] Dodanie paska menu --- eMetrykant.iml | 21 ------ .../pl/koder95/eme/fx/PersonalDataView.java | 64 +++++++++++++++++-- .../pl/koder95/eme/fx/PersonalDataView.fxml | 24 +++++-- .../pl/koder95/eme/strings.properties | 17 +++-- 4 files changed, 91 insertions(+), 35 deletions(-) delete mode 100644 eMetrykant.iml diff --git a/eMetrykant.iml b/eMetrykant.iml deleted file mode 100644 index d3cdc04..0000000 --- a/eMetrykant.iml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/java/pl/koder95/eme/fx/PersonalDataView.java b/src/main/java/pl/koder95/eme/fx/PersonalDataView.java index e3c9117..8899fec 100644 --- a/src/main/java/pl/koder95/eme/fx/PersonalDataView.java +++ b/src/main/java/pl/koder95/eme/fx/PersonalDataView.java @@ -1,21 +1,28 @@ package pl.koder95.eme.fx; +import javafx.application.Platform; +import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; -import javafx.scene.control.Label; -import javafx.scene.control.TextField; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; import org.controlsfx.control.textfield.AutoCompletionBinding; import org.controlsfx.control.textfield.TextFields; import pl.koder95.eme.core.CabinetWorkers; import pl.koder95.eme.core.spi.CabinetAnalyzer; import pl.koder95.eme.core.spi.PersonalDataModel; +import pl.koder95.eme.dfs.IndexList; import java.net.URL; -import java.util.ListResourceBundle; -import java.util.Properties; import java.util.ResourceBundle; +import static pl.koder95.eme.Main.BUNDLE; + /** * Kontroler dla widoku modelu danych osobowych. * @@ -25,6 +32,8 @@ */ public class PersonalDataView implements Initializable { + @FXML + private BorderPane main; @FXML private Label personalData; @@ -82,6 +91,51 @@ private void setPersonalDataModel(PersonalDataModel model) { } else setPersonalDataModel(EMPTY_DATA); } + public void close(ActionEvent actionEvent) { + Alert a = new Alert(Alert.AlertType.CONFIRMATION); + Scene scene = main.getScene(); + if (scene != null) { + a.initOwner(scene.getWindow()); + } + a.setTitle(BUNDLE.getString("ALERT_CONFIRM_CLOSE_TITLE")); + a.setHeaderText(BUNDLE.getString("ALERT_CONFIRM_CLOSE_HEADER")); + a.setContentText(BUNDLE.getString("ALERT_CONFIRM_CLOSE_CONTENT")); + a.showAndWait(); + if (a.getResult() == ButtonType.OK) { + Platform.exit(); + } + } + + public void reload(ActionEvent actionEvent) { + Scene scene = main.getScene(); + if (scene != null) { + Dialog dialog = new Dialog<>(); + ProgressIndicator indicator = new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS); + Label label = new Label("Czekaj..."); + label.setFont(Font.font(label.getFont().getFamily(), FontWeight.BOLD, 24)); + VBox v = new VBox(indicator, label); + v.setMaxWidth(Double.MAX_VALUE); + v.setSpacing(10); + v.setFillWidth(true); + v.setAlignment(Pos.CENTER); + DialogPane pane = new DialogPane(); + pane.setContent(v); + dialog.setDialogPane(pane); + Thread thread = new Thread(() -> { + for (IndexList value : IndexList.values()) { + value.clear(); + value.load(); + } + Platform.runLater(() -> { + dialog.setResult(true); + dialog.close(); + }); + }); + dialog.setOnShown(event -> Platform.runLater(thread::start)); + dialog.show(); + } + } + private static final PersonalDataModel EMPTY_DATA = new PersonalDataModel() { @Override public String getSurname() { diff --git a/src/main/resources/pl/koder95/eme/fx/PersonalDataView.fxml b/src/main/resources/pl/koder95/eme/fx/PersonalDataView.fxml index c447062..6f2c141 100644 --- a/src/main/resources/pl/koder95/eme/fx/PersonalDataView.fxml +++ b/src/main/resources/pl/koder95/eme/fx/PersonalDataView.fxml @@ -5,7 +5,7 @@ - +
@@ -69,11 +69,11 @@ + + +
- - - @@ -84,6 +84,22 @@ + + + + + + + + + + + + + + + +
diff --git a/src/main/resources/pl/koder95/eme/strings.properties b/src/main/resources/pl/koder95/eme/strings.properties index 6468608..42fbc8b 100644 --- a/src/main/resources/pl/koder95/eme/strings.properties +++ b/src/main/resources/pl/koder95/eme/strings.properties @@ -1,9 +1,16 @@ -THR_ARG_TAG_IS_WRONG=Podany w\u0119ze\u0142 nie jest znacznikiem lub nie posiada atrybut\u00f3w. -THR_UPDATE_FILES_NOT_FOUND=Nie znaleziono odpowiednich plik\u00f3w do aktualizacji. -THR_USER_NOT_SELECTED=U\u017cytkownik nie zosta\u0142 wybrany. +THR_ARG_TAG_IS_WRONG=Podany w\u0119ze\u0142 nie jest znacznikiem lub nie posiada atrybut\u00F3w. +THR_UPDATE_FILES_NOT_FOUND=Nie znaleziono odpowiednich plik\u00F3w do aktualizacji. +THR_USER_NOT_SELECTED=U\u017Cytkownik nie zosta\u0142 wybrany. FX_LABEL_PERSONAL_DATA=NAZWISKO Imi\u0119 -FX_LABEL_NUMBER_OF_ACTS=Liczba wczytanych akt\u00f3w: +FX_LABEL_NUMBER_OF_ACTS=Liczba wczytanych akt\u00F3w: FX_TITLED_PANE_BAPTISM=Chrzest FX_TITLED_PANE_CONFIRMATION=Bierzmowanie FX_TITLED_PANE_MARRIAGE=\u015Alub -FX_TITLED_PANE_DECEASE=Pogrzeb \ No newline at end of file +FX_TITLED_PANE_DECEASE=Pogrzeb +FX_MENU_FILE=Plik +FX_MENUITEM_CLOSE=Zamknij +FX_MENUITEM_IMPORT=Importuj... +ALERT_CONFIRM_CLOSE_TITLE=Czy na pewno chcesz zamkn\u0105\u0107 program? +ALERT_CONFIRM_CLOSE_HEADER=Potwierd\u017A zamkni\u0119cie programu +ALERT_CONFIRM_CLOSE_CONTENT=Zanim naci\u015Bniesz \"OK\" upewnij si\u0119, \u017Ce zmiany zosta\u0142y zapisane, aby ich nie utraci\u0107. +FX_MENUITEM_RELOAD=Ponownie wczytaj dane \ No newline at end of file From 4b8ea48c672346460e27d42a193f35937b891f12 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Fri, 31 Jan 2025 16:59:27 +0100 Subject: [PATCH 02/18] Update IndexContainer.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dodanie domyślnych implementacji metod --- .../pl/koder95/eme/dfs/IndexContainer.java | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/main/java/pl/koder95/eme/dfs/IndexContainer.java b/src/main/java/pl/koder95/eme/dfs/IndexContainer.java index aa97aad..df4a18b 100644 --- a/src/main/java/pl/koder95/eme/dfs/IndexContainer.java +++ b/src/main/java/pl/koder95/eme/dfs/IndexContainer.java @@ -31,8 +31,14 @@ public interface IndexContainer { /** * @param id identyfikator {@literal >} 0 * @return indeks + * @throws IndexOutOfBoundsException identyfikator nie jest większy od 0 */ - Index get(int id); + default Index get(int id) { + if (id > 0) + return getLoaded().get(id - 1); + else + throw new IndexOutOfBoundsException("ID have to be greater than 0"); + } /** * @return lista wczytanych indeksów @@ -42,20 +48,48 @@ public interface IndexContainer { /** * Usuwa wczytane dane i zwalnia pamięć dla potencjalnie nowych danych. */ - void clear(); + default void clear() { + getLoaded().clear(); + } /** * @return liczba indeksów wczytanych oraz ostatni utworzony identyfikator */ - int size(); + default int size() { + return getLoaded().size(); + } /** * @return pierwszy indeks */ - Index getFirst(); + default Index getFirst() { + return get(1); + } /** * @return ostatni indeks */ - Index getLast(); + default Index getLast() { + return get(size()); + } + + /** + * @param i indeks + * @return kolejny indeks + */ + default Index getNext(Index i) { + List loaded = getLoaded(); + int index = loaded.indexOf(i); + return index < 0? null : loaded.get(index-1); + } + + /** + * @param i indeks + * @return poprzedni indeks + */ + default Index getPrev(Index i) { + List loaded = getLoaded(); + int index = loaded.indexOf(i); + return index < loaded.size()? loaded.get(index+1) : null; + } } From 4f1e88c7aecdc64f5130720be302e283152a3dda Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Fri, 31 Jan 2025 17:00:30 +0100 Subject: [PATCH 03/18] Update IndexList.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Usunięcie implementacji metod, które mają swoje domyślne implementacje --- .../java/pl/koder95/eme/dfs/IndexList.java | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/src/main/java/pl/koder95/eme/dfs/IndexList.java b/src/main/java/pl/koder95/eme/dfs/IndexList.java index c82e6ff..0738e35 100644 --- a/src/main/java/pl/koder95/eme/dfs/IndexList.java +++ b/src/main/java/pl/koder95/eme/dfs/IndexList.java @@ -73,11 +73,6 @@ public enum IndexList implements IndexContainer { this.name = name; this.nameQueue = nameQueue; } - - @Override - public Index get(int id) { - return loaded.get(id-1); - } @Override public List getLoaded() { @@ -113,46 +108,6 @@ public String getName() { return name; } - @Override - public int size() { - return loaded.size(); - } - - @Override - public Index getFirst() { - return get(size()); - } - - /** - * @param i indeks - * @return kolejny indeks - */ - public Index getNext(Index i) { - int index = loaded.indexOf(i); - return index < 0? null : loaded.get(index-1); - } - - /** - * @param i indeks - * @return poprzedni indeks - */ - public Index getPrev(Index i) { - int index = loaded.indexOf(i); - return index < loaded.size()? loaded.get(index+1) : null; - } - - @Override - public Index getLast() { - return get(1); - } - - @Override - public void clear() { - if (loaded == null) return; - loaded.clear(); - Main.releaseMemory(); - } - private static List BOOKS; private static void loadBooks() { From d616f6f094ca4ff66312e707f61e23e807470417 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Sat, 1 Feb 2025 20:37:51 +0100 Subject: [PATCH 04/18] Update CabinetWorker.java --- .../koder95/eme/core/spi/CabinetWorker.java | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/main/java/pl/koder95/eme/core/spi/CabinetWorker.java b/src/main/java/pl/koder95/eme/core/spi/CabinetWorker.java index cfe1ef4..45f97c8 100644 --- a/src/main/java/pl/koder95/eme/core/spi/CabinetWorker.java +++ b/src/main/java/pl/koder95/eme/core/spi/CabinetWorker.java @@ -47,36 +47,30 @@ public interface CabinetWorker { * Pracownik ładuje dane ze {@link DataSource źródła} do {@link FilingCabinet szafy aktowej}. */ default void load() { - Map> personalData = getDataSource().getPersonalData(); - personalData.forEach((surname, names) -> names.forEach((name) -> { - getCabinet().add(surname, name, createBriefcase( - getDataSource().getBaptism(surname, name), - getDataSource().getConfirmation(surname, name), - getDataSource().getMarriage(surname, name), - getDataSource().getDecease(surname, name)) - ); - })); + DataSource dataSource = getDataSource(); + Map> personalData = dataSource.getPersonalData(); + FilingCabinet cabinet = getCabinet(); + personalData.forEach((surname, names) -> names.forEach((name) -> cabinet.add(surname, name, createBriefcase( + dataSource.getBaptism(surname, name), + dataSource.getConfirmation(surname, name), + dataSource.getMarriage(surname, name), + dataSource.getDecease(surname, name)) + ))); } /** * Pracownik wysyła dane do {@link DataTarget celu}, aby zostały zapisane. */ default void save() { - Map> personalData = getCabinet().getPersonalData(); + FilingCabinet cabinet = getCabinet(); + Map> personalData = cabinet.getPersonalData(); + DataTarget dataTarget = getDataTarget(); personalData.forEach((surname, names) -> names.forEach((name) -> { - Briefcase briefcase = getCabinet().get(surname, name); - Arrays.stream(briefcase.getBaptism()).forEach(a -> { - getDataTarget().setBaptism(surname, name, a); - }); - Arrays.stream(briefcase.getConfirmation()).forEach(a -> { - getDataTarget().setBaptism(surname, name, a); - }); - Arrays.stream(briefcase.getMarriage()).forEach(a -> { - getDataTarget().setBaptism(surname, name, a); - }); - Arrays.stream(briefcase.getDecease()).forEach(a -> { - getDataTarget().setBaptism(surname, name, a); - }); + Briefcase briefcase = cabinet.get(surname, name); + Arrays.stream(briefcase.getBaptism()).forEach(a -> dataTarget.setBaptism(surname, name, a)); + Arrays.stream(briefcase.getConfirmation()).forEach(a -> dataTarget.setBaptism(surname, name, a)); + Arrays.stream(briefcase.getMarriage()).forEach(a -> dataTarget.setBaptism(surname, name, a)); + Arrays.stream(briefcase.getDecease()).forEach(a -> dataTarget.setBaptism(surname, name, a)); })); } } From bd23332ca35c39e4dcd2bf7ea3074aee28df5d26 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Thu, 13 Nov 2025 23:35:58 +0100 Subject: [PATCH 05/18] Migration to Java 21 Introduced module-info.java to enable Java module system, moved JavaFX Application logic from Main.java to new App.java, and updated pom.xml to use Java 21 and latest JavaFX, ControlsFX, and GitHub API dependencies. This refactor modernizes the project structure, improves maintainability, and prepares the application for future enhancements. --- pom.xml | 22 +++-- src/main/java/module-info.java | 13 +++ src/main/java/pl/koder95/eme/App.java | 102 ++++++++++++++++++++++++ src/main/java/pl/koder95/eme/Files.java | 40 +++++++++- src/main/java/pl/koder95/eme/Main.java | 83 ++----------------- 5 files changed, 176 insertions(+), 84 deletions(-) create mode 100644 src/main/java/module-info.java create mode 100644 src/main/java/pl/koder95/eme/App.java diff --git a/pom.xml b/pom.xml index 186dcc9..dad1188 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ https://github.com/koder95/eMetrykant pl.koder95 eMetrykant - 0.4.4 + 0.5.0-SNAPSHOT https://github.com/koder95/eMetrykant search, metric, church @@ -29,22 +29,34 @@ - 1.8 - 1.8 + 21 + 21 UTF-8 ${project.build.directory}/lib + 21.0.8 + 11.2.2 + + org.openjfx + javafx-fxml + ${javafx.version} + + + org.openjfx + javafx-controls + ${javafx.version} + org.controlsfx controlsfx - 8.40.18 + ${controlsfx.version} org.kohsuke github-api - 1.326 + 1.330 diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..b88027f --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,13 @@ +module eme { + requires java.xml; + requires javafx.base; + requires javafx.controls; + requires javafx.fxml; + requires javafx.graphics; + requires org.kohsuke.github.api; + requires org.controlsfx.controls; + + exports pl.koder95.eme; + opens pl.koder95.eme; + opens pl.koder95.eme.fx; +} \ No newline at end of file diff --git a/src/main/java/pl/koder95/eme/App.java b/src/main/java/pl/koder95/eme/App.java new file mode 100644 index 0000000..d4d84ee --- /dev/null +++ b/src/main/java/pl/koder95/eme/App.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2018 Kamil Jan Mularski [@koder95] + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package pl.koder95.eme; + +import static pl.koder95.eme.Main.*; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.image.Image; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import pl.koder95.eme.au.SelfUpdateTask; +import pl.koder95.eme.core.*; +import pl.koder95.eme.core.spi.CabinetAnalyzer; +import pl.koder95.eme.core.spi.FilingCabinet; +import pl.koder95.eme.dfs.IndexList; +import pl.koder95.eme.fx.PersonalDataView; +import pl.koder95.eme.git.RepositoryInfo; + +import java.net.URL; +import java.util.Arrays; + +public class App extends Application { + + private Parent root = null; + private SelfUpdateTask task = null; + + @Override + public void init() throws Exception { + super.init(); + task = new SelfUpdateTask(); + if (getParameters().getUnnamed().isEmpty()) { + Arrays.stream(IndexList.values()).forEach(IndexList::load); + } + + FilingCabinet cabinet = new TreeFilingCabinet(); + IndexListDataSource source = new IndexListDataSource(); + + SuggestionProvider suggestionProvider = new SuggestionProvider(cabinet); + AbstractCabinetAnalyzer worker = new SimpleCabinetAnalyzer(cabinet, source, null, suggestionProvider); + CabinetWorkers.register(CabinetAnalyzer.class, worker); + worker.load(); + + String resource = "PersonalDataView.fxml"; + URL url = PersonalDataView.class.getResource(resource); + root = FXMLLoader.load(url, BUNDLE); + } + + @Override + public void start(Stage primaryStage) { + primaryStage.getIcons().add(new Image(FAVICON_PATH)); + primaryStage.setTitle("eMetrykant " + Version.get()); + Version latestRelease = RepositoryInfo.get().getLatestReleaseVersion(); + if (latestRelease != null && + (latestRelease.compareTo(Version.get()) > 0 || !getParameters().getUnnamed().isEmpty())) { + ProgressBar updating = new ProgressBar(); + Label title = new Label(); + Label message = new Label(); + VBox root = new VBox(5d, title, updating, message); + + updating.setMinSize(400d, 35d); + updating.progressProperty().bind(task.progressProperty()); + title.textProperty().bind(task.titleProperty()); + message.textProperty().bind(task.messageProperty()); + + root.setAlignment(Pos.CENTER); + + primaryStage.initStyle(StageStyle.UTILITY); + primaryStage.setScene(new Scene(root)); + primaryStage.setAlwaysOnTop(true); + primaryStage.setResizable(false); + primaryStage.setMinWidth(400); + primaryStage.setMinHeight(100); + primaryStage.show(); + new Thread(task).start(); + } + else { + primaryStage.setScene(new Scene(root)); + primaryStage.setOnCloseRequest(event -> System.exit(0)); + primaryStage.show(); + } + } +} diff --git a/src/main/java/pl/koder95/eme/Files.java b/src/main/java/pl/koder95/eme/Files.java index 9da0d04..933955b 100644 --- a/src/main/java/pl/koder95/eme/Files.java +++ b/src/main/java/pl/koder95/eme/Files.java @@ -28,7 +28,45 @@ * @since 0.1.5 */ public final class Files { - + + /** + * Folder, gdzie znajdują się pliki konfiguracyjne użytkownika. + */ + public static final Path CONFIGS = getUserConfigsDir(); + + private static Path getUserConfigsDir() { + String os = System.getProperty("os.name").toLowerCase(); + String userHome = System.getProperty("user.home"); + + if (os.contains("win")) { + // Windows – klasyczne %LOCALAPPDATA% + String localAppData = System.getenv("LOCALAPPDATA"); + if (localAppData != null) { + return Path.of(localAppData); + } + // fallback (bardzo rzadko potrzebny) + return Path.of(userHome, "AppData", "Local"); + } + + if (os.contains("mac")) { + // macOS – odpowiednik ~/Library/Application Support + return Path.of(userHome, "Library", "Application Support"); + } + + // UNIX/Linux/BSD zgodnie z XDG + String xdgDataHome = System.getenv("XDG_DATA_HOME"); + if (xdgDataHome != null && !xdgDataHome.isBlank()) { + return Path.of(xdgDataHome); + } + + // domyślny katalog wg XDG + return Path.of(userHome, ".local", "share"); + } + + /** + * Folder, gdzie znajdują się pliki programu. + */ + public static final Path CONFIG_DIR = CONFIGS.resolve("eMetrykant"); /** * Folder, gdzie znajdują się pliki programu. */ diff --git a/src/main/java/pl/koder95/eme/Main.java b/src/main/java/pl/koder95/eme/Main.java index 4783edc..7653a83 100644 --- a/src/main/java/pl/koder95/eme/Main.java +++ b/src/main/java/pl/koder95/eme/Main.java @@ -17,28 +17,10 @@ package pl.koder95.eme; import javafx.application.Application; -import javafx.fxml.FXMLLoader; -import javafx.geometry.Pos; -import javafx.scene.Parent; -import javafx.scene.Scene; -import javafx.scene.control.Alert; -import javafx.scene.control.Label; -import javafx.scene.control.ProgressBar; -import javafx.scene.image.Image; -import javafx.scene.layout.VBox; -import javafx.stage.Stage; -import javafx.stage.StageStyle; import pl.koder95.eme.au.SelfUpdate; -import pl.koder95.eme.au.SelfUpdateTask; import pl.koder95.eme.core.*; -import pl.koder95.eme.core.spi.CabinetAnalyzer; -import pl.koder95.eme.core.spi.FilingCabinet; -import pl.koder95.eme.dfs.IndexList; -import pl.koder95.eme.git.RepositoryInfo; -import java.io.IOException; import java.text.Collator; -import java.util.Arrays; import java.util.Locale; import java.util.ResourceBundle; import java.util.regex.Pattern; @@ -50,7 +32,7 @@ * @version 0.4.4, 2024-12-02 * @since 0.0.201 */ -public class Main extends Application { +public class Main { /** * Domyślny pakiet językowy. */ @@ -92,71 +74,16 @@ else if (args[0].equals("-u")) { return; } } - Main.launch(args); + Application.launch(App.class, args); } - + /** * @since 0.0.203 * @see MemoryUtils#releaseMemory() + * @deprecated use {@link MemoryUtils#releaseMemory()} */ + @Deprecated public static void releaseMemory() { MemoryUtils.releaseMemory(); } - - private Parent root = null; - private SelfUpdateTask task = null; - - @Override - public void init() throws Exception { - super.init(); - task = new SelfUpdateTask(); - if (getParameters().getUnnamed().isEmpty()) { - Arrays.stream(IndexList.values()).forEach(IndexList::load); - } - - FilingCabinet cabinet = new TreeFilingCabinet(); - IndexListDataSource source = new IndexListDataSource(); - - SuggestionProvider suggestionProvider = new SuggestionProvider(cabinet); - AbstractCabinetAnalyzer worker = new SimpleCabinetAnalyzer(cabinet, source, null, suggestionProvider); - CabinetWorkers.register(CabinetAnalyzer.class, worker); - worker.load(); - - root = FXMLLoader.load(ClassLoader.getSystemResource("pl/koder95/eme/fx/PersonalDataView.fxml"), BUNDLE); - } - - @Override - public void start(Stage primaryStage) { - primaryStage.getIcons().add(new Image(FAVICON_PATH)); - primaryStage.setTitle("eMetrykant " + Version.get()); - Version latestRelease = RepositoryInfo.get().getLatestReleaseVersion(); - if (latestRelease != null && - (latestRelease.compareTo(Version.get()) > 0 || !getParameters().getUnnamed().isEmpty())) { - ProgressBar updating = new ProgressBar(); - Label title = new Label(); - Label message = new Label(); - VBox root = new VBox(5d, title, updating, message); - - updating.setMinSize(400d, 35d); - updating.progressProperty().bind(task.progressProperty()); - title.textProperty().bind(task.titleProperty()); - message.textProperty().bind(task.messageProperty()); - - root.setAlignment(Pos.CENTER); - - primaryStage.initStyle(StageStyle.UTILITY); - primaryStage.setScene(new Scene(root)); - primaryStage.setAlwaysOnTop(true); - primaryStage.setResizable(false); - primaryStage.setMinWidth(400); - primaryStage.setMinHeight(100); - primaryStage.show(); - new Thread(task).start(); - } - else { - primaryStage.setScene(new Scene(root)); - primaryStage.setOnCloseRequest(event -> System.exit(0)); - primaryStage.show(); - } - } } From 25a6e6dd50fbfec5175cfed3ad590f20cb840353 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Mon, 1 Dec 2025 22:15:18 +0100 Subject: [PATCH 06/18] Update ActNumber.java --- src/main/java/pl/koder95/eme/dfs/ActNumber.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/pl/koder95/eme/dfs/ActNumber.java b/src/main/java/pl/koder95/eme/dfs/ActNumber.java index e66031f..cdd4bbf 100644 --- a/src/main/java/pl/koder95/eme/dfs/ActNumber.java +++ b/src/main/java/pl/koder95/eme/dfs/ActNumber.java @@ -68,9 +68,10 @@ public String toString() { @Override public int compareTo(ActNumber o) { - if (getYear() > o.getYear()) return 1; - else if (getYear() < o.getYear()) return -1; - else { + int compared = Integer.compare(getYear(), o.getYear()); + if (compared != 0) { + return compared; + } else { Matcher digit1 = DIGITS_STRING_PATTERN.matcher(getSign()); Matcher digit2 = DIGITS_STRING_PATTERN.matcher(o.getSign()); if (digit1.find() && digit2.find()) { From 362b5b4217d774c87e8a441a8e64798901b1dfcd Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Sat, 13 Dec 2025 23:25:12 +0100 Subject: [PATCH 07/18] Refactor App update logic and parameter handling Extracted unnamed parameter check into a helper method and refactored update logic in the start method for clarity. Introduced an overloaded start method to handle update scenarios more cleanly and improved readability by reducing inline logic. --- src/main/java/pl/koder95/eme/App.java | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/pl/koder95/eme/App.java b/src/main/java/pl/koder95/eme/App.java index d4d84ee..99e8652 100644 --- a/src/main/java/pl/koder95/eme/App.java +++ b/src/main/java/pl/koder95/eme/App.java @@ -43,12 +43,16 @@ public class App extends Application { private Parent root = null; private SelfUpdateTask task = null; + + private boolean isRunWithoutUnnamedParameters() { + return getParameters().getUnnamed().isEmpty(); + } @Override public void init() throws Exception { super.init(); task = new SelfUpdateTask(); - if (getParameters().getUnnamed().isEmpty()) { + if (isRunWithoutUnnamedParameters()) { Arrays.stream(IndexList.values()).forEach(IndexList::load); } @@ -56,7 +60,7 @@ public void init() throws Exception { IndexListDataSource source = new IndexListDataSource(); SuggestionProvider suggestionProvider = new SuggestionProvider(cabinet); - AbstractCabinetAnalyzer worker = new SimpleCabinetAnalyzer(cabinet, source, null, suggestionProvider); + CabinetAnalyzer worker = new SimpleCabinetAnalyzer(cabinet, source, null, suggestionProvider); CabinetWorkers.register(CabinetAnalyzer.class, worker); worker.load(); @@ -67,11 +71,16 @@ public void init() throws Exception { @Override public void start(Stage primaryStage) { + Version latestRelease = RepositoryInfo.get().getLatestReleaseVersion(); + boolean runWithOptions = !isRunWithoutUnnamedParameters() && getParameters().getUnnamed().contains("-u"); + boolean isNotLatestVersion = latestRelease != null && latestRelease.compareTo(Version.get()) > 0; + start(primaryStage, isNotLatestVersion || runWithOptions); + } + + public void start(Stage primaryStage, boolean update) { primaryStage.getIcons().add(new Image(FAVICON_PATH)); primaryStage.setTitle("eMetrykant " + Version.get()); - Version latestRelease = RepositoryInfo.get().getLatestReleaseVersion(); - if (latestRelease != null && - (latestRelease.compareTo(Version.get()) > 0 || !getParameters().getUnnamed().isEmpty())) { + if (update) { ProgressBar updating = new ProgressBar(); Label title = new Label(); Label message = new Label(); @@ -92,8 +101,7 @@ public void start(Stage primaryStage) { primaryStage.setMinHeight(100); primaryStage.show(); new Thread(task).start(); - } - else { + } else { primaryStage.setScene(new Scene(root)); primaryStage.setOnCloseRequest(event -> System.exit(0)); primaryStage.show(); From dbd77dbbd4b8a26dec5092703d0cdc66fce177c3 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Sat, 13 Dec 2025 23:26:25 +0100 Subject: [PATCH 08/18] Create PersonalData.java --- .../pl/koder95/eme/model/PersonalData.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/main/java/pl/koder95/eme/model/PersonalData.java diff --git a/src/main/java/pl/koder95/eme/model/PersonalData.java b/src/main/java/pl/koder95/eme/model/PersonalData.java new file mode 100644 index 0000000..76f727e --- /dev/null +++ b/src/main/java/pl/koder95/eme/model/PersonalData.java @@ -0,0 +1,40 @@ +package pl.koder95.eme.model; + +import pl.koder95.eme.core.spi.PersonalDataModel; + +public record PersonalData(String surname, String name, + String ban, String can, String man, String dan) + implements PersonalDataModel { + private static final String NOMEN = "N."; + private static final String EAN = "–"; + + @Override + public String getSurname() { + return surname == null || surname.isBlank() ? NOMEN : surname; + } + + @Override + public String getName() { + return name == null || name.isBlank() ? NOMEN : name; + } + + @Override + public String getBaptismAN() { + return ban == null || ban.isBlank() ? EAN : ban; + } + + @Override + public String getConfirmationAN() { + return can == null || can.isBlank() ? EAN : can; + } + + @Override + public String getMarriageAN() { + return man == null || man.isBlank() ? EAN : man; + } + + @Override + public String getDeceaseAN() { + return dan == null || dan.isBlank() ? EAN : dan; + } +} From dc84f5a70248ebe4067921d8831edff7abf5cf24 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Thu, 11 Dec 2025 23:33:16 +0100 Subject: [PATCH 09/18] Create IndexNodeInterpreter and implementation --- .../koder95/eme/dfs/IndexNodeInterpreter.java | 8 ++++++ .../dfs/impl/IndexNodeInterpreterImpl.java | 27 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/main/java/pl/koder95/eme/dfs/IndexNodeInterpreter.java create mode 100644 src/main/java/pl/koder95/eme/dfs/impl/IndexNodeInterpreterImpl.java diff --git a/src/main/java/pl/koder95/eme/dfs/IndexNodeInterpreter.java b/src/main/java/pl/koder95/eme/dfs/IndexNodeInterpreter.java new file mode 100644 index 0000000..6567c70 --- /dev/null +++ b/src/main/java/pl/koder95/eme/dfs/IndexNodeInterpreter.java @@ -0,0 +1,8 @@ +package pl.koder95.eme.dfs; + +import java.util.Map; +import org.w3c.dom.Node; + +public interface IndexNodeInterpreter { + Map interpret(Node node); +} diff --git a/src/main/java/pl/koder95/eme/dfs/impl/IndexNodeInterpreterImpl.java b/src/main/java/pl/koder95/eme/dfs/impl/IndexNodeInterpreterImpl.java new file mode 100644 index 0000000..f2975cb --- /dev/null +++ b/src/main/java/pl/koder95/eme/dfs/impl/IndexNodeInterpreterImpl.java @@ -0,0 +1,27 @@ +package pl.koder95.eme.dfs.impl; + +import java.util.HashMap; +import java.util.Map; + +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import pl.koder95.eme.dfs.IndexNodeInterpreter; + +public class IndexNodeInterpreterImpl implements IndexNodeInterpreter { + @Override + public Map interpret(Node node) { + if (node == null || node.getNodeName().equalsIgnoreCase("index")) { + throw new IllegalArgumentException("Node is null or node is not an index"); + } + Map data = new HashMap<>(); + NamedNodeMap attrs = node.getAttributes(); // pobiera listę atrybutów + while (attrs.getLength() > 0) { + Node attr = attrs.item(0); // pobiera pierwszy atrybut + String key = attr.getNodeName(); // - nazwa atrybutu + String value = attr.getTextContent(); // - wartość atrybutu + data.put(key, value); // dodanie nazwy i wartości atrybutu do danych + attrs.removeNamedItem(key); // usuwa odczytany atrybut + } + return data; + } +} From f4bd17980b185ea610ece3d49580d03d3c02f2d9 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Thu, 11 Dec 2025 23:01:11 +0100 Subject: [PATCH 10/18] Refactor Index class Refactored the Index class to centralize data initialization and attribute interpretation. Introduced a static interpret method for attribute extraction and updated constructors to use this method, improving code clarity and immutability of the data map. Add create method to Index for data map Introduces a static create method to the Index class, allowing instantiation from a data map. The method returns null if the map is null or missing the 'an' key, improving safety and convenience when creating Index objects from map data. Refactor Index.create to simplify validation logic Removed the separate validate method and integrated its logic into the create method by delegating to another create overload. This streamlines the creation and validation process for Index instances. --- src/main/java/pl/koder95/eme/dfs/Index.java | 52 +++++++++------------ 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/src/main/java/pl/koder95/eme/dfs/Index.java b/src/main/java/pl/koder95/eme/dfs/Index.java index f5d16b8..506832d 100644 --- a/src/main/java/pl/koder95/eme/dfs/Index.java +++ b/src/main/java/pl/koder95/eme/dfs/Index.java @@ -17,11 +17,10 @@ package pl.koder95.eme.dfs; -import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import pl.koder95.eme.Visited; +import pl.koder95.eme.dfs.impl.IndexNodeInterpreterImpl; -import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -31,58 +30,49 @@ * przechowywanych na parafii. * * @author Kamil Jan Mularski [@koder95] - * @version 0.4.0, 2020-08-26 + * @version 0.5.0, 2025-12-11 * @since 0.1.11 */ public class Index implements Visited { - private final Map data = new HashMap<>(); // dane indeksu + private static final IndexNodeInterpreter NODE_INTERPRETER = new IndexNodeInterpreterImpl(); + private final Map data; // dane indeksu private ActNumber an; private final Book owner; - - private Index(Book owner, Node index) { - this.owner = owner; - NamedNodeMap attrs = index.getAttributes(); // pobiera listę atrybutów - while (attrs.getLength() > 0) { - Node attr = attrs.item(0); // pobiera pierwszy atrybut - String key = attr.getNodeName(); // - nazwa atrybutu - String value = attr.getTextContent(); // - wartość atrybutu - data.put(key, value); // dodanie nazwy i wartości atrybutu do danych - attrs.removeNamedItem(key); // usuwa odczytany atrybut - } - } - - private Index(Book owner) { + + private Index(Book owner, Map data, ActNumber an) { this.owner = owner; + this.an = an; + this.data = Map.copyOf(data); } /** * Tworzy nowy indeks na podstawie węzła obiektowego modelu dokumentu XML. * + * @param owner księga, do której indeks należy * @param index węzeł XML * @return nowy indeks lub {@code null} jeśli węzeł {@code index} nazywa się * inaczej niż "index" */ - public static Index create(Node index) { - return create(null, index); + public static Index create(Book owner, Node index) { + if (index == null || !index.hasAttributes()) return new Index(owner, Map.of(), null); + if (!index.getNodeName().equalsIgnoreCase("index")) return null; + return create(owner, NODE_INTERPRETER.interpret(index)); } /** - * Tworzy nowy indeks na podstawie węzła obiektowego modelu dokumentu XML. + * Tworzy nowy indeks na podstawie mapy danych. * * @param owner księga, do której indeks należy - * @param index węzeł XML - * @return nowy indeks lub {@code null} jeśli węzeł {@code index} nazywa się - * inaczej niż "index" + * @param data mapa danych + * @since 0.5.0 + * @return nowy indeks lub {@code null}, jeśli mapa to {@code null} lub jeśli nie znaleziono numeru aktu */ - public static Index create(Book owner, Node index) { - if (index == null) return new Index(owner); - if (!index.getNodeName().equalsIgnoreCase("index")) return null; - Index i = index.hasAttributes()? new Index(owner, index) : new Index(owner); - return i.getDataNames().contains("an") && !i.getData("an").isEmpty()? - i : null; + public static Index create(Book owner, Map data) { + if (data == null || !data.containsKey("an")) return null; + return new Index(owner, data, ActNumber.parseActNumber(data.get("an"))); } - + /** * @param name nazwa unikatowa określająca informacje, którą indeks * przechowuje From c406ab51f279597f05b3510c96a38d548274afbf Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Sat, 13 Dec 2025 23:28:13 +0100 Subject: [PATCH 11/18] Create DataService.java --- .../java/pl/koder95/eme/service/DataService.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/pl/koder95/eme/service/DataService.java diff --git a/src/main/java/pl/koder95/eme/service/DataService.java b/src/main/java/pl/koder95/eme/service/DataService.java new file mode 100644 index 0000000..426cf80 --- /dev/null +++ b/src/main/java/pl/koder95/eme/service/DataService.java @@ -0,0 +1,13 @@ +package pl.koder95.eme.service; + +import pl.koder95.eme.core.spi.PersonalDataModel; + +import java.util.Collection; +import java.util.function.Predicate; + +public interface DataService { + void load(); + void save(); + Collection findPeople(Predicate predicate); + Collection findPeople(String userText, boolean cancelled); +} From 3816488b64f67649b8021010051ff1995c0a5211 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Fri, 12 Dec 2025 00:06:07 +0100 Subject: [PATCH 12/18] Create BookType.java --- src/main/java/pl/koder95/eme/dfs/BookType.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/pl/koder95/eme/dfs/BookType.java diff --git a/src/main/java/pl/koder95/eme/dfs/BookType.java b/src/main/java/pl/koder95/eme/dfs/BookType.java new file mode 100644 index 0000000..1f42a98 --- /dev/null +++ b/src/main/java/pl/koder95/eme/dfs/BookType.java @@ -0,0 +1,8 @@ +package pl.koder95.eme.dfs; + +public enum BookType { + LIBER_BAPTISMORUM, + LIBER_CONFIRMATORUM, + LIBER_MATRIMONIORUM, + LIBER_DEFUNCTORUM +} From 2c6725ffb690ca1e5ebb7e55da2a1fd077069920 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Fri, 12 Dec 2025 01:25:01 +0100 Subject: [PATCH 13/18] Add IndexFactory interface and implementations Introduced the IndexFactory interface and two implementations: BasicIndexFactory and StandardIndexFactory. These classes provide a flexible way to create Index objects based on input data and book type matching logic. --- .../pl/koder95/eme/factory/IndexFactory.java | 11 ++++++ .../eme/factory/impl/BasicIndexFactory.java | 30 ++++++++++++++++ .../factory/impl/StandardIndexFactory.java | 35 +++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 src/main/java/pl/koder95/eme/factory/IndexFactory.java create mode 100644 src/main/java/pl/koder95/eme/factory/impl/BasicIndexFactory.java create mode 100644 src/main/java/pl/koder95/eme/factory/impl/StandardIndexFactory.java diff --git a/src/main/java/pl/koder95/eme/factory/IndexFactory.java b/src/main/java/pl/koder95/eme/factory/IndexFactory.java new file mode 100644 index 0000000..dc10964 --- /dev/null +++ b/src/main/java/pl/koder95/eme/factory/IndexFactory.java @@ -0,0 +1,11 @@ +package pl.koder95.eme.factory; + +import pl.koder95.eme.dfs.BookType; +import pl.koder95.eme.dfs.Index; + +import java.util.Map; + +public interface IndexFactory { + + Index create(Map data); +} diff --git a/src/main/java/pl/koder95/eme/factory/impl/BasicIndexFactory.java b/src/main/java/pl/koder95/eme/factory/impl/BasicIndexFactory.java new file mode 100644 index 0000000..79e33fb --- /dev/null +++ b/src/main/java/pl/koder95/eme/factory/impl/BasicIndexFactory.java @@ -0,0 +1,30 @@ +package pl.koder95.eme.factory.impl; + +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import pl.koder95.eme.dfs.Book; +import pl.koder95.eme.dfs.BookType; +import pl.koder95.eme.dfs.Index; +import pl.koder95.eme.dfs.IndexNodeInterpreter; +import pl.koder95.eme.factory.IndexFactory; + +public class BasicIndexFactory implements IndexFactory { + + private final Function, BookType> bookTypeMatcher; + private final Map bookMap; + + public BasicIndexFactory(Function, BookType> bookTypeMatcher, Map bookMap) { + this.bookTypeMatcher = bookTypeMatcher; + this.bookMap = bookMap; + } + + @Override + public Index create(Map data) { + if (data == null) return null; + BookType bookType = bookTypeMatcher.apply(data.keySet()); + if (bookType == null) return null; + return Index.create(bookMap.get(bookType), data); + } +} diff --git a/src/main/java/pl/koder95/eme/factory/impl/StandardIndexFactory.java b/src/main/java/pl/koder95/eme/factory/impl/StandardIndexFactory.java new file mode 100644 index 0000000..1571feb --- /dev/null +++ b/src/main/java/pl/koder95/eme/factory/impl/StandardIndexFactory.java @@ -0,0 +1,35 @@ +package pl.koder95.eme.factory.impl; + +import pl.koder95.eme.dfs.Book; +import pl.koder95.eme.dfs.BookType; + +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.function.BiPredicate; + +public class StandardIndexFactory extends BasicIndexFactory { + + public StandardIndexFactory(BiPredicate, BookType> bookTypePattern) { + super(strings -> Arrays.stream(BookType.values()) + .filter(bookType -> bookTypePattern.test(strings, bookType)) + .findFirst().orElseThrow(), + Map.of( + BookType.LIBER_BAPTISMORUM, new Book("Księga ochrzczonych"), + BookType.LIBER_CONFIRMATORUM, new Book("Księga bierzmowanych"), + BookType.LIBER_MATRIMONIORUM, new Book("Księga zaślubionych"), + BookType.LIBER_DEFUNCTORUM, new Book("Księga zmarłych") + ) + ); + } + + public StandardIndexFactory() { + this((strings, bookType) -> switch (bookType) { + case null -> false; + case LIBER_CONFIRMATORUM -> strings.contains("confirmation-name"); + case LIBER_DEFUNCTORUM -> strings.contains("death-date-time"); + case LIBER_MATRIMONIORUM -> strings.contains("husband-surname"); + default -> strings.contains("surname"); + }); + } +} From ff50342a0ad15743d8e29bd294f3e29762e6fc25 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Tue, 16 Dec 2025 16:28:47 +0100 Subject: [PATCH 14/18] Refactor reload logic and add ProgressDialog class Moved the progress dialog and index list reloading logic from PersonalDataView to a new ProgressDialog class for better separation of concerns. Refactored PersonalDataView to use the new ProgressDialog and improved method structure for input handling and auto-completion. --- .../pl/koder95/eme/fx/PersonalDataView.java | 77 ++++++++----------- .../pl/koder95/eme/fx/ProgressDialog.java | 67 ++++++++++++++++ 2 files changed, 98 insertions(+), 46 deletions(-) create mode 100644 src/main/java/pl/koder95/eme/fx/ProgressDialog.java diff --git a/src/main/java/pl/koder95/eme/fx/PersonalDataView.java b/src/main/java/pl/koder95/eme/fx/PersonalDataView.java index 8899fec..12f8aa3 100644 --- a/src/main/java/pl/koder95/eme/fx/PersonalDataView.java +++ b/src/main/java/pl/koder95/eme/fx/PersonalDataView.java @@ -50,33 +50,41 @@ public class PersonalDataView implements Initializable { private Label numberOfActs; @FXML - private Object searching; + private TextField searching; @Override public void initialize(URL location, ResourceBundle resources) { - CabinetAnalyzer analyzer = CabinetWorkers.get(CabinetAnalyzer.class); - if (searching instanceof TextField) { - TextField field = (TextField) searching; - AutoCompletionBinding autoCompletionBinding = TextFields.bindAutoCompletion( - field, - analyzer.getSuggestionProvider(), - analyzer.getPersonalDataConverter() - ); - autoCompletionBinding.setOnAutoCompleted(event -> setPersonalDataModel(event.getCompletion())); - field.setOnAction(event -> setPersonalDataModel( - analyzer.getPersonalDataConverter().fromString(field.getText()) - )); - field.textProperty().addListener( - (observable, oldValue, newValue) -> { - if (oldValue.length() < newValue.length()) { - field.setText(newValue.toUpperCase()); - } - } - ); - } + installCabinetAnalyzer(CabinetWorkers.get(CabinetAnalyzer.class)); + } + + private void installCabinetAnalyzer(CabinetAnalyzer analyzer) { + setupAutoCompletion(analyzer); + setupInputActions(analyzer); numberOfActs.setText(analyzer.getNumberOfActs() + ""); } + private void setupInputActions(CabinetAnalyzer analyzer) { + searching.setOnAction(event -> setPersonalDataModel( + analyzer.getPersonalDataConverter().fromString(searching.getText()) + )); + searching.textProperty().addListener( + (observable, oldValue, newValue) -> { + if (oldValue.length() < newValue.length()) { + searching.setText(newValue.toUpperCase()); + } + } + ); + } + + private void setupAutoCompletion(CabinetAnalyzer analyzer) { + AutoCompletionBinding binding = TextFields.bindAutoCompletion( + searching, + analyzer.getSuggestionProvider(), + analyzer.getPersonalDataConverter() + ); + binding.setOnAutoCompleted(event -> setPersonalDataModel(event.getCompletion())); + } + private void setPersonalDataModel(PersonalDataModel model) { if (model != null) { String personalData = model.getSurname().toUpperCase(); @@ -106,33 +114,10 @@ public void close(ActionEvent actionEvent) { } } - public void reload(ActionEvent actionEvent) { + public void reload() { Scene scene = main.getScene(); if (scene != null) { - Dialog dialog = new Dialog<>(); - ProgressIndicator indicator = new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS); - Label label = new Label("Czekaj..."); - label.setFont(Font.font(label.getFont().getFamily(), FontWeight.BOLD, 24)); - VBox v = new VBox(indicator, label); - v.setMaxWidth(Double.MAX_VALUE); - v.setSpacing(10); - v.setFillWidth(true); - v.setAlignment(Pos.CENTER); - DialogPane pane = new DialogPane(); - pane.setContent(v); - dialog.setDialogPane(pane); - Thread thread = new Thread(() -> { - for (IndexList value : IndexList.values()) { - value.clear(); - value.load(); - } - Platform.runLater(() -> { - dialog.setResult(true); - dialog.close(); - }); - }); - dialog.setOnShown(event -> Platform.runLater(thread::start)); - dialog.show(); + ProgressDialog.start(); } } diff --git a/src/main/java/pl/koder95/eme/fx/ProgressDialog.java b/src/main/java/pl/koder95/eme/fx/ProgressDialog.java new file mode 100644 index 0000000..5ccc3ca --- /dev/null +++ b/src/main/java/pl/koder95/eme/fx/ProgressDialog.java @@ -0,0 +1,67 @@ +package pl.koder95.eme.fx; + +import javafx.application.Platform; +import javafx.geometry.Pos; +import javafx.scene.Parent; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.layout.VBox; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import pl.koder95.eme.dfs.IndexList; + +import java.util.concurrent.atomic.AtomicLong; + +public class ProgressDialog extends Dialog { + private static final AtomicLong PROGRESS_DIALOG_THREAD_SEQ = new AtomicLong(); + + private ProgressDialog() { + this.setDialogPane(createDialogPane()); + setOnShown(event -> Platform.runLater(createProgressThread()::start)); + } + + private Thread createProgressThread() { + return new Thread(() -> { + reloadAllIndexLists(); + Platform.runLater(() -> { + setResult(true); + close(); + }); + }, "Progress Dialog Thread #" + PROGRESS_DIALOG_THREAD_SEQ.incrementAndGet()); + } + + private DialogPane createDialogPane() { + ProgressIndicator indicator = new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS); + Label label = new Label("Czekaj..."); + label.setFont(Font.font(label.getFont().getFamily(), FontWeight.BOLD, 24)); + DialogPane pane = new DialogPane(); + pane.setContent(createMainPane(indicator, label)); + return pane; + } + + private Parent createMainPane(ProgressIndicator indicator, Label label) { + VBox main = new VBox(indicator, label); + main.setMaxWidth(Double.MAX_VALUE); + main.setSpacing(10); + main.setFillWidth(true); + main.setAlignment(Pos.CENTER); + return main; + } + + private static void reloadAllIndexLists() { + for (IndexList value : IndexList.values()) { + reload(value); + } + } + + private static void reload(IndexList value) { + value.clear(); + value.load(); + } + + public static void start() { + Platform.runLater(() -> new ProgressDialog().showAndWait()); + } +} From 03c86f0abb516d09f139b38fe98fcca05a12c815 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Thu, 11 Dec 2025 23:01:11 +0100 Subject: [PATCH 15/18] Refactor Index class constructors Refactored the Index class to centralize data initialization and attribute interpretation. Introduced a static interpret method for attribute extraction and updated constructors to use this method, improving code clarity and immutability of the data map. --- src/main/java/pl/koder95/eme/dfs/Index.java | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/main/java/pl/koder95/eme/dfs/Index.java b/src/main/java/pl/koder95/eme/dfs/Index.java index 506832d..1292f92 100644 --- a/src/main/java/pl/koder95/eme/dfs/Index.java +++ b/src/main/java/pl/koder95/eme/dfs/Index.java @@ -57,22 +57,17 @@ private Index(Book owner, Map data, ActNumber an) { public static Index create(Book owner, Node index) { if (index == null || !index.hasAttributes()) return new Index(owner, Map.of(), null); if (!index.getNodeName().equalsIgnoreCase("index")) return null; - return create(owner, NODE_INTERPRETER.interpret(index)); + Map data = NODE_INTERPRETER.interpret(index); + return validate(new Index(owner, data, ActNumber.parseActNumber(data.get("an")))); } - /** - * Tworzy nowy indeks na podstawie mapy danych. - * - * @param owner księga, do której indeks należy - * @param data mapa danych - * @since 0.5.0 - * @return nowy indeks lub {@code null}, jeśli mapa to {@code null} lub jeśli nie znaleziono numeru aktu - */ - public static Index create(Book owner, Map data) { - if (data == null || !data.containsKey("an")) return null; - return new Index(owner, data, ActNumber.parseActNumber(data.get("an"))); + private static Index validate(Index index) { + if (index == null || !index.getDataNames().contains("an") || index.getData("an").isEmpty()) { + return null; + } + return index; } - + /** * @param name nazwa unikatowa określająca informacje, którą indeks * przechowuje From a7ee6ed2eed7e1cf12ce12182495dd75d950d449 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Fri, 12 Dec 2025 00:00:15 +0100 Subject: [PATCH 16/18] Add create method to Index for data map Introduces a static create method to the Index class, allowing instantiation from a data map. The method returns null if the map is null or missing the 'an' key, improving safety and convenience when creating Index objects from map data. --- src/main/java/pl/koder95/eme/dfs/Index.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/pl/koder95/eme/dfs/Index.java b/src/main/java/pl/koder95/eme/dfs/Index.java index 1292f92..e7bd001 100644 --- a/src/main/java/pl/koder95/eme/dfs/Index.java +++ b/src/main/java/pl/koder95/eme/dfs/Index.java @@ -67,7 +67,19 @@ private static Index validate(Index index) { } return index; } - + + /** + * Tworzy nowy indeks na podstawie mapy danych. + * + * @param owner księga, do której indeks należy + * @param data mapa danych + * @return nowy indeks lub {@code null}, jeśli mapa to {@code null} lub jeśli nie znaleziono numeru aktu + */ + public static Index create(Book owner, Map data) { + if (data == null || !data.containsKey("an")) return null; + return new Index(owner, data, ActNumber.parseActNumber(data.get("an"))); + } + /** * @param name nazwa unikatowa określająca informacje, którą indeks * przechowuje From 14ddddee9a7b320324a6755d1473d79745655229 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Fri, 12 Dec 2025 00:03:57 +0100 Subject: [PATCH 17/18] Refactor Index.create to simplify validation logic Removed the separate validate method and integrated its logic into the create method by delegating to another create overload. This streamlines the creation and validation process for Index instances. --- src/main/java/pl/koder95/eme/dfs/Index.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/java/pl/koder95/eme/dfs/Index.java b/src/main/java/pl/koder95/eme/dfs/Index.java index e7bd001..506832d 100644 --- a/src/main/java/pl/koder95/eme/dfs/Index.java +++ b/src/main/java/pl/koder95/eme/dfs/Index.java @@ -57,15 +57,7 @@ private Index(Book owner, Map data, ActNumber an) { public static Index create(Book owner, Node index) { if (index == null || !index.hasAttributes()) return new Index(owner, Map.of(), null); if (!index.getNodeName().equalsIgnoreCase("index")) return null; - Map data = NODE_INTERPRETER.interpret(index); - return validate(new Index(owner, data, ActNumber.parseActNumber(data.get("an")))); - } - - private static Index validate(Index index) { - if (index == null || !index.getDataNames().contains("an") || index.getData("an").isEmpty()) { - return null; - } - return index; + return create(owner, NODE_INTERPRETER.interpret(index)); } /** @@ -73,6 +65,7 @@ private static Index validate(Index index) { * * @param owner księga, do której indeks należy * @param data mapa danych + * @since 0.5.0 * @return nowy indeks lub {@code null}, jeśli mapa to {@code null} lub jeśli nie znaleziono numeru aktu */ public static Index create(Book owner, Map data) { From c1bf8741d03b86d3f2d0f30eb99ec8f531261295 Mon Sep 17 00:00:00 2001 From: Kamil Jan Mularski Date: Tue, 7 Jul 2026 22:44:03 +0200 Subject: [PATCH 18/18] Refactor architecture with IoC container Introduces ApplicationContext as a lightweight IoC container for managing application dependencies. Reorganizes domain models (ActNumber, Book, Index) into pl.koder95.eme.domain.index package. Creates new application services layer (PersonalDataQueryService, AppCloseService, IndexReloadService) and bootstrap configuration (AppConfig). Refactors App and PersonalDataView to use constructor-injected dependencies instead of static references. Adds IndexRepository abstraction with InMemoryIndexRepository implementation for managing index data lifecycle. Updates various core classes to use new domain models. --- src/main/java/module-info.java | 1 + src/main/java/pl/koder95/eme/App.java | 127 ++++++++----- .../eme/application/AppCloseService.java | 39 ++++ .../eme/application/IndexReloadService.java | 21 +++ .../application/PersonalDataPresentation.java | 63 +++++++ .../application/PersonalDataQueryService.java | 74 ++++++++ .../koder95/eme/application/package-info.java | 4 + .../pl/koder95/eme/bootstrap/AppConfig.java | 15 ++ .../eme/bootstrap/ApplicationContext.java | 102 ++++++++++ .../koder95/eme/bootstrap/package-info.java | 4 + .../eme/core/AbstractCabinetAnalyzer.java | 2 + .../eme/core/AbstractCabinetWorker.java | 2 +- .../eme/core/IndexContainerDataSource.java | 66 ++++--- .../koder95/eme/core/IndexListDataSource.java | 63 +++---- .../pl/koder95/eme/core/NoOpDataTarget.java | 30 +++ .../eme/core/PersonalDataModelConverter.java | 2 +- .../pl/koder95/eme/core/SimpleBriefcase.java | 2 +- .../eme/core/SimpleCabinetAnalyzer.java | 2 +- .../pl/koder95/eme/core/spi/Briefcase.java | 2 +- .../koder95/eme/core/spi/CabinetAnalyzer.java | 7 + .../koder95/eme/core/spi/CabinetWorker.java | 8 +- .../pl/koder95/eme/core/spi/DataSource.java | 2 +- .../pl/koder95/eme/core/spi/DataTarget.java | 2 +- .../koder95/eme/core/spi/IndexDataSource.java | 15 ++ .../pl/koder95/eme/core/spi/IndexFilter.java | 16 ++ .../koder95/eme/core/spi/IndexRepository.java | 23 +++ .../java/pl/koder95/eme/dfs/ActNumber.java | 124 ------------- src/main/java/pl/koder95/eme/dfs/Book.java | 137 -------------- src/main/java/pl/koder95/eme/dfs/Index.java | 111 ----------- .../pl/koder95/eme/dfs/IndexContainer.java | 95 ---------- .../java/pl/koder95/eme/dfs/IndexList.java | 120 ------------ .../java/pl/koder95/eme/dfs/package-info.java | 22 --- .../koder95/eme/domain/index/ActNumber.java | 105 +++++++++++ .../pl/koder95/eme/domain/index/Book.java | 47 +++++ .../pl/koder95/eme/domain/index/BookType.java | 29 +++ .../pl/koder95/eme/domain/index/Index.java | 86 +++++++++ .../eme/domain/index/package-info.java | 4 + .../pl/koder95/eme/domain/package-info.java | 4 + .../java/pl/koder95/eme/fx/FxDialogs.java | 68 +++++++ .../pl/koder95/eme/fx/PersonalDataView.java | 174 +++++++++--------- .../java/pl/koder95/eme/fx/package-info.java | 4 + .../java/pl/koder95/eme/git/package-info.java | 4 + .../eme/io/FileXmlIndexDataSource.java | 52 ++++++ .../eme/io/InMemoryIndexRepository.java | 85 +++++++++ .../java/pl/koder95/eme/io/IndexLoader.java | 75 ++++++++ .../java/pl/koder95/eme/io/package-info.java | 4 + .../java/pl/koder95/eme/xml/package-info.java | 4 + .../pl/koder95/eme/strings.properties | 5 +- 48 files changed, 1242 insertions(+), 811 deletions(-) create mode 100644 src/main/java/pl/koder95/eme/application/AppCloseService.java create mode 100644 src/main/java/pl/koder95/eme/application/IndexReloadService.java create mode 100644 src/main/java/pl/koder95/eme/application/PersonalDataPresentation.java create mode 100644 src/main/java/pl/koder95/eme/application/PersonalDataQueryService.java create mode 100644 src/main/java/pl/koder95/eme/application/package-info.java create mode 100644 src/main/java/pl/koder95/eme/bootstrap/AppConfig.java create mode 100644 src/main/java/pl/koder95/eme/bootstrap/ApplicationContext.java create mode 100644 src/main/java/pl/koder95/eme/bootstrap/package-info.java create mode 100644 src/main/java/pl/koder95/eme/core/NoOpDataTarget.java create mode 100644 src/main/java/pl/koder95/eme/core/spi/IndexDataSource.java create mode 100644 src/main/java/pl/koder95/eme/core/spi/IndexFilter.java create mode 100644 src/main/java/pl/koder95/eme/core/spi/IndexRepository.java delete mode 100644 src/main/java/pl/koder95/eme/dfs/ActNumber.java delete mode 100644 src/main/java/pl/koder95/eme/dfs/Book.java delete mode 100644 src/main/java/pl/koder95/eme/dfs/Index.java delete mode 100644 src/main/java/pl/koder95/eme/dfs/IndexContainer.java delete mode 100644 src/main/java/pl/koder95/eme/dfs/IndexList.java delete mode 100644 src/main/java/pl/koder95/eme/dfs/package-info.java create mode 100644 src/main/java/pl/koder95/eme/domain/index/ActNumber.java create mode 100644 src/main/java/pl/koder95/eme/domain/index/Book.java create mode 100644 src/main/java/pl/koder95/eme/domain/index/BookType.java create mode 100644 src/main/java/pl/koder95/eme/domain/index/Index.java create mode 100644 src/main/java/pl/koder95/eme/domain/index/package-info.java create mode 100644 src/main/java/pl/koder95/eme/domain/package-info.java create mode 100644 src/main/java/pl/koder95/eme/fx/FxDialogs.java create mode 100644 src/main/java/pl/koder95/eme/fx/package-info.java create mode 100644 src/main/java/pl/koder95/eme/git/package-info.java create mode 100644 src/main/java/pl/koder95/eme/io/FileXmlIndexDataSource.java create mode 100644 src/main/java/pl/koder95/eme/io/InMemoryIndexRepository.java create mode 100644 src/main/java/pl/koder95/eme/io/IndexLoader.java create mode 100644 src/main/java/pl/koder95/eme/io/package-info.java create mode 100644 src/main/java/pl/koder95/eme/xml/package-info.java diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index b88027f..925e5c6 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,5 +1,6 @@ module eme { requires java.xml; + requires java.logging; requires javafx.base; requires javafx.controls; requires javafx.fxml; diff --git a/src/main/java/pl/koder95/eme/App.java b/src/main/java/pl/koder95/eme/App.java index 99e8652..149e62c 100644 --- a/src/main/java/pl/koder95/eme/App.java +++ b/src/main/java/pl/koder95/eme/App.java @@ -18,6 +18,7 @@ import static pl.koder95.eme.Main.*; import javafx.application.Application; +import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.geometry.Pos; import javafx.scene.Parent; @@ -29,82 +30,108 @@ import javafx.stage.Stage; import javafx.stage.StageStyle; import pl.koder95.eme.au.SelfUpdateTask; -import pl.koder95.eme.core.*; -import pl.koder95.eme.core.spi.CabinetAnalyzer; -import pl.koder95.eme.core.spi.FilingCabinet; -import pl.koder95.eme.dfs.IndexList; +import pl.koder95.eme.bootstrap.AppConfig; +import pl.koder95.eme.bootstrap.ApplicationContext; +import pl.koder95.eme.application.AppCloseService; +import pl.koder95.eme.application.IndexReloadService; +import pl.koder95.eme.application.PersonalDataQueryService; +import pl.koder95.eme.fx.FxDialogs; import pl.koder95.eme.fx.PersonalDataView; import pl.koder95.eme.git.RepositoryInfo; import java.net.URL; -import java.util.Arrays; public class App extends Application { private Parent root = null; private SelfUpdateTask task = null; + private AppCloseService appCloseService = null; + private ApplicationContext applicationContext = null; - private boolean isRunWithoutUnnamedParameters() { - return getParameters().getUnnamed().isEmpty(); - } - @Override public void init() throws Exception { super.init(); task = new SelfUpdateTask(); - if (isRunWithoutUnnamedParameters()) { - Arrays.stream(IndexList.values()).forEach(IndexList::load); - } + root = createMainView(); + } - FilingCabinet cabinet = new TreeFilingCabinet(); - IndexListDataSource source = new IndexListDataSource(); + private Parent createMainView() throws Exception { + applicationContext = new ApplicationContext(); + applicationContext.initialize(); - SuggestionProvider suggestionProvider = new SuggestionProvider(cabinet); - CabinetAnalyzer worker = new SimpleCabinetAnalyzer(cabinet, source, null, suggestionProvider); - CabinetWorkers.register(CabinetAnalyzer.class, worker); - worker.load(); + PersonalDataQueryService personalDataQueryService = applicationContext.getPersonalDataQueryService(); + IndexReloadService indexReloadService = applicationContext.getIndexReloadService(); + appCloseService = applicationContext.getAppCloseService(); + FxDialogs dialogs = applicationContext.getDialogs(); + AppConfig appConfig = applicationContext.getAppConfig(); String resource = "PersonalDataView.fxml"; URL url = PersonalDataView.class.getResource(resource); - root = FXMLLoader.load(url, BUNDLE); + FXMLLoader loader = new FXMLLoader(url, appConfig.bundle()); + loader.setControllerFactory(type -> { + if (type == PersonalDataView.class) { + return new PersonalDataView(personalDataQueryService, indexReloadService, appCloseService, dialogs, appConfig.bundle()); + } + throw new IllegalArgumentException("Nieobsługiwany kontroler FXML: " + type.getName()); + }); + return loader.load(); } + /** + * Uruchamia okno i deleguje wybór scenariusza startowego. + */ @Override public void start(Stage primaryStage) { - Version latestRelease = RepositoryInfo.get().getLatestReleaseVersion(); - boolean runWithOptions = !isRunWithoutUnnamedParameters() && getParameters().getUnnamed().contains("-u"); - boolean isNotLatestVersion = latestRelease != null && latestRelease.compareTo(Version.get()) > 0; - start(primaryStage, isNotLatestVersion || runWithOptions); + configureCommonWindow(primaryStage); + if (shouldStartUpdateFlow()) { + showUpdateWindow(primaryStage); + } else { + showMainWindow(primaryStage); + } } - public void start(Stage primaryStage, boolean update) { + private void configureCommonWindow(Stage primaryStage) { primaryStage.getIcons().add(new Image(FAVICON_PATH)); primaryStage.setTitle("eMetrykant " + Version.get()); - if (update) { - ProgressBar updating = new ProgressBar(); - Label title = new Label(); - Label message = new Label(); - VBox root = new VBox(5d, title, updating, message); - - updating.setMinSize(400d, 35d); - updating.progressProperty().bind(task.progressProperty()); - title.textProperty().bind(task.titleProperty()); - message.textProperty().bind(task.messageProperty()); - - root.setAlignment(Pos.CENTER); - - primaryStage.initStyle(StageStyle.UTILITY); - primaryStage.setScene(new Scene(root)); - primaryStage.setAlwaysOnTop(true); - primaryStage.setResizable(false); - primaryStage.setMinWidth(400); - primaryStage.setMinHeight(100); - primaryStage.show(); - new Thread(task).start(); - } else { - primaryStage.setScene(new Scene(root)); - primaryStage.setOnCloseRequest(event -> System.exit(0)); - primaryStage.show(); - } + } + + private boolean shouldStartUpdateFlow() { + Version latestRelease = RepositoryInfo.get().getLatestReleaseVersion(); + return latestRelease != null + && (latestRelease.compareTo(Version.get()) > 0 || !getParameters().getUnnamed().isEmpty()); + } + + private void showUpdateWindow(Stage primaryStage) { + ProgressBar updating = new ProgressBar(); + Label title = new Label(); + Label message = new Label(); + VBox updateRoot = new VBox(5d, title, updating, message); + + updating.setMinSize(400d, 35d); + updating.progressProperty().bind(task.progressProperty()); + title.textProperty().bind(task.titleProperty()); + message.textProperty().bind(task.messageProperty()); + + updateRoot.setAlignment(Pos.CENTER); + + primaryStage.initStyle(StageStyle.UTILITY); + primaryStage.setScene(new Scene(updateRoot)); + primaryStage.setAlwaysOnTop(true); + primaryStage.setResizable(false); + primaryStage.setMinWidth(400); + primaryStage.setMinHeight(100); + primaryStage.show(); + Thread updateThread = new Thread(task); + updateThread.setDaemon(true); + updateThread.start(); + } + + private void showMainWindow(Stage primaryStage) { + primaryStage.setScene(new Scene(root)); + primaryStage.setOnCloseRequest(event -> { + event.consume(); + appCloseService.closeWithConfirmation(primaryStage.getScene(), Platform::exit); + }); + primaryStage.show(); } } diff --git a/src/main/java/pl/koder95/eme/application/AppCloseService.java b/src/main/java/pl/koder95/eme/application/AppCloseService.java new file mode 100644 index 0000000..b8fbe50 --- /dev/null +++ b/src/main/java/pl/koder95/eme/application/AppCloseService.java @@ -0,0 +1,39 @@ +package pl.koder95.eme.application; + +import javafx.application.Platform; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import pl.koder95.eme.bootstrap.AppConfig; +import pl.koder95.eme.fx.FxDialogs; + +/** + * Serwis aplikacyjny odpowiedzialny za scenariusz zamykania aplikacji. + */ +public class AppCloseService { + + private final AppConfig appConfig; + private final FxDialogs dialogs; + + public AppCloseService(AppConfig appConfig, FxDialogs dialogs) { + this.appConfig = appConfig; + this.dialogs = dialogs; + } + + public void closeWithConfirmation(Scene scene) { + closeWithConfirmation(scene, Platform::exit); + } + + public void closeWithConfirmation(Scene scene, Runnable onConfirm) { + Alert alert = dialogs.createConfirmationAlert( + scene, + appConfig.bundle().getString("ALERT_CONFIRM_CLOSE_TITLE"), + appConfig.bundle().getString("ALERT_CONFIRM_CLOSE_HEADER"), + appConfig.bundle().getString("ALERT_CONFIRM_CLOSE_CONTENT") + ); + alert.showAndWait(); + if (alert.getResult() == ButtonType.OK) { + onConfirm.run(); + } + } +} diff --git a/src/main/java/pl/koder95/eme/application/IndexReloadService.java b/src/main/java/pl/koder95/eme/application/IndexReloadService.java new file mode 100644 index 0000000..70860e7 --- /dev/null +++ b/src/main/java/pl/koder95/eme/application/IndexReloadService.java @@ -0,0 +1,21 @@ +package pl.koder95.eme.application; + +import pl.koder95.eme.core.spi.IndexRepository; + +import java.util.Objects; + +/** + * Serwis aplikacyjny odpowiedzialny za przeładowanie indeksów. + */ +public class IndexReloadService { + + private final IndexRepository indexRepository; + + public IndexReloadService(IndexRepository indexRepository) { + this.indexRepository = Objects.requireNonNull(indexRepository, "indexRepository must not be null"); + } + + public void reloadAll() { + indexRepository.reloadAll(); + } +} diff --git a/src/main/java/pl/koder95/eme/application/PersonalDataPresentation.java b/src/main/java/pl/koder95/eme/application/PersonalDataPresentation.java new file mode 100644 index 0000000..f0afc20 --- /dev/null +++ b/src/main/java/pl/koder95/eme/application/PersonalDataPresentation.java @@ -0,0 +1,63 @@ +package pl.koder95.eme.application; + +import pl.koder95.eme.core.spi.PersonalDataModel; + +/** + * Model prezentacyjny danych osobowych dla UI. + */ +public record PersonalDataPresentation( + String surname, + String name, + String baptismAN, + String confirmationAN, + String marriageAN, + String deceaseAN +) implements PersonalDataModel { + + public PersonalDataPresentation { + surname = normalize(surname, "-"); + name = normalize(name, ""); + baptismAN = normalize(baptismAN, "-"); + confirmationAN = normalize(confirmationAN, "-"); + marriageAN = normalize(marriageAN, "-"); + deceaseAN = normalize(deceaseAN, "-"); + } + + public String fullName() { + return name.isBlank() ? surname.toUpperCase() : surname.toUpperCase() + " " + name; + } + + @Override + public String getSurname() { + return surname; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getBaptismAN() { + return baptismAN; + } + + @Override + public String getConfirmationAN() { + return confirmationAN; + } + + @Override + public String getMarriageAN() { + return marriageAN; + } + + @Override + public String getDeceaseAN() { + return deceaseAN; + } + + private static String normalize(String value, String defaultValue) { + return value == null || value.isBlank() ? defaultValue : value; + } +} diff --git a/src/main/java/pl/koder95/eme/application/PersonalDataQueryService.java b/src/main/java/pl/koder95/eme/application/PersonalDataQueryService.java new file mode 100644 index 0000000..de9e214 --- /dev/null +++ b/src/main/java/pl/koder95/eme/application/PersonalDataQueryService.java @@ -0,0 +1,74 @@ +package pl.koder95.eme.application; + +import javafx.util.Callback; +import javafx.util.StringConverter; +import pl.koder95.eme.core.IndexListDataSource; +import pl.koder95.eme.core.spi.CabinetAnalyzer; +import pl.koder95.eme.core.spi.IndexRepository; +import pl.koder95.eme.core.spi.PersonalDataModel; + +import java.util.Collection; +import java.util.Objects; + +import static org.controlsfx.control.textfield.AutoCompletionBinding.ISuggestionRequest; + +/** + * Serwis aplikacyjny odpowiedzialny za wyszukiwanie i mapowanie danych osobowych. + */ +public class PersonalDataQueryService { + + private static final PersonalDataPresentation EMPTY_DATA = new PersonalDataPresentation( + null, null, null, null, null, null + ); + + private final CabinetAnalyzer analyzer; + private final IndexRepository indexRepository; + + public PersonalDataQueryService(CabinetAnalyzer analyzer, IndexRepository indexRepository) { + this.analyzer = Objects.requireNonNull(analyzer, "analyzer must not be null"); + this.indexRepository = Objects.requireNonNull(indexRepository, "indexRepository must not be null"); + } + + public Callback> getSuggestionProvider() { + return analyzer.getSuggestionProvider(); + } + + public StringConverter getPersonalDataConverter() { + return analyzer.getPersonalDataConverter(); + } + + /** + * Ponownie ładuje analizator na podstawie aktualnego źródła danych. + * + *

Przed wywołaniem tej metody należy wykonać + * {@link IndexReloadService#reloadAll()}, aby repozytorium indeksów zostało + * odświeżone. Metoda podmienia źródło analizatora na + * {@link IndexListDataSource} i wywołuje {@link CabinetAnalyzer#load()}.

+ */ + public void reloadAnalyzer() { + analyzer.setDataSource(new IndexListDataSource(indexRepository)); + analyzer.load(); + } + + public int getNumberOfActs() { + return analyzer.getNumberOfActs(); + } + + public PersonalDataModel fromText(String text) { + return analyzer.getPersonalDataConverter().fromString(text); + } + + public PersonalDataPresentation toPresentation(PersonalDataModel model) { + if (model == null) { + return EMPTY_DATA; + } + return new PersonalDataPresentation( + model.getSurname(), + model.getName(), + model.getBaptismAN(), + model.getConfirmationAN(), + model.getMarriageAN(), + model.getDeceaseAN() + ); + } +} diff --git a/src/main/java/pl/koder95/eme/application/package-info.java b/src/main/java/pl/koder95/eme/application/package-info.java new file mode 100644 index 0000000..ed21c32 --- /dev/null +++ b/src/main/java/pl/koder95/eme/application/package-info.java @@ -0,0 +1,4 @@ +/** + * Serwisy warstwy aplikacyjnej. + */ +package pl.koder95.eme.application; diff --git a/src/main/java/pl/koder95/eme/bootstrap/AppConfig.java b/src/main/java/pl/koder95/eme/bootstrap/AppConfig.java new file mode 100644 index 0000000..4b10018 --- /dev/null +++ b/src/main/java/pl/koder95/eme/bootstrap/AppConfig.java @@ -0,0 +1,15 @@ +package pl.koder95.eme.bootstrap; + +import java.text.Collator; +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * Konfiguracja aplikacji przekazywana przez IoC. + */ +public record AppConfig( + ResourceBundle bundle, + Locale locale, + Collator collator +) { +} diff --git a/src/main/java/pl/koder95/eme/bootstrap/ApplicationContext.java b/src/main/java/pl/koder95/eme/bootstrap/ApplicationContext.java new file mode 100644 index 0000000..d8a5401 --- /dev/null +++ b/src/main/java/pl/koder95/eme/bootstrap/ApplicationContext.java @@ -0,0 +1,102 @@ +package pl.koder95.eme.bootstrap; + +import pl.koder95.eme.Main; +import pl.koder95.eme.application.AppCloseService; +import pl.koder95.eme.application.IndexReloadService; +import pl.koder95.eme.application.PersonalDataQueryService; +import pl.koder95.eme.core.IndexListDataSource; +import pl.koder95.eme.core.NoOpDataTarget; +import pl.koder95.eme.core.SimpleCabinetAnalyzer; +import pl.koder95.eme.core.SuggestionProvider; +import pl.koder95.eme.core.TreeFilingCabinet; +import pl.koder95.eme.core.spi.CabinetAnalyzer; +import pl.koder95.eme.core.spi.FilingCabinet; +import pl.koder95.eme.core.spi.IndexRepository; +import pl.koder95.eme.fx.FxDialogs; +import pl.koder95.eme.io.InMemoryIndexRepository; + +/** + * Prosty kontener IoC aplikacji. + * + *

Skupia tworzenie i utrzymywanie zależności w jednym miejscu, + * aby ograniczyć tworzenie obiektów w warstwie UI.

+ */ +public class ApplicationContext { + + private static final CabinetAnalyzer DEFAULT_CABINET_ANALYZER = createCabinetAnalyzer(); + private final IndexRepository indexRepository; + private final CabinetAnalyzer cabinetAnalyzer; + private final PersonalDataQueryService personalDataQueryService; + private final IndexReloadService indexReloadService; + private final AppConfig appConfig; + private final FxDialogs dialogs; + private final AppCloseService appCloseService; + private volatile boolean initialized; + + public ApplicationContext(CabinetAnalyzer analyzer) { + this.cabinetAnalyzer = analyzer; + this.indexRepository = new InMemoryIndexRepository(); + this.personalDataQueryService = new PersonalDataQueryService(cabinetAnalyzer, indexRepository); + this.indexReloadService = new IndexReloadService(indexRepository); + this.appConfig = new AppConfig(Main.BUNDLE, Main.POLISH, Main.DEFAULT_COLLATOR); + this.dialogs = new FxDialogs(); + this.appCloseService = new AppCloseService(appConfig, dialogs); + this.initialized = false; + } + + public ApplicationContext() { + this(DEFAULT_CABINET_ANALYZER); + } + + private static CabinetAnalyzer createCabinetAnalyzer() { + FilingCabinet cabinet = new TreeFilingCabinet(); + SuggestionProvider suggestionProvider = new SuggestionProvider(cabinet); + SimpleCabinetAnalyzer analyzer = new SimpleCabinetAnalyzer(cabinet, suggestionProvider); + analyzer.setDataTarget(new NoOpDataTarget()); + return analyzer; + } + + public PersonalDataQueryService getPersonalDataQueryService() { + ensureInitialized(); + return personalDataQueryService; + } + + public IndexReloadService getIndexReloadService() { + ensureInitialized(); + return indexReloadService; + } + + public AppConfig getAppConfig() { + return appConfig; + } + + public FxDialogs getDialogs() { + return dialogs; + } + + public AppCloseService getAppCloseService() { + return appCloseService; + } + + /** + * Inicjalizuje kontekst po konstrukcji (wywoływane np. z {@code App.init()}). + * Metoda musi zostać wywołana po utworzeniu kontekstu i przed użyciem beanów zależnych od danych. + * + * @throws IllegalStateException gdy kontekst został już zainicjalizowany + */ + public synchronized void initialize() { + if (initialized) { + throw new IllegalStateException("ApplicationContext został już zainicjalizowany."); + } + indexRepository.reloadAll(); + cabinetAnalyzer.setDataSource(new IndexListDataSource(indexRepository)); + cabinetAnalyzer.load(); + initialized = true; + } + + private void ensureInitialized() { + if (!initialized) { + throw new IllegalStateException("ApplicationContext musi zostać zainicjalizowany przez initialize()."); + } + } +} diff --git a/src/main/java/pl/koder95/eme/bootstrap/package-info.java b/src/main/java/pl/koder95/eme/bootstrap/package-info.java new file mode 100644 index 0000000..0317a98 --- /dev/null +++ b/src/main/java/pl/koder95/eme/bootstrap/package-info.java @@ -0,0 +1,4 @@ +/** + * Bootstrapping i konfiguracja aplikacji. + */ +package pl.koder95.eme.bootstrap; diff --git a/src/main/java/pl/koder95/eme/core/AbstractCabinetAnalyzer.java b/src/main/java/pl/koder95/eme/core/AbstractCabinetAnalyzer.java index 38f5e4f..2ef4576 100644 --- a/src/main/java/pl/koder95/eme/core/AbstractCabinetAnalyzer.java +++ b/src/main/java/pl/koder95/eme/core/AbstractCabinetAnalyzer.java @@ -88,6 +88,8 @@ public int getNumberOfActs() { @Override public void load() { + numberOfActs = 0f; + getCabinet().clear(); Map> personalData = getDataSource().getPersonalData(); personalData.forEach((surname, names) -> names.forEach((name) -> { DataSource dataSource = getDataSource(); diff --git a/src/main/java/pl/koder95/eme/core/AbstractCabinetWorker.java b/src/main/java/pl/koder95/eme/core/AbstractCabinetWorker.java index 460068f..811e023 100644 --- a/src/main/java/pl/koder95/eme/core/AbstractCabinetWorker.java +++ b/src/main/java/pl/koder95/eme/core/AbstractCabinetWorker.java @@ -1,7 +1,7 @@ package pl.koder95.eme.core; import pl.koder95.eme.core.spi.*; -import pl.koder95.eme.dfs.ActNumber; +import pl.koder95.eme.domain.index.ActNumber; /** * Pracownik szafy ze zdefiniowaną szafą aktową. Umożliwia także ustawienie odpowiedniego źródła danych diff --git a/src/main/java/pl/koder95/eme/core/IndexContainerDataSource.java b/src/main/java/pl/koder95/eme/core/IndexContainerDataSource.java index 48cb378..f3870c6 100644 --- a/src/main/java/pl/koder95/eme/core/IndexContainerDataSource.java +++ b/src/main/java/pl/koder95/eme/core/IndexContainerDataSource.java @@ -2,12 +2,16 @@ import pl.koder95.eme.Visitor; import pl.koder95.eme.core.spi.DataSource; -import pl.koder95.eme.dfs.*; +import pl.koder95.eme.domain.index.ActNumber; +import pl.koder95.eme.domain.index.Book; +import pl.koder95.eme.domain.index.BookType; +import pl.koder95.eme.domain.index.Index; import java.util.*; +import java.util.stream.Collectors; /** - * Źródło danych, które pobiera informacje z {@link IndexContainer kontenera indeksów}. + * Źródło danych, które pobiera informacje z kolekcji indeksów. * * @author Kamil Jan Mularski [@Koder95] * @version 0.4.1, 2021-11-07 @@ -15,6 +19,9 @@ */ public class IndexContainerDataSource implements DataSource, Visitor { + private static final java.util.logging.Logger LOGGER = + java.util.logging.Logger.getLogger(IndexContainerDataSource.class.getName()); + private final Map>> baptisms = new TreeMap<>(); private final Map>> confirmations = new TreeMap<>(); private final Map>> marriages = new TreeMap<>(); @@ -22,11 +29,11 @@ public class IndexContainerDataSource implements DataSource, Visitor { private final Map> personalData = new TreeMap<>(); /** - * Tworzy źródło danych na podstawie wczytanych indeksów do {@link IndexContainer kontenera}. - * @param container kontener, którego indeksy zostaną przejrzane, aby pozyskać informacje + * Tworzy źródło danych na podstawie kolekcji indeksów. + * @param indexes indeksy do analizy */ - public IndexContainerDataSource(IndexContainer container) { - if (container != null) container.getLoaded().forEach(this::visit); + public IndexContainerDataSource(Collection indexes) { + if (indexes != null) indexes.forEach(this::visit); } @Override @@ -51,25 +58,29 @@ public ActNumber[] getDecease(String surname, String name) { @Override public Map> getPersonalData() { - return personalData; + return personalData.entrySet().stream() + .map(entry -> + Map.entry(entry.getKey(), Collections.unmodifiableSet(entry.getValue())) + ).collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); } private static ActNumber[] get(Map>> map, String surname, String name) { - for (Map.Entry>> entry : map.entrySet()) { - if (entry.getKey().equals(surname)) { - for (Map.Entry> e : entry.getValue().entrySet()) { - if (e.getKey().equals(name)) { - if (e.getValue() == null) return new ActNumber[0]; - return e.getValue().toArray(new ActNumber[0]); - } - } - } + if (surname == null || name == null) { + return new ActNumber[0]; + } + Map> namesBySurname = map.get(surname); + if (namesBySurname == null) { + return new ActNumber[0]; + } + Set numbers = namesBySurname.get(name); + if (numbers == null) { + return new ActNumber[0]; } - return null; + return numbers.toArray(new ActNumber[0]); } private void set(Map>> map, String surname, String name, - ActNumber number) { + ActNumber number) { surname = removeUTF8BOM(surname.trim()); name = removeUTF8BOM(name.trim()); if (!map.containsKey(surname)) { @@ -108,15 +119,24 @@ private void setDecease(Index i) { @Override public void visit(Index i) { - String bookName = i.getOwner().getName(); - if (bookName.equalsIgnoreCase("Księga ochrzczonych")) { + if (i == null) { + return; + } + Book owner = i.getOwner(); + if (owner == null) { + return; + } + String bookName = owner.getName(); + if (bookName.equalsIgnoreCase(BookType.LIBER_BAPTISMORUM.getBookName())) { setBaptism(i); - } else if (bookName.equalsIgnoreCase("Księga bierzmowanych")) { + } else if (bookName.equalsIgnoreCase(BookType.LIBER_CONFIRMATORUM.getBookName())) { setConfirmation(i); - } else if (bookName.equalsIgnoreCase("Księga zaślubionych")) { + } else if (bookName.equalsIgnoreCase(BookType.LIBER_MATRIMONIORUM.getBookName())) { setMarriage(i); - } else if (bookName.equalsIgnoreCase("Księga zmarłych")) { + } else if (bookName.equalsIgnoreCase(BookType.LIBER_DEFUNCTORUM.getBookName())) { setDecease(i); + } else { + LOGGER.warning(() -> "Unrecognized book name, index skipped: " + bookName); } } diff --git a/src/main/java/pl/koder95/eme/core/IndexListDataSource.java b/src/main/java/pl/koder95/eme/core/IndexListDataSource.java index 6cc6032..b305de0 100644 --- a/src/main/java/pl/koder95/eme/core/IndexListDataSource.java +++ b/src/main/java/pl/koder95/eme/core/IndexListDataSource.java @@ -1,34 +1,43 @@ package pl.koder95.eme.core; import pl.koder95.eme.core.spi.DataSource; -import pl.koder95.eme.dfs.ActNumber; -import pl.koder95.eme.dfs.IndexList; +import pl.koder95.eme.core.spi.IndexRepository; +import pl.koder95.eme.domain.index.ActNumber; +import pl.koder95.eme.domain.index.BookType; +import pl.koder95.eme.io.InMemoryIndexRepository; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; /** - * Źródło danych zawierające cztery pod-źródła, czyli instancje {@link IndexContainerDataSource źródeł}, - * które pobierają informacje z każdej instancji {@link IndexList listy indeksów}. - * - * @author Kamil Jan Mularski [@Koder95] - * @version 0.4.0, 2020-08-26 - * @since 0.4.0 + * Źródło danych budowane z repozytorium indeksów. */ public class IndexListDataSource implements DataSource { private final IndexContainerDataSource baptisms; private final IndexContainerDataSource confirmations; private final IndexContainerDataSource marriages; private final IndexContainerDataSource deceases; - private Map> personalData; + private final Map> personalData; - /** - * Tworzy nową instancję wczytując do niej dane z czterech {@link IndexList list indeksów}. - */ public IndexListDataSource() { - baptisms = new IndexContainerDataSource(IndexList.LIBER_BAPTISMORUM); - confirmations = new IndexContainerDataSource(IndexList.LIBER_CONFIRMATORUM); - marriages = new IndexContainerDataSource(IndexList.LIBER_MATRIMONIORUM); - deceases = new IndexContainerDataSource(IndexList.LIBER_DEFUNCTORUM); + this(new InMemoryIndexRepository()); + } + + public IndexListDataSource(IndexRepository indexRepository) { + baptisms = new IndexContainerDataSource(indexRepository.getIndices(BookType.LIBER_BAPTISMORUM)); + confirmations = new IndexContainerDataSource(indexRepository.getIndices(BookType.LIBER_CONFIRMATORUM)); + marriages = new IndexContainerDataSource(indexRepository.getIndices(BookType.LIBER_MATRIMONIORUM)); + deceases = new IndexContainerDataSource(indexRepository.getIndices(BookType.LIBER_DEFUNCTORUM)); + + Map> merged = new HashMap<>(); + putAll(merged, baptisms.getPersonalData()); + putAll(merged, confirmations.getPersonalData()); + putAll(merged, marriages.getPersonalData()); + putAll(merged, deceases.getPersonalData()); + this.personalData = Collections.unmodifiableMap(merged); } @Override @@ -53,26 +62,10 @@ public ActNumber[] getDecease(String surname, String name) { @Override public Map> getPersonalData() { - if (personalData == null) { - Map> merge0 = merge(baptisms.getPersonalData(), confirmations.getPersonalData()); - Map> merge1 = merge(marriages.getPersonalData(), deceases.getPersonalData()); - Map> finalMerge = merge(merge0, merge1); - merge0.clear(); - merge1.clear(); - personalData = finalMerge; - } return personalData; } - private static Map> merge(Map> pdata0, - Map> pdata1) { - Map> merged = new TreeMap<>(pdata0); - pdata1.forEach((surname, names) -> { - if (!merged.containsKey(surname)) { - merged.put(surname, new HashSet<>()); - } - merged.get(surname).addAll(names); - }); - return merged; + private static void putAll(Map> merged, Map> source) { + source.forEach((surname, names) -> merged.computeIfAbsent(surname, ignored -> new HashSet<>()).addAll(names)); } } diff --git a/src/main/java/pl/koder95/eme/core/NoOpDataTarget.java b/src/main/java/pl/koder95/eme/core/NoOpDataTarget.java new file mode 100644 index 0000000..b5d0fc3 --- /dev/null +++ b/src/main/java/pl/koder95/eme/core/NoOpDataTarget.java @@ -0,0 +1,30 @@ +package pl.koder95.eme.core; + +import pl.koder95.eme.core.spi.DataTarget; +import pl.koder95.eme.domain.index.ActNumber; + +/** + * Pusty cel danych używany, gdy zapis nie jest wymagany. + */ +public class NoOpDataTarget implements DataTarget { + + @Override + public void setBaptism(String surname, String name, ActNumber an) { + // no-op + } + + @Override + public void setConfirmation(String surname, String name, ActNumber an) { + // no-op + } + + @Override + public void setMarriage(String surname, String name, ActNumber an) { + // no-op + } + + @Override + public void setDecease(String surname, String name, ActNumber an) { + // no-op + } +} diff --git a/src/main/java/pl/koder95/eme/core/PersonalDataModelConverter.java b/src/main/java/pl/koder95/eme/core/PersonalDataModelConverter.java index 3cfb429..651e4a1 100644 --- a/src/main/java/pl/koder95/eme/core/PersonalDataModelConverter.java +++ b/src/main/java/pl/koder95/eme/core/PersonalDataModelConverter.java @@ -4,7 +4,7 @@ import pl.koder95.eme.core.spi.Briefcase; import pl.koder95.eme.core.spi.FilingCabinet; import pl.koder95.eme.core.spi.PersonalDataModel; -import pl.koder95.eme.dfs.ActNumber; +import pl.koder95.eme.domain.index.ActNumber; import java.util.Arrays; import java.util.function.Function; diff --git a/src/main/java/pl/koder95/eme/core/SimpleBriefcase.java b/src/main/java/pl/koder95/eme/core/SimpleBriefcase.java index f57c4e8..1897262 100644 --- a/src/main/java/pl/koder95/eme/core/SimpleBriefcase.java +++ b/src/main/java/pl/koder95/eme/core/SimpleBriefcase.java @@ -1,7 +1,7 @@ package pl.koder95.eme.core; import pl.koder95.eme.core.spi.Briefcase; -import pl.koder95.eme.dfs.ActNumber; +import pl.koder95.eme.domain.index.ActNumber; /** * Prosta implementacja interfejsu {@link Briefcase aktówki}, diff --git a/src/main/java/pl/koder95/eme/core/SimpleCabinetAnalyzer.java b/src/main/java/pl/koder95/eme/core/SimpleCabinetAnalyzer.java index eff28b9..181a1ac 100644 --- a/src/main/java/pl/koder95/eme/core/SimpleCabinetAnalyzer.java +++ b/src/main/java/pl/koder95/eme/core/SimpleCabinetAnalyzer.java @@ -4,7 +4,7 @@ import pl.koder95.eme.core.spi.DataSource; import pl.koder95.eme.core.spi.DataTarget; import pl.koder95.eme.core.spi.FilingCabinet; -import pl.koder95.eme.dfs.ActNumber; +import pl.koder95.eme.domain.index.ActNumber; /** * Rozszerza {@link AbstractCabinetAnalyzer ogólnego analizatora szafy} diff --git a/src/main/java/pl/koder95/eme/core/spi/Briefcase.java b/src/main/java/pl/koder95/eme/core/spi/Briefcase.java index 8cfb3cd..bf65b8f 100644 --- a/src/main/java/pl/koder95/eme/core/spi/Briefcase.java +++ b/src/main/java/pl/koder95/eme/core/spi/Briefcase.java @@ -1,6 +1,6 @@ package pl.koder95.eme.core.spi; -import pl.koder95.eme.dfs.ActNumber; +import pl.koder95.eme.domain.index.ActNumber; import java.util.Set; diff --git a/src/main/java/pl/koder95/eme/core/spi/CabinetAnalyzer.java b/src/main/java/pl/koder95/eme/core/spi/CabinetAnalyzer.java index 6ac6d03..560b063 100644 --- a/src/main/java/pl/koder95/eme/core/spi/CabinetAnalyzer.java +++ b/src/main/java/pl/koder95/eme/core/spi/CabinetAnalyzer.java @@ -33,4 +33,11 @@ public interface CabinetAnalyzer extends CabinetWorker { * jest do prezentacji sugestii wyszukiwania */ StringConverter getPersonalDataConverter(); + + /** + * Ustawia źródło danych wykorzystywane przy ponownym ładowaniu analizatora. + * + * @param source nowe źródło danych + */ + void setDataSource(DataSource source); } diff --git a/src/main/java/pl/koder95/eme/core/spi/CabinetWorker.java b/src/main/java/pl/koder95/eme/core/spi/CabinetWorker.java index 45f97c8..a4e24e9 100644 --- a/src/main/java/pl/koder95/eme/core/spi/CabinetWorker.java +++ b/src/main/java/pl/koder95/eme/core/spi/CabinetWorker.java @@ -1,6 +1,6 @@ package pl.koder95.eme.core.spi; -import pl.koder95.eme.dfs.ActNumber; +import pl.koder95.eme.domain.index.ActNumber; import java.util.Arrays; import java.util.Map; @@ -68,9 +68,9 @@ default void save() { personalData.forEach((surname, names) -> names.forEach((name) -> { Briefcase briefcase = cabinet.get(surname, name); Arrays.stream(briefcase.getBaptism()).forEach(a -> dataTarget.setBaptism(surname, name, a)); - Arrays.stream(briefcase.getConfirmation()).forEach(a -> dataTarget.setBaptism(surname, name, a)); - Arrays.stream(briefcase.getMarriage()).forEach(a -> dataTarget.setBaptism(surname, name, a)); - Arrays.stream(briefcase.getDecease()).forEach(a -> dataTarget.setBaptism(surname, name, a)); + Arrays.stream(briefcase.getConfirmation()).forEach(a -> dataTarget.setConfirmation(surname, name, a)); + Arrays.stream(briefcase.getMarriage()).forEach(a -> dataTarget.setMarriage(surname, name, a)); + Arrays.stream(briefcase.getDecease()).forEach(a -> dataTarget.setDecease(surname, name, a)); })); } } diff --git a/src/main/java/pl/koder95/eme/core/spi/DataSource.java b/src/main/java/pl/koder95/eme/core/spi/DataSource.java index 96943ce..68144ce 100644 --- a/src/main/java/pl/koder95/eme/core/spi/DataSource.java +++ b/src/main/java/pl/koder95/eme/core/spi/DataSource.java @@ -1,6 +1,6 @@ package pl.koder95.eme.core.spi; -import pl.koder95.eme.dfs.ActNumber; +import pl.koder95.eme.domain.index.ActNumber; import java.util.Map; import java.util.Set; diff --git a/src/main/java/pl/koder95/eme/core/spi/DataTarget.java b/src/main/java/pl/koder95/eme/core/spi/DataTarget.java index 014e952..608032a 100644 --- a/src/main/java/pl/koder95/eme/core/spi/DataTarget.java +++ b/src/main/java/pl/koder95/eme/core/spi/DataTarget.java @@ -1,6 +1,6 @@ package pl.koder95.eme.core.spi; -import pl.koder95.eme.dfs.ActNumber; +import pl.koder95.eme.domain.index.ActNumber; import java.util.Collection; import java.util.Set; diff --git a/src/main/java/pl/koder95/eme/core/spi/IndexDataSource.java b/src/main/java/pl/koder95/eme/core/spi/IndexDataSource.java new file mode 100644 index 0000000..f762133 --- /dev/null +++ b/src/main/java/pl/koder95/eme/core/spi/IndexDataSource.java @@ -0,0 +1,15 @@ +package pl.koder95.eme.core.spi; + +import org.w3c.dom.Document; + +import java.io.IOException; + +/** + * XML-owe źródło dokumentu indeksów. + * + *

Kontrakt jest celowo specyficzny dla XML i zwraca {@link Document}.

+ */ +public interface IndexDataSource { + + Document loadDocument() throws IOException; +} diff --git a/src/main/java/pl/koder95/eme/core/spi/IndexFilter.java b/src/main/java/pl/koder95/eme/core/spi/IndexFilter.java new file mode 100644 index 0000000..f642201 --- /dev/null +++ b/src/main/java/pl/koder95/eme/core/spi/IndexFilter.java @@ -0,0 +1,16 @@ +package pl.koder95.eme.core.spi; + +import pl.koder95.eme.domain.index.Index; + +/** + * Punkt rozszerzeń dla filtrowania rekordów indeksów. + */ +@FunctionalInterface +public interface IndexFilter { + + boolean accept(Index index); + + static IndexFilter acceptAll() { + return index -> true; + } +} diff --git a/src/main/java/pl/koder95/eme/core/spi/IndexRepository.java b/src/main/java/pl/koder95/eme/core/spi/IndexRepository.java new file mode 100644 index 0000000..13730f3 --- /dev/null +++ b/src/main/java/pl/koder95/eme/core/spi/IndexRepository.java @@ -0,0 +1,23 @@ +package pl.koder95.eme.core.spi; + +import pl.koder95.eme.domain.index.BookType; +import pl.koder95.eme.domain.index.Index; + +import java.util.List; + +/** + * Repozytorium indeksów z możliwością przeładowania danych. + */ +public interface IndexRepository { + + /** + * Zwraca indeksy dla wskazanego typu księgi. + * + * @param type typ księgi; wartość {@code null} nie jest dozwolona + * @return zawsze nie-{@code null} lista indeksów; gdy brak danych zwracana jest pusta lista + * @throws NullPointerException gdy {@code type == null} + */ + List getIndices(BookType type); + + void reloadAll(); +} diff --git a/src/main/java/pl/koder95/eme/dfs/ActNumber.java b/src/main/java/pl/koder95/eme/dfs/ActNumber.java deleted file mode 100644 index cdd4bbf..0000000 --- a/src/main/java/pl/koder95/eme/dfs/ActNumber.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2017 Kamil Jan Mularski [@koder95] - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package pl.koder95.eme.dfs; - -import java.io.Serializable; -import java.util.regex.Matcher; - -import javafx.beans.property.*; -import pl.koder95.eme.Main; -import static pl.koder95.eme.Main.DIGITS_STRING_PATTERN; - -/** - * Klasa reprezentuje numer aktu. Zawiera sygnaturę, czyli niepowtarzalny dla - * danego roku ciąg znaków (zwykle liczbę naturalną), oraz rok. - * - * @author Kamil Jan Mularski [@koder95] - * @version 0.4.0, 2020-08-26 - * @since 0.1.11 - */ -public class ActNumber implements Comparable, Serializable { - - private final ReadOnlyStringWrapper sign; - private final ReadOnlyIntegerWrapper year; - - /** - * Podstawowy konstruktor. - * - * @param sign sygnatura - * @param year rok - */ - public ActNumber(String sign, int year) { - this.sign = new ReadOnlyStringWrapper(sign); - this.year = new ReadOnlyIntegerWrapper(year); - } - - /** - * @return sygnatura - */ - public String getSign() { - return signProperty().get(); - } - - /** - * @return rok - */ - public int getYear() { - return yearProperty().get(); - } - - @Override - public String toString() { - return getSign() + "/" + getYear(); - } - - @Override - public int compareTo(ActNumber o) { - int compared = Integer.compare(getYear(), o.getYear()); - if (compared != 0) { - return compared; - } else { - Matcher digit1 = DIGITS_STRING_PATTERN.matcher(getSign()); - Matcher digit2 = DIGITS_STRING_PATTERN.matcher(o.getSign()); - if (digit1.find() && digit2.find()) { - int act1 = Integer.parseInt(digit1.group(1)); - int act2 = Integer.parseInt(digit2.group(1)); - return Integer.compare(act1, act2); - } - String remainder1 = getSign().substring(digit1.end(1)+1); - String remainder2 = getSign().substring(digit2.end(1)+1); - return Main.DEFAULT_COLLATOR.compare(remainder1, remainder2); - } - } - - /** - * @return właściwość: sygnatura - * @since 0.3.1 - */ - public ReadOnlyStringProperty signProperty() { - return sign.getReadOnlyProperty(); - } - - /** - * @return właściwość: rok - * @since 0.3.1 - */ - public ReadOnlyIntegerProperty yearProperty() { - return year.getReadOnlyProperty(); - } - - /** - * @param s ciąg znaków - * @return numer aktu, chyba że ciąg jest nie możliwy do sparsowania, wtedy - * {@code null} - */ - public static ActNumber parseActNumber(String s) { - if (!s.contains("/")) return null; - - int slash = s.indexOf("/"); - if (slash == 0 || slash == s.length()-1) return null; - - String sign = s.substring(0, slash); - String yearS = s.substring(slash+1); - try { - int year = Integer.parseInt(yearS); - return new ActNumber(sign, year); - } catch (NumberFormatException ex) { - return null; - } - } -} diff --git a/src/main/java/pl/koder95/eme/dfs/Book.java b/src/main/java/pl/koder95/eme/dfs/Book.java deleted file mode 100644 index 9b5cff8..0000000 --- a/src/main/java/pl/koder95/eme/dfs/Book.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2018 Kamil Jan Mularski [@koder95] - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package pl.koder95.eme.dfs; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import javax.xml.parsers.ParserConfigurationException; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; -import pl.koder95.eme.xml.XMLLoader; - -/** - * Klasa reprezentuje księgę zawierającą indeksy z danymi osobowymi. - * - * @author Kamil Jan Mularski [@koder95] - * @version 0.4.0, 2020-08-26 - * @since 0.1.10 - */ -public class Book { - - final ObservableList indices; - private final String name; - - /** - * Tworzy księgę o podanej nazwie. - * @param name nazwa księgi - */ - public Book(String name) { - this.name = name; - this.indices = FXCollections.observableArrayList(); - } - - /** - * @return nazwa księgi - */ - public String getName() { - return name; - } - - /** - * Wczytuje indeksy z listy węzłów i dodaje je do księgi. - * - * @param indices lista węzłów - */ - public void load(NodeList indices) { - if (indices == null) return; - LinkedList linked = new LinkedList<>(); - for (int i = 0; i < indices.getLength(); i++) { - Index ix = Index.create(this, indices.item(i)); - if (ix != null) linked.add(ix); - } - this.indices.addAll(linked); - linked.clear(); - } - - /** - * Wczytuje księgę z węzła XML. - * - * @param book węzeł dokumentu XML - * @return księga z indeksami - */ - public static Book load(Node book) { - if (book == null) return null; - if (book.getNodeName().equalsIgnoreCase("book")) { - if (book.hasAttributes()) { - Book b = new Book(XMLLoader.getAttrV(book, "name")); - b.load(book.getChildNodes()); - return b; - } else return new Book(""); - } else return null; - } - - /** - * Wczytuje księgi z pliku XML. - * - * @param xml plik XML - * @return lista ksiąg z indeksami - * @throws IOException problemy z odczytaniem pliku - * @throws SAXException błąd SAX - * @throws ParserConfigurationException problemy z konfiguracją parsera XML - */ - public static List load(File xml) throws IOException, - ParserConfigurationException, SAXException { - LinkedList books = new LinkedList<>(); - if (!xml.exists()) { - try (BufferedWriter writer = Files.newBufferedWriter(xml.toPath())) { - writer.write( - "\n" + - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - "" - ); - } - } - Document doc = XMLLoader.loadDOM(xml); - Element indices = doc.getDocumentElement(); - if (indices.getNodeName().equalsIgnoreCase("indices")) { - NodeList bookNodes = indices.getElementsByTagName("book"); - for (int i = 0; i < bookNodes.getLength(); i++) { - Book b = load(bookNodes.item(i)); - if (b != null) books.add(b); - } - } - ArrayList array = new ArrayList<>(books); - books.clear(); - return array; - } -} diff --git a/src/main/java/pl/koder95/eme/dfs/Index.java b/src/main/java/pl/koder95/eme/dfs/Index.java deleted file mode 100644 index 506832d..0000000 --- a/src/main/java/pl/koder95/eme/dfs/Index.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2018 Kamil Jan Mularski [@koder95] - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package pl.koder95.eme.dfs; - -import org.w3c.dom.Node; -import pl.koder95.eme.Visited; -import pl.koder95.eme.dfs.impl.IndexNodeInterpreterImpl; - -import java.util.Map; -import java.util.Set; - -/** - * Klasa reprezentuje indeks, czyli zbiór danych osobowych. Może również - * posiadać numer aktu, który sprawia, że można odszukać dany indeks w księgach - * przechowywanych na parafii. - * - * @author Kamil Jan Mularski [@koder95] - * @version 0.5.0, 2025-12-11 - * @since 0.1.11 - */ -public class Index implements Visited { - - private static final IndexNodeInterpreter NODE_INTERPRETER = new IndexNodeInterpreterImpl(); - private final Map data; // dane indeksu - private ActNumber an; - private final Book owner; - - private Index(Book owner, Map data, ActNumber an) { - this.owner = owner; - this.an = an; - this.data = Map.copyOf(data); - } - - /** - * Tworzy nowy indeks na podstawie węzła obiektowego modelu dokumentu XML. - * - * @param owner księga, do której indeks należy - * @param index węzeł XML - * @return nowy indeks lub {@code null} jeśli węzeł {@code index} nazywa się - * inaczej niż "index" - */ - public static Index create(Book owner, Node index) { - if (index == null || !index.hasAttributes()) return new Index(owner, Map.of(), null); - if (!index.getNodeName().equalsIgnoreCase("index")) return null; - return create(owner, NODE_INTERPRETER.interpret(index)); - } - - /** - * Tworzy nowy indeks na podstawie mapy danych. - * - * @param owner księga, do której indeks należy - * @param data mapa danych - * @since 0.5.0 - * @return nowy indeks lub {@code null}, jeśli mapa to {@code null} lub jeśli nie znaleziono numeru aktu - */ - public static Index create(Book owner, Map data) { - if (data == null || !data.containsKey("an")) return null; - return new Index(owner, data, ActNumber.parseActNumber(data.get("an"))); - } - - /** - * @param name nazwa unikatowa określająca informacje, którą indeks - * przechowuje - * @return informacja o indeksie - */ - public String getData(String name) { - return data.getOrDefault(name, ""); - } - - /** - * @return zestaw nazw informacji o indeksie - */ - public Set getDataNames() { - return data.keySet(); - } - - /** - * @return numer aktu - */ - public ActNumber getActNumber() { - if (an == null) an = ActNumber.parseActNumber(getData("an")); - return an; - } - - /** - * @return księga, do której należy akt - */ - public Book getOwner() { - return owner; - } - - @Override - public String toString() { - return getActNumber() + " " + data.toString(); - } -} diff --git a/src/main/java/pl/koder95/eme/dfs/IndexContainer.java b/src/main/java/pl/koder95/eme/dfs/IndexContainer.java deleted file mode 100644 index df4a18b..0000000 --- a/src/main/java/pl/koder95/eme/dfs/IndexContainer.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2017 Kamil Jan Mularski [@koder95] - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package pl.koder95.eme.dfs; - -import java.util.List; - -/** - * Zawiera metody dla klasy, która przechowywać będzie indeksy. - * - * @author Kamil Jan Mularski [@koder95] - * @version 0.3.1, 2020-05-20 - * @since 0.1.11 - */ -public interface IndexContainer { - - /** - * @param id identyfikator {@literal >} 0 - * @return indeks - * @throws IndexOutOfBoundsException identyfikator nie jest większy od 0 - */ - default Index get(int id) { - if (id > 0) - return getLoaded().get(id - 1); - else - throw new IndexOutOfBoundsException("ID have to be greater than 0"); - } - - /** - * @return lista wczytanych indeksów - */ - List getLoaded(); - - /** - * Usuwa wczytane dane i zwalnia pamięć dla potencjalnie nowych danych. - */ - default void clear() { - getLoaded().clear(); - } - - /** - * @return liczba indeksów wczytanych oraz ostatni utworzony identyfikator - */ - default int size() { - return getLoaded().size(); - } - - /** - * @return pierwszy indeks - */ - default Index getFirst() { - return get(1); - } - - /** - * @return ostatni indeks - */ - default Index getLast() { - return get(size()); - } - - /** - * @param i indeks - * @return kolejny indeks - */ - default Index getNext(Index i) { - List loaded = getLoaded(); - int index = loaded.indexOf(i); - return index < 0? null : loaded.get(index-1); - } - - /** - * @param i indeks - * @return poprzedni indeks - */ - default Index getPrev(Index i) { - List loaded = getLoaded(); - int index = loaded.indexOf(i); - return index < loaded.size()? loaded.get(index+1) : null; - } -} diff --git a/src/main/java/pl/koder95/eme/dfs/IndexList.java b/src/main/java/pl/koder95/eme/dfs/IndexList.java deleted file mode 100644 index 0738e35..0000000 --- a/src/main/java/pl/koder95/eme/dfs/IndexList.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2017 Kamil Jan Mularski [@koder95] - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package pl.koder95.eme.dfs; - -import java.io.IOException; -import java.util.*; -import javax.xml.parsers.ParserConfigurationException; -import org.xml.sax.SAXException; -import pl.koder95.eme.Files; -import pl.koder95.eme.Main; -import pl.koder95.eme.MemoryUtils; - -/** - * Zawiera wszystkie typy ksiąg i zawierających się w nich zbiorach indeksów. - * - * @author Kamil Jan Mularski [@koder95] - * @version 0.4.1, 2021-11-07 - * @since 0.1.11 - */ -public enum IndexList implements IndexContainer { - - /** - * Zbiór indeksów osób ochrzczonych. Indeksy zawierają dane: nazwisko, - * imiona, nr aktu, rok chrztu. - */ - LIBER_BAPTISMORUM("Księga ochrzczonych", new LinkedList<>(Arrays.asList( - "name", "surname", "an" - ))), - - /** - * Zbiór indeksów osób bierzmowanych. Indeksy zawierają dane: nazwisko, - * imiona, nr aktu, rok bierzmowania. - */ - LIBER_CONFIRMATORUM("Księga bierzmowanych", new LinkedList<>(Arrays.asList( - "name", "surname", "an" - ))), - - /** - * Zbiór indeksów osób zaślubionych. Indeksy zawierają dane: nazwisko męża, - * imiona męża, nazwisko panieńskie żony, imiona żony, nr aktu, rok ślubu. - */ - LIBER_MATRIMONIORUM("Księga zaślubionych", new LinkedList<>(Arrays.asList( - "husband-surname", "husband-name", "wife-surname", "wife-name", "an" - ))), - - /** - * Zbiór indeksów osób zmarłych. Indeksy zawierają dane: nazwisko, - * imiona, nr aktu, rok śmierci. - */ - LIBER_DEFUNCTORUM("Księga zmarłych", new LinkedList<>(Arrays.asList( - "name", "surname", "an" - ))); - - private List loaded = new LinkedList<>(); - private final String name; - private final Queue nameQueue; - - IndexList(String name, Queue nameQueue) { - this.name = name; - this.nameQueue = nameQueue; - } - - @Override - public List getLoaded() { - return loaded; - } - - /** - * Wczytuje z dysku indeksy zapisane w pliku XML. - */ - public void load() { - MemoryUtils.memory(); - this.loaded = new LinkedList<>(); - if (BOOKS == null) loadBooks(); - BOOKS.stream().filter((b)->b.getName().equalsIgnoreCase(name)) - .forEach((b)->{ - MemoryUtils.memory(); - this.loaded.addAll(b.indices); - MemoryUtils.memory(); - b.indices.forEach((i) -> i.getDataNames().stream() - .filter(nameQueue::contains) - .forEachOrdered(nameQueue::add)); - MemoryUtils.memory(); - }); - List loaded = this.loaded; - this.loaded = new ArrayList<>(loaded); - loaded.clear(); - } - - /** - * @return nazwa księgi - */ - public String getName() { - return name; - } - - private static List BOOKS; - - private static void loadBooks() { - try { - BOOKS = Book.load(Files.INDICES_XML.toFile()); - } catch (IOException | SAXException | ParserConfigurationException ex) { - System.err.println(ex); - } - } -} diff --git a/src/main/java/pl/koder95/eme/dfs/package-info.java b/src/main/java/pl/koder95/eme/dfs/package-info.java deleted file mode 100644 index 1742e27..0000000 --- a/src/main/java/pl/koder95/eme/dfs/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2018 Kamil Jan Mularski [@koder95] - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * Paczka zawiera klasy do zarządzania systemem formatu danych. - * Nazwa paczki jest skrótem z ang. Data Format System. - */ -package pl.koder95.eme.dfs; diff --git a/src/main/java/pl/koder95/eme/domain/index/ActNumber.java b/src/main/java/pl/koder95/eme/domain/index/ActNumber.java new file mode 100644 index 0000000..2c4da38 --- /dev/null +++ b/src/main/java/pl/koder95/eme/domain/index/ActNumber.java @@ -0,0 +1,105 @@ +package pl.koder95.eme.domain.index; + +import pl.koder95.eme.Main; + +import java.io.Serializable; +import java.util.Objects; +import java.util.regex.Matcher; + +import static pl.koder95.eme.Main.DIGITS_STRING_PATTERN; + +/** + * Reprezentuje numer aktu (sygnatura/rok). + */ +public class ActNumber implements Comparable, Serializable { + + private static final long serialVersionUID = 1L; + + private final String sign; + private final int year; + + public ActNumber(String sign, int year) { + String normalizedSign = Objects.requireNonNull(sign, "sign must not be null").trim(); + if (normalizedSign.isEmpty()) { + throw new IllegalArgumentException("sign must not be blank"); + } + this.sign = normalizedSign; + this.year = year; + } + + public String getSign() { + return sign; + } + + public int getYear() { + return year; + } + + @Override + public String toString() { + return getSign() + "/" + getYear(); + } + + @Override + public int compareTo(ActNumber o) { + int compared = Integer.compare(getYear(), o.getYear()); + if (compared != 0) { + return compared; + } + + Matcher digit1 = DIGITS_STRING_PATTERN.matcher(getSign()); + Matcher digit2 = DIGITS_STRING_PATTERN.matcher(o.getSign()); + + boolean hasDigits1 = digit1.find() && digit1.group(1) != null && !digit1.group(1).isEmpty(); + boolean hasDigits2 = digit2.find() && digit2.group(1) != null && !digit2.group(1).isEmpty(); + + if (hasDigits1 && hasDigits2) { + int act1 = Integer.parseInt(digit1.group(1)); + int act2 = Integer.parseInt(digit2.group(1)); + int byNumber = Integer.compare(act1, act2); + if (byNumber != 0) { + return byNumber; + } + + String remainder1 = getSign().substring(Math.min(getSign().length(), digit1.end(1))); + String remainder2 = o.getSign().substring(Math.min(o.getSign().length(), digit2.end(1))); + return Main.DEFAULT_COLLATOR.compare(remainder1, remainder2); + } + + return Main.DEFAULT_COLLATOR.compare(getSign(), o.getSign()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ActNumber other)) { + return false; + } + return year == other.year && sign.equals(other.sign); + } + + @Override + public int hashCode() { + return Objects.hash(sign, year); + } + + public static ActNumber parseActNumber(String s) { + if (s == null || !s.contains("/")) { + return null; + } + int slash = s.indexOf('/'); + if (slash == 0 || slash == s.length() - 1) { + return null; + } + String sign = s.substring(0, slash); + String yearS = s.substring(slash + 1); + try { + int year = Integer.parseInt(yearS); + return new ActNumber(sign, year); + } catch (NumberFormatException | IllegalArgumentException ex) { + return null; + } + } +} diff --git a/src/main/java/pl/koder95/eme/domain/index/Book.java b/src/main/java/pl/koder95/eme/domain/index/Book.java new file mode 100644 index 0000000..c346b42 --- /dev/null +++ b/src/main/java/pl/koder95/eme/domain/index/Book.java @@ -0,0 +1,47 @@ +package pl.koder95.eme.domain.index; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Księga zawierająca indeksy. + */ +public class Book { + + private final List indices; + private final String name; + + public Book(String name) { + String normalizedName = Objects.requireNonNull(name, "name must not be null").trim(); + if (normalizedName.isEmpty()) { + throw new IllegalArgumentException("name must not be blank"); + } + this.name = normalizedName; + this.indices = new ArrayList<>(); + } + + public String getName() { + return name; + } + + public List getIndices() { + return Collections.unmodifiableList(indices); + } + + public void addIndex(Index index) { + if (index != null) { + indices.add(index); + } + } + + public void addIndices(Collection records) { + if (records != null) { + for (Index record : records) { + addIndex(record); + } + } + } +} diff --git a/src/main/java/pl/koder95/eme/domain/index/BookType.java b/src/main/java/pl/koder95/eme/domain/index/BookType.java new file mode 100644 index 0000000..e38595b --- /dev/null +++ b/src/main/java/pl/koder95/eme/domain/index/BookType.java @@ -0,0 +1,29 @@ +package pl.koder95.eme.domain.index; + +import java.util.List; + +/** + * Typ księgi indeksowej. + */ +public enum BookType { + LIBER_BAPTISMORUM("Księga ochrzczonych", List.of("name", "surname", "an")), + LIBER_CONFIRMATORUM("Księga bierzmowanych", List.of("name", "surname", "an")), + LIBER_MATRIMONIORUM("Księga zaślubionych", List.of("husband-surname", "husband-name", "wife-surname", "wife-name", "an")), + LIBER_DEFUNCTORUM("Księga zmarłych", List.of("name", "surname", "an")); + + private final String bookName; + private final List fieldSchema; + + BookType(String bookName, List fieldSchema) { + this.bookName = bookName; + this.fieldSchema = fieldSchema; + } + + public String getBookName() { + return bookName; + } + + public List getFieldSchema() { + return fieldSchema; + } +} diff --git a/src/main/java/pl/koder95/eme/domain/index/Index.java b/src/main/java/pl/koder95/eme/domain/index/Index.java new file mode 100644 index 0000000..4c4c653 --- /dev/null +++ b/src/main/java/pl/koder95/eme/domain/index/Index.java @@ -0,0 +1,86 @@ +package pl.koder95.eme.domain.index; + +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import pl.koder95.eme.Visited; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +/** + * Pojedynczy rekord indeksu danych osobowych. + */ +public class Index implements Visited { + + private static final Logger LOGGER = Logger.getLogger(Index.class.getName()); + + private final Map data = new HashMap<>(); + private volatile ActNumber an; + private final Book owner; + + private Index(Book owner, Node index) { + this.owner = owner; + NamedNodeMap attrs = index.getAttributes(); + for (int i = 0; i < attrs.getLength(); i++) { + Node attr = attrs.item(i); + if (attr != null) { + String key = attr.getNodeName(); + String value = attr.getTextContent(); + data.put(key, value); + } + } + } + + private Index(Book owner) { + this.owner = owner; + } + + public static Index create(Node index) { + return create(null, index); + } + + public static Index create(Book owner, Node index) { + if (index == null) { + return null; + } + if (!index.getNodeName().equalsIgnoreCase("index")) { + return null; + } + Index i = index.hasAttributes() ? new Index(owner, index) : new Index(owner); + if (i.getDataNames().contains("an") && !i.getData("an").isEmpty()) { + return i; + } + + String ownerName = owner == null ? "" : owner.getName(); + LOGGER.warning(() -> "Pominięto indeks bez atrybutu 'an'. owner=" + ownerName + + ", nodeName=" + index.getNodeName()); + return null; + } + + public String getData(String name) { + return data.getOrDefault(name, ""); + } + + public Set getDataNames() { + return Collections.unmodifiableSet(data.keySet()); + } + + public ActNumber getActNumber() { + if (an == null) { + an = ActNumber.parseActNumber(getData("an")); + } + return an; + } + + public Book getOwner() { + return owner; + } + + @Override + public String toString() { + return getActNumber() + " " + data; + } +} diff --git a/src/main/java/pl/koder95/eme/domain/index/package-info.java b/src/main/java/pl/koder95/eme/domain/index/package-info.java new file mode 100644 index 0000000..500f836 --- /dev/null +++ b/src/main/java/pl/koder95/eme/domain/index/package-info.java @@ -0,0 +1,4 @@ +/** + * Modele domenowe indeksów. + */ +package pl.koder95.eme.domain.index; diff --git a/src/main/java/pl/koder95/eme/domain/package-info.java b/src/main/java/pl/koder95/eme/domain/package-info.java new file mode 100644 index 0000000..b2655ff --- /dev/null +++ b/src/main/java/pl/koder95/eme/domain/package-info.java @@ -0,0 +1,4 @@ +/** + * Typy domenowe aplikacji. + */ +package pl.koder95.eme.domain; diff --git a/src/main/java/pl/koder95/eme/fx/FxDialogs.java b/src/main/java/pl/koder95/eme/fx/FxDialogs.java new file mode 100644 index 0000000..516aef1 --- /dev/null +++ b/src/main/java/pl/koder95/eme/fx/FxDialogs.java @@ -0,0 +1,68 @@ +package pl.koder95.eme.fx; + +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.layout.VBox; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; + +/** + * Fabryka dialogów JavaFX używanych przez kontrolery i serwisy UI. + */ +public class FxDialogs { + + /** + * Tworzy dialog potwierdzenia. + */ + public Alert createConfirmationAlert(Scene ownerScene, String title, String header, String content) { + return createAlert(Alert.AlertType.CONFIRMATION, ownerScene, title, header, content); + } + + /** + * Tworzy dialog błędu. + */ + public Alert createErrorAlert(Scene ownerScene, String title, String header, String content) { + return createAlert(Alert.AlertType.ERROR, ownerScene, title, header, content); + } + + /** + * Tworzy dialog postępu bez akcji anulowania. + */ + public Dialog createProgressDialog(Scene ownerScene, String messageText) { + Dialog dialog = new Dialog<>(); + ProgressIndicator indicator = new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS); + Label label = new Label(messageText); + label.setFont(Font.font(label.getFont().getFamily(), FontWeight.BOLD, 24)); + + VBox root = new VBox(indicator, label); + root.setMaxWidth(Double.MAX_VALUE); + root.setSpacing(10); + root.setFillWidth(true); + root.setAlignment(Pos.CENTER); + + DialogPane pane = new DialogPane(); + pane.setContent(root); + dialog.setDialogPane(pane); + + if (ownerScene != null) { + dialog.initOwner(ownerScene.getWindow()); + } + return dialog; + } + + private Alert createAlert(Alert.AlertType type, Scene ownerScene, String title, String header, String content) { + Alert alert = new Alert(type); + if (ownerScene != null) { + alert.initOwner(ownerScene.getWindow()); + } + alert.setTitle(title); + alert.setHeaderText(header); + alert.setContentText(content); + return alert; + } +} diff --git a/src/main/java/pl/koder95/eme/fx/PersonalDataView.java b/src/main/java/pl/koder95/eme/fx/PersonalDataView.java index 12f8aa3..1eb1f85 100644 --- a/src/main/java/pl/koder95/eme/fx/PersonalDataView.java +++ b/src/main/java/pl/koder95/eme/fx/PersonalDataView.java @@ -4,24 +4,26 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.geometry.Pos; import javafx.scene.Scene; -import javafx.scene.control.*; +import javafx.scene.control.Dialog; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; import javafx.scene.layout.BorderPane; -import javafx.scene.layout.VBox; -import javafx.scene.text.Font; -import javafx.scene.text.FontWeight; +import javafx.scene.control.*; import org.controlsfx.control.textfield.AutoCompletionBinding; import org.controlsfx.control.textfield.TextFields; -import pl.koder95.eme.core.CabinetWorkers; -import pl.koder95.eme.core.spi.CabinetAnalyzer; +import pl.koder95.eme.application.AppCloseService; +import pl.koder95.eme.application.IndexReloadService; +import pl.koder95.eme.application.PersonalDataPresentation; +import pl.koder95.eme.application.PersonalDataQueryService; import pl.koder95.eme.core.spi.PersonalDataModel; -import pl.koder95.eme.dfs.IndexList; import java.net.URL; +import java.util.Objects; import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; -import static pl.koder95.eme.Main.BUNDLE; /** * Kontroler dla widoku modelu danych osobowych. @@ -32,6 +34,26 @@ */ public class PersonalDataView implements Initializable { + private static final Logger LOGGER = Logger.getLogger(PersonalDataView.class.getName()); + + private final PersonalDataQueryService personalDataQueryService; + private final IndexReloadService indexReloadService; + private final AppCloseService appCloseService; + private final FxDialogs dialogs; + private final ResourceBundle bundle; + + public PersonalDataView(PersonalDataQueryService personalDataQueryService, + IndexReloadService indexReloadService, + AppCloseService appCloseService, + FxDialogs dialogs, + ResourceBundle bundle) { + this.personalDataQueryService = Objects.requireNonNull(personalDataQueryService, "personalDataQueryService must not be null"); + this.indexReloadService = Objects.requireNonNull(indexReloadService, "indexReloadService must not be null"); + this.appCloseService = Objects.requireNonNull(appCloseService, "appCloseService must not be null"); + this.dialogs = Objects.requireNonNull(dialogs, "dialogs must not be null"); + this.bundle = Objects.requireNonNull(bundle, "bundle must not be null"); + } + @FXML private BorderPane main; @FXML @@ -54,18 +76,27 @@ public class PersonalDataView implements Initializable { @Override public void initialize(URL location, ResourceBundle resources) { - installCabinetAnalyzer(CabinetWorkers.get(CabinetAnalyzer.class)); + installCabinetAnalyzer(); } - private void installCabinetAnalyzer(CabinetAnalyzer analyzer) { - setupAutoCompletion(analyzer); - setupInputActions(analyzer); - numberOfActs.setText(analyzer.getNumberOfActs() + ""); + private void installCabinetAnalyzer() { + setupAutoCompletion(); + setupInputActions(); + numberOfActs.setText(String.valueOf(personalDataQueryService.getNumberOfActs())); } - private void setupInputActions(CabinetAnalyzer analyzer) { + private void setupAutoCompletion() { + AutoCompletionBinding binding = TextFields.bindAutoCompletion( + searching, + personalDataQueryService.getSuggestionProvider(), + personalDataQueryService.getPersonalDataConverter() + ); + binding.setOnAutoCompleted(event -> setPersonalDataModel(event.getCompletion())); + } + + private void setupInputActions() { searching.setOnAction(event -> setPersonalDataModel( - analyzer.getPersonalDataConverter().fromString(searching.getText()) + personalDataQueryService.getPersonalDataConverter().fromString(searching.getText()) )); searching.textProperty().addListener( (observable, oldValue, newValue) -> { @@ -76,80 +107,59 @@ private void setupInputActions(CabinetAnalyzer analyzer) { ); } - private void setupAutoCompletion(CabinetAnalyzer analyzer) { - AutoCompletionBinding binding = TextFields.bindAutoCompletion( - searching, - analyzer.getSuggestionProvider(), - analyzer.getPersonalDataConverter() - ); - binding.setOnAutoCompleted(event -> setPersonalDataModel(event.getCompletion())); - } - private void setPersonalDataModel(PersonalDataModel model) { - if (model != null) { - String personalData = model.getSurname().toUpperCase(); - if (!model.getName().isEmpty()) { - personalData += " " + model.getName(); - } - this.personalData.setText(personalData); - this.baptism.setText(model.getBaptismAN()); - this.confirmation.setText(model.getConfirmationAN()); - this.marriage.setText(model.getMarriageAN()); - this.decease.setText(model.getDeceaseAN()); - } else setPersonalDataModel(EMPTY_DATA); + PersonalDataPresentation viewData = personalDataQueryService.toPresentation(model); + this.personalData.setText(viewData.fullName()); + this.baptism.setText(viewData.getBaptismAN()); + this.confirmation.setText(viewData.getConfirmationAN()); + this.marriage.setText(viewData.getMarriageAN()); + this.decease.setText(viewData.getDeceaseAN()); } - + /** + * Obsługuje próbę zamknięcia aplikacji z potwierdzeniem. + */ public void close(ActionEvent actionEvent) { - Alert a = new Alert(Alert.AlertType.CONFIRMATION); - Scene scene = main.getScene(); - if (scene != null) { - a.initOwner(scene.getWindow()); - } - a.setTitle(BUNDLE.getString("ALERT_CONFIRM_CLOSE_TITLE")); - a.setHeaderText(BUNDLE.getString("ALERT_CONFIRM_CLOSE_HEADER")); - a.setContentText(BUNDLE.getString("ALERT_CONFIRM_CLOSE_CONTENT")); - a.showAndWait(); - if (a.getResult() == ButtonType.OK) { - Platform.exit(); - } + appCloseService.closeWithConfirmation(main.getScene()); } + /** + * Ponownie wczytuje dane indeksów i odświeża licznik aktów. + */ public void reload() { Scene scene = main.getScene(); if (scene != null) { - ProgressDialog.start(); + Dialog dialog = dialogs.createProgressDialog(scene, bundle.getString("FX_RELOAD_PROGRESS_MESSAGE")); + Thread thread = new Thread(() -> { + Exception reloadException = null; + try { + indexReloadService.reloadAll(); + personalDataQueryService.reloadAnalyzer(); + } catch (Exception ex) { + reloadException = ex; + LOGGER.log(Level.SEVERE, "Błąd podczas przeładowania indeksów", ex); + } finally { + Exception finalReloadException = reloadException; + Platform.runLater(() -> { + if (finalReloadException != null) { + dialogs.createErrorAlert( + scene, + bundle.getString("ALERT_RELOAD_ERROR_TITLE"), + bundle.getString("ALERT_RELOAD_ERROR_HEADER"), + finalReloadException.getMessage() != null + ? finalReloadException.getMessage() + : finalReloadException.toString() + ).showAndWait(); + } else { + numberOfActs.setText(String.valueOf(personalDataQueryService.getNumberOfActs())); + } + dialog.setResult(finalReloadException == null); + dialog.close(); + }); + } + }); + thread.setDaemon(true); + dialog.setOnShown(event -> thread.start()); + dialog.show(); } } - - private static final PersonalDataModel EMPTY_DATA = new PersonalDataModel() { - @Override - public String getSurname() { - return "-"; - } - - @Override - public String getName() { - return ""; - } - - @Override - public String getBaptismAN() { - return "-"; - } - - @Override - public String getConfirmationAN() { - return "-"; - } - - @Override - public String getMarriageAN() { - return "-"; - } - - @Override - public String getDeceaseAN() { - return "-"; - } - }; } diff --git a/src/main/java/pl/koder95/eme/fx/package-info.java b/src/main/java/pl/koder95/eme/fx/package-info.java new file mode 100644 index 0000000..e0b9767 --- /dev/null +++ b/src/main/java/pl/koder95/eme/fx/package-info.java @@ -0,0 +1,4 @@ +/** + * Warstwa UI JavaFX. + */ +package pl.koder95.eme.fx; diff --git a/src/main/java/pl/koder95/eme/git/package-info.java b/src/main/java/pl/koder95/eme/git/package-info.java new file mode 100644 index 0000000..ed6282b --- /dev/null +++ b/src/main/java/pl/koder95/eme/git/package-info.java @@ -0,0 +1,4 @@ +/** + * Integracja z danymi repozytorium Git. + */ +package pl.koder95.eme.git; diff --git a/src/main/java/pl/koder95/eme/io/FileXmlIndexDataSource.java b/src/main/java/pl/koder95/eme/io/FileXmlIndexDataSource.java new file mode 100644 index 0000000..e2ee676 --- /dev/null +++ b/src/main/java/pl/koder95/eme/io/FileXmlIndexDataSource.java @@ -0,0 +1,52 @@ +package pl.koder95.eme.io; + +import org.w3c.dom.Document; +import org.xml.sax.SAXException; +import pl.koder95.eme.core.spi.IndexDataSource; +import pl.koder95.eme.domain.index.BookType; +import pl.koder95.eme.xml.XMLLoader; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +/** + * Domyślne źródło danych indeksów z pliku XML. + */ +public class FileXmlIndexDataSource implements IndexDataSource { + + private final Path xmlPath; + + public FileXmlIndexDataSource(Path xmlPath) { + this.xmlPath = xmlPath; + } + + @Override + public Document loadDocument() throws IOException { + ensureFileExists(); + try { + return XMLLoader.loadDOM(xmlPath.toFile()); + } catch (ParserConfigurationException | SAXException e) { + throw new IOException(e); + } + } + + private void ensureFileExists() throws IOException { + if (Files.exists(xmlPath)) { + return; + } + try (BufferedWriter writer = Files.newBufferedWriter(xmlPath, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { + writer.write("\n\n"); + for (BookType type : BookType.values()) { + writer.write(" \n"); + } + writer.write(""); + } catch (FileAlreadyExistsException ignored) { + // wyścig między sprawdzeniem Files.exists(...) a CREATE_NEW - można bezpiecznie zignorować + } + } +} diff --git a/src/main/java/pl/koder95/eme/io/InMemoryIndexRepository.java b/src/main/java/pl/koder95/eme/io/InMemoryIndexRepository.java new file mode 100644 index 0000000..a8330ac --- /dev/null +++ b/src/main/java/pl/koder95/eme/io/InMemoryIndexRepository.java @@ -0,0 +1,85 @@ +package pl.koder95.eme.io; + +import pl.koder95.eme.Files; +import pl.koder95.eme.MemoryUtils; +import pl.koder95.eme.core.spi.IndexFilter; +import pl.koder95.eme.core.spi.IndexRepository; +import pl.koder95.eme.domain.index.Book; +import pl.koder95.eme.domain.index.BookType; +import pl.koder95.eme.domain.index.Index; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Repozytorium indeksów utrzymujące cache w pamięci i odświeżanie z XML. + */ +public class InMemoryIndexRepository implements IndexRepository { + + private static final Logger LOGGER = Logger.getLogger(InMemoryIndexRepository.class.getName()); + + private final IndexLoader loader; + private final Map> loaded = new EnumMap<>(BookType.class); + private volatile boolean loadedOnce; + + public InMemoryIndexRepository() { + this(new IndexLoader(new FileXmlIndexDataSource(Files.INDICES_XML), IndexFilter.acceptAll())); + } + + public InMemoryIndexRepository(IndexLoader loader) { + this.loader = Objects.requireNonNull(loader, "loader must not be null"); + for (BookType type : BookType.values()) { + loaded.put(type, new ArrayList<>()); + } + this.loadedOnce = false; + } + + @Override + public synchronized List getIndices(BookType type) { + Objects.requireNonNull(type, "type must not be null"); + ensureLoaded(); + return Collections.unmodifiableList(loaded.computeIfAbsent(type, ignored -> new ArrayList<>())); + } + + @Override + public synchronized void reloadAll() { + MemoryUtils.memory(); + try { + List books = loader.loadBooks(); + for (BookType type : BookType.values()) { + List selected = new ArrayList<>(); + books.stream() + .filter(book -> book.getName().equalsIgnoreCase(type.getBookName())) + .forEach(book -> { + MemoryUtils.memory(); + selected.addAll(book.getIndices()); + MemoryUtils.memory(); + }); + List existing = loaded.computeIfAbsent(type, ignored -> new ArrayList<>()); + existing.clear(); + existing.addAll(selected); + } + loadedOnce = true; + } catch (IOException ex) { + for (BookType type : BookType.values()) { + loaded.computeIfAbsent(type, ignored -> new ArrayList<>()).clear(); + } + loadedOnce = false; + LOGGER.log(Level.SEVERE, "Failed to reload indices", ex); + throw new IllegalStateException("Failed to reload indices", ex); + } + } + + private void ensureLoaded() { + if (!loadedOnce) { + reloadAll(); + } + } +} diff --git a/src/main/java/pl/koder95/eme/io/IndexLoader.java b/src/main/java/pl/koder95/eme/io/IndexLoader.java new file mode 100644 index 0000000..a530fce --- /dev/null +++ b/src/main/java/pl/koder95/eme/io/IndexLoader.java @@ -0,0 +1,75 @@ +package pl.koder95.eme.io; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import pl.koder95.eme.core.spi.IndexDataSource; +import pl.koder95.eme.core.spi.IndexFilter; +import pl.koder95.eme.domain.index.Book; +import pl.koder95.eme.domain.index.Index; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.logging.Logger; + +/** + * Loader odpowiedzialny za wczytywanie i filtrowanie rekordów indeksów. + */ +public class IndexLoader { + + private static final Logger LOGGER = Logger.getLogger(IndexLoader.class.getName()); + + private final IndexDataSource dataSource; + private final IndexFilter filter; + + public IndexLoader(IndexDataSource dataSource, IndexFilter filter) { + this.dataSource = Objects.requireNonNull(dataSource, "dataSource must not be null"); + this.filter = filter == null ? IndexFilter.acceptAll() : filter; + } + + public List loadBooks() throws IOException { + Document doc = dataSource.loadDocument(); + if (doc == null) { + throw new IOException("Failed to load document: data source returned null"); + } + Element indices = doc.getDocumentElement(); + List books = new ArrayList<>(); + if (indices != null && indices.getNodeName().equalsIgnoreCase("indices")) { + NodeList bookNodes = indices.getElementsByTagName("book"); + for (int i = 0; i < bookNodes.getLength(); i++) { + Book b = parseBook(bookNodes.item(i)); + if (b != null) { + books.add(b); + } + } + } + return books; + } + + private Book parseBook(Node bookNode) { + if (bookNode == null || !bookNode.getNodeName().equalsIgnoreCase("book") || !bookNode.hasAttributes()) { + return null; + } + String bookName = Optional.ofNullable(bookNode.getAttributes().getNamedItem("name")) + .map(Node::getTextContent) + .map(String::trim) + .orElse(null); + if (bookName == null || bookName.isBlank()) { + LOGGER.warning("Pominięto bez poprawnego atrybutu 'name'."); + return null; + } + Book book = new Book(bookName); + NodeList indices = bookNode.getChildNodes(); + for (int i = 0; i < indices.getLength(); i++) { + Index index = Index.create(book, indices.item(i)); + if (index != null && filter.accept(index)) { + book.addIndex(index); + } + } + return book; + } +} diff --git a/src/main/java/pl/koder95/eme/io/package-info.java b/src/main/java/pl/koder95/eme/io/package-info.java new file mode 100644 index 0000000..bbb7141 --- /dev/null +++ b/src/main/java/pl/koder95/eme/io/package-info.java @@ -0,0 +1,4 @@ +/** + * Implementacje I/O i parserów indeksów. + */ +package pl.koder95.eme.io; diff --git a/src/main/java/pl/koder95/eme/xml/package-info.java b/src/main/java/pl/koder95/eme/xml/package-info.java new file mode 100644 index 0000000..dac6254 --- /dev/null +++ b/src/main/java/pl/koder95/eme/xml/package-info.java @@ -0,0 +1,4 @@ +/** + * Narzędzia XML. + */ +package pl.koder95.eme.xml; diff --git a/src/main/resources/pl/koder95/eme/strings.properties b/src/main/resources/pl/koder95/eme/strings.properties index 42fbc8b..eb695ce 100644 --- a/src/main/resources/pl/koder95/eme/strings.properties +++ b/src/main/resources/pl/koder95/eme/strings.properties @@ -13,4 +13,7 @@ FX_MENUITEM_IMPORT=Importuj... ALERT_CONFIRM_CLOSE_TITLE=Czy na pewno chcesz zamkn\u0105\u0107 program? ALERT_CONFIRM_CLOSE_HEADER=Potwierd\u017A zamkni\u0119cie programu ALERT_CONFIRM_CLOSE_CONTENT=Zanim naci\u015Bniesz \"OK\" upewnij si\u0119, \u017Ce zmiany zosta\u0142y zapisane, aby ich nie utraci\u0107. -FX_MENUITEM_RELOAD=Ponownie wczytaj dane \ No newline at end of file +FX_MENUITEM_RELOAD=Ponownie wczytaj dane +FX_RELOAD_PROGRESS_MESSAGE=Czekaj... +ALERT_RELOAD_ERROR_TITLE=B\u0142\u0105d od\u015Bwie\u017Cania danych +ALERT_RELOAD_ERROR_HEADER=Nie uda\u0142o si\u0119 ponownie wczyta\u0107 danych