diff --git a/JCash.iml b/JCash.iml
index c90834f..c3dc060 100644
--- a/JCash.iml
+++ b/JCash.iml
@@ -7,5 +7,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Main.java b/src/Main.java
deleted file mode 100644
index 3e59c38..0000000
--- a/src/Main.java
+++ /dev/null
@@ -1,5 +0,0 @@
-public class Main {
- public static void main(String[] args) {
- System.out.println("Hello world!");
- }
-}
\ No newline at end of file
diff --git a/src/me/teridax/jcash/Main.java b/src/me/teridax/jcash/Main.java
new file mode 100644
index 0000000..75463e8
--- /dev/null
+++ b/src/me/teridax/jcash/Main.java
@@ -0,0 +1,92 @@
+package me.teridax.jcash;
+
+import me.teridax.jcash.gui.Loader;
+import me.teridax.jcash.gui.account.AccountController;
+import me.teridax.jcash.gui.login.LoginController;
+
+import javax.swing.*;
+
+public final class Main {
+
+ /**
+ * Main instance of this program. Contains the primary window.
+ */
+ private static Main instance;
+
+ /**
+ * Primary window of this program
+ */
+ private final JFrame window;
+
+ private Main() {
+ // create main window and set defaults
+ this.window = new JFrame();
+ this.window.setTitle("Bankautomat");
+ this.window.setLocationByPlatform(true);
+ this.window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ }
+
+ public static void main(String[] args) {
+ // create main instance and show the login screen
+ instance();
+ getInstance().showLoginScreen();
+ }
+
+ public static Main getInstance() {
+ return instance;
+ }
+
+ /**
+ * Attempts to create a new instance of the singleton class Main.
+ * This method throws an exception if the class is already instantiated.
+ * @throws IllegalStateException if the class is already instantiated
+ * @pre the static instance of this class should be null.
+ * @post the static instance of this class will be set
+ */
+ public static void instance() {
+ if (null != instance)
+ throw new IllegalStateException(Main.class.getName() + " is already initialized");
+
+ Main.instance = new Main();
+ }
+
+ /**
+ * Shows the open dialog for selecting a database file. After selection, it then proceeds to prompt login.
+ * Afterward the selected account can be managed.
+ * This method is non-blocking and all work described is performed asynchronously on the AWT Event dispatcher.
+ */
+ public void showLoginScreen() {
+ SwingUtilities.invokeLater(() -> {
+ try {
+ // select db file
+ var path = Loader.load();
+ // read database and login
+ var login = new LoginController(path);
+
+ // when we have logged in set the account viewer as window content
+ login.addAccountSelectionListener(account -> {
+ var profileCont = new AccountController(account);
+ this.window.setContentPane(profileCont.getView());
+ this.window.revalidate();
+ this.window.repaint();
+ });
+
+ // we are not logged in yet, so show the login prompt on the main window
+ this.window.setContentPane(login.getView());
+ this.window.setSize(800, 600);
+ this.window.setVisible(true);
+
+ } catch (IllegalStateException e) {
+ System.out.println("no file selected. goodbye");
+ }
+ });
+ }
+
+ /**
+ * Logs the user out of the database, hiding the main window.
+ */
+ public void logout() {
+ window.setContentPane(new JLabel("you're logged out"));
+ window.setVisible(false);
+ }
+}
\ No newline at end of file
diff --git a/src/me/teridax/jcash/banking/Account.java b/src/me/teridax/jcash/banking/Account.java
index 7ef9445..bf4fc4f 100644
--- a/src/me/teridax/jcash/banking/Account.java
+++ b/src/me/teridax/jcash/banking/Account.java
@@ -1,6 +1,6 @@
package me.teridax.jcash.banking;
-import me.teridax.jcash.decode.Decoder;
+import me.teridax.jcash.decode.StringUtils;
import java.util.Objects;
@@ -9,7 +9,7 @@ public abstract class Account {
private final int iban;
private final int pin;
- private final double balance;
+ private double balance;
public Account(int iban, int pin, double balance) {
this.iban = iban;
@@ -20,17 +20,17 @@ public abstract class Account {
public static Account fromColumns(String[] columns) {
Objects.requireNonNull(columns);
- var iban = Decoder.decodeUniqueIdentificationNumber(columns[0]);
- var pin = Decoder.decodeUniqueIdentificationNumber(columns[1]);
- var balance = Decoder.decodeCurrency(columns[2]);
- var type = Decoder.decodeName(columns[3]);
+ var iban = StringUtils.decodeUniqueIdentificationNumber(columns[0]);
+ var pin = StringUtils.decodeUniqueIdentificationNumber(columns[1]);
+ var balance = StringUtils.decodeCurrency(columns[2]);
+ var type = StringUtils.decodeName(columns[3]);
try {
if (type.equals("Sparkonto")) {
- var interest = Decoder.decodePercent(columns[4]);
+ var interest = StringUtils.decodePercent(columns[4]);
return new SavingsAccount(iban, pin, balance, interest);
} else if (type.equals("Girokonto")) {
- var overdraft = Decoder.decodeCurrency(columns[5]);
+ var overdraft = StringUtils.decodeCurrency(columns[5]);
return new Girokonto(iban, pin, balance, overdraft);
} else {
throw new IllegalArgumentException("Invalid account type: " + type);
@@ -64,4 +64,21 @@ public abstract class Account {
return false;
}
+
+ public String getDescription() {
+ return String.format("%s (%s)", iban, getClass().getSimpleName());
+ }
+
+ public void deposit(double amount) {
+ this.balance += amount;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("@Account [iban=%d, pin=%d, balance=%.2f]", iban, pin, balance);
+ }
+
+ public void takeoff(double amount) {
+ this.balance = Math.max(0, this.balance - amount);
+ }
}
diff --git a/src/me/teridax/jcash/banking/Bank.java b/src/me/teridax/jcash/banking/Bank.java
index 13bad12..03b347b 100644
--- a/src/me/teridax/jcash/banking/Bank.java
+++ b/src/me/teridax/jcash/banking/Bank.java
@@ -1,6 +1,7 @@
package me.teridax.jcash.banking;
import java.util.*;
+import java.util.regex.Pattern;
@SuppressWarnings("unused")
public final class Bank {
@@ -46,14 +47,24 @@ public final class Bank {
}
public static String validateBlz(String maybeBlz) {
- var builder = new StringBuilder();
- maybeBlz.trim().chars().filter(ch -> Character.isDigit(ch) || Character.isLetter(ch)).forEach(builder::append);
- var blz = builder.toString();
+ var pattern = Pattern.compile("[\\w\\d-_]+");
+ var matcher = pattern.matcher(maybeBlz);
- if (blz.isEmpty()) {
- throw new IllegalArgumentException("Bank Blz must only contain letters and digits");
+ if (matcher.find())
+ return matcher.group();
+
+ throw new IllegalArgumentException("not a valid BLZ: " + maybeBlz);
+ }
+
+ public Optional getAccount(int iban) {
+ for (var owner : this.accounts.entrySet()) {
+ for (var account : owner.getValue()) {
+ var tmp = account.getIban();
+ if (tmp == iban) {
+ return Optional.of(new Profile(owner.getKey(), this, account, getAccountsOfOwner(owner.getKey())));
+ }
+ }
}
-
- return blz;
+ return Optional.empty();
}
}
diff --git a/src/me/teridax/jcash/banking/BankingManagementSystem.java b/src/me/teridax/jcash/banking/BankingManagementSystem.java
index 35d8839..57fe941 100644
--- a/src/me/teridax/jcash/banking/BankingManagementSystem.java
+++ b/src/me/teridax/jcash/banking/BankingManagementSystem.java
@@ -1,6 +1,6 @@
package me.teridax.jcash.banking;
-import me.teridax.jcash.decode.Decoder;
+import me.teridax.jcash.decode.StringUtils;
import java.io.IOException;
import java.nio.file.Files;
@@ -34,7 +34,7 @@ public final class BankingManagementSystem {
var account = Account.fromColumns(tail(columns, 2));
var blz = Bank.validateBlz(columns[1]);
- var name = Decoder.decodeName(columns[0]);
+ var name = StringUtils.decodeName(columns[0]);
var bankOfLine = new Bank(blz, name);
var bankOfSet = bms.banks.stream().filter(b -> b.equals(bankOfLine)).findFirst();
@@ -54,4 +54,8 @@ public final class BankingManagementSystem {
throw new IllegalArgumentException("Could not parse file " + file, e);
}
}
+
+ public Optional getBank(String blz) {
+ return this.banks.stream().filter(b -> b.getBlz().equals(blz)).findFirst();
+ }
}
diff --git a/src/me/teridax/jcash/banking/Owner.java b/src/me/teridax/jcash/banking/Owner.java
index 07b1bab..5497df1 100644
--- a/src/me/teridax/jcash/banking/Owner.java
+++ b/src/me/teridax/jcash/banking/Owner.java
@@ -1,6 +1,6 @@
package me.teridax.jcash.banking;
-import me.teridax.jcash.decode.Decoder;
+import me.teridax.jcash.decode.StringUtils;
import java.util.Objects;
@@ -28,12 +28,12 @@ public final class Owner {
if (columns.length != 6)
throw new IllegalArgumentException("Invalid number of columns: " + columns.length);
- var uid = Decoder.decodeUniqueIdentificationNumber(columns[0]);
- var familyName = Decoder.decodeName(columns[1]);
- var name = Decoder.decodeName(columns[2]);
- var street = Decoder.decodeStreet(columns[3]);
- var zip = Decoder.decodeUniqueIdentificationNumber(columns[4]);
- var city = Decoder.decodeName(columns[5]);
+ var uid = StringUtils.decodeUniqueIdentificationNumber(columns[0]);
+ var familyName = StringUtils.decodeName(columns[1]);
+ var name = StringUtils.decodeName(columns[2]);
+ var street = StringUtils.decodeStreet(columns[3]);
+ var zip = StringUtils.decodeUniqueIdentificationNumber(columns[4]);
+ var city = StringUtils.decodeName(columns[5]);
return new Owner(uid, familyName, name, zip, city, street);
}
diff --git a/src/me/teridax/jcash/banking/Profile.java b/src/me/teridax/jcash/banking/Profile.java
new file mode 100644
index 0000000..9ac33a6
--- /dev/null
+++ b/src/me/teridax/jcash/banking/Profile.java
@@ -0,0 +1,41 @@
+package me.teridax.jcash.banking;
+
+public class Profile {
+
+ private Owner owner;
+ private Bank bank;
+ private Account primaryAccount;
+ private Account[] accounts;
+
+ public Profile(Owner owner, Bank bank, Account account, Account[] accounts) {
+ this.owner = owner;
+ this.bank = bank;
+ this.accounts = accounts;
+ this.primaryAccount = account;
+ }
+
+ public Account getPrimaryAccount() {
+ return primaryAccount;
+ }
+
+ public Owner getOwner() {
+ return owner;
+ }
+
+ public Bank getBank() {
+ return bank;
+ }
+
+ public Account[] getAccounts() {
+ return accounts;
+ }
+
+ public void setPrimaryAccount(String description) {
+ for (Account account : accounts) {
+ if (account.getDescription().equals(description)) {
+ this.primaryAccount = account;
+ break;
+ }
+ }
+ }
+}
diff --git a/src/me/teridax/jcash/banking/Testing.java b/src/me/teridax/jcash/banking/Tests.java
similarity index 95%
rename from src/me/teridax/jcash/banking/Testing.java
rename to src/me/teridax/jcash/banking/Tests.java
index dee3fa9..ab644fb 100644
--- a/src/me/teridax/jcash/banking/Testing.java
+++ b/src/me/teridax/jcash/banking/Tests.java
@@ -4,7 +4,7 @@ import org.junit.Test;
import java.nio.file.Paths;
-public class Testing {
+public class Tests {
@Test
public void test() {
diff --git a/src/me/teridax/jcash/decode/Decoder.java b/src/me/teridax/jcash/decode/StringUtils.java
similarity index 67%
rename from src/me/teridax/jcash/decode/Decoder.java
rename to src/me/teridax/jcash/decode/StringUtils.java
index 4af70b8..5145466 100644
--- a/src/me/teridax/jcash/decode/Decoder.java
+++ b/src/me/teridax/jcash/decode/StringUtils.java
@@ -10,11 +10,29 @@ import java.util.regex.Pattern;
import static junit.framework.TestCase.assertEquals;
-public class Decoder {
+/**
+ * Utility class for converting various single line strings into a specific data type according
+ * to a format mostly dictated by a locale.
+ */
+public class StringUtils {
+ /**
+ * Locale to use when converting strings
+ */
private static final Locale LOCALE = Locale.GERMANY;
- private static final NumberFormat LOCAL_NUMBER_FORMAT = NumberFormat.getInstance(LOCALE);
+ /**
+ * NumberFormat to use when converting strings
+ */
+ public static final NumberFormat LOCAL_NUMBER_FORMAT = NumberFormat.getInstance(LOCALE);
+ /**
+ * Attempts to convert the given string into a double value representing a percentage.
+ * The percentage is stored in the range [0,1] and can linearly be mapped to [0, 100] by multiplying with 100.
+ * @param number the string to convert
+ * @return the double value
+ * @throws IllegalArgumentException when the format is invalid
+ * @throws NullPointerException when the argument is null
+ */
public static double decodePercent(String number) throws IllegalArgumentException, NullPointerException {
Objects.requireNonNull(number);
@@ -28,6 +46,13 @@ public class Decoder {
}
}
+ /**
+ * Attempts to convert the given string into a currency value.
+ * @param currency the string to convert
+ * @return the double value
+ * @throws IllegalArgumentException when the format is invalid
+ * @throws NullPointerException when the argument is null
+ */
public static double decodeCurrency(String currency) throws IllegalArgumentException, NullPointerException {
Objects.requireNonNull(currency);
@@ -38,6 +63,14 @@ public class Decoder {
}
}
+ /**
+ * Attempts to convert the given string into universally unique number.
+ * This function does not check for duplicates. The number must be a positive integer.
+ * @param number the string to convert
+ * @return the integer serial number
+ * @throws IllegalArgumentException when the format is invalid
+ * @throws NullPointerException when the argument is null
+ */
public static int decodeUniqueIdentificationNumber(String number) throws IllegalArgumentException, NullPointerException {
Objects.requireNonNull(number);
@@ -56,6 +89,14 @@ public class Decoder {
}
}
+ /**
+ * Attempts to convert the given string into a name.
+ * This method performs validation and trimming.
+ * @param name the string to convert
+ * @return the qualified name
+ * @throws IllegalArgumentException when the format is invalid
+ * @throws NullPointerException when the argument is null
+ */
public static String decodeName(String name) throws IllegalArgumentException, NullPointerException {
Objects.requireNonNull(name);
@@ -70,6 +111,13 @@ public class Decoder {
}
}
+ /**
+ * Attempts to convert the given string into a street and an optional house address.
+ * @param street the string to convert
+ * @return the address name
+ * @throws IllegalArgumentException when the format is invalid
+ * @throws NullPointerException when the argument is null
+ */
public static String decodeStreet(String street) throws IllegalArgumentException, NullPointerException {
Objects.requireNonNull(street);
diff --git a/src/me/teridax/jcash/gui/Loader.java b/src/me/teridax/jcash/gui/Loader.java
new file mode 100644
index 0000000..f12593d
--- /dev/null
+++ b/src/me/teridax/jcash/gui/Loader.java
@@ -0,0 +1,49 @@
+package me.teridax.jcash.gui;
+
+import me.teridax.jcash.banking.BankingManagementSystem;
+
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import java.io.File;
+
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+
+/**
+ * Utility class for loading a BMS configuration from a csv file.
+ */
+public class Loader {
+
+ /**
+ * Filter that only allows for files with *.csv extension
+ */
+ private static final FileNameExtensionFilter FILE_FILTER = new FileNameExtensionFilter("Comma separated value spreadsheet", "csv", "CSV");
+
+ private Loader() {}
+
+ /**
+ * Load a BMS from a csv file. Opens up a dialog which prompts the user to select a single file.
+ * Once the file is selected this function will try to parse the contents to a BMS and return the instance.
+ * @return a valid BMS instance loaded from a file
+ * @throws IllegalStateException When either no file is selected or the selected files content is invalid
+ */
+ public static BankingManagementSystem load() throws IllegalStateException {
+ var fileChooser = new JFileChooser();
+ fileChooser.setMultiSelectionEnabled(false);
+ fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+ fileChooser.setFileFilter(FILE_FILTER);
+ fileChooser.setDialogType(JFileChooser.OPEN_DIALOG);
+ fileChooser.setAcceptAllFileFilterUsed(false);
+ fileChooser.setCurrentDirectory(new File("/home/teridax/IdeaProjects/JCash/res"));
+
+ if (fileChooser.showDialog(null, "Load database") == APPROVE_OPTION) {
+ // parse file content
+ try {
+ return BankingManagementSystem.loadFromCsv(fileChooser.getSelectedFile().toPath());
+ } catch (Exception e) {
+ throw new IllegalStateException("Unable to load database", e);
+ }
+ }
+
+ throw new IllegalStateException("No file selected");
+ }
+}
diff --git a/src/me/teridax/jcash/gui/account/AccountController.java b/src/me/teridax/jcash/gui/account/AccountController.java
new file mode 100644
index 0000000..ab849b1
--- /dev/null
+++ b/src/me/teridax/jcash/gui/account/AccountController.java
@@ -0,0 +1,48 @@
+package me.teridax.jcash.gui.account;
+
+import me.teridax.jcash.Main;
+import me.teridax.jcash.banking.Profile;
+import me.teridax.jcash.gui.deposit.DepositDialog;
+import me.teridax.jcash.gui.takeoff.TakeoffDialog;
+import me.teridax.jcash.gui.transfer.TransferDialog;
+
+public class AccountController {
+
+ private final AccountView view;
+
+ public AccountController(Profile profile) {
+ this.view = new AccountView();
+ this.view.setProfile(profile);
+
+ createListeners(profile);
+ }
+
+ private void createListeners(Profile profile) {
+ this.view.getAccountSelection().addActionListener(e -> {
+ var description = ((String) this.view.getAccountSelection().getSelectedItem());
+ profile.setPrimaryAccount(description);
+ this.view.setProfile(profile);
+ });
+
+ this.view.getLogout().addActionListener(e -> {
+ Main.getInstance().logout();
+ Main.getInstance().showLoginScreen();
+ });
+
+ this.view.getDeposit().addActionListener(e -> {
+ new DepositDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile));
+ });
+
+ this.view.getTakeoff().addActionListener(e -> {
+ new TakeoffDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile));
+ });
+
+ this.view.getTransfer().addActionListener(e -> {
+ new TransferDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile));
+ });
+ }
+
+ public AccountView getView() {
+ return view;
+ }
+}
diff --git a/src/me/teridax/jcash/gui/account/AccountView.java b/src/me/teridax/jcash/gui/account/AccountView.java
new file mode 100644
index 0000000..ecdf560
--- /dev/null
+++ b/src/me/teridax/jcash/gui/account/AccountView.java
@@ -0,0 +1,170 @@
+package me.teridax.jcash.gui.account;
+
+import me.teridax.jcash.banking.*;
+import me.teridax.jcash.decode.StringUtils;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class AccountView extends JPanel {
+
+ private JTextField iban;
+ private JTextField name;
+ private JTextField address;
+ private JTextField bankName;
+ private JTextField blz;
+ private JTextField type;
+ private JTextField typeSpecialProperty;
+ private JTextField balance;
+
+ private JLabel typeSpecialLabel;
+
+ private JComboBox accountSelection;
+
+ private JPanel content;
+
+ private JButton logout;
+ private JButton transfer;
+ private JButton deposit;
+ private JButton takeoff;
+
+ public AccountView() {
+ createComponents();
+ createLayout();
+
+ setBorder(BorderFactory.createEmptyBorder(8,8,8,8));
+ }
+
+ public void setProfile(Profile profile) {
+ 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(StringUtils.LOCAL_NUMBER_FORMAT.format(account.getBalance()) + " €");
+
+ this.type.setText(account.getClass().getSimpleName());
+ if (account instanceof Girokonto) {
+ this.typeSpecialLabel.setText("Overdraft");
+ this.typeSpecialProperty.setText( StringUtils.LOCAL_NUMBER_FORMAT.format(((Girokonto) account).getOverdraft()) + " €");
+ } else if (account instanceof SavingsAccount) {
+ this.typeSpecialLabel.setText("Interest rate");
+ this.typeSpecialProperty.setText( ((SavingsAccount) account).getInterest() + " %" );
+ }
+
+ this.accountSelection.removeAllItems();
+
+ for (var otherAccount : profile.getAccounts()) {
+ this.accountSelection.addItem(otherAccount.getDescription());
+ }
+ this.accountSelection.setSelectedItem(account.getDescription());
+ }
+
+ private void createLayout() {
+ setLayout(new BorderLayout(16, 16));
+ add(new JScrollPane(content), BorderLayout.CENTER);
+
+ content.setLayout(new GridBagLayout());
+ var constraints = new GridBagConstraints();
+
+ constraints.gridwidth = 4;
+ constraints.insets = new Insets(12,12,12,12);
+
+ var accountSelectionPanel = new JPanel(new BorderLayout(12, 0));
+ accountSelectionPanel.add(iban, BorderLayout.CENTER);
+ accountSelectionPanel.add(accountSelection, BorderLayout.EAST);
+
+ addInputRow(constraints, content, accountSelectionPanel, 1, new JLabel("IBAN", SwingConstants.RIGHT));
+ addInputRow(constraints, content, name, 2, new JLabel("Name/Family-name", SwingConstants.RIGHT));
+ addInputRow(constraints, content, address, 3, new JLabel("Address", SwingConstants.RIGHT));
+ addInputRow(constraints, content, bankName, 4, new JLabel("Bank", SwingConstants.RIGHT));
+ addInputRow(constraints, content, blz, 5, new JLabel("BLZ", SwingConstants.RIGHT));
+ addInputRow(constraints, content, type, 6, new JLabel("Account", SwingConstants.RIGHT));
+ addInputRow(constraints, content, typeSpecialProperty, 7, typeSpecialLabel);
+ addInputRow(constraints, content, balance, 8, new JLabel("Balance", SwingConstants.RIGHT));
+
+ var buttonPanel = Box.createHorizontalBox();
+ buttonPanel.add(Box.createHorizontalStrut(4));
+ buttonPanel.add(logout);
+ buttonPanel.add(Box.createHorizontalStrut(4));
+ buttonPanel.add(Box.createGlue());
+ buttonPanel.add(transfer);
+ buttonPanel.add(Box.createHorizontalStrut(4));
+ buttonPanel.add(deposit);
+ buttonPanel.add(Box.createHorizontalStrut(4));
+ buttonPanel.add(takeoff);
+ add(buttonPanel, BorderLayout.SOUTH);
+ }
+
+ private void createComponents() {
+ this.blz = new JTextField();
+ this.iban = new JTextField();
+ this.address = new JTextField();
+ this.bankName = new JTextField();
+ this.name = new JTextField();
+ this.type = new JTextField();
+ this.balance = new JTextField();
+ this.typeSpecialProperty = new JTextField();
+
+ this.blz.setEditable(false);
+ this.iban.setEditable(false);
+ this.address.setEditable(false);
+ this.bankName.setEditable(false);
+ this.name.setEditable(false);
+ this.type.setEditable(false);
+ this.balance.setEditable(false);
+ this.typeSpecialProperty.setEditable(false);
+
+ this.typeSpecialLabel = new JLabel("", SwingConstants.RIGHT);
+
+ this.accountSelection = new JComboBox<>();
+
+ this.content = new JPanel();
+
+ this.logout = new JButton("Logout");
+ this.transfer = new JButton("Transfer");
+ this.deposit = new JButton("Deposit");
+ this.takeoff = new JButton("Takeoff");
+ }
+
+ private void addInputRow(GridBagConstraints constraints, JComponent target, JComponent comp, int row, JLabel label) {
+ constraints.gridwidth = 1;
+ constraints.gridx = 1;
+ constraints.gridy = row;
+ constraints.weightx = 0;
+ constraints.fill = GridBagConstraints.HORIZONTAL;
+ target.add(label, constraints);
+
+ constraints.gridx = 2;
+ constraints.gridy = row;
+ constraints.weightx = 1;
+ constraints.fill = GridBagConstraints.HORIZONTAL;
+ target.add(comp, constraints);
+ }
+
+ public JComboBox getAccountSelection() {
+ return accountSelection;
+ }
+
+ public JButton getLogout() {
+ return logout;
+ }
+
+ public JButton getTransfer() {
+ return transfer;
+ }
+
+ public JButton getDeposit() {
+ return deposit;
+ }
+
+ public JButton getTakeoff() {
+ return takeoff;
+ }
+}
diff --git a/src/me/teridax/jcash/gui/deposit/DepositDialog.java b/src/me/teridax/jcash/gui/deposit/DepositDialog.java
new file mode 100644
index 0000000..00e9e5b
--- /dev/null
+++ b/src/me/teridax/jcash/gui/deposit/DepositDialog.java
@@ -0,0 +1,17 @@
+package me.teridax.jcash.gui.deposit;
+
+import me.teridax.jcash.banking.Account;
+
+public class DepositDialog {
+
+ public DepositDialog(Account account, Runnable onDeposit) {
+ var view = new DepositView(account);
+ view.getDeposit().addActionListener(e -> {
+ account.deposit(view.getAmount());
+ onDeposit.run();
+ view.dispose();
+ });
+ view.getCancel().addActionListener(e -> view.dispose());
+ view.showDialog();
+ }
+}
diff --git a/src/me/teridax/jcash/gui/deposit/DepositView.java b/src/me/teridax/jcash/gui/deposit/DepositView.java
new file mode 100644
index 0000000..06529cc
--- /dev/null
+++ b/src/me/teridax/jcash/gui/deposit/DepositView.java
@@ -0,0 +1,115 @@
+package me.teridax.jcash.gui.deposit;
+
+import me.teridax.jcash.banking.Account;
+import me.teridax.jcash.decode.StringUtils;
+
+import javax.swing.*;
+import java.awt.*;
+import java.text.NumberFormat;
+import java.text.ParseException;
+
+public class DepositView {
+
+ private JDialog dialog;
+ private JButton cancel;
+ private JButton deposit;
+ private JFormattedTextField value;
+
+ public DepositView(Account account) {
+ createComponents(account);
+ layoutComponents();
+ }
+
+ public void showDialog() {
+ dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.setTitle("Deposit money");
+ dialog.pack();
+ dialog.setResizable(false);
+ dialog.setLocationRelativeTo(null);
+ dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+ dialog.setVisible(true);
+ }
+
+ private void layoutComponents() {
+ dialog.getContentPane().setLayout(new GridBagLayout());
+
+ var c = new GridBagConstraints();
+
+ 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("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;
+ dialog.getContentPane().add(new JLabel("Value", SwingConstants.RIGHT), c);
+
+ c.gridx = 1;
+ c.gridy = 1;
+ 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.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.weightx = 0;
+ dialog.getContentPane().add(new JLabel("€"), c);
+
+ var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+ buttonPanel.add(cancel);
+ buttonPanel.add(deposit);
+
+ c.gridx = 0;
+ c.gridy = 2;
+ 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(Account account) {
+ this.dialog = new JDialog();
+
+ this.cancel = new JButton("Cancel");
+ this.deposit = new JButton("Deposit");
+ this.value = new JFormattedTextField(StringUtils.LOCAL_NUMBER_FORMAT);
+
+ this.dialog.setContentPane(new JPanel(new GridBagLayout()));
+ }
+
+ public double getAmount() {
+ if (value.getText().isBlank())
+ return 0;
+
+ try {
+ return NumberFormat.getNumberInstance().parse(value.getText()).doubleValue();
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public JButton getCancel() {
+ return cancel;
+ }
+
+ public JButton getDeposit() {
+ return deposit;
+ }
+
+ public void dispose() {
+ this.dialog.dispose();
+ }
+}
diff --git a/src/me/teridax/jcash/gui/login/AccountSelectionListener.java b/src/me/teridax/jcash/gui/login/AccountSelectionListener.java
new file mode 100644
index 0000000..abeac4f
--- /dev/null
+++ b/src/me/teridax/jcash/gui/login/AccountSelectionListener.java
@@ -0,0 +1,8 @@
+package me.teridax.jcash.gui.login;
+
+import me.teridax.jcash.banking.Profile;
+
+public interface AccountSelectionListener {
+
+ void onAccountSelected(Profile account);
+}
diff --git a/src/me/teridax/jcash/gui/login/LoginController.java b/src/me/teridax/jcash/gui/login/LoginController.java
new file mode 100644
index 0000000..06a614f
--- /dev/null
+++ b/src/me/teridax/jcash/gui/login/LoginController.java
@@ -0,0 +1,73 @@
+package me.teridax.jcash.gui.login;
+
+import me.teridax.jcash.banking.BankingManagementSystem;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.util.Optional;
+
+public class LoginController {
+
+ private final LoginView view;
+ private final LoginData data;
+
+ private AccountSelectionListener listener;
+
+ public LoginController(BankingManagementSystem bms) {
+ this.view = new LoginView();
+ this.data = new LoginData(bms);
+
+ addActionListeners();
+ }
+
+ private void addActionListeners() {
+ this.view.getLogin().addActionListener(this::login);
+ }
+
+ private Optional getIban() {
+ try {
+ var iban = this.view.getIban().getText();
+ return Optional.of(Integer.parseUnsignedInt(iban));
+ } catch (NumberFormatException e) {
+ return Optional.empty();
+ }
+ }
+
+ private Optional getPin() {
+ try {
+ var iban = this.view.getPin().getPassword();
+ return Optional.of(Integer.parseUnsignedInt(new String(iban)));
+ } catch (NumberFormatException e) {
+ return Optional.empty();
+ }
+ }
+
+ private void login(ActionEvent ignored) {
+ var blz = this.view.getBlz().getText();
+ var iban = this.getIban();
+ if (iban.isEmpty()) {
+ JOptionPane.showMessageDialog(null, "invalid IBAN", "Faulty login attempt", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+ var pin = this.getPin();
+ if (pin.isEmpty()) {
+ JOptionPane.showMessageDialog(null, "invalid pin", "Faulty login attempt", JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ var account = this.data.authenticateAccount(blz, iban.get(), pin.get());
+ if (account.isPresent()) {
+ this.listener.onAccountSelected(account.get());
+ } else {
+ JOptionPane.showMessageDialog(null, "invalid login credentials", "Faulty login attempt", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ public void addAccountSelectionListener(AccountSelectionListener listener) {
+ this.listener = listener;
+ }
+
+ public LoginView getView() {
+ return view;
+ }
+}
diff --git a/src/me/teridax/jcash/gui/login/LoginData.java b/src/me/teridax/jcash/gui/login/LoginData.java
new file mode 100644
index 0000000..3183c99
--- /dev/null
+++ b/src/me/teridax/jcash/gui/login/LoginData.java
@@ -0,0 +1,24 @@
+package me.teridax.jcash.gui.login;
+
+import me.teridax.jcash.banking.BankingManagementSystem;
+import me.teridax.jcash.banking.Profile;
+
+import java.util.Optional;
+
+public class LoginData {
+
+ private final BankingManagementSystem bms;
+
+ public LoginData(BankingManagementSystem bms) {
+ this.bms = bms;
+ }
+
+ public Optional authenticateAccount(String blz, int iban, int pin) {
+ var optionalBank = bms.getBank(blz);
+ if (optionalBank.isEmpty())
+ return Optional.empty();
+
+ var profile = optionalBank.get().getAccount(iban);
+ return profile.filter(value -> value.getPrimaryAccount().getPin() == pin);
+ }
+}
diff --git a/src/me/teridax/jcash/gui/login/LoginView.java b/src/me/teridax/jcash/gui/login/LoginView.java
new file mode 100644
index 0000000..6d869c0
--- /dev/null
+++ b/src/me/teridax/jcash/gui/login/LoginView.java
@@ -0,0 +1,90 @@
+package me.teridax.jcash.gui.login;
+
+import javax.swing.*;
+import javax.swing.text.NumberFormatter;
+import java.awt.*;
+import java.text.NumberFormat;
+
+public class LoginView extends JPanel {
+
+ private final JFormattedTextField blz;
+ private final JFormattedTextField iban;
+ private final JPasswordField pin;
+ private final JButton login;
+
+ public LoginView() {
+ this.blz = new JFormattedTextField("MA2424");
+ this.iban = new JFormattedTextField(getNumberFormat());
+ this.iban.setText("4711");
+ this.pin = new JPasswordField("1234");
+ this.login = new JButton("Login");
+
+ setLayout(new BorderLayout(16, 16));
+ var content = new JPanel();
+ this.setBorder(BorderFactory.createEmptyBorder(8,8,8,8));
+ add(new JScrollPane(content), BorderLayout.CENTER);
+ add(new JLabel("Bankautomat"), BorderLayout.NORTH);
+
+ var layout = new GridBagLayout();
+ var constraints = new GridBagConstraints();
+ content.setLayout(layout);
+
+ constraints.gridwidth = 4;
+ constraints.insets = new Insets(12,12,12,12);
+
+ addInputRow(constraints, content, blz, 1, "BLZ");
+ addInputRow(constraints, content, iban, 2, "Kontonummer");
+ addInputRow(constraints, content, pin, 3, "Passwort");
+
+ constraints.gridy = 4;
+ constraints.anchor = GridBagConstraints.PAGE_END;
+ constraints.weightx = 0;
+ constraints.fill = GridBagConstraints.NONE;
+ constraints.insets = new Insets(12,12,12,12);
+ content.add(login, constraints);
+ }
+
+ private NumberFormatter getNumberFormat() {
+ var format = NumberFormat.getIntegerInstance();
+ format.setGroupingUsed(false);
+
+ var formatter = new NumberFormatter(format);
+ formatter.setValueClass(Integer.class);
+ formatter.setMinimum(0);
+ formatter.setMaximum(Integer.MAX_VALUE);
+ formatter.setAllowsInvalid(false);
+
+ return formatter;
+ }
+
+ private void addInputRow(GridBagConstraints constraints, JComponent target, JComponent comp, int row, String name) {
+ constraints.gridwidth = 1;
+ constraints.gridx = 1;
+ constraints.gridy = row;
+ constraints.weightx = 0;
+ constraints.fill = GridBagConstraints.HORIZONTAL;
+ target.add(new JLabel(name, SwingConstants.RIGHT), constraints);
+
+ constraints.gridx = 2;
+ constraints.gridy = row;
+ constraints.weightx = 1;
+ constraints.fill = GridBagConstraints.HORIZONTAL;
+ target.add(comp, constraints);
+ }
+
+ public JTextField getBlz() {
+ return blz;
+ }
+
+ public JTextField getIban() {
+ return iban;
+ }
+
+ public JPasswordField getPin() {
+ return pin;
+ }
+
+ public JButton getLogin() {
+ return login;
+ }
+}
diff --git a/src/me/teridax/jcash/gui/takeoff/TakeoffDialog.java b/src/me/teridax/jcash/gui/takeoff/TakeoffDialog.java
new file mode 100644
index 0000000..44e05c6
--- /dev/null
+++ b/src/me/teridax/jcash/gui/takeoff/TakeoffDialog.java
@@ -0,0 +1,17 @@
+package me.teridax.jcash.gui.takeoff;
+
+import me.teridax.jcash.banking.Account;
+
+public class TakeoffDialog {
+
+ public TakeoffDialog(Account account, Runnable onTakeoff) {
+ var view = new TakeoffView(account);
+ view.getTakeoff().addActionListener(e -> {
+ account.takeoff(view.getAmount());
+ onTakeoff.run();
+ view.dispose();
+ });
+ view.getCancel().addActionListener(e -> view.dispose());
+ view.showDialog();
+ }
+}
diff --git a/src/me/teridax/jcash/gui/takeoff/TakeoffView.java b/src/me/teridax/jcash/gui/takeoff/TakeoffView.java
new file mode 100644
index 0000000..4eed846
--- /dev/null
+++ b/src/me/teridax/jcash/gui/takeoff/TakeoffView.java
@@ -0,0 +1,118 @@
+package me.teridax.jcash.gui.takeoff;
+
+import me.teridax.jcash.banking.Account;
+import me.teridax.jcash.decode.StringUtils;
+
+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;
+
+public class TakeoffView {
+
+ private JDialog dialog;
+ private JButton cancel;
+ private JButton takeoff;
+ private JFormattedTextField value;
+
+ public TakeoffView(Account account) {
+ createComponents(account);
+ layoutComponents();
+ }
+
+ public void showDialog() {
+ dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.setTitle("Takeoff money");
+ dialog.pack();
+ dialog.setResizable(false);
+ dialog.setLocationRelativeTo(null);
+ dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+ dialog.setVisible(true);
+ }
+
+ private void layoutComponents() {
+ var c = new GridBagConstraints();
+
+ 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("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;
+ dialog.getContentPane().add(new JLabel("Value", SwingConstants.RIGHT), c);
+
+ c.gridx = 1;
+ c.gridy = 1;
+ 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.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.weightx = 0;
+ dialog.getContentPane().add(new JLabel("€"), c);
+
+ var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+ buttonPanel.add(cancel);
+ buttonPanel.add(takeoff);
+
+ c.gridx = 0;
+ c.gridy = 2;
+ 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(Account account) {
+ this.dialog = new JDialog();
+
+ this.cancel = new JButton("Cancel");
+ this.takeoff = new JButton("Takeoff");
+ this.value = new JFormattedTextField(StringUtils.LOCAL_NUMBER_FORMAT);
+
+ this.dialog.setContentPane(new JPanel(new GridBagLayout()));
+ }
+
+ public double getAmount() {
+ if (value.getText().isBlank()) {
+ showMessageDialog(null, "invalid amount", "currency must not be blank", ERROR_MESSAGE);
+ return 0;
+ }
+
+ try {
+ return NumberFormat.getNumberInstance().parse(value.getText()).doubleValue();
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public JButton getCancel() {
+ return cancel;
+ }
+
+ public JButton getTakeoff() {
+ return takeoff;
+ }
+
+ public void dispose() {
+ this.dialog.dispose();
+ }
+}
diff --git a/src/me/teridax/jcash/gui/transfer/TransferDialog.java b/src/me/teridax/jcash/gui/transfer/TransferDialog.java
new file mode 100644
index 0000000..9f3aa4d
--- /dev/null
+++ b/src/me/teridax/jcash/gui/transfer/TransferDialog.java
@@ -0,0 +1,17 @@
+package me.teridax.jcash.gui.transfer;
+
+import me.teridax.jcash.banking.Account;
+
+public class TransferDialog {
+
+ public TransferDialog(Account account, Runnable onDeposit) {
+ var view = new TransferView(account);
+ view.getTransfer().addActionListener(e -> {
+ account.takeoff(view.getAmount());
+ onDeposit.run();
+ view.dispose();
+ });
+ view.getCancel().addActionListener(e -> view.dispose());
+ view.showDialog();
+ }
+}
diff --git a/src/me/teridax/jcash/gui/transfer/TransferView.java b/src/me/teridax/jcash/gui/transfer/TransferView.java
new file mode 100644
index 0000000..80b00ee
--- /dev/null
+++ b/src/me/teridax/jcash/gui/transfer/TransferView.java
@@ -0,0 +1,144 @@
+package me.teridax.jcash.gui.transfer;
+
+import me.teridax.jcash.banking.Account;
+import me.teridax.jcash.decode.StringUtils;
+
+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;
+
+public class TransferView {
+
+ private JDialog dialog;
+ private JButton cancel;
+ private JButton transfer;
+ private JFormattedTextField iban;
+ private JFormattedTextField blz;
+ private JFormattedTextField value;
+
+ public TransferView(Account account) {
+ createComponents(account);
+ layoutComponents();
+ }
+
+ public void showDialog() {
+ dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
+ dialog.setTitle("Transfer money");
+ dialog.pack();
+ dialog.setSize(dialog.getWidth() * 2, dialog.getHeight());
+ dialog.setResizable(false);
+ dialog.setLocationRelativeTo(null);
+ dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+ dialog.setVisible(true);
+ }
+
+ private void layoutComponents() {
+ var c = new GridBagConstraints();
+
+ 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("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;
+ dialog.getContentPane().add(new JLabel("BLZ", SwingConstants.RIGHT), c);
+
+ c.gridx = 1;
+ c.gridy = 1;
+ 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.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LAST_LINE_END;
+ c.weightx = 0;
+ dialog.getContentPane().add(new JLabel("IBAN", SwingConstants.RIGHT), c);
+
+ c.gridx = 3;
+ c.gridy = 1;
+ 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.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LAST_LINE_END;
+ c.weightx = 0;
+ dialog.getContentPane().add(new JLabel("Betrag", SwingConstants.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(value, c);
+
+ var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+ buttonPanel.add(cancel);
+ buttonPanel.add(transfer);
+
+ c.gridx = 0;
+ c.gridy = 3;
+ 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(Account account) {
+ this.dialog = new JDialog();
+
+ this.cancel = new JButton("Cancel");
+ this.transfer = new JButton("Transfer");
+ this.value = new JFormattedTextField(StringUtils.LOCAL_NUMBER_FORMAT);
+ this.iban = new JFormattedTextField();
+ this.blz = new JFormattedTextField();
+
+ this.dialog.setContentPane(new JPanel(new GridBagLayout()));
+ }
+
+ public double getAmount() {
+ if (value.getText().isBlank()) {
+ showMessageDialog(null, "invalid amount", "currency must not be blank", ERROR_MESSAGE);
+ return 0;
+ }
+
+ try {
+ return NumberFormat.getNumberInstance().parse(value.getText()).doubleValue();
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public JButton getCancel() {
+ return cancel;
+ }
+
+ public JButton getTransfer() {
+ return transfer;
+ }
+
+ public void dispose() {
+ this.dialog.dispose();
+ }
+}