From f1f743d23351e9f318ab53a1ace733c8d7d35a21 Mon Sep 17 00:00:00 2001 From: teridax Date: Sat, 22 Jul 2023 13:58:52 +0200 Subject: [PATCH] fixed banking dialogs input validation --- src/me/teridax/jcash/banking/Bank.java | 1 + .../teridax/jcash/decode/StringDecoder.java | 12 +++ .../jcash/gui/InvalidInputException.java | 18 ++++ .../jcash/gui/NumberDocumentFilter.java | 17 ++++ src/me/teridax/jcash/gui/Utils.java | 16 +++- .../jcash/gui/account/AccountController.java | 25 +++-- .../jcash/gui/account/AccountView.java | 54 ++++++----- .../jcash/gui/deposit/DepositController.java | 71 ++++++++++++++ .../jcash/gui/deposit/DepositData.java | 14 +++ .../jcash/gui/deposit/DepositDialog.java | 37 -------- .../jcash/gui/deposit/DepositView.java | 79 ++++++++++----- .../jcash/gui/login/LoginController.java | 12 +-- src/me/teridax/jcash/gui/login/LoginData.java | 19 +++- .../jcash/gui/takeoff/TakeoffController.java | 67 +++++++++++++ .../jcash/gui/takeoff/TakeoffData.java | 14 +++ .../jcash/gui/takeoff/TakeoffDialog.java | 38 -------- .../jcash/gui/takeoff/TakeoffView.java | 84 ++++++++++------ .../gui/transfer/TransferController.java | 72 ++++++++++++++ .../jcash/gui/transfer/TransferDialog.java | 46 --------- .../jcash/gui/transfer/TransferView.java | 95 ++++++++++++------- 20 files changed, 545 insertions(+), 246 deletions(-) create mode 100644 src/me/teridax/jcash/gui/InvalidInputException.java create mode 100644 src/me/teridax/jcash/gui/NumberDocumentFilter.java create mode 100644 src/me/teridax/jcash/gui/deposit/DepositController.java create mode 100644 src/me/teridax/jcash/gui/deposit/DepositData.java delete mode 100644 src/me/teridax/jcash/gui/deposit/DepositDialog.java create mode 100644 src/me/teridax/jcash/gui/takeoff/TakeoffController.java create mode 100644 src/me/teridax/jcash/gui/takeoff/TakeoffData.java delete mode 100644 src/me/teridax/jcash/gui/takeoff/TakeoffDialog.java create mode 100644 src/me/teridax/jcash/gui/transfer/TransferController.java delete mode 100644 src/me/teridax/jcash/gui/transfer/TransferDialog.java diff --git a/src/me/teridax/jcash/banking/Bank.java b/src/me/teridax/jcash/banking/Bank.java index 9d24ff0..1330004 100644 --- a/src/me/teridax/jcash/banking/Bank.java +++ b/src/me/teridax/jcash/banking/Bank.java @@ -4,6 +4,7 @@ import me.teridax.jcash.Logging; import me.teridax.jcash.banking.accounts.Account; import me.teridax.jcash.banking.accounts.Owner; import me.teridax.jcash.banking.management.Profile; +import me.teridax.jcash.gui.Utils; import java.util.*; import java.util.regex.Pattern; diff --git a/src/me/teridax/jcash/decode/StringDecoder.java b/src/me/teridax/jcash/decode/StringDecoder.java index ba56388..dd53aeb 100644 --- a/src/me/teridax/jcash/decode/StringDecoder.java +++ b/src/me/teridax/jcash/decode/StringDecoder.java @@ -2,6 +2,7 @@ package me.teridax.jcash.decode; import me.teridax.jcash.lang.Locales; +import javax.swing.text.NumberFormatter; import java.text.NumberFormat; import java.text.ParseException; import java.util.Objects; @@ -17,6 +18,17 @@ public class StringDecoder { return NumberFormat.getInstance(Locales.getDefaultLocale()); } + public static NumberFormatter getNumberFormatter(double maxValue) { + var formatter = new NumberFormatter(); + formatter.setValueClass(Double.class); + formatter.setMinimum(0d); + formatter.setMaximum(maxValue); + formatter.setAllowsInvalid(true); + formatter.setCommitsOnValidEdit(true); + + return formatter; + } + /** * Attempts to convert the given string into a double value representing a percentage. * The output value will be in the range [0, 100]. Strings formatted without a percentage diff --git a/src/me/teridax/jcash/gui/InvalidInputException.java b/src/me/teridax/jcash/gui/InvalidInputException.java new file mode 100644 index 0000000..73fb872 --- /dev/null +++ b/src/me/teridax/jcash/gui/InvalidInputException.java @@ -0,0 +1,18 @@ +package me.teridax.jcash.gui; + +import java.text.ParseException; + +public class InvalidInputException extends IllegalStateException { + + public InvalidInputException(String message, Exception cause) { + super(message, cause); + } + + public InvalidInputException(String message) { + super(message); + } + + public InvalidInputException(Exception cause) { + super(cause); + } +} diff --git a/src/me/teridax/jcash/gui/NumberDocumentFilter.java b/src/me/teridax/jcash/gui/NumberDocumentFilter.java new file mode 100644 index 0000000..8a5188f --- /dev/null +++ b/src/me/teridax/jcash/gui/NumberDocumentFilter.java @@ -0,0 +1,17 @@ +package me.teridax.jcash.gui; + +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.DocumentFilter; +import javax.swing.text.NumberFormatter; + +public class NumberDocumentFilter extends DocumentFilter { + @Override + public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException { + super.insertString(fb, offset, string, attr); + } + + private String filterString(String text) { + return ""; + } +} diff --git a/src/me/teridax/jcash/gui/Utils.java b/src/me/teridax/jcash/gui/Utils.java index 45d1bc6..9e57fd9 100644 --- a/src/me/teridax/jcash/gui/Utils.java +++ b/src/me/teridax/jcash/gui/Utils.java @@ -1,12 +1,17 @@ package me.teridax.jcash.gui; +import me.teridax.jcash.Main; +import me.teridax.jcash.lang.Locales; + import javax.swing.*; import java.awt.*; +import java.text.Format; +import java.text.NumberFormat; public class Utils { public static String addHeading(String title) { - return String.format("%s", title); + return String.format("

%s

", title); } /** @@ -58,4 +63,13 @@ public class Utils { constraints.fill = GridBagConstraints.HORIZONTAL; target.add(comp, constraints); } + + /** + * Opens an error message dialog. This function will block the calling thread until the error message + * disposed. + * @param message the message to show to the user + */ + public static void error(String message) { + JOptionPane.showMessageDialog(Main.getInstance().getWindow(), message, "Error occurred", JOptionPane.ERROR_MESSAGE); + } } diff --git a/src/me/teridax/jcash/gui/account/AccountController.java b/src/me/teridax/jcash/gui/account/AccountController.java index 7280bb1..330358e 100644 --- a/src/me/teridax/jcash/gui/account/AccountController.java +++ b/src/me/teridax/jcash/gui/account/AccountController.java @@ -4,9 +4,9 @@ import me.teridax.jcash.Logging; import me.teridax.jcash.Main; import me.teridax.jcash.banking.management.BankingManagementSystem; import me.teridax.jcash.banking.management.Profile; -import me.teridax.jcash.gui.deposit.DepositDialog; -import me.teridax.jcash.gui.takeoff.TakeoffDialog; -import me.teridax.jcash.gui.transfer.TransferDialog; +import me.teridax.jcash.gui.deposit.DepositController; +import me.teridax.jcash.gui.takeoff.TakeoffController; +import me.teridax.jcash.gui.transfer.TransferController; /** * Controller for controlling the gui of an account. @@ -31,14 +31,25 @@ public class AccountController { private void createListeners(Profile profile) { this.view.getAccountSelection().addActionListener(e -> changeAccount()); - this.view.getLogout().addActionListener(e -> logout()); + this.view.getDeposit().addActionListener(e -> depositMoney()); + this.view.getTakeoff().addActionListener(e -> takeoffMoney()); + this.view.getTransfer().addActionListener(e -> transferMoney()); + } - this.view.getDeposit().addActionListener(e -> new DepositDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile))); + private void depositMoney() { + new DepositController(profile.getPrimaryAccount()); + this.view.updateAccountVariables(profile); + } - this.view.getTakeoff().addActionListener(e -> new TakeoffDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile))); + private void transferMoney() { + new TransferController(profile.getPrimaryAccount(), data.getBms()); + this.view.updateAccountVariables(profile); + } - this.view.getTransfer().addActionListener(e -> new TransferDialog(profile.getPrimaryAccount(), data.getBms(), () -> this.view.setProfile(profile))); + private void takeoffMoney() { + new TakeoffController(profile.getPrimaryAccount()); + this.view.updateAccountVariables(profile); } private void logout() { diff --git a/src/me/teridax/jcash/gui/account/AccountView.java b/src/me/teridax/jcash/gui/account/AccountView.java index 009a81f..e314a0c 100644 --- a/src/me/teridax/jcash/gui/account/AccountView.java +++ b/src/me/teridax/jcash/gui/account/AccountView.java @@ -38,37 +38,14 @@ public class AccountView extends JPanel { } public void setProfile(Profile profile) { - Logging.LOGGER.finer("Changing profile of account view"); - var bank = profile.getBank(); - var account = profile.getPrimaryAccount(); - var owner = profile.getOwner(); - - this.blz.setText(bank.getBlz()); - this.bankName.setText(bank.getName()); - - this.iban.setText(String.valueOf(account.getIban())); - this.name.setText(owner.getName() + " " + owner.getFamilyName()); - this.address.setText(owner.getStreet() + " " + owner.getCity()); - - this.balance.setText(StringDecoder.getNumberFormat().format(account.getBalance()) + " €"); - - this.type.setText(translate(account.getClass().getSimpleName())); - if (account instanceof CurrentAccount) { - this.typeSpecialLabel.setText(translate("Overdraft")); - this.typeSpecialProperty.setText(StringDecoder.getNumberFormat().format(((CurrentAccount) account).getOverdraft()) + " €"); - } else if (account instanceof SavingsAccount) { - this.typeSpecialLabel.setText(translate("Interest rate")); - this.typeSpecialProperty.setText(((SavingsAccount) account).getInterest() + " %"); - } else { - Logging.LOGGER.severe("Type of new primary account cannot be determined: " + account.getClass().getName()); - } + this.updateAccountVariables(profile); this.accountSelection.removeAllItems(); for (var otherAccount : profile.getAccounts()) { this.accountSelection.addItem(otherAccount.getDescription()); } - this.accountSelection.setSelectedItem(account.getDescription()); + this.accountSelection.setSelectedItem(profile.getPrimaryAccount().getDescription()); } private void createLayout() { @@ -156,4 +133,31 @@ public class AccountView extends JPanel { public JButton getTakeoff() { return takeoff; } + + public void updateAccountVariables(Profile profile) { + Logging.LOGGER.finer("Updating account view"); + var bank = profile.getBank(); + var account = profile.getPrimaryAccount(); + var owner = profile.getOwner(); + + this.blz.setText(bank.getBlz()); + this.bankName.setText(bank.getName()); + + this.iban.setText(String.valueOf(account.getIban())); + this.name.setText(owner.getName() + " " + owner.getFamilyName()); + this.address.setText(owner.getStreet() + " " + owner.getCity()); + + this.balance.setText(StringDecoder.getNumberFormat().format(account.getBalance()) + " €"); + + this.type.setText(translate(account.getClass().getSimpleName())); + if (account instanceof CurrentAccount) { + this.typeSpecialLabel.setText(translate("Overdraft")); + this.typeSpecialProperty.setText(StringDecoder.getNumberFormat().format(((CurrentAccount) account).getOverdraft()) + " €"); + } else if (account instanceof SavingsAccount) { + this.typeSpecialLabel.setText(translate("Interest rate")); + this.typeSpecialProperty.setText(((SavingsAccount) account).getInterest() + " %"); + } else { + Logging.LOGGER.severe("Type of new primary account cannot be determined: " + account.getClass().getName()); + } + } } diff --git a/src/me/teridax/jcash/gui/deposit/DepositController.java b/src/me/teridax/jcash/gui/deposit/DepositController.java new file mode 100644 index 0000000..55a574e --- /dev/null +++ b/src/me/teridax/jcash/gui/deposit/DepositController.java @@ -0,0 +1,71 @@ +package me.teridax.jcash.gui.deposit; + +import me.teridax.jcash.Logging; +import me.teridax.jcash.banking.accounts.Account; +import me.teridax.jcash.gui.InvalidInputException; +import me.teridax.jcash.gui.Utils; + +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.text.ParseException; + +public class DepositController { + + private final DepositView view; + private final Account account; + private final DepositData data; + + public DepositController(Account account) { + this.account = account; + + this.data = new DepositData(account.getBalance()); + this.view = new DepositView(this.data.getMaxValue()); + + this.view.getDeposit().addActionListener(e -> depositMoney()); + this.view.getCancel().addActionListener(e -> view.dispose()); + this.view.getValue().getDocument().addDocumentListener(new DocumentListener() { + private void validateInputState() { + var balance = account.getBalance(); + try { + view.getValue().commitEdit(); + var amount = view.getAmount(); + view.setCommittedValue(amount, balance + amount); + view.getDeposit().setEnabled(true); + } catch (InvalidInputException | ParseException ex) { + view.setCommittedValue(0, balance); + view.getDeposit().setEnabled(false); + } + } + + @Override + public void insertUpdate(DocumentEvent documentEvent) { + validateInputState(); + } + + @Override + public void removeUpdate(DocumentEvent documentEvent) { + validateInputState(); + } + + @Override + public void changedUpdate(DocumentEvent documentEvent) { + validateInputState(); + } + }); + this.view.showDialog(); + } + + private void depositMoney() { + try { + var amount = view.getAmount(); + Logging.LOGGER.fine("Depositing money of account: " + account.getIban() + " amount: " + amount); + account.deposit(amount); + } catch (IllegalArgumentException ex) { + Logging.LOGGER.severe("Cannot deposit money of account: " + account.getIban() + " because: " + ex.getMessage()); + Utils.error(ex.getMessage()); + } catch (InvalidInputException ex) { + Utils.error(ex.getMessage()); + } + view.dispose(); + } +} diff --git a/src/me/teridax/jcash/gui/deposit/DepositData.java b/src/me/teridax/jcash/gui/deposit/DepositData.java new file mode 100644 index 0000000..71187fd --- /dev/null +++ b/src/me/teridax/jcash/gui/deposit/DepositData.java @@ -0,0 +1,14 @@ +package me.teridax.jcash.gui.deposit; + +public class DepositData { + + private double maxValue; + + public DepositData(double maxValue) { + this.maxValue = maxValue; + } + + public double getMaxValue() { + return maxValue; + } +} diff --git a/src/me/teridax/jcash/gui/deposit/DepositDialog.java b/src/me/teridax/jcash/gui/deposit/DepositDialog.java deleted file mode 100644 index 352a967..0000000 --- a/src/me/teridax/jcash/gui/deposit/DepositDialog.java +++ /dev/null @@ -1,37 +0,0 @@ -package me.teridax.jcash.gui.deposit; - -import me.teridax.jcash.Logging; -import me.teridax.jcash.banking.accounts.Account; - -public class DepositDialog { - - private final DepositView view; - private final Account account; - private final Runnable onDeposit; - - public DepositDialog(Account account, Runnable onDeposit) { - this.account = account; - this.onDeposit = onDeposit; - - this.view = new DepositView(); - - this.view.getDeposit().addActionListener(e -> depositMoney()); - this.view.getCancel().addActionListener(e -> view.dispose()); - this.view.showDialog(); - } - - private void depositMoney() { - var amount = view.getAmount(); - - Logging.LOGGER.fine("Depositing money of account: " + account.getIban() + " amount: " + amount); - - try { - account.deposit(amount); - onDeposit.run(); - } catch (IllegalArgumentException ex) { - Logging.LOGGER.severe("Cannot deposit money of account: " + account.getIban() + " because: " + ex.getMessage()); - } finally { - view.dispose(); - } - } -} diff --git a/src/me/teridax/jcash/gui/deposit/DepositView.java b/src/me/teridax/jcash/gui/deposit/DepositView.java index 82159d2..95fcf8a 100644 --- a/src/me/teridax/jcash/gui/deposit/DepositView.java +++ b/src/me/teridax/jcash/gui/deposit/DepositView.java @@ -3,10 +3,10 @@ package me.teridax.jcash.gui.deposit; import me.teridax.jcash.Logging; import me.teridax.jcash.decode.StringDecoder; import me.teridax.jcash.gui.IconProvider; +import me.teridax.jcash.gui.InvalidInputException; import javax.swing.*; import java.awt.*; -import java.text.NumberFormat; import java.text.ParseException; import static me.teridax.jcash.lang.Translator.translate; @@ -16,10 +16,12 @@ public class DepositView { private JDialog dialog; private JButton cancel; private JButton deposit; + private JLabel enteredValue; + private JLabel balanceAfterDeposit; private JFormattedTextField value; - public DepositView() { - createComponents(); + public DepositView(double maxValue) { + createComponents(maxValue); layoutComponents(); } @@ -42,71 +44,97 @@ public class DepositView { c.gridx = 0; c.gridy = 0; - c.weightx = 1; - c.weighty = 1; - c.gridwidth = 3; - c.fill = GridBagConstraints.HORIZONTAL; - c.anchor = GridBagConstraints.CENTER; - c.insets = new Insets(4, 4, 4, 4); - dialog.getContentPane().add(new JLabel(translate("Deposit money")), c); - - c.gridx = 0; - c.gridy = 1; c.gridwidth = 1; c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0; + c.insets = new Insets(6, 6, 6, 6); dialog.getContentPane().add(new JLabel(translate("Value"), SwingConstants.RIGHT), c); c.gridx = 1; - c.gridy = 1; + c.gridy = 0; c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0.5; dialog.getContentPane().add(value, c); c.gridx = 2; - c.gridy = 1; + c.gridy = 0; c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.LINE_START; c.weightx = 0; dialog.getContentPane().add(new JLabel("€"), c); + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 1; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LAST_LINE_END; + dialog.getContentPane().add(new JLabel("Value to deposit:", SwingConstants.RIGHT), c); + + c.gridx = 1; + c.gridy = 1; + c.gridwidth = 2; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LAST_LINE_END; + dialog.getContentPane().add(enteredValue, c); + + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 1; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LAST_LINE_END; + dialog.getContentPane().add(new JLabel("Balance after deposit:", SwingConstants.RIGHT), c); + + c.gridx = 1; + c.gridy = 2; + c.gridwidth = 2; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LAST_LINE_END; + dialog.getContentPane().add(balanceAfterDeposit, c); + var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING)); buttonPanel.add(cancel); buttonPanel.add(deposit); c.gridx = 0; - c.gridy = 2; + c.gridy = 3; c.gridwidth = 3; c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.LAST_LINE_END; - c.insets = new Insets(10, 10, 10, 10); dialog.getContentPane().add(buttonPanel, c); } - private void createComponents() { + private void createComponents(double maxValue) { this.dialog = new JDialog(); this.cancel = new JButton(translate("Cancel")); this.deposit = new JButton(translate("Deposit")); - this.value = new JFormattedTextField(StringDecoder.getNumberFormat()); + this.value = new JFormattedTextField(StringDecoder.getNumberFormatter(Double.MAX_VALUE)); + this.enteredValue = new JLabel(); + this.balanceAfterDeposit = new JLabel(StringDecoder.getNumberFormat().format(maxValue)); + + this.deposit.setEnabled(false); this.dialog.setContentPane(new JPanel(new GridBagLayout())); } - public double getAmount() { + public double getAmount() throws InvalidInputException { if (value.getText().isBlank()) - return 0; + throw new InvalidInputException("currency value is blank or has been invalid whilst entered"); try { - return NumberFormat.getNumberInstance().parse(value.getText()).doubleValue(); + return StringDecoder.getNumberFormat().parse(value.getText()).doubleValue(); } catch (ParseException e) { Logging.LOGGER.severe("Amount text field contains invalid value: " + value); - throw new RuntimeException(e); + throw new InvalidInputException(e); } } + public JFormattedTextField getValue() { + return value; + } + public JButton getCancel() { return cancel; } @@ -118,4 +146,9 @@ public class DepositView { public void dispose() { this.dialog.dispose(); } + + public void setCommittedValue(double amount, double after) { + enteredValue.setText(StringDecoder.getNumberFormat().format(amount)); + balanceAfterDeposit.setText(StringDecoder.getNumberFormat().format(after)); + } } diff --git a/src/me/teridax/jcash/gui/login/LoginController.java b/src/me/teridax/jcash/gui/login/LoginController.java index bda542b..9a90a09 100644 --- a/src/me/teridax/jcash/gui/login/LoginController.java +++ b/src/me/teridax/jcash/gui/login/LoginController.java @@ -2,6 +2,7 @@ package me.teridax.jcash.gui.login; import me.teridax.jcash.Logging; import me.teridax.jcash.banking.management.BankingManagementSystem; +import me.teridax.jcash.gui.Utils; import java.awt.event.ActionEvent; import java.util.Optional; @@ -52,15 +53,13 @@ public class LoginController { var blz = this.view.getBlz().getText(); var iban = this.getIban(); if (iban.isEmpty()) { - Logging.LOGGER.severe("IBAN is invalid: " + iban); - showMessageDialog(null, translate("Invalid IBAN"), translate("Faulty login attempt"), ERROR_MESSAGE); + Utils.error("no IBAN entered"); return; } var pin = this.getPin(); if (pin.isEmpty()) { - Logging.LOGGER.severe("PIN is invalid: " + pin); - showMessageDialog(null, translate("Invalid pin"), translate("Faulty login attempt"), ERROR_MESSAGE); + Utils.error("no PIN entered"); return; } @@ -68,9 +67,8 @@ public class LoginController { if (account.isPresent()) { this.listener.onAccountSelected(account.get()); } else { - Logging.LOGGER.severe("invalid login credentials: " + iban + " / " + pin); - showMessageDialog(null, translate("Invalid login credentials"), translate("Faulty login attempt"), ERROR_MESSAGE); - } + Logging.LOGGER.warning("invalid login credentials: " + iban + " / " + pin); + } } public void addAccountSelectionListener(AccountSelectionListener listener) { diff --git a/src/me/teridax/jcash/gui/login/LoginData.java b/src/me/teridax/jcash/gui/login/LoginData.java index 3619372..3c937c8 100644 --- a/src/me/teridax/jcash/gui/login/LoginData.java +++ b/src/me/teridax/jcash/gui/login/LoginData.java @@ -3,6 +3,7 @@ package me.teridax.jcash.gui.login; import me.teridax.jcash.Logging; import me.teridax.jcash.banking.management.BankingManagementSystem; import me.teridax.jcash.banking.management.Profile; +import me.teridax.jcash.gui.Utils; import java.util.Optional; @@ -27,12 +28,26 @@ public class LoginData { */ public Optional authenticateAccount(String blz, int iban, int pin) { Logging.LOGGER.info("Authenticating account " + iban); + var optionalBank = bms.getBank(blz); - if (optionalBank.isEmpty()) + if (optionalBank.isEmpty()) { + Utils.error("Unknown BLZ: " + blz); return Optional.empty(); + } var profile = optionalBank.get().getAccount(iban); - return profile.filter(value -> value.getPrimaryAccount().getPin() == pin); + if (profile.isEmpty()) { + Utils.error("Unknown account: " + iban); + return Optional.empty(); + } + + var account = profile.filter(value -> value.getPrimaryAccount().getPin() == pin); + if (account.isEmpty()) { + Utils.error("PIN does not match"); + return Optional.empty(); + } + + return account; } public BankingManagementSystem getBms() { diff --git a/src/me/teridax/jcash/gui/takeoff/TakeoffController.java b/src/me/teridax/jcash/gui/takeoff/TakeoffController.java new file mode 100644 index 0000000..71a465a --- /dev/null +++ b/src/me/teridax/jcash/gui/takeoff/TakeoffController.java @@ -0,0 +1,67 @@ +package me.teridax.jcash.gui.takeoff; + +import me.teridax.jcash.Logging; +import me.teridax.jcash.banking.accounts.Account; +import me.teridax.jcash.gui.InvalidInputException; +import me.teridax.jcash.gui.Utils; + +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.text.ParseException; + +public class TakeoffController { + + private final Account account; + private final TakeoffView view; + private final TakeoffData data; + + public TakeoffController(Account account) { + this.account = account; + + this.data = new TakeoffData(account.getBalance()); + this.view = new TakeoffView(this.data.getMaxValue()); + + this.view.getTakeoff().addActionListener(e -> takeOff()); + this.view.getCancel().addActionListener(e -> view.dispose()); + this.view.getValue().getDocument().addDocumentListener(new DocumentListener() { + private void validateInputState() { + var balance = account.getBalance(); + try { + view.getValue().commitEdit(); + var amount = view.getAmount(); + view.setCommittedValue(amount, balance - amount); + view.getTakeoff().setEnabled(true); + } catch (InvalidInputException | ParseException ex) { + view.setCommittedValue(0, balance); + view.getTakeoff().setEnabled(false); + } + } + + @Override + public void insertUpdate(DocumentEvent documentEvent) { + validateInputState(); + } + + @Override + public void removeUpdate(DocumentEvent documentEvent) { + validateInputState(); + } + + @Override + public void changedUpdate(DocumentEvent documentEvent) { + validateInputState(); + } + }); + this.view.showDialog(); + } + + private void takeOff() { + try { + account.takeoff(view.getAmount()); + } catch (IllegalArgumentException | InvalidInputException ex) { + Logging.LOGGER.severe("Could not take off money: " + ex.getMessage()); + Utils.error("Reason: " + ex.getMessage()); + } + view.dispose(); + } +} diff --git a/src/me/teridax/jcash/gui/takeoff/TakeoffData.java b/src/me/teridax/jcash/gui/takeoff/TakeoffData.java new file mode 100644 index 0000000..d3c694b --- /dev/null +++ b/src/me/teridax/jcash/gui/takeoff/TakeoffData.java @@ -0,0 +1,14 @@ +package me.teridax.jcash.gui.takeoff; + +public class TakeoffData { + + private final double maxValue; + + public TakeoffData(double maxValue) { + this.maxValue = maxValue; + } + + public double getMaxValue() { + return maxValue; + } +} diff --git a/src/me/teridax/jcash/gui/takeoff/TakeoffDialog.java b/src/me/teridax/jcash/gui/takeoff/TakeoffDialog.java deleted file mode 100644 index 18f338f..0000000 --- a/src/me/teridax/jcash/gui/takeoff/TakeoffDialog.java +++ /dev/null @@ -1,38 +0,0 @@ -package me.teridax.jcash.gui.takeoff; - -import me.teridax.jcash.Logging; -import me.teridax.jcash.Main; -import me.teridax.jcash.banking.accounts.Account; - -import static javax.swing.JOptionPane.ERROR_MESSAGE; -import static javax.swing.JOptionPane.showMessageDialog; -import static me.teridax.jcash.lang.Translator.translate; - -public class TakeoffDialog { - - private final Account account; - private final Runnable onTakeoff; - private final TakeoffView view; - - public TakeoffDialog(Account account, Runnable onTakeoff) { - this.account = account; - this.onTakeoff = onTakeoff; - - this.view = new TakeoffView(); - - this.view.getTakeoff().addActionListener(e -> takeOff()); - this.view.getCancel().addActionListener(e -> view.dispose()); - this.view.showDialog(); - } - - private void takeOff() { - try { - account.takeoff(view.getAmount()); - onTakeoff.run(); - view.dispose(); - } catch (IllegalArgumentException ex) { - Logging.LOGGER.severe("Could not take off money: " + ex.getMessage()); - showMessageDialog(Main.getInstance().getWindow(), "Reason: " + ex.getMessage(), translate("Could not take off money"), ERROR_MESSAGE); - } - } -} diff --git a/src/me/teridax/jcash/gui/takeoff/TakeoffView.java b/src/me/teridax/jcash/gui/takeoff/TakeoffView.java index 92db0d9..e977494 100644 --- a/src/me/teridax/jcash/gui/takeoff/TakeoffView.java +++ b/src/me/teridax/jcash/gui/takeoff/TakeoffView.java @@ -1,15 +1,14 @@ package me.teridax.jcash.gui.takeoff; +import me.teridax.jcash.Logging; import me.teridax.jcash.decode.StringDecoder; import me.teridax.jcash.gui.IconProvider; +import me.teridax.jcash.gui.InvalidInputException; import javax.swing.*; import java.awt.*; -import java.text.NumberFormat; import java.text.ParseException; -import static javax.swing.JOptionPane.ERROR_MESSAGE; -import static javax.swing.JOptionPane.showMessageDialog; import static me.teridax.jcash.lang.Translator.translate; public class TakeoffView { @@ -17,10 +16,12 @@ public class TakeoffView { private JDialog dialog; private JButton cancel; private JButton takeoff; + private JLabel enteredValue; + private JLabel balanceAfterDeposit; private JFormattedTextField value; - public TakeoffView() { - createComponents(); + public TakeoffView(double maxValue) { + createComponents(maxValue); layoutComponents(); } @@ -41,42 +42,61 @@ public class TakeoffView { c.gridx = 0; c.gridy = 0; - c.weightx = 1; - c.weighty = 1; - c.gridwidth = 3; - c.fill = GridBagConstraints.HORIZONTAL; - c.anchor = GridBagConstraints.CENTER; - c.insets = new Insets(4, 4, 4, 4); - dialog.getContentPane().add(new JLabel(translate("Takeoff money")), c); - - c.gridx = 0; - c.gridy = 1; c.gridwidth = 1; c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0; + c.insets = new Insets(6,6,6,6); dialog.getContentPane().add(new JLabel(translate("Value"), SwingConstants.RIGHT), c); c.gridx = 1; - c.gridy = 1; + c.gridy = 0; c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0.5; dialog.getContentPane().add(value, c); c.gridx = 2; - c.gridy = 1; + c.gridy = 0; c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.LINE_START; c.weightx = 0; dialog.getContentPane().add(new JLabel("€"), c); + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 1; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LAST_LINE_END; + dialog.getContentPane().add(new JLabel("Value to deposit:", SwingConstants.RIGHT), c); + + c.gridx = 1; + c.gridy = 1; + c.gridwidth = 2; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LAST_LINE_END; + dialog.getContentPane().add(enteredValue, c); + + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 1; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LAST_LINE_END; + dialog.getContentPane().add(new JLabel("Balance after takeoff:", SwingConstants.RIGHT), c); + + c.gridx = 1; + c.gridy = 2; + c.gridwidth = 2; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LAST_LINE_END; + dialog.getContentPane().add(balanceAfterDeposit, c); + var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING)); buttonPanel.add(cancel); buttonPanel.add(takeoff); c.gridx = 0; - c.gridy = 2; + c.gridy = 3; c.gridwidth = 3; c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.LAST_LINE_END; @@ -84,29 +104,35 @@ public class TakeoffView { dialog.getContentPane().add(buttonPanel, c); } - private void createComponents() { + private void createComponents(double maxValue) { this.dialog = new JDialog(); this.cancel = new JButton(translate("Cancel")); this.takeoff = new JButton(translate("Takeoff")); - this.value = new JFormattedTextField(StringDecoder.getNumberFormat()); + this.value = new JFormattedTextField(StringDecoder.getNumberFormatter(maxValue)); + this.enteredValue = new JLabel(); + this.balanceAfterDeposit = new JLabel(StringDecoder.getNumberFormat().format(maxValue)); this.dialog.setContentPane(new JPanel(new GridBagLayout())); } - public double getAmount() { - if (value.getText().isBlank()) { - showMessageDialog(null, translate("Invalid amount"), translate("Currency must not be blank"), ERROR_MESSAGE); - return 0; - } + public double getAmount() throws InvalidInputException { + if (value.getText().isBlank()) + throw new InvalidInputException("currency value is blank or has been invalid whilst entered"); try { - return NumberFormat.getNumberInstance().parse(value.getText()).doubleValue(); + return StringDecoder.getNumberFormat().parse(value.getText()).doubleValue(); } catch (ParseException e) { - throw new RuntimeException(e); + Logging.LOGGER.severe("Amount text field contains invalid value: " + value); + throw new InvalidInputException(e); } } + public void setCommittedValue(double amount, double after) { + enteredValue.setText(StringDecoder.getNumberFormat().format(amount)); + balanceAfterDeposit.setText(StringDecoder.getNumberFormat().format(after)); + } + public JButton getCancel() { return cancel; } @@ -115,6 +141,10 @@ public class TakeoffView { return takeoff; } + public JFormattedTextField getValue() { + return value; + } + public void dispose() { this.dialog.dispose(); } diff --git a/src/me/teridax/jcash/gui/transfer/TransferController.java b/src/me/teridax/jcash/gui/transfer/TransferController.java new file mode 100644 index 0000000..9aaa3c1 --- /dev/null +++ b/src/me/teridax/jcash/gui/transfer/TransferController.java @@ -0,0 +1,72 @@ +package me.teridax.jcash.gui.transfer; + +import me.teridax.jcash.Logging; +import me.teridax.jcash.Main; +import me.teridax.jcash.banking.accounts.Account; +import me.teridax.jcash.banking.management.BankingManagementSystem; +import me.teridax.jcash.gui.InvalidInputException; +import me.teridax.jcash.gui.Utils; + +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.text.ParseException; + +/** + * Dialog class for transferring some value from one account to another + */ +public class TransferController { + + private final Account account; + private final TransferData transferData; + private final TransferView view; + + public TransferController(Account account, BankingManagementSystem bms) { + this.account = account; + + this.view = new TransferView(account.getBalance()); + this.transferData = new TransferData(bms); + this.view.getTransfer().addActionListener(e -> transfer()); + this.view.getCancel().addActionListener(e -> view.dispose());this.view.getValue().getDocument().addDocumentListener(new DocumentListener() { + private void validateInputState() { + var balance = account.getBalance(); + try { + view.getValue().commitEdit(); + var amount = view.getAmount(); + view.setCommittedValue(amount, balance - amount); + view.getTransfer().setEnabled(true); + } catch (InvalidInputException | ParseException ex) { + view.setCommittedValue(0, balance); + view.getTransfer().setEnabled(false); + } + } + + @Override + public void insertUpdate(DocumentEvent documentEvent) { + validateInputState(); + } + + @Override + public void removeUpdate(DocumentEvent documentEvent) { + validateInputState(); + } + + @Override + public void changedUpdate(DocumentEvent documentEvent) { + validateInputState(); + } + }); + this.view.showDialog(); + } + + private void transfer() { + try { + var amount = view.getAmount(); + this.account.takeoff(amount); + this.transferData.transferValue(amount, view.getBlz(), view.getIban()); + } catch (IllegalArgumentException | InvalidInputException ex) { + Logging.LOGGER.severe("Could not transfer: " + ex.getMessage()); + Utils.error("Reason: " + ex.getMessage()); + } + this.view.dispose(); + } +} diff --git a/src/me/teridax/jcash/gui/transfer/TransferDialog.java b/src/me/teridax/jcash/gui/transfer/TransferDialog.java deleted file mode 100644 index 66333ce..0000000 --- a/src/me/teridax/jcash/gui/transfer/TransferDialog.java +++ /dev/null @@ -1,46 +0,0 @@ -package me.teridax.jcash.gui.transfer; - -import me.teridax.jcash.Logging; -import me.teridax.jcash.Main; -import me.teridax.jcash.banking.accounts.Account; -import me.teridax.jcash.banking.management.BankingManagementSystem; - -import static javax.swing.JOptionPane.ERROR_MESSAGE; -import static javax.swing.JOptionPane.showMessageDialog; -import static me.teridax.jcash.lang.Translator.translate; - -/** - * Dialog class for transferring some value from one account to another - */ -public class TransferDialog { - - private final Account account; - private final Runnable onDeposit; - private final TransferData transferData; - private final TransferView transferView; - - public TransferDialog(Account account, BankingManagementSystem bms, Runnable onDeposit) { - this.account = account; - this.onDeposit = onDeposit; - - this.transferView = new TransferView(); - this.transferData = new TransferData(bms); - this.transferView.getTransfer().addActionListener(e -> transfer()); - this.transferView.getCancel().addActionListener(e -> transferView.dispose()); - this.transferView.showDialog(); - } - - private void transfer() { - try { - var amount = transferView.getAmount(); - - this.account.takeoff(amount); - this.transferData.transferValue(amount, transferView.getBlz(), transferView.getIban()); - this.onDeposit.run(); - this.transferView.dispose(); - } catch (IllegalArgumentException ex) { - Logging.LOGGER.severe("Could not transfer: " + ex.getMessage()); - showMessageDialog(Main.getInstance().getWindow(), translate("Invalid account"), translate("Could not transfer"), ERROR_MESSAGE); - } - } -} diff --git a/src/me/teridax/jcash/gui/transfer/TransferView.java b/src/me/teridax/jcash/gui/transfer/TransferView.java index f2d5be1..11e6e27 100644 --- a/src/me/teridax/jcash/gui/transfer/TransferView.java +++ b/src/me/teridax/jcash/gui/transfer/TransferView.java @@ -3,12 +3,12 @@ package me.teridax.jcash.gui.transfer; import me.teridax.jcash.Logging; import me.teridax.jcash.decode.StringDecoder; import me.teridax.jcash.gui.IconProvider; +import me.teridax.jcash.gui.InvalidInputException; import javax.swing.*; import java.awt.*; +import java.text.ParseException; -import static javax.swing.JOptionPane.ERROR_MESSAGE; -import static javax.swing.JOptionPane.showMessageDialog; import static me.teridax.jcash.lang.Translator.translate; public class TransferView { @@ -20,8 +20,11 @@ public class TransferView { private JFormattedTextField blz; private JFormattedTextField value; - public TransferView() { - createComponents(); + private JLabel balanceAfterTransfer; + private JLabel enteredValue; + + public TransferView(double maxValue) { + createComponents(maxValue); layoutComponents(); } @@ -42,97 +45,123 @@ public class TransferView { c.gridx = 0; c.gridy = 0; - c.weightx = 1; - c.weighty = 1; - c.gridwidth = 3; - c.fill = GridBagConstraints.HORIZONTAL; - c.anchor = GridBagConstraints.CENTER; - c.insets = new Insets(4, 4, 4, 4); - dialog.getContentPane().add(new JLabel(translate("Transfer money")), c); - - c.gridx = 0; - c.gridy = 1; c.gridwidth = 1; c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0; + c.insets = new Insets(6,6,6,6); dialog.getContentPane().add(new JLabel(translate("BLZ"), SwingConstants.RIGHT), c); c.gridx = 1; - c.gridy = 1; + c.gridy = 0; c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0.5; dialog.getContentPane().add(blz, c); c.gridx = 2; - c.gridy = 1; + c.gridy = 0; c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0; dialog.getContentPane().add(new JLabel(translate("IBAN"), SwingConstants.RIGHT), c); c.gridx = 3; - c.gridy = 1; + c.gridy = 0; c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 1; dialog.getContentPane().add(iban, c); c.gridx = 0; - c.gridy = 2; + c.gridy = 1; c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0; dialog.getContentPane().add(new JLabel(translate("Betrag"), SwingConstants.RIGHT), c); c.gridx = 1; - c.gridy = 2; + c.gridy = 1; c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0.5; dialog.getContentPane().add(value, c); + c.gridx = 0; + c.gridy = 2; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LAST_LINE_END; + c.weightx = 0.5; + dialog.getContentPane().add(new JLabel("entered value:", JLabel.RIGHT), c); + + c.gridx = 1; + c.gridy = 2; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LAST_LINE_END; + c.weightx = 0.5; + dialog.getContentPane().add(enteredValue, c); + + c.gridx = 0; + c.gridy = 3; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LAST_LINE_END; + c.weightx = 0.5; + dialog.getContentPane().add(new JLabel("Balance after transfer:", JLabel.RIGHT), c); + + c.gridx = 1; + c.gridy = 3; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LAST_LINE_END; + c.weightx = 0.5; + dialog.getContentPane().add(balanceAfterTransfer, c); + var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING)); buttonPanel.add(cancel); buttonPanel.add(transfer); c.gridx = 0; - c.gridy = 3; + c.gridy = 4; c.gridwidth = 4; c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.LAST_LINE_END; - c.insets = new Insets(10, 10, 10, 10); dialog.getContentPane().add(buttonPanel, c); } - private void createComponents() { + private void createComponents(double maxValue) { this.dialog = new JDialog(); this.cancel = new JButton(translate("Cancel")); this.transfer = new JButton(translate("Transfer")); - this.value = new JFormattedTextField(StringDecoder.getNumberFormat()); + this.value = new JFormattedTextField(StringDecoder.getNumberFormatter(maxValue)); this.iban = new JFormattedTextField(); this.blz = new JFormattedTextField(); + this.enteredValue = new JLabel(); + this.balanceAfterTransfer = new JLabel(StringDecoder.getNumberFormat().format(maxValue)); this.dialog.setContentPane(new JPanel(new GridBagLayout())); } - public double getAmount() { - if (value.getText().isBlank()) { - Logging.LOGGER.severe("Amount is empty"); - showMessageDialog(null, translate("invalid amount"), translate("currency must not be blank"), ERROR_MESSAGE); - return 0; - } + public double getAmount() throws InvalidInputException { + if (value.getText().isBlank()) + throw new InvalidInputException("currency value is blank or has been invalid whilst entered"); try { - return StringDecoder.decodeCurrency(value.getText()); - } catch (IllegalArgumentException e) { - Logging.LOGGER.severe("Invalid amount: " + value.getText()); - throw new RuntimeException(e); + return StringDecoder.getNumberFormat().parse(value.getText()).doubleValue(); + } catch (ParseException e) { + Logging.LOGGER.severe("Amount text field contains invalid value: " + value); + throw new InvalidInputException(e); } } + public void setCommittedValue(double amount, double after) { + enteredValue.setText(StringDecoder.getNumberFormat().format(amount)); + balanceAfterTransfer.setText(StringDecoder.getNumberFormat().format(after)); + } + + public JFormattedTextField getValue() { + return value; + } + public JButton getCancel() { return cancel; }