diff --git a/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java b/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java index 2727486..2694104 100644 --- a/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java @@ -1,6 +1,8 @@ package com.andrewlalis.perfin.control; import com.andrewlalis.javafx_scene_router.RouteSelectionListener; +import com.andrewlalis.perfin.data.AccountHistoryItemRepository; +import com.andrewlalis.perfin.data.AccountRepository; import com.andrewlalis.perfin.data.util.DateUtil; import com.andrewlalis.perfin.model.Account; import com.andrewlalis.perfin.model.Profile; @@ -62,7 +64,8 @@ public class AccountViewController implements RouteSelectionListener { accountNumberLabel.setText(account.getAccountNumber()); accountCurrencyLabel.setText(account.getCurrency().getDisplayName()); accountCreatedAtLabel.setText(DateUtil.formatUTCAsLocalWithZone(account.getCreatedAt())); - Profile.getCurrent().getDataSource().getAccountBalanceText(account, accountBalanceLabel::setText); + Profile.getCurrent().getDataSource().getAccountBalanceText(account) + .thenAccept(accountBalanceLabel::setText); reloadHistory(); } @@ -93,7 +96,7 @@ public class AccountViewController implements RouteSelectionListener { "later if you need to." ); if (confirmResult) { - Profile.getCurrent().getDataSource().useAccountRepository(repo -> repo.archive(account.id)); + Profile.getCurrent().getDataSource().useRepo(AccountRepository.class, repo -> repo.archive(account.id)); router.replace("accounts"); } } @@ -104,7 +107,7 @@ public class AccountViewController implements RouteSelectionListener { "status?" ); if (confirm) { - Profile.getCurrent().getDataSource().useAccountRepository(repo -> repo.unarchive(account.id)); + Profile.getCurrent().getDataSource().useRepo(AccountRepository.class, repo -> repo.unarchive(account.id)); router.replace("accounts"); } } @@ -119,31 +122,27 @@ public class AccountViewController implements RouteSelectionListener { "want to hide it." ); if (confirm) { - Profile.getCurrent().getDataSource().useAccountRepository(repo -> repo.delete(account)); + Profile.getCurrent().getDataSource().useRepo(AccountRepository.class, repo -> repo.delete(account)); router.replace("accounts"); } } @FXML public void loadMoreHistory() { - Thread.ofVirtual().start(() -> { - try (var historyRepo = Profile.getCurrent().getDataSource().getAccountHistoryItemRepository()) { - List historyItems = historyRepo.findMostRecentForAccount( - account.id, - loadHistoryFrom, - historyLoadSize - ); - if (historyItems.size() < historyLoadSize) { - Platform.runLater(() -> loadMoreHistoryButton.setDisable(true)); - } else { - loadHistoryFrom = historyItems.getLast().getTimestamp(); - } - List nodes = historyItems.stream() - .map(item -> AccountHistoryItemTile.forItem(item, historyRepo, this)) - .toList(); - Platform.runLater(() -> historyItemsVBox.getChildren().addAll(nodes)); - } catch (Exception e) { - throw new RuntimeException(e); + Profile.getCurrent().getDataSource().useRepoAsync(AccountHistoryItemRepository.class, repo -> { + List historyItems = repo.findMostRecentForAccount( + account.id, + loadHistoryFrom, + historyLoadSize + ); + if (historyItems.size() < historyLoadSize) { + Platform.runLater(() -> loadMoreHistoryButton.setDisable(true)); + } else { + loadHistoryFrom = historyItems.getLast().getTimestamp(); } + List nodes = historyItems.stream() + .map(item -> AccountHistoryItemTile.forItem(item, repo, this)) + .toList(); + Platform.runLater(() -> historyItemsVBox.getChildren().addAll(nodes)); }); } } diff --git a/src/main/java/com/andrewlalis/perfin/control/AccountsViewController.java b/src/main/java/com/andrewlalis/perfin/control/AccountsViewController.java index bc70353..7234eb6 100644 --- a/src/main/java/com/andrewlalis/perfin/control/AccountsViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/AccountsViewController.java @@ -1,6 +1,7 @@ package com.andrewlalis.perfin.control; import com.andrewlalis.javafx_scene_router.RouteSelectionListener; +import com.andrewlalis.perfin.data.AccountRepository; import com.andrewlalis.perfin.data.util.CurrencyUtil; import com.andrewlalis.perfin.model.Account; import com.andrewlalis.perfin.model.MoneyValue; @@ -48,14 +49,14 @@ public class AccountsViewController implements RouteSelectionListener { public void refreshAccounts() { Profile.whenLoaded(profile -> { - Thread.ofVirtual().start(() -> profile.getDataSource().useAccountRepository(repo -> { + profile.getDataSource().useRepoAsync(AccountRepository.class, repo -> { List accounts = repo.findAllOrderedByRecentHistory(); Platform.runLater(() -> accountsPane.getChildren() .setAll(accounts.stream() .map(AccountTile::new) .toList() )); - })); + }); // Compute grand totals! Thread.ofVirtual().start(() -> { var totals = profile.getDataSource().getCombinedAccountBalances(); diff --git a/src/main/java/com/andrewlalis/perfin/control/BalanceRecordViewController.java b/src/main/java/com/andrewlalis/perfin/control/BalanceRecordViewController.java index d435767..642294a 100644 --- a/src/main/java/com/andrewlalis/perfin/control/BalanceRecordViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/BalanceRecordViewController.java @@ -1,6 +1,7 @@ package com.andrewlalis.perfin.control; import com.andrewlalis.javafx_scene_router.RouteSelectionListener; +import com.andrewlalis.perfin.data.BalanceRecordRepository; import com.andrewlalis.perfin.data.util.CurrencyUtil; import com.andrewlalis.perfin.data.util.DateUtil; import com.andrewlalis.perfin.model.Attachment; @@ -40,19 +41,16 @@ public class BalanceRecordViewController implements RouteSelectionListener { timestampLabel.setText(DateUtil.formatUTCAsLocalWithZone(balanceRecord.getTimestamp())); balanceLabel.setText(CurrencyUtil.formatMoney(balanceRecord.getMoneyAmount())); currencyLabel.setText(balanceRecord.getCurrency().getDisplayName()); - - Thread.ofVirtual().start(() -> Profile.getCurrent().getDataSource().useBalanceRecordRepository(repo -> { + Profile.getCurrent().getDataSource().useRepoAsync(BalanceRecordRepository.class, repo -> { List attachments = repo.findAttachments(balanceRecord.id); Platform.runLater(() -> attachmentsViewPane.setAttachments(attachments)); - })); + }); } @FXML public void delete() { boolean confirm = Popups.confirm("Are you sure you want to delete this balance record? This may have an effect on the derived balance of your account, as shown in Perfin."); if (confirm) { - Profile.getCurrent().getDataSource().useBalanceRecordRepository(repo -> { - repo.deleteById(balanceRecord.id); - }); + Profile.getCurrent().getDataSource().useRepo(BalanceRecordRepository.class, repo -> repo.deleteById(balanceRecord.id)); router.navigateBackAndClear(); } } diff --git a/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java b/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java index 01bddc1..de1d02d 100644 --- a/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java +++ b/src/main/java/com/andrewlalis/perfin/control/CreateBalanceRecordController.java @@ -1,6 +1,8 @@ package com.andrewlalis.perfin.control; import com.andrewlalis.javafx_scene_router.RouteSelectionListener; +import com.andrewlalis.perfin.data.AccountRepository; +import com.andrewlalis.perfin.data.BalanceRecordRepository; import com.andrewlalis.perfin.data.util.CurrencyUtil; import com.andrewlalis.perfin.data.util.DateUtil; import com.andrewlalis.perfin.data.util.FileUtil; @@ -59,12 +61,12 @@ public class CreateBalanceRecordController implements RouteSelectionListener { return; } BigDecimal reportedBalance = new BigDecimal(newValue); - Thread.ofVirtual().start(() -> Profile.getCurrent().getDataSource().useAccountRepository(repo -> { + Profile.getCurrent().getDataSource().useRepoAsync(AccountRepository.class, repo -> { BigDecimal derivedBalance = repo.deriveCurrentBalance(account.id); Platform.runLater(() -> balanceWarningLabel.visibleProperty().set( !reportedBalance.setScale(derivedBalance.scale(), RoundingMode.HALF_UP).equals(derivedBalance) )); - })); + }); }); var formValid = timestampValid.and(balanceValid); @@ -83,12 +85,12 @@ public class CreateBalanceRecordController implements RouteSelectionListener { public void onRouteSelected(Object context) { this.account = (Account) context; timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT)); - Thread.ofVirtual().start(() -> Profile.getCurrent().getDataSource().useAccountRepository(repo -> { + Profile.getCurrent().getDataSource().useRepoAsync(AccountRepository.class, repo -> { BigDecimal value = repo.deriveCurrentBalance(account.id); Platform.runLater(() -> balanceField.setText( CurrencyUtil.formatMoneyAsBasicNumber(new MoneyValue(value, account.getCurrency())) )); - })); + }); attachmentSelectionArea.clear(); } @@ -102,7 +104,7 @@ public class CreateBalanceRecordController implements RouteSelectionListener { localTimestamp.atZone(ZoneId.systemDefault()).format(DateUtil.DEFAULT_DATETIME_FORMAT_WITH_ZONE) )); if (confirm && confirmIfInconsistentBalance(reportedBalance)) { - Profile.getCurrent().getDataSource().useBalanceRecordRepository(repo -> { + Profile.getCurrent().getDataSource().useRepo(BalanceRecordRepository.class, repo -> { repo.insert( DateUtil.localToUTC(localTimestamp), account.id, @@ -120,12 +122,10 @@ public class CreateBalanceRecordController implements RouteSelectionListener { } private boolean confirmIfInconsistentBalance(BigDecimal reportedBalance) { - BigDecimal currentDerivedBalance; - try (var accountRepo = Profile.getCurrent().getDataSource().getAccountRepository()) { - currentDerivedBalance = accountRepo.deriveCurrentBalance(account.id); - } catch (Exception e) { - throw new RuntimeException(e); - } + BigDecimal currentDerivedBalance = Profile.getCurrent().getDataSource().mapRepo( + AccountRepository.class, + repo -> repo.deriveCurrentBalance(account.id) + ); if (!reportedBalance.setScale(currentDerivedBalance.scale(), RoundingMode.HALF_UP).equals(currentDerivedBalance)) { String msg = "The balance you reported (%s) doesn't match the balance that Perfin derived from your account's transactions (%s). It's encouraged to go back and add any missing transactions first, but you may proceed now if you understand the consequences of an inconsistent account balance history.\n\nAre you absolutely sure you want to create this balance record?".formatted( CurrencyUtil.formatMoney(new MoneyValue(reportedBalance, account.getCurrency())), diff --git a/src/main/java/com/andrewlalis/perfin/control/EditTransactionController.java b/src/main/java/com/andrewlalis/perfin/control/EditTransactionController.java index 220e120..e111a6e 100644 --- a/src/main/java/com/andrewlalis/perfin/control/EditTransactionController.java +++ b/src/main/java/com/andrewlalis/perfin/control/EditTransactionController.java @@ -1,6 +1,7 @@ package com.andrewlalis.perfin.control; import com.andrewlalis.javafx_scene_router.RouteSelectionListener; +import com.andrewlalis.perfin.data.TransactionRepository; import com.andrewlalis.perfin.data.pagination.PageRequest; import com.andrewlalis.perfin.data.pagination.Sort; import com.andrewlalis.perfin.data.util.CurrencyUtil; @@ -104,23 +105,25 @@ public class EditTransactionController implements RouteSelectionListener { } @FXML public void save() { - LocalDateTime utcTimestamp = DateUtil.localToUTC(parseTimestamp()); - BigDecimal amount = new BigDecimal(amountField.getText()); - Currency currency = currencyChoiceBox.getValue(); - String description = getSanitizedDescription(); - CreditAndDebitAccounts linkedAccounts = getSelectedAccounts(); - List attachments = attachmentsSelectionArea.getSelectedFiles(); - Profile.getCurrent().getDataSource().useTransactionRepository(repo -> { - repo.insert( - utcTimestamp, - amount, - currency, - description, - linkedAccounts, - attachments - ); - }); - router.navigateBackAndClear(); + final long idToNavigate; + if (transaction == null) { + LocalDateTime utcTimestamp = DateUtil.localToUTC(parseTimestamp()); + BigDecimal amount = new BigDecimal(amountField.getText()); + Currency currency = currencyChoiceBox.getValue(); + String description = getSanitizedDescription(); + CreditAndDebitAccounts linkedAccounts = getSelectedAccounts(); + List attachments = attachmentsSelectionArea.getSelectedFiles(); + Profile.getCurrent().getDataSource().useRepo(TransactionRepository.class, repo -> { + repo.insert( + utcTimestamp, + amount, + currency, + description, + linkedAccounts, + attachments + ); + }); + } } @FXML public void cancel() { diff --git a/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java b/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java index e62c156..be8feb2 100644 --- a/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java @@ -1,5 +1,6 @@ package com.andrewlalis.perfin.control; +import com.andrewlalis.perfin.data.TransactionRepository; import com.andrewlalis.perfin.data.util.CurrencyUtil; import com.andrewlalis.perfin.data.util.DateUtil; import com.andrewlalis.perfin.model.Attachment; @@ -44,27 +45,19 @@ public class TransactionViewController { amountLabel.setText(CurrencyUtil.formatMoney(transaction.getMoneyAmount())); timestampLabel.setText(DateUtil.formatUTCAsLocalWithZone(transaction.getTimestamp())); descriptionLabel.setText(transaction.getDescription()); - - Thread.ofVirtual().start(() -> { - Profile.getCurrent().getDataSource().useTransactionRepository(repo -> { - CreditAndDebitAccounts accounts = repo.findLinkedAccounts(transaction.id); - Platform.runLater(() -> { - accounts.ifDebit(acc -> { - debitAccountLink.setText(acc.getShortName()); - debitAccountLink.setOnAction(event -> router.navigate("account", acc)); - }); - accounts.ifCredit(acc -> { - creditAccountLink.setText(acc.getShortName()); - creditAccountLink.setOnAction(event -> router.navigate("account", acc)); - }); + Profile.getCurrent().getDataSource().useRepoAsync(TransactionRepository.class, repo -> { + CreditAndDebitAccounts accounts = repo.findLinkedAccounts(transaction.id); + List attachments = repo.findAttachments(transaction.id); + Platform.runLater(() -> { + accounts.ifDebit(acc -> { + debitAccountLink.setText(acc.getShortName()); + debitAccountLink.setOnAction(event -> router.navigate("account", acc)); }); - }); - }); - - Thread.ofVirtual().start(() -> { - Profile.getCurrent().getDataSource().useTransactionRepository(repo -> { - List attachments = repo.findAttachments(transaction.id); - Platform.runLater(() -> attachmentsViewPane.setAttachments(attachments)); + accounts.ifCredit(acc -> { + creditAccountLink.setText(acc.getShortName()); + creditAccountLink.setOnAction(event -> router.navigate("account", acc)); + }); + attachmentsViewPane.setAttachments(attachments); }); }); } @@ -84,10 +77,8 @@ public class TransactionViewController { "it's derived from the most recent balance-record, and transactions." ); if (confirm) { - Profile.getCurrent().getDataSource().useTransactionRepository(repo -> { - repo.delete(transaction.id); - router.replace("transactions"); - }); + Profile.getCurrent().getDataSource().useRepo(TransactionRepository.class, repo -> repo.delete(transaction.id)); + router.replace("transactions"); } } diff --git a/src/main/java/com/andrewlalis/perfin/control/TransactionsViewController.java b/src/main/java/com/andrewlalis/perfin/control/TransactionsViewController.java index 4cccda5..10a9618 100644 --- a/src/main/java/com/andrewlalis/perfin/control/TransactionsViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/TransactionsViewController.java @@ -1,6 +1,8 @@ package com.andrewlalis.perfin.control; import com.andrewlalis.javafx_scene_router.RouteSelectionListener; +import com.andrewlalis.perfin.data.AccountRepository; +import com.andrewlalis.perfin.data.TransactionRepository; import com.andrewlalis.perfin.data.pagination.Page; import com.andrewlalis.perfin.data.pagination.PageRequest; import com.andrewlalis.perfin.data.pagination.Sort; @@ -122,27 +124,25 @@ public class TransactionsViewController implements RouteSelectionListener { transactionsVBox.getChildren().clear(); // Clear the transactions before reload initially. // Refresh account filter options. - Thread.ofVirtual().start(() -> { - Profile.getCurrent().getDataSource().useAccountRepository(repo -> { - List accounts = repo.findAll(PageRequest.unpaged(Sort.asc("name"))).items(); - accounts.add(null); - Platform.runLater(() -> { - filterByAccountComboBox.getItems().clear(); - filterByAccountComboBox.getItems().addAll(accounts); - filterByAccountComboBox.getSelectionModel().selectLast(); - filterByAccountComboBox.getButtonCell().updateIndex(accounts.size() - 1); - }); + Profile.getCurrent().getDataSource().useRepoAsync(AccountRepository.class, repo -> { + List accounts = repo.findAll(PageRequest.unpaged(Sort.asc("name"))).items(); + accounts.add(null); + Platform.runLater(() -> { + filterByAccountComboBox.getItems().clear(); + filterByAccountComboBox.getItems().addAll(accounts); + filterByAccountComboBox.getSelectionModel().selectLast(); + filterByAccountComboBox.getButtonCell().updateIndex(accounts.size() - 1); }); }); // If a transaction id is given in the route context, navigate to the page it's on and select it. if (context instanceof RouteContext ctx && ctx.selectedTransactionId != null) { - Thread.ofVirtual().start(() -> { - Profile.getCurrent().getDataSource().useTransactionRepository(repo -> { - repo.findById(ctx.selectedTransactionId).ifPresent(tx -> { - long offset = repo.countAllAfter(tx.id); - int pageNumber = (int) (offset / paginationControls.getItemsPerPage()) + 1; + Profile.getCurrent().getDataSource().useRepoAsync(TransactionRepository.class, repo -> { + repo.findById(ctx.selectedTransactionId).ifPresent(tx -> { + long offset = repo.countAllAfter(tx.id); + int pageNumber = (int) (offset / paginationControls.getItemsPerPage()) + 1; + Platform.runLater(() -> { paginationControls.setPage(pageNumber).thenRun(() -> selectedTransaction.set(tx)); }); }); diff --git a/src/main/java/com/andrewlalis/perfin/data/AccountEntryRepository.java b/src/main/java/com/andrewlalis/perfin/data/AccountEntryRepository.java index 4e1d4c0..c50c08c 100644 --- a/src/main/java/com/andrewlalis/perfin/data/AccountEntryRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/AccountEntryRepository.java @@ -7,7 +7,7 @@ import java.time.LocalDateTime; import java.util.Currency; import java.util.List; -public interface AccountEntryRepository extends AutoCloseable { +public interface AccountEntryRepository extends Repository, AutoCloseable { long insert( LocalDateTime timestamp, long accountId, diff --git a/src/main/java/com/andrewlalis/perfin/data/AccountHistoryItemRepository.java b/src/main/java/com/andrewlalis/perfin/data/AccountHistoryItemRepository.java index f3e9838..a669d03 100644 --- a/src/main/java/com/andrewlalis/perfin/data/AccountHistoryItemRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/AccountHistoryItemRepository.java @@ -9,7 +9,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Optional; -public interface AccountHistoryItemRepository extends AutoCloseable { +public interface AccountHistoryItemRepository extends Repository, AutoCloseable { void recordAccountEntry(LocalDateTime timestamp, long accountId, long entryId); void recordBalanceRecord(LocalDateTime timestamp, long accountId, long recordId); void recordText(LocalDateTime timestamp, long accountId, String text); diff --git a/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java b/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java index 3fc4e1f..b8f97ff 100644 --- a/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java @@ -13,7 +13,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; -public interface AccountRepository extends AutoCloseable { +public interface AccountRepository extends Repository, AutoCloseable { long insert(AccountType type, String accountNumber, String name, Currency currency); Page findAll(PageRequest pagination); List findAllOrderedByRecentHistory(); diff --git a/src/main/java/com/andrewlalis/perfin/data/AttachmentRepository.java b/src/main/java/com/andrewlalis/perfin/data/AttachmentRepository.java index 34e4dd3..c5c2c34 100644 --- a/src/main/java/com/andrewlalis/perfin/data/AttachmentRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/AttachmentRepository.java @@ -5,7 +5,7 @@ import com.andrewlalis.perfin.model.Attachment; import java.nio.file.Path; import java.util.Optional; -public interface AttachmentRepository extends AutoCloseable { +public interface AttachmentRepository extends Repository, AutoCloseable { Attachment insert(Path sourcePath); Optional findById(long attachmentId); Optional findByIdentifier(String identifier); diff --git a/src/main/java/com/andrewlalis/perfin/data/BalanceRecordRepository.java b/src/main/java/com/andrewlalis/perfin/data/BalanceRecordRepository.java index 7931ad8..ee9050c 100644 --- a/src/main/java/com/andrewlalis/perfin/data/BalanceRecordRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/BalanceRecordRepository.java @@ -10,7 +10,7 @@ import java.util.Currency; import java.util.List; import java.util.Optional; -public interface BalanceRecordRepository extends AutoCloseable { +public interface BalanceRecordRepository extends Repository, AutoCloseable { long insert(LocalDateTime utcTimestamp, long accountId, BigDecimal balance, Currency currency, List attachments); BalanceRecord findLatestByAccountId(long accountId); Optional findClosestBefore(long accountId, LocalDateTime utcTimestamp); diff --git a/src/main/java/com/andrewlalis/perfin/data/DataSource.java b/src/main/java/com/andrewlalis/perfin/data/DataSource.java index f2351e1..ca008de 100644 --- a/src/main/java/com/andrewlalis/perfin/data/DataSource.java +++ b/src/main/java/com/andrewlalis/perfin/data/DataSource.java @@ -2,8 +2,6 @@ package com.andrewlalis.perfin.data; import com.andrewlalis.perfin.data.pagination.PageRequest; import com.andrewlalis.perfin.data.util.CurrencyUtil; -import com.andrewlalis.perfin.data.util.DbUtil; -import com.andrewlalis.perfin.data.util.ThrowableConsumer; import com.andrewlalis.perfin.model.Account; import com.andrewlalis.perfin.model.AccountType; import com.andrewlalis.perfin.model.MoneyValue; @@ -11,11 +9,11 @@ import javafx.application.Platform; import java.math.BigDecimal; import java.nio.file.Path; -import java.util.Currency; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; /** * Interface for methods to obtain any data from a {@link com.andrewlalis.perfin.model.Profile} @@ -35,30 +33,70 @@ public interface DataSource { AttachmentRepository getAttachmentRepository(); AccountHistoryItemRepository getAccountHistoryItemRepository(); - default void useAccountRepository(ThrowableConsumer repoConsumer) { - DbUtil.useClosable(this::getAccountRepository, repoConsumer); + // Repository helper methods: + + @SuppressWarnings("unchecked") + default T mapRepo(Class repoType, Function action) { + Supplier repoSupplier = getRepo(repoType); + if (repoSupplier == null) throw new IllegalArgumentException("Repository type " + repoType + " is not supported."); + boolean repoCloseable = Arrays.asList(repoType.getInterfaces()).contains(AutoCloseable.class); + if (repoCloseable) { + try (AutoCloseable c = (AutoCloseable) repoSupplier.get()) { + R repo = (R) c; + return action.apply(repo); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + R repo = repoSupplier.get(); + return action.apply(repo); + } } - default void useBalanceRecordRepository(ThrowableConsumer repoConsumer) { - DbUtil.useClosable(this::getBalanceRecordRepository, repoConsumer); + default CompletableFuture mapRepoAsync(Class repoType, Function action) { + CompletableFuture cf = new CompletableFuture<>(); + Thread.ofVirtual().start(() -> { + cf.complete(mapRepo(repoType, action)); + }); + return cf; } - default void useTransactionRepository(ThrowableConsumer repoConsumer) { - DbUtil.useClosable(this::getTransactionRepository, repoConsumer); + default void useRepo(Class repoType, Consumer action) { + mapRepo(repoType, (Function) repo -> { + action.accept(repo); + return null; + }); } - default void useAttachmentRepository(ThrowableConsumer repoConsumer) { - DbUtil.useClosable(this::getAttachmentRepository, repoConsumer); + default CompletableFuture useRepoAsync(Class repoType, Consumer action) { + return mapRepoAsync(repoType, repo -> { + action.accept(repo); + return null; + }); + } + + @SuppressWarnings("unchecked") + private Supplier getRepo(Class type) { + final Map, Supplier> repoSuppliers = Map.of( + AccountRepository.class, this::getAccountRepository, + BalanceRecordRepository.class, this::getBalanceRecordRepository, + TransactionRepository.class, this::getTransactionRepository, + AttachmentRepository.class, this::getAttachmentRepository, + AccountHistoryItemRepository.class, this::getAccountHistoryItemRepository + ); + return (Supplier) repoSuppliers.get(type); } // Utility methods: - default void getAccountBalanceText(Account account, Consumer balanceConsumer) { - Thread.ofVirtual().start(() -> useAccountRepository(repo -> { + default CompletableFuture getAccountBalanceText(Account account) { + CompletableFuture cf = new CompletableFuture<>(); + mapRepoAsync(AccountRepository.class, repo -> { BigDecimal balance = repo.deriveCurrentBalance(account.id); MoneyValue money = new MoneyValue(balance, account.getCurrency()); - Platform.runLater(() -> balanceConsumer.accept(CurrencyUtil.formatMoney(money))); - })); + return CurrencyUtil.formatMoney(money); + }).thenAccept(s -> Platform.runLater(() -> cf.complete(s))); + return cf; } default Map getCombinedAccountBalances() { diff --git a/src/main/java/com/andrewlalis/perfin/data/Repository.java b/src/main/java/com/andrewlalis/perfin/data/Repository.java new file mode 100644 index 0000000..a91996a --- /dev/null +++ b/src/main/java/com/andrewlalis/perfin/data/Repository.java @@ -0,0 +1,4 @@ +package com.andrewlalis.perfin.data; + +public interface Repository { +} diff --git a/src/main/java/com/andrewlalis/perfin/data/TransactionRepository.java b/src/main/java/com/andrewlalis/perfin/data/TransactionRepository.java index 4a4eecb..e6943fc 100644 --- a/src/main/java/com/andrewlalis/perfin/data/TransactionRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/TransactionRepository.java @@ -14,7 +14,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; -public interface TransactionRepository extends AutoCloseable { +public interface TransactionRepository extends Repository, AutoCloseable { long insert( LocalDateTime utcTimestamp, BigDecimal amount, diff --git a/src/main/java/com/andrewlalis/perfin/view/component/AccountSelectionBox.java b/src/main/java/com/andrewlalis/perfin/view/component/AccountSelectionBox.java index 022e92e..2b38968 100644 --- a/src/main/java/com/andrewlalis/perfin/view/component/AccountSelectionBox.java +++ b/src/main/java/com/andrewlalis/perfin/view/component/AccountSelectionBox.java @@ -1,5 +1,6 @@ package com.andrewlalis.perfin.view.component; +import com.andrewlalis.perfin.data.AccountRepository; import com.andrewlalis.perfin.data.util.CurrencyUtil; import com.andrewlalis.perfin.model.Account; import com.andrewlalis.perfin.model.MoneyValue; @@ -109,13 +110,13 @@ public class AccountSelectionBox extends ComboBox { nameLabel.setText(item.getName() + " (" + item.getAccountNumberSuffix() + ")"); if (showBalanceProp.get()) { - Thread.ofVirtual().start(() -> Profile.getCurrent().getDataSource().useAccountRepository(repo -> { + Profile.getCurrent().getDataSource().useRepoAsync(AccountRepository.class, repo -> { BigDecimal balance = repo.deriveCurrentBalance(item.id); Platform.runLater(() -> { balanceLabel.setText(CurrencyUtil.formatMoney(new MoneyValue(balance, item.getCurrency()))); balanceLabel.setVisible(true); }); - })); + }); } } } diff --git a/src/main/java/com/andrewlalis/perfin/view/component/AccountTile.java b/src/main/java/com/andrewlalis/perfin/view/component/AccountTile.java index 8907f25..ee4a415 100644 --- a/src/main/java/com/andrewlalis/perfin/view/component/AccountTile.java +++ b/src/main/java/com/andrewlalis/perfin/view/component/AccountTile.java @@ -1,5 +1,6 @@ package com.andrewlalis.perfin.view.component; +import com.andrewlalis.perfin.data.AccountRepository; import com.andrewlalis.perfin.data.util.CurrencyUtil; import com.andrewlalis.perfin.model.Account; import com.andrewlalis.perfin.model.AccountType; @@ -80,7 +81,7 @@ public class AccountTile extends BorderPane { Label balanceLabel = new Label("Computing balance..."); balanceLabel.getStyleClass().addAll("mono-font"); balanceLabel.setDisable(true); - Thread.ofVirtual().start(() -> Profile.getCurrent().getDataSource().useAccountRepository(repo -> { + Profile.getCurrent().getDataSource().useRepoAsync(AccountRepository.class, repo -> { BigDecimal balance = repo.deriveCurrentBalance(account.id); String text = CurrencyUtil.formatMoney(new MoneyValue(balance, account.getCurrency())); Platform.runLater(() -> { @@ -92,10 +93,6 @@ public class AccountTile extends BorderPane { } balanceLabel.setDisable(false); }); - })); - Profile.getCurrent().getDataSource().getAccountBalanceText(account, text -> { - balanceLabel.setText(text); - balanceLabel.setDisable(false); }); propertiesPane.getChildren().addAll( diff --git a/src/main/java/com/andrewlalis/perfin/view/component/TransactionTile.java b/src/main/java/com/andrewlalis/perfin/view/component/TransactionTile.java index e889371..fd0ff44 100644 --- a/src/main/java/com/andrewlalis/perfin/view/component/TransactionTile.java +++ b/src/main/java/com/andrewlalis/perfin/view/component/TransactionTile.java @@ -1,5 +1,6 @@ package com.andrewlalis.perfin.view.component; +import com.andrewlalis.perfin.data.TransactionRepository; import com.andrewlalis.perfin.data.util.CurrencyUtil; import com.andrewlalis.perfin.data.util.DateUtil; import com.andrewlalis.perfin.model.CreditAndDebitAccounts; @@ -99,13 +100,9 @@ public class TransactionTile extends BorderPane { } private CompletableFuture getCreditAndDebitAccounts(Transaction transaction) { - CompletableFuture cf = new CompletableFuture<>(); - Thread.ofVirtual().start(() -> { - Profile.getCurrent().getDataSource().useTransactionRepository(repo -> { - CreditAndDebitAccounts accounts = repo.findLinkedAccounts(transaction.id); - cf.complete(accounts); - }); - }); - return cf; + return Profile.getCurrent().getDataSource().mapRepoAsync( + TransactionRepository.class, + repo -> repo.findLinkedAccounts(transaction.id) + ); } }