added javadoc and implemented transfer functionality

This commit is contained in:
Sven Vogel 2023-07-01 16:13:36 +02:00
parent a9ae1dc55a
commit d5d5a50e2d
24 changed files with 398 additions and 112 deletions

View File

@ -65,7 +65,7 @@ public final class Main {
// when we have logged in set the account viewer as window content // when we have logged in set the account viewer as window content
login.addAccountSelectionListener(account -> { login.addAccountSelectionListener(account -> {
var profileCont = new AccountController(account); var profileCont = new AccountController(account, login.getData().getBms());
this.window.setContentPane(profileCont.getView()); this.window.setContentPane(profileCont.getView());
this.window.revalidate(); this.window.revalidate();
this.window.repaint(); this.window.repaint();
@ -78,6 +78,7 @@ public final class Main {
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
System.out.println("no file selected. goodbye"); System.out.println("no file selected. goodbye");
System.exit(0);
} }
}); });
} }

View File

@ -1,14 +1,27 @@
package me.teridax.jcash.banking; package me.teridax.jcash.banking;
import me.teridax.jcash.decode.StringUtils; import me.teridax.jcash.decode.StringDecoder;
import java.util.Objects; import java.util.Objects;
/**
* Base class for bank accounts.
* Stores the iban, pin and balance.
*/
@SuppressWarnings("unused") @SuppressWarnings("unused")
public abstract class Account { public abstract class Account {
/**
* International bank account number
*/
private final int iban; private final int iban;
/**
* Personal identification number
*/
private final int pin; private final int pin;
/**
* Balance of this account
*/
private double balance; private double balance;
public Account(int iban, int pin, double balance) { public Account(int iban, int pin, double balance) {
@ -17,20 +30,30 @@ public abstract class Account {
this.balance = balance; this.balance = balance;
} }
public static Account fromColumns(String[] columns) { /**
* Parses a row of a fixed amount of columns into an account.
* This function will attempt to create an instance of two classes which inherit from Account.
* @param columns array of 6 strings
* @return either an instance of {@link me.teridax.jcash.banking.SavingsAccount} or an instance of {@link me.teridax.jcash.banking.CurrentAccount}
* @throws IllegalArgumentException if the account type cannot be determined or the provided data is invalid
* @throws NullPointerException if columns is null
*/
public static Account fromColumns(String[] columns) throws IllegalArgumentException, NullPointerException {
Objects.requireNonNull(columns); Objects.requireNonNull(columns);
var iban = StringUtils.decodeUniqueIdentificationNumber(columns[0]); // deserialize fields
var pin = StringUtils.decodeUniqueIdentificationNumber(columns[1]); var iban = StringDecoder.decodeUniqueIdentificationNumber(columns[0]);
var balance = StringUtils.decodeCurrency(columns[2]); var pin = StringDecoder.decodeUniqueIdentificationNumber(columns[1]);
var type = StringUtils.decodeName(columns[3]); var balance = StringDecoder.decodeCurrency(columns[2]);
var type = StringDecoder.decodeName(columns[3]);
// try to detect the specific runtime class to deserialize
try { try {
if (type.equals("Sparkonto")) { if (type.equals("Sparkonto")) {
var interest = StringUtils.decodePercent(columns[4]); var interest = StringDecoder.decodePercent(columns[4]);
return new SavingsAccount(iban, pin, balance, interest); return new SavingsAccount(iban, pin, balance, interest);
} else if (type.equals("Girokonto")) { } else if (type.equals("Girokonto")) {
var overdraft = StringUtils.decodeCurrency(columns[5]); var overdraft = StringDecoder.decodeCurrency(columns[5]);
return new CurrentAccount(iban, pin, balance, overdraft); return new CurrentAccount(iban, pin, balance, overdraft);
} else { } else {
throw new IllegalArgumentException("Invalid account type: " + type); throw new IllegalArgumentException("Invalid account type: " + type);
@ -65,6 +88,12 @@ public abstract class Account {
return false; return false;
} }
/**
* Returns a description of the account in form a string.
* This method is not equal to {@link #toString()} but is intended to be
* a method used to retrieve a formatted representation for guis.
* @return a basic description of the account in form a string
*/
public String getDescription() { public String getDescription() {
return String.format("%s (%s)", iban, getClass().getSimpleName()); return String.format("%s (%s)", iban, getClass().getSimpleName());
} }
@ -78,6 +107,11 @@ public abstract class Account {
return String.format("@Account [iban=%d, pin=%d, balance=%.2f]", iban, pin, balance); return String.format("@Account [iban=%d, pin=%d, balance=%.2f]", iban, pin, balance);
} }
/**
* Takeoff a certain amount of money from this accounts balance.
* Saturates the result if the value to subtract is greater than the balance present.
* @param amount the amount of money to subtract from the accounts balance
*/
public void takeoff(double amount) { public void takeoff(double amount) {
this.balance = Math.max(0, this.balance - amount); this.balance = Math.max(0, this.balance - amount);
} }

View File

@ -3,10 +3,24 @@ package me.teridax.jcash.banking;
import java.util.*; import java.util.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/**
* Bank storing a name, blz and a list of owners and their accounts for this bank.
*/
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class Bank { public final class Bank {
/**
* The name of the bank
*/
private final String name; private final String name;
/**
* Bankleitzahl. Identification number of banks in germany
*/
private final String blz; private final String blz;
/**
* A map of banking accounts associated with their respective owner
*/
private final Map<Owner, Set<Account>> accounts; private final Map<Owner, Set<Account>> accounts;
Bank(String blz, String name) { Bank(String blz, String name) {
@ -23,6 +37,13 @@ public final class Bank {
return name; return name;
} }
/**
* Add a new account and its associated owner to this bank.
* If the owner is already present the account is added to the existing owner.
* If the account is already present for the owner the old account gets overwritten.
* @param owner the owner of the account
* @param account the account of the owner
*/
public void addAccount(Owner owner, Account account) { public void addAccount(Owner owner, Account account) {
var set = this.accounts.getOrDefault(owner, new HashSet<>()); var set = this.accounts.getOrDefault(owner, new HashSet<>());
set.add(account); set.add(account);
@ -42,12 +63,23 @@ public final class Bank {
return Objects.hash(blz); return Objects.hash(blz);
} }
/**
* Retrieve all accounts owned by the specific owner.
* @param owner the owner for which accounts are to be retrieved
* @return all accounts owned by the owner and managed by this bank
*/
public Account[] getAccountsOfOwner(Owner owner) { public Account[] getAccountsOfOwner(Owner owner) {
return accounts.get(owner).toArray(Account[]::new); return accounts.get(owner).toArray(Account[]::new);
} }
public static String validateBlz(String maybeBlz) { /**
var pattern = Pattern.compile("[\\w\\d-_]+"); * Validates the given BLZ. If the blz is not valid an exception is thrown.
* @param maybeBlz a string to be tested for being a blz
* @return returns the correctly formatted blz
* @throws IllegalArgumentException if the supplied argument is not a valid blz
*/
public static String validateBlz(String maybeBlz) throws IllegalArgumentException {
var pattern = Pattern.compile("[\\w-_]+");
var matcher = pattern.matcher(maybeBlz); var matcher = pattern.matcher(maybeBlz);
if (matcher.find()) if (matcher.find())
@ -56,11 +88,17 @@ public final class Bank {
throw new IllegalArgumentException("not a valid BLZ: " + maybeBlz); throw new IllegalArgumentException("not a valid BLZ: " + maybeBlz);
} }
/**
* Return a profile of the specified international bank account number.
* @param iban the number to create a profile for
* @return the profile for the iban account
*/
public Optional<Profile> getAccount(int iban) { public Optional<Profile> getAccount(int iban) {
// find the account which has the supplied iban
for (var owner : this.accounts.entrySet()) { for (var owner : this.accounts.entrySet()) {
for (var account : owner.getValue()) { for (var account : owner.getValue()) {
var tmp = account.getIban(); if (iban == account.getIban()) {
if (tmp == iban) {
return Optional.of(new Profile(owner.getKey(), this, account, getAccountsOfOwner(owner.getKey()))); return Optional.of(new Profile(owner.getKey(), this, account, getAccountsOfOwner(owner.getKey())));
} }
} }

View File

@ -1,42 +1,90 @@
package me.teridax.jcash.banking; package me.teridax.jcash.banking;
import me.teridax.jcash.decode.StringUtils; import me.teridax.jcash.decode.StringDecoder;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
/**
* Management system for banks and all accounts provided by these banks and their respective account owners.
* This class serves a read only database which can only modify runtime data without any respect to CRUD or the ACID
* principles.
*/
public final class BankingManagementSystem { public final class BankingManagementSystem {
/**
* A set of banks
*/
private final Set<Bank> banks; private final Set<Bank> banks;
private BankingManagementSystem() { private BankingManagementSystem() {
this.banks = new HashSet<>(); this.banks = new HashSet<>();
} }
/**
* Utility method for retrieving the tail of a string array.
* This method return all items which follow the n-th index denoted by index.
* @param array the array to take the tail of
* @param index the amount trailing indices to skip
* @return an array containing the last elements of the supplied array
*/
private static String[] tail(String[] array, int index) { private static String[] tail(String[] array, int index) {
return Arrays.stream(array).skip(index).toArray(String[]::new); return Arrays.stream(array).skip(index).toArray(String[]::new);
} }
public static BankingManagementSystem loadFromCsv(Path file) { /**
* Loads an instance of this class from a csv file.
* The csv file must comply to the formats of {@link StringDecoder} and must contain
* the following columns in this exact order:
* <ol>
* <li>Bank name</li>
* <li>Bank BLZ</li>
* <li>Account IBAN</li>
* <li>Account PIN</li>
* <li>Account Balance</li>
* <li>Account type</li>
* <li>Account Interest rate</li>
* <li>Account Overdraft</li>
* <li>Owner uuid</li>
* <li>Owner family name</li>
* <li>Owner name</li>
* <li>Owner street</li>
* <li>Owner plz</li>
* <li>Owner city</li>
* </ol>
* The file can contain a header line which gets skipped when reading.
* Note that the first line is always skipped and the name of every column is not checked in any way.
* @param file the file to parse
* @throws IllegalArgumentException if the file cannot be read or the containing data is invalid
* @return a valid BMS
*/
public static BankingManagementSystem loadFromCsv(Path file) throws IllegalArgumentException {
try { try {
var bms = new BankingManagementSystem(); var bms = new BankingManagementSystem();
var content = Files.readString(file); var content = Files.readString(file);
// read line by line
// and skip the first line
content.lines().skip(1).forEach(line -> { content.lines().skip(1).forEach(line -> {
// split the line into columns
var columns = line.split(";"); var columns = line.split(";");
// one line must contain exactly 14 columns
if (columns.length != 14) if (columns.length != 14)
throw new IllegalArgumentException("invalid column count: " + columns.length); throw new IllegalArgumentException("invalid column count: " + columns.length);
// read basic fields
var owner = Owner.fromColumns(tail(columns, 8)); var owner = Owner.fromColumns(tail(columns, 8));
var account = Account.fromColumns(tail(columns, 2)); var account = Account.fromColumns(tail(columns, 2));
var blz = Bank.validateBlz(columns[1]); var blz = Bank.validateBlz(columns[1]);
var name = StringUtils.decodeName(columns[0]); var name = StringDecoder.decodeName(columns[0]);
var bankOfLine = new Bank(blz, name); var bankOfLine = new Bank(blz, name);
// add banks to bms
var bankOfSet = bms.banks.stream().filter(b -> b.equals(bankOfLine)).findFirst(); var bankOfSet = bms.banks.stream().filter(b -> b.equals(bankOfLine)).findFirst();
if (bankOfSet.isPresent()) { if (bankOfSet.isPresent()) {
bankOfSet.get().addAccount(owner, account); bankOfSet.get().addAccount(owner, account);
@ -55,6 +103,11 @@ public final class BankingManagementSystem {
} }
} }
/**
* Return a bank with the given blz.
* @param blz the blz to search bank of
* @return the bank with this blz or none
*/
public Optional<Bank> getBank(String blz) { public Optional<Bank> getBank(String blz) {
return this.banks.stream().filter(b -> b.getBlz().equals(blz)).findFirst(); return this.banks.stream().filter(b -> b.getBlz().equals(blz)).findFirst();
} }

View File

@ -1,7 +1,14 @@
package me.teridax.jcash.banking; package me.teridax.jcash.banking;
public class CurrentAccount extends Account { /**
* Immutable currency account storing only overdraft.
* English equivalent to "Girokonto"
*/
public final class CurrentAccount extends Account {
/**
* Overdraft amount in currency.
*/
private final double overdraft; private final double overdraft;
public CurrentAccount(int iban, int pin, double balance, double overdraft) { public CurrentAccount(int iban, int pin, double balance, double overdraft) {

View File

@ -4,7 +4,7 @@ import org.junit.Test;
import java.nio.file.Paths; import java.nio.file.Paths;
public class Tests { public class DataClassTests {
@Test @Test
public void test() { public void test() {

View File

@ -1,17 +1,26 @@
package me.teridax.jcash.banking; package me.teridax.jcash.banking;
import me.teridax.jcash.decode.StringUtils; import me.teridax.jcash.decode.StringDecoder;
import java.util.Objects; import java.util.Objects;
/**
* Represents a person owning an account.
*/
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class Owner { public final class Owner {
private int uid; /**
private String familyName; * unique identifier
private String name; */
private String street; private final int uid;
private int zip; private final String familyName;
private String city; private final String name;
private final String street;
/**
* postal code
*/
private final int zip;
private final String city;
private Owner(int uid, String familyName, String name, int zip, String city, String street) { private Owner(int uid, String familyName, String name, int zip, String city, String street) {
this.uid = uid; this.uid = uid;
@ -22,18 +31,26 @@ public final class Owner {
this.street = street; this.street = street;
} }
/**
* Create a new instance of this class parsed from the columns.
* @param columns the fields of this class as strings
* @return an instance of this class
* @throws IllegalArgumentException if the supplied columns is invalid
* @throws NullPointerException if columns is null
*/
public static Owner fromColumns(String... columns) throws IllegalArgumentException, NullPointerException { public static Owner fromColumns(String... columns) throws IllegalArgumentException, NullPointerException {
Objects.requireNonNull(columns); Objects.requireNonNull(columns);
if (columns.length != 6) if (columns.length != 6)
throw new IllegalArgumentException("Invalid number of columns: " + columns.length); throw new IllegalArgumentException("Invalid number of columns: " + columns.length);
var uid = StringUtils.decodeUniqueIdentificationNumber(columns[0]); // decode fields
var familyName = StringUtils.decodeName(columns[1]); var uid = StringDecoder.decodeUniqueIdentificationNumber(columns[0]);
var name = StringUtils.decodeName(columns[2]); var familyName = StringDecoder.decodeName(columns[1]);
var street = StringUtils.decodeStreet(columns[3]); var name = StringDecoder.decodeName(columns[2]);
var zip = StringUtils.decodeUniqueIdentificationNumber(columns[4]); var street = StringDecoder.decodeStreet(columns[3]);
var city = StringUtils.decodeName(columns[5]); var zip = StringDecoder.decodeUniqueIdentificationNumber(columns[4]);
var city = StringDecoder.decodeName(columns[5]);
return new Owner(uid, familyName, name, zip, city, street); return new Owner(uid, familyName, name, zip, city, street);
} }

View File

@ -1,11 +1,17 @@
package me.teridax.jcash.banking; package me.teridax.jcash.banking;
/**
* Groups an owner and all of its accounts registered at a specific bank together.
* The profile is oriented around a primary account of the owner.
* This class is meant to be a read only reference for easier runtime processing of
* data packed into nested structure of objects.
*/
public class Profile { public class Profile {
private Owner owner; private final Owner owner;
private Bank bank; private final Bank bank;
private Account primaryAccount; private Account primaryAccount;
private Account[] accounts; private final Account[] accounts;
public Profile(Owner owner, Bank bank, Account account, Account[] accounts) { public Profile(Owner owner, Bank bank, Account account, Account[] accounts) {
this.owner = owner; this.owner = owner;
@ -30,6 +36,11 @@ public class Profile {
return accounts; return accounts;
} }
/**
* Set the primary account of this profile based on a descriptive text.
* This method may not change anything if no account can be found with a matching description
* @param description the description to find a matching account for
*/
public void setPrimaryAccount(String description) { public void setPrimaryAccount(String description) {
for (Account account : accounts) { for (Account account : accounts) {
if (account.getDescription().equals(description)) { if (account.getDescription().equals(description)) {

View File

@ -1,8 +1,15 @@
package me.teridax.jcash.banking; package me.teridax.jcash.banking;
/**
* Savings account representing a german "Sparkonto".
* To the balance a certain interest rate is added.
*/
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class SavingsAccount extends Account { public final class SavingsAccount extends Account {
/**
* interest rate applied to the balance of the super class
*/
private final double interest; private final double interest;
public SavingsAccount(int iban, int pin, double balance, double interest) { public SavingsAccount(int iban, int pin, double balance, double interest) {

View File

@ -14,7 +14,7 @@ import static junit.framework.TestCase.assertEquals;
* Utility class for converting various single line strings into a specific data type according * Utility class for converting various single line strings into a specific data type according
* to a format mostly dictated by a locale. * to a format mostly dictated by a locale.
*/ */
public class StringUtils { public class StringDecoder {
/** /**
* Locale to use when converting strings * Locale to use when converting strings

View File

@ -1,18 +1,26 @@
package me.teridax.jcash.gui.account; package me.teridax.jcash.gui.account;
import me.teridax.jcash.Main; import me.teridax.jcash.Main;
import me.teridax.jcash.banking.BankingManagementSystem;
import me.teridax.jcash.banking.Profile; import me.teridax.jcash.banking.Profile;
import me.teridax.jcash.gui.deposit.DepositDialog; import me.teridax.jcash.gui.deposit.DepositDialog;
import me.teridax.jcash.gui.takeoff.TakeoffDialog; import me.teridax.jcash.gui.takeoff.TakeoffDialog;
import me.teridax.jcash.gui.transfer.TransferDialog; import me.teridax.jcash.gui.transfer.TransferDialog;
/**
* Controller for controlling the gui of an account.
*/
public class AccountController { public class AccountController {
private final AccountData data;
/**
* GUI if an account
*/
private final AccountView view; private final AccountView view;
public AccountController(Profile profile) { public AccountController(Profile profile, BankingManagementSystem bms) {
this.view = new AccountView(); this.view = new AccountView();
this.view.setProfile(profile); this.view.setProfile(profile);
this.data = new AccountData(bms);
createListeners(profile); createListeners(profile);
} }
@ -29,17 +37,11 @@ public class AccountController {
Main.getInstance().showLoginScreen(); Main.getInstance().showLoginScreen();
}); });
this.view.getDeposit().addActionListener(e -> { this.view.getDeposit().addActionListener(e -> new DepositDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile)));
new DepositDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile));
});
this.view.getTakeoff().addActionListener(e -> { this.view.getTakeoff().addActionListener(e -> new TakeoffDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile)));
new TakeoffDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile));
});
this.view.getTransfer().addActionListener(e -> { this.view.getTransfer().addActionListener(e -> new TransferDialog(profile.getPrimaryAccount(), data.getBms(), () -> this.view.setProfile(profile)));
new TransferDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile));
});
} }
public AccountView getView() { public AccountView getView() {

View File

@ -0,0 +1,16 @@
package me.teridax.jcash.gui.account;
import me.teridax.jcash.banking.BankingManagementSystem;
public class AccountData {
private final BankingManagementSystem bms;
public AccountData(BankingManagementSystem bms) {
this.bms = bms;
}
public BankingManagementSystem getBms() {
return bms;
}
}

View File

@ -1,11 +1,13 @@
package me.teridax.jcash.gui.account; package me.teridax.jcash.gui.account;
import me.teridax.jcash.banking.*; import me.teridax.jcash.banking.*;
import me.teridax.jcash.decode.StringUtils; import me.teridax.jcash.decode.StringDecoder;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import static javax.swing.SwingConstants.RIGHT;
public class AccountView extends JPanel { public class AccountView extends JPanel {
private JTextField iban; private JTextField iban;
@ -16,13 +18,8 @@ public class AccountView extends JPanel {
private JTextField type; private JTextField type;
private JTextField typeSpecialProperty; private JTextField typeSpecialProperty;
private JTextField balance; private JTextField balance;
private JLabel typeSpecialLabel; private JLabel typeSpecialLabel;
private JComboBox<String> accountSelection; private JComboBox<String> accountSelection;
private JPanel content;
private JButton logout; private JButton logout;
private JButton transfer; private JButton transfer;
private JButton deposit; private JButton deposit;
@ -47,12 +44,12 @@ public class AccountView extends JPanel {
this.name.setText(owner.getName() + " " + owner.getFamilyName()); this.name.setText(owner.getName() + " " + owner.getFamilyName());
this.address.setText(owner.getStreet() + " " + owner.getCity()); this.address.setText(owner.getStreet() + " " + owner.getCity());
this.balance.setText(StringUtils.LOCAL_NUMBER_FORMAT.format(account.getBalance()) + ""); this.balance.setText(StringDecoder.LOCAL_NUMBER_FORMAT.format(account.getBalance()) + "");
this.type.setText(account.getClass().getSimpleName()); this.type.setText(account.getClass().getSimpleName());
if (account instanceof CurrentAccount) { if (account instanceof CurrentAccount) {
this.typeSpecialLabel.setText("Overdraft"); this.typeSpecialLabel.setText("Overdraft");
this.typeSpecialProperty.setText( StringUtils.LOCAL_NUMBER_FORMAT.format(((CurrentAccount) account).getOverdraft()) + ""); this.typeSpecialProperty.setText( StringDecoder.LOCAL_NUMBER_FORMAT.format(((CurrentAccount) account).getOverdraft()) + "");
} else if (account instanceof SavingsAccount) { } else if (account instanceof SavingsAccount) {
this.typeSpecialLabel.setText("Interest rate"); this.typeSpecialLabel.setText("Interest rate");
this.typeSpecialProperty.setText( ((SavingsAccount) account).getInterest() + " %" ); this.typeSpecialProperty.setText( ((SavingsAccount) account).getInterest() + " %" );
@ -67,10 +64,11 @@ public class AccountView extends JPanel {
} }
private void createLayout() { private void createLayout() {
setLayout(new BorderLayout(16, 16)); var content = new JPanel(new GridBagLayout());
add(new JScrollPane(content), BorderLayout.CENTER);
this.setLayout(new BorderLayout(16, 16));
this.add(new JScrollPane(content), BorderLayout.CENTER);
content.setLayout(new GridBagLayout());
var constraints = new GridBagConstraints(); var constraints = new GridBagConstraints();
constraints.gridwidth = 4; constraints.gridwidth = 4;
@ -80,14 +78,14 @@ public class AccountView extends JPanel {
accountSelectionPanel.add(iban, BorderLayout.CENTER); accountSelectionPanel.add(iban, BorderLayout.CENTER);
accountSelectionPanel.add(accountSelection, BorderLayout.EAST); accountSelectionPanel.add(accountSelection, BorderLayout.EAST);
addInputRow(constraints, content, accountSelectionPanel, 1, new JLabel("IBAN", SwingConstants.RIGHT)); addInputRow(constraints, content, accountSelectionPanel, 1, new JLabel("IBAN", RIGHT));
addInputRow(constraints, content, name, 2, new JLabel("Name/Family-name", SwingConstants.RIGHT)); addInputRow(constraints, content, name, 2, new JLabel("Name/Family-name", RIGHT));
addInputRow(constraints, content, address, 3, new JLabel("Address", SwingConstants.RIGHT)); addInputRow(constraints, content, address, 3, new JLabel("Address", RIGHT));
addInputRow(constraints, content, bankName, 4, new JLabel("Bank", SwingConstants.RIGHT)); addInputRow(constraints, content, bankName, 4, new JLabel("Bank", RIGHT));
addInputRow(constraints, content, blz, 5, new JLabel("BLZ", SwingConstants.RIGHT)); addInputRow(constraints, content, blz, 5, new JLabel("BLZ", RIGHT));
addInputRow(constraints, content, type, 6, new JLabel("Account", SwingConstants.RIGHT)); addInputRow(constraints, content, type, 6, new JLabel("Account", RIGHT));
addInputRow(constraints, content, typeSpecialProperty, 7, typeSpecialLabel); addInputRow(constraints, content, typeSpecialProperty, 7, typeSpecialLabel);
addInputRow(constraints, content, balance, 8, new JLabel("Balance", SwingConstants.RIGHT)); addInputRow(constraints, content, balance, 8, new JLabel("Balance", RIGHT));
var buttonPanel = Box.createHorizontalBox(); var buttonPanel = Box.createHorizontalBox();
buttonPanel.add(Box.createHorizontalStrut(4)); buttonPanel.add(Box.createHorizontalStrut(4));
@ -121,18 +119,25 @@ public class AccountView extends JPanel {
this.balance.setEditable(false); this.balance.setEditable(false);
this.typeSpecialProperty.setEditable(false); this.typeSpecialProperty.setEditable(false);
this.typeSpecialLabel = new JLabel("", SwingConstants.RIGHT); this.typeSpecialLabel = new JLabel("", RIGHT);
this.accountSelection = new JComboBox<>(); this.accountSelection = new JComboBox<>();
this.content = new JPanel();
this.logout = new JButton("Logout"); this.logout = new JButton("Logout");
this.transfer = new JButton("Transfer"); this.transfer = new JButton("Transfer");
this.deposit = new JButton("Deposit"); this.deposit = new JButton("Deposit");
this.takeoff = new JButton("Takeoff"); this.takeoff = new JButton("Takeoff");
} }
/**
* Add a new row of components to the specified target component.
* This will add label to the right side of the next row and the specified component to the left.
* @param constraints the constraint to use. Must be non-null
* @param target the base component to add a row to
* @param comp the component to add to the left side
* @param row the row to add the components to
* @param label the label to add to the left side
*/
private void addInputRow(GridBagConstraints constraints, JComponent target, JComponent comp, int row, JLabel label) { private void addInputRow(GridBagConstraints constraints, JComponent target, JComponent comp, int row, JLabel label) {
constraints.gridwidth = 1; constraints.gridwidth = 1;
constraints.gridx = 1; constraints.gridx = 1;

View File

@ -5,7 +5,7 @@ import me.teridax.jcash.banking.Account;
public class DepositDialog { public class DepositDialog {
public DepositDialog(Account account, Runnable onDeposit) { public DepositDialog(Account account, Runnable onDeposit) {
var view = new DepositView(account); var view = new DepositView();
view.getDeposit().addActionListener(e -> { view.getDeposit().addActionListener(e -> {
account.deposit(view.getAmount()); account.deposit(view.getAmount());
onDeposit.run(); onDeposit.run();

View File

@ -1,7 +1,7 @@
package me.teridax.jcash.gui.deposit; package me.teridax.jcash.gui.deposit;
import me.teridax.jcash.banking.Account; import me.teridax.jcash.banking.Account;
import me.teridax.jcash.decode.StringUtils; import me.teridax.jcash.decode.StringDecoder;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@ -15,8 +15,8 @@ public class DepositView {
private JButton deposit; private JButton deposit;
private JFormattedTextField value; private JFormattedTextField value;
public DepositView(Account account) { public DepositView() {
createComponents(account); createComponents();
layoutComponents(); layoutComponents();
} }
@ -80,12 +80,12 @@ public class DepositView {
dialog.getContentPane().add(buttonPanel, c); dialog.getContentPane().add(buttonPanel, c);
} }
private void createComponents(Account account) { private void createComponents() {
this.dialog = new JDialog(); this.dialog = new JDialog();
this.cancel = new JButton("Cancel"); this.cancel = new JButton("Cancel");
this.deposit = new JButton("Deposit"); this.deposit = new JButton("Deposit");
this.value = new JFormattedTextField(StringUtils.LOCAL_NUMBER_FORMAT); this.value = new JFormattedTextField(StringDecoder.LOCAL_NUMBER_FORMAT);
this.dialog.setContentPane(new JPanel(new GridBagLayout())); this.dialog.setContentPane(new JPanel(new GridBagLayout()));
} }

View File

@ -2,7 +2,15 @@ package me.teridax.jcash.gui.login;
import me.teridax.jcash.banking.Profile; import me.teridax.jcash.banking.Profile;
/**
* Listens for changes in a selected account.
*/
public interface AccountSelectionListener { public interface AccountSelectionListener {
/**
* Run when a new account is selected.
* The selected account is set as the primary account of the profile
* @param account the profile for the selected account
*/
void onAccountSelected(Profile account); void onAccountSelected(Profile account);
} }

View File

@ -6,6 +6,9 @@ import javax.swing.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.util.Optional; import java.util.Optional;
import static javax.swing.JOptionPane.ERROR_MESSAGE;
import static javax.swing.JOptionPane.showMessageDialog;
public class LoginController { public class LoginController {
private final LoginView view; private final LoginView view;
@ -46,12 +49,13 @@ public class LoginController {
var blz = this.view.getBlz().getText(); var blz = this.view.getBlz().getText();
var iban = this.getIban(); var iban = this.getIban();
if (iban.isEmpty()) { if (iban.isEmpty()) {
JOptionPane.showMessageDialog(null, "invalid IBAN", "Faulty login attempt", JOptionPane.ERROR_MESSAGE); showMessageDialog(null, "invalid IBAN", "Faulty login attempt", ERROR_MESSAGE);
return; return;
} }
var pin = this.getPin(); var pin = this.getPin();
if (pin.isEmpty()) { if (pin.isEmpty()) {
JOptionPane.showMessageDialog(null, "invalid pin", "Faulty login attempt", JOptionPane.ERROR_MESSAGE); showMessageDialog(null, "invalid pin", "Faulty login attempt", ERROR_MESSAGE);
return; return;
} }
@ -59,7 +63,7 @@ public class LoginController {
if (account.isPresent()) { if (account.isPresent()) {
this.listener.onAccountSelected(account.get()); this.listener.onAccountSelected(account.get());
} else { } else {
JOptionPane.showMessageDialog(null, "invalid login credentials", "Faulty login attempt", JOptionPane.ERROR_MESSAGE); showMessageDialog(null, "invalid login credentials", "Faulty login attempt", ERROR_MESSAGE);
} }
} }
@ -70,4 +74,8 @@ public class LoginController {
public LoginView getView() { public LoginView getView() {
return view; return view;
} }
public LoginData getData() {
return data;
}
} }

View File

@ -5,6 +5,9 @@ import me.teridax.jcash.banking.Profile;
import java.util.Optional; import java.util.Optional;
/**
* Wrapper class for a {@link me.teridax.jcash.banking.BankingManagementSystem}
*/
public class LoginData { public class LoginData {
private final BankingManagementSystem bms; private final BankingManagementSystem bms;
@ -13,6 +16,13 @@ public class LoginData {
this.bms = bms; this.bms = bms;
} }
/**
* authenticate the specified account with the provided pin.
* @param blz the bank identifier
* @param iban the account identifier
* @param pin the pin for the account to authenticate with
* @return an optional wrapping the specified account if authentication was successful
*/
public Optional<Profile> authenticateAccount(String blz, int iban, int pin) { public Optional<Profile> authenticateAccount(String blz, int iban, int pin) {
var optionalBank = bms.getBank(blz); var optionalBank = bms.getBank(blz);
if (optionalBank.isEmpty()) if (optionalBank.isEmpty())
@ -21,4 +31,8 @@ public class LoginData {
var profile = optionalBank.get().getAccount(iban); var profile = optionalBank.get().getAccount(iban);
return profile.filter(value -> value.getPrimaryAccount().getPin() == pin); return profile.filter(value -> value.getPrimaryAccount().getPin() == pin);
} }
public BankingManagementSystem getBms() {
return bms;
}
} }

View File

@ -7,34 +7,32 @@ import java.text.NumberFormat;
public class LoginView extends JPanel { public class LoginView extends JPanel {
private final JFormattedTextField blz; private JFormattedTextField blz;
private final JFormattedTextField iban; private JFormattedTextField iban;
private final JPasswordField pin; private JPasswordField pin;
private final JButton login; private JButton login;
public LoginView() { public LoginView() {
this.blz = new JFormattedTextField("MA2424"); createComponents();
this.iban = new JFormattedTextField(getNumberFormat()); layoutComponents();
this.iban.setText("4711"); }
this.pin = new JPasswordField("1234");
this.login = new JButton("Login"); private void layoutComponents() {
var content = new JPanel(new GridBagLayout());
setLayout(new BorderLayout(16, 16));
var content = new JPanel();
this.setBorder(BorderFactory.createEmptyBorder(8,8,8,8)); this.setBorder(BorderFactory.createEmptyBorder(8,8,8,8));
add(new JScrollPane(content), BorderLayout.CENTER); this.setLayout(new BorderLayout(16, 16));
add(new JLabel("Bankautomat"), BorderLayout.NORTH); this.add(new JScrollPane(content), BorderLayout.CENTER);
this.add(new JLabel("Bankautomat"), BorderLayout.NORTH);
var layout = new GridBagLayout();
var constraints = new GridBagConstraints(); var constraints = new GridBagConstraints();
content.setLayout(layout);
constraints.gridwidth = 4; constraints.gridwidth = 4;
constraints.insets = new Insets(12,12,12,12); constraints.insets = new Insets(12,12,12,12);
addInputRow(constraints, content, blz, 1, "BLZ"); addInputRow(constraints, content, blz, 1, "BLZ");
addInputRow(constraints, content, iban, 2, "Kontonummer"); addInputRow(constraints, content, iban, 2, "IBAN");
addInputRow(constraints, content, pin, 3, "Passwort"); addInputRow(constraints, content, pin, 3, "PIN");
constraints.gridy = 4; constraints.gridy = 4;
constraints.anchor = GridBagConstraints.PAGE_END; constraints.anchor = GridBagConstraints.PAGE_END;
@ -44,6 +42,14 @@ public class LoginView extends JPanel {
content.add(login, constraints); content.add(login, constraints);
} }
private void createComponents() {
this.blz = new JFormattedTextField("MA2424");
this.iban = new JFormattedTextField(getNumberFormat());
this.iban.setText("4711");
this.pin = new JPasswordField("1234");
this.login = new JButton("Login");
}
private NumberFormatter getNumberFormat() { private NumberFormatter getNumberFormat() {
var format = NumberFormat.getIntegerInstance(); var format = NumberFormat.getIntegerInstance();
format.setGroupingUsed(false); format.setGroupingUsed(false);

View File

@ -5,7 +5,7 @@ import me.teridax.jcash.banking.Account;
public class TakeoffDialog { public class TakeoffDialog {
public TakeoffDialog(Account account, Runnable onTakeoff) { public TakeoffDialog(Account account, Runnable onTakeoff) {
var view = new TakeoffView(account); var view = new TakeoffView();
view.getTakeoff().addActionListener(e -> { view.getTakeoff().addActionListener(e -> {
account.takeoff(view.getAmount()); account.takeoff(view.getAmount());
onTakeoff.run(); onTakeoff.run();

View File

@ -1,7 +1,6 @@
package me.teridax.jcash.gui.takeoff; package me.teridax.jcash.gui.takeoff;
import me.teridax.jcash.banking.Account; import me.teridax.jcash.decode.StringDecoder;
import me.teridax.jcash.decode.StringUtils;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@ -18,8 +17,8 @@ public class TakeoffView {
private JButton takeoff; private JButton takeoff;
private JFormattedTextField value; private JFormattedTextField value;
public TakeoffView(Account account) { public TakeoffView() {
createComponents(account); createComponents();
layoutComponents(); layoutComponents();
} }
@ -81,12 +80,12 @@ public class TakeoffView {
dialog.getContentPane().add(buttonPanel, c); dialog.getContentPane().add(buttonPanel, c);
} }
private void createComponents(Account account) { private void createComponents() {
this.dialog = new JDialog(); this.dialog = new JDialog();
this.cancel = new JButton("Cancel"); this.cancel = new JButton("Cancel");
this.takeoff = new JButton("Takeoff"); this.takeoff = new JButton("Takeoff");
this.value = new JFormattedTextField(StringUtils.LOCAL_NUMBER_FORMAT); this.value = new JFormattedTextField(StringDecoder.LOCAL_NUMBER_FORMAT);
this.dialog.setContentPane(new JPanel(new GridBagLayout())); this.dialog.setContentPane(new JPanel(new GridBagLayout()));
} }

View File

@ -0,0 +1,39 @@
package me.teridax.jcash.gui.transfer;
import me.teridax.jcash.banking.BankingManagementSystem;
import me.teridax.jcash.decode.StringDecoder;
public class TransferData {
private final BankingManagementSystem bms;
public TransferData(BankingManagementSystem bms) {
this.bms = bms;
}
/**
* Transfers a certain amount of money to the specified account of the specified bank
* @param amount the amount to transfer
* @param blz the bank that manages the account
* @param ibanString the internal bank account number to transfer money to
* @throws IllegalArgumentException if the bank or the account do not exist
*/
public void transferValue(double amount, String blz, String ibanString) throws IllegalArgumentException {
var bank = bms.getBank(blz);
if (bank.isEmpty())
throw new IllegalArgumentException("Bank not found: " + blz);
var iban = 0;
try {
iban = StringDecoder.decodeUniqueIdentificationNumber(ibanString);
} catch (Exception ex) {
throw new IllegalArgumentException("IBAN has invalid format: " + ibanString);
}
var account = bank.get().getAccount(iban);
if (account.isEmpty())
throw new IllegalArgumentException("Account not found: " + iban);
account.get().getPrimaryAccount().deposit(amount);
}
}

View File

@ -1,15 +1,29 @@
package me.teridax.jcash.gui.transfer; package me.teridax.jcash.gui.transfer;
import me.teridax.jcash.banking.Account; import me.teridax.jcash.banking.Account;
import me.teridax.jcash.banking.BankingManagementSystem;
import javax.swing.*;
import static javax.swing.JOptionPane.ERROR_MESSAGE;
import static javax.swing.JOptionPane.showMessageDialog;
public class TransferDialog { public class TransferDialog {
public TransferDialog(Account account, Runnable onDeposit) { public TransferDialog(Account account, BankingManagementSystem bms, Runnable onDeposit) {
var view = new TransferView(account); var view = new TransferView();
var data = new TransferData(bms);
view.getTransfer().addActionListener(e -> { view.getTransfer().addActionListener(e -> {
account.takeoff(view.getAmount()); try {
var amount = view.getAmount();
data.transferValue(amount, view.getBlz(), view.getIban());
account.takeoff(amount);
onDeposit.run(); onDeposit.run();
view.dispose(); view.dispose();
} catch (IllegalArgumentException ex) {
showMessageDialog(null, "invalid account", "Could not transfer", ERROR_MESSAGE);
}
}); });
view.getCancel().addActionListener(e -> view.dispose()); view.getCancel().addActionListener(e -> view.dispose());
view.showDialog(); view.showDialog();

View File

@ -1,7 +1,6 @@
package me.teridax.jcash.gui.transfer; package me.teridax.jcash.gui.transfer;
import me.teridax.jcash.banking.Account; import me.teridax.jcash.decode.StringDecoder;
import me.teridax.jcash.decode.StringUtils;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@ -20,8 +19,8 @@ public class TransferView {
private JFormattedTextField blz; private JFormattedTextField blz;
private JFormattedTextField value; private JFormattedTextField value;
public TransferView(Account account) { public TransferView() {
createComponents(account); createComponents();
layoutComponents(); layoutComponents();
} }
@ -105,12 +104,12 @@ public class TransferView {
dialog.getContentPane().add(buttonPanel, c); dialog.getContentPane().add(buttonPanel, c);
} }
private void createComponents(Account account) { private void createComponents() {
this.dialog = new JDialog(); this.dialog = new JDialog();
this.cancel = new JButton("Cancel"); this.cancel = new JButton("Cancel");
this.transfer = new JButton("Transfer"); this.transfer = new JButton("Transfer");
this.value = new JFormattedTextField(StringUtils.LOCAL_NUMBER_FORMAT); this.value = new JFormattedTextField(StringDecoder.LOCAL_NUMBER_FORMAT);
this.iban = new JFormattedTextField(); this.iban = new JFormattedTextField();
this.blz = new JFormattedTextField(); this.blz = new JFormattedTextField();
@ -138,6 +137,14 @@ public class TransferView {
return transfer; return transfer;
} }
public String getIban() {
return iban.getText();
}
public String getBlz() {
return blz.getText();
}
public void dispose() { public void dispose() {
this.dialog.dispose(); this.dialog.dispose();
} }