Compare commits

..

5 Commits

Author SHA1 Message Date
Sven Vogel 571c516fc8 added information about submission grade 2023-08-07 16:12:48 +00:00
Sven Vogel 24c069b98e added support for windows-1252 2023-07-23 22:47:13 +02:00
Sven Vogel 787b806f9a added more javadoc 2023-07-23 20:15:41 +02:00
Sven Vogel 2fd1fc2b22 added some javadoc 2023-07-23 15:45:06 +02:00
Sven Vogel 88deb75ed7 added mainframe class 2023-07-23 14:56:41 +02:00
26 changed files with 482 additions and 130 deletions

View File

@ -4,6 +4,8 @@
Draft program for the Java class of semester 2. The goal was to simulate basic cash machine that can read customer data from a `.csv` file and let the user view the data with crude forms of authentication. Draft program for the Java class of semester 2. The goal was to simulate basic cash machine that can read customer data from a `.csv` file and let the user view the data with crude forms of authentication.
> This project was graded with 100,0 out of 100,0 points
## Overview ## Overview
The program can read `.csv` file from disk and allows the user login to an account specified. The program can read `.csv` file from disk and allows the user login to an account specified.

View File

@ -7,6 +7,8 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.logging.*; import java.util.logging.*;
import static java.nio.file.Path.of;
/** /**
* Utility class for providing a global logger for the entire application instance at runtime * Utility class for providing a global logger for the entire application instance at runtime
*/ */
@ -45,7 +47,7 @@ public final class Logging {
* @param level the level to set the handler to * @param level the level to set the handler to
*/ */
private static void createConsoleLogger(Level level) { private static void createConsoleLogger(Level level) {
var ch = new ConsoleHandler(); ConsoleHandler ch = new ConsoleHandler();
ch.setLevel(level); ch.setLevel(level);
LOGGER.addHandler(ch); LOGGER.addHandler(ch);
} }
@ -60,15 +62,15 @@ public final class Logging {
*/ */
private static void createFileLogger(Level level) { private static void createFileLogger(Level level) {
// setup log file name // setup log file name
var now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
var dateTime = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT).format(now); String dateTime = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT).format(now);
var logFileName = LOG_FOLDER_NAME + dateTime + ".log"; String logFileName = LOG_FOLDER_NAME + dateTime + ".log";
// setup the folder for the logs // setup the folder for the logs
initializeLogFolder(); initializeLogFolder();
try { try {
var fh = new FileHandler(logFileName); FileHandler fh = new FileHandler(logFileName);
fh.setLevel(level); fh.setLevel(level);
LOGGER.addHandler(fh); LOGGER.addHandler(fh);
} catch (Exception e) { } catch (Exception e) {
@ -81,7 +83,7 @@ public final class Logging {
* If the folder does not exist, the function will create a new folder. * If the folder does not exist, the function will create a new folder.
*/ */
private static void initializeLogFolder() { private static void initializeLogFolder() {
var folderPath = Path.of(LOG_FOLDER_NAME); Path folderPath = of(LOG_FOLDER_NAME);
if (Files.isDirectory(folderPath)) if (Files.isDirectory(folderPath))
return; return;

View File

@ -1,11 +1,9 @@
package me.teridax.jcash; package me.teridax.jcash;
import me.teridax.jcash.banking.management.BankingManagementSystem;
import me.teridax.jcash.gui.IconProvider; import me.teridax.jcash.gui.IconProvider;
import me.teridax.jcash.gui.Loader; import me.teridax.jcash.gui.Loader;
import me.teridax.jcash.gui.MainFrame;
import me.teridax.jcash.gui.Utils; import me.teridax.jcash.gui.Utils;
import me.teridax.jcash.gui.account.AccountController;
import me.teridax.jcash.gui.login.LoginController;
import me.teridax.jcash.lang.Locales; import me.teridax.jcash.lang.Locales;
import javax.swing.*; import javax.swing.*;
@ -16,82 +14,38 @@ import java.util.logging.Level;
import static me.teridax.jcash.Logging.LOGGER; import static me.teridax.jcash.Logging.LOGGER;
import static me.teridax.jcash.Logging.initializeSystemLogger; import static me.teridax.jcash.Logging.initializeSystemLogger;
import static me.teridax.jcash.lang.Translator.translate;
public final class Main { public final class Main {
private static final String LOGIN_SCREEN_STRING_IDENT = "LoginScreen";
private static final String PROFILE_SCREEN_STRING_IDENT = "ProfileScreen";
private static final String VERSION = "v2.0.0";
/** /**
* Main instance of this program. Contains the primary window. * Main instance of this program. Contains the primary window.
*/ */
private static Main instance; private static Main instance;
/** /**
* Primary window of this program * Primary class for controlling GUI of this application
*/ */
private final JFrame window; private final MainFrame window;
private final CardLayout layout;
private BankingManagementSystem bms;
private LoginController loginMask;
private AccountController accountController;
private Main() { private Main() {
// create main window and set defaults this.window = new MainFrame();
this.window = new JFrame();
this.window.setTitle(translate("Cashmachine") + getInfoString());
this.window.setLocationByPlatform(true);
this.window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.window.setIconImage(IconProvider.getWindowIcon());
this.layout = new CardLayout();
this.window.getContentPane().setLayout(this.layout);
initialize();
}
private String getInfoString() {
return " locale: [" + Locales.getDefaultLocale().toString() + "] " + VERSION;
}
private void initialize() {
// create the login mask
this.loginMask = new LoginController();
// when we have logged in set the account viewer as window content
this.loginMask.addAccountSelectionListener(account -> {
LOGGER.finer("account selected: " + Objects.toString(account, "null"));
accountController.setProfile(account, bms);
layout.show(window.getContentPane(), PROFILE_SCREEN_STRING_IDENT);
});
this.window.getContentPane().add(loginMask.getView(), LOGIN_SCREEN_STRING_IDENT);
// create the account viewer
this.accountController = new AccountController();
this.window.getContentPane().add(accountController.getView(), PROFILE_SCREEN_STRING_IDENT);
} }
/**
* Prompts the user a dialog to select a file to load the database from.
* If a valid database has been read a login screen will be shown.
* If no file was selected or the database was invalid the application will close.
*/
public void loadDatabase() { public void loadDatabase() {
try { try {
this.bms = Loader.load(); var bms = Loader.load();
this.window.setBms(bms);
} catch (Exception e) { } catch (Exception e) {
LOGGER.severe("Failed to load database: " + e.getMessage()); LOGGER.severe("Failed to load database: " + e.getMessage());
Utils.error("Failed to load database"); Utils.error("Failed to load database");
System.exit(1); System.exit(1);
} }
this.loginMask.setBankingManagementSystem(bms);
showLoginScreen();
this.window.pack();
this.window.setResizable(false);
this.window.setLocationRelativeTo(null);
this.window.setVisible(true);
} }
public static void main(String[] args) { public static void main(String[] args) {
@ -181,24 +135,16 @@ public final class Main {
Main.instance = new Main(); Main.instance = new Main();
} }
/** public JFrame getWindow() {
* Shows the open dialog for selecting a database file. After selection, it then proceeds to prompt login. return this.window.getWindow();
* 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() {
this.layout.show(this.window.getContentPane(), LOGIN_SCREEN_STRING_IDENT);
} }
/** /**
* Logs the user out of the database, hiding the main window. * Logs the user out of the currently open account.
* This will show the login mask and clear the password field or the previous
* login attempt.
*/ */
public void logout() { public void logout() {
this.loginMask.logout(); this.window.logout();
this.layout.show(this.window.getContentPane(), LOGIN_SCREEN_STRING_IDENT);
}
public JFrame getWindow() {
return this.window;
} }
} }

View File

@ -6,6 +6,8 @@ import me.teridax.jcash.banking.accounts.Owner;
import me.teridax.jcash.decode.StringDecoder; import me.teridax.jcash.decode.StringDecoder;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
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.*;
@ -23,6 +25,13 @@ public final class BankingManagementSystem {
* Separator used to separate columns of CSV files * Separator used to separate columns of CSV files
*/ */
private static final String SEPARATOR = ";"; private static final String SEPARATOR = ";";
/**
* Charsets to try when decoding the source file
*/
private static final Charset[] ENCODINGS = {
StandardCharsets.UTF_8,
Charset.forName("windows-1252")
};
/** /**
* A set of banks * A set of banks
@ -76,7 +85,7 @@ public final class BankingManagementSystem {
LOGGER.fine("parsing banking management system from file: " + Objects.toString(file, "null")); LOGGER.fine("parsing banking management system from file: " + Objects.toString(file, "null"));
try { try {
var bms = new BankingManagementSystem(); var bms = new BankingManagementSystem();
var content = Files.readString(file); var content = getSource(file);
// read line by line // read line by line
// and skip the first line // and skip the first line
@ -114,15 +123,33 @@ public final class BankingManagementSystem {
return bms; return bms;
} catch (IOException e) {
LOGGER.severe("Could not read file: " + file + " due to: " + e.getMessage());
throw new IllegalArgumentException("Could not read file " + file, e);
} catch (IllegalArgumentException | NullPointerException e) { } catch (IllegalArgumentException | NullPointerException e) {
LOGGER.severe("Could not parse file: " + file + " due to: " + e.getMessage()); LOGGER.severe("Could not parse file: " + file + " due to: " + e.getMessage());
throw new IllegalArgumentException("Could not parse file " + file, e); throw new IllegalArgumentException("Could not parse file " + file, e);
} }
} }
/**
* Attempts to read the entire file into a string.
* This method tires out all encodings in {@link #ENCODINGS}
* @param file the file to read
* @throws IllegalArgumentException if the file cannot be read
* @return the content of the file
*/
private static String getSource(Path file) throws IllegalArgumentException {
Exception lastException = null;
for (var encoding : ENCODINGS) {
try {
return Files.readString(file, encoding);
} catch (IOException e) {
LOGGER.severe("Could not read file: " + file + " due to: " + e.getMessage());
lastException = e;
}
}
assert lastException != null;
throw new IllegalArgumentException("Invalid encoding, or IO exception: " + lastException.getMessage());
}
/** /**
* Return a bank with the given blz. * Return a bank with the given blz.
* *

View File

@ -18,6 +18,10 @@ public class StringDecoder {
return NumberFormat.getInstance(Locales.getDefaultLocale()); return NumberFormat.getInstance(Locales.getDefaultLocale());
} }
/**
* Returns a NumberFormatter for parsing double values in the appropriate locale.
* @return the number formatter
*/
public static NumberFormatter getNumberFormatter(double maxValue) { public static NumberFormatter getNumberFormatter(double maxValue) {
var formatter = new NumberFormatter(); var formatter = new NumberFormatter();
formatter.setValueClass(Double.class); formatter.setValueClass(Double.class);
@ -29,7 +33,11 @@ public class StringDecoder {
return formatter; return formatter;
} }
public static Object getIntegerNumberFormatter() { /**
* Returns a NumberFormatter for parsing integer values in the appropriate locale.
* @return the number formatter
*/
public static NumberFormatter getIntegerNumberFormatter() {
var formatter = new NumberFormatter(); var formatter = new NumberFormatter();
formatter.setValueClass(Integer.class); formatter.setValueClass(Integer.class);
formatter.setMinimum(0d); formatter.setMinimum(0d);

View File

@ -7,14 +7,28 @@ import java.util.Objects;
import static me.teridax.jcash.Logging.LOGGER; import static me.teridax.jcash.Logging.LOGGER;
/**
* Static class for providing the capabilities to load images from file.
*/
public class IconProvider { public class IconProvider {
private static final Image DEFAULT_IMAGE = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB); private static final Image DEFAULT_IMAGE = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB);
/**
* Fetches the windows icon.
* @return the windows icon
*/
public static Image getWindowIcon() { public static Image getWindowIcon() {
return loadIcon("res/register.png"); return loadIcon("res/register.png");
} }
/**
* Loads the specified image from disk.
* If the file cannot be made into an image because its corrupted or the file cannot be read,
* the default image is returned {@link #DEFAULT_IMAGE}
* @param path the path to the image
* @return the specified image or {@link #DEFAULT_IMAGE}
*/
private static Image loadIcon(String path) { private static Image loadIcon(String path) {
try { try {
var is = Objects.requireNonNull(IconProvider.class.getResourceAsStream(path)); var is = Objects.requireNonNull(IconProvider.class.getResourceAsStream(path));
@ -26,6 +40,10 @@ public class IconProvider {
return DEFAULT_IMAGE; return DEFAULT_IMAGE;
} }
/**
* Fetches the background image used for the login screen
* @return login screen background image
*/
public static Image getBackground() { public static Image getBackground() {
return loadIcon("res/background.png"); return loadIcon("res/background.png");
} }

View File

@ -1,7 +1,10 @@
package me.teridax.jcash.gui; package me.teridax.jcash.gui;
import java.text.ParseException;
/**
* Exception thrown when some user input is invalid
*/
@SuppressWarnings("unused")
public class InvalidInputException extends IllegalStateException { public class InvalidInputException extends IllegalStateException {
public InvalidInputException(String message, Exception cause) { public InvalidInputException(String message, Exception cause) {

View File

@ -0,0 +1,128 @@
package me.teridax.jcash.gui;
import me.teridax.jcash.banking.management.BankingManagementSystem;
import me.teridax.jcash.gui.account.AccountController;
import me.teridax.jcash.gui.login.LoginController;
import me.teridax.jcash.lang.Locales;
import javax.swing.*;
import java.awt.*;
import java.util.Objects;
import static me.teridax.jcash.Logging.LOGGER;
import static me.teridax.jcash.lang.Translator.translate;
public class MainFrame {
/**
* Constant used to identify the login screen on the cardlayout
*/
private static final String LOGIN_SCREEN_STRING_IDENT = "LoginScreen";
/**
* Constant used to identify the profile screen on the cardlayout
*/
private static final String PROFILE_SCREEN_STRING_IDENT = "ProfileScreen";
/**
* Version of this application
*/
private static final String VERSION = "v2.1.0";
/**
* Primary window of this program
*/
private final JFrame window;
/**
* Primary layout of this application
*/
private final CardLayout layout;
/**
* Database containing every bank, account and owner available
*/
private BankingManagementSystem bms;
private LoginController loginMask;
private AccountController accountController;
public MainFrame() {
// create main window and set defaults
this.window = new JFrame();
this.window.setTitle(translate("Cashmachine") + getInfoString());
this.window.setLocationByPlatform(true);
this.window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.window.setIconImage(IconProvider.getWindowIcon());
this.layout = new CardLayout();
this.window.getContentPane().setLayout(this.layout);
initialize();
}
/**
* Creates and returns a general information string about this application
* @return the locale and the current version as string
*/
private String getInfoString() {
return " locale: [" + Locales.getDefaultLocale().toString() + "] " + VERSION;
}
/**
* Initializes the GUI components of login screen and profile view
*/
private void initialize() {
// create the login mask
this.loginMask = new LoginController();
// when we have logged in set the account viewer as window content
this.loginMask.addAccountSelectionListener(account -> {
LOGGER.finer("account selected: " + Objects.toString(account, "null"));
accountController.setProfile(account, bms);
layout.show(window.getContentPane(), PROFILE_SCREEN_STRING_IDENT);
});
this.window.getContentPane().add(loginMask.getView(), LOGIN_SCREEN_STRING_IDENT);
// create the account viewer
this.accountController = new AccountController();
this.window.getContentPane().add(accountController.getView(), PROFILE_SCREEN_STRING_IDENT);
}
/**
* Sets the BMS of this application to use for the GUI.
* This method will show the login screen to the user
* @param bms the BMS to use for the GUI
*/
public void setBms(BankingManagementSystem bms) {
this.bms = bms;
this.loginMask.setBankingManagementSystem(bms);
this.showLoginScreen();
this.window.pack();
this.window.setResizable(false);
this.window.setLocationRelativeTo(null);
this.window.setVisible(true);
}
/**
* 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.
*/
private void showLoginScreen() {
this.layout.show(this.window.getContentPane(), LOGIN_SCREEN_STRING_IDENT);
}
/**
* Logs the user out of the database, hiding the main window.
*/
public void logout() {
this.loginMask.logout();
this.layout.show(this.window.getContentPane(), LOGIN_SCREEN_STRING_IDENT);
}
public JFrame getWindow() {
return this.window;
}
}

View File

@ -10,6 +10,14 @@ import java.text.NumberFormat;
public class Utils { public class Utils {
/**
* Formats the string so that it will be displayed as a Heading 1 element by JLabels.
* This embeds the given string into two html tags.
* Note that eny html entities in the string will be formatted as valid HTML entities.
* Meaning they won't show up in as plain text.
* @param title the title to format.
* @return the given string embedded into <pre>&lt;html>&lt;h1>$string&lt;/h1>&lt;/html></pre>
*/
public static String addHeading(String title) { public static String addHeading(String title) {
return String.format("<html><h1>%s</h1></html>", title); return String.format("<html><h1>%s</h1></html>", title);
} }

View File

@ -25,6 +25,11 @@ public class AccountController {
this.data = new AccountData(); this.data = new AccountData();
} }
/**
* Sets the profile and BMS used to manage banking.
* @param profile the profile used to manage the account
* @param bms the BMS used access other banking accounts
*/
public void setProfile(Profile profile, BankingManagementSystem bms) { public void setProfile(Profile profile, BankingManagementSystem bms) {
this.profile = profile; this.profile = profile;
this.view.setProfile(profile); this.view.setProfile(profile);
@ -32,6 +37,9 @@ public class AccountController {
this.createListeners(); this.createListeners();
} }
/**
* Create listeners for GUI components
*/
private void createListeners() { private void createListeners() {
this.view.getAccountSelection().addActionListener(e -> changeAccount()); this.view.getAccountSelection().addActionListener(e -> changeAccount());
this.view.getLogout().addActionListener(e -> logout()); this.view.getLogout().addActionListener(e -> logout());
@ -40,16 +48,25 @@ public class AccountController {
this.view.getTransfer().addActionListener(e -> transferMoney()); this.view.getTransfer().addActionListener(e -> transferMoney());
} }
/**
* Open dialog to deposit money
*/
private void depositMoney() { private void depositMoney() {
new DepositController(profile.getPrimaryAccount()); new DepositController(profile.getPrimaryAccount());
this.view.updateAccountVariables(profile); this.view.updateAccountVariables(profile);
} }
/**
* Open dialog to transfer money
*/
private void transferMoney() { private void transferMoney() {
new TransferController(profile.getPrimaryAccount(), data.getBms()); new TransferController(profile.getPrimaryAccount(), data.getBms());
this.view.updateAccountVariables(profile); this.view.updateAccountVariables(profile);
} }
/**
* Open dialog to take off money
*/
private void takeoffMoney() { private void takeoffMoney() {
new TakeoffController(profile.getPrimaryAccount()); new TakeoffController(profile.getPrimaryAccount());
this.view.updateAccountVariables(profile); this.view.updateAccountVariables(profile);
@ -58,9 +75,11 @@ public class AccountController {
private void logout() { private void logout() {
Logging.LOGGER.fine("Logging out of account"); Logging.LOGGER.fine("Logging out of account");
Main.getInstance().logout(); Main.getInstance().logout();
Main.getInstance().showLoginScreen();
} }
/**
* Change the selected account.
*/
private void changeAccount() { private void changeAccount() {
var description = ((String) this.view.getAccountSelection().getSelectedItem()); var description = ((String) this.view.getAccountSelection().getSelectedItem());
Logging.LOGGER.fine("Changing primary account selected: " + description); Logging.LOGGER.fine("Changing primary account selected: " + description);

View File

@ -2,6 +2,9 @@ package me.teridax.jcash.gui.account;
import me.teridax.jcash.banking.management.BankingManagementSystem; import me.teridax.jcash.banking.management.BankingManagementSystem;
/**
* Data storage class for account management
*/
public class AccountData { public class AccountData {
private BankingManagementSystem bms; private BankingManagementSystem bms;

View File

@ -40,6 +40,10 @@ public class AccountView extends JPanel {
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
} }
/**
* The profile to manage via the GUI.
* @param profile the profile to manage
*/
public void setProfile(Profile profile) { public void setProfile(Profile profile) {
this.updateAccountVariables(profile); this.updateAccountVariables(profile);
@ -137,8 +141,15 @@ public class AccountView extends JPanel {
return takeoff; return takeoff;
} }
/**
* Writes the accessible class fields of the primary account
* into the text fields. Also updates the combo box for
* all associated accounts.
* @param profile the profile to update from
*/
public void updateAccountVariables(Profile profile) { public void updateAccountVariables(Profile profile) {
Logging.LOGGER.finer("Updating account view"); Logging.LOGGER.finer("Updating account view");
// temporarily extract data
var bank = profile.getBank(); var bank = profile.getBank();
var account = profile.getPrimaryAccount(); var account = profile.getPrimaryAccount();
var owner = profile.getOwner(); var owner = profile.getOwner();
@ -152,6 +163,8 @@ public class AccountView extends JPanel {
this.balance.setText(StringDecoder.getNumberFormat().format(account.getBalance()) + ""); this.balance.setText(StringDecoder.getNumberFormat().format(account.getBalance()) + "");
// update account type specific fields
this.type.setText(translate(account.getClass().getSimpleName())); this.type.setText(translate(account.getClass().getSimpleName()));
if (account instanceof CurrentAccount) { if (account instanceof CurrentAccount) {
this.typeSpecialLabel.setText(translate("Overdraft")); this.typeSpecialLabel.setText(translate("Overdraft"));

View File

@ -9,21 +9,30 @@ import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
import java.text.ParseException; import java.text.ParseException;
/**
* Class for controlling the deposit operation via a dialog.
*/
public class DepositController { public class DepositController {
private final DepositView view; private final DepositView view;
/**
* Account to deposit money to.
*/
private final Account account; private final Account account;
private final DepositData data;
public DepositController(Account account) { public DepositController(Account account) {
this.account = account; this.account = account;
this.data = new DepositData(account.getBalance()); this.view = new DepositView(account.getBalance());
this.view = new DepositView(this.data.getMaxValue());
this.view.getDeposit().addActionListener(e -> depositMoney()); this.view.getDeposit().addActionListener(e -> depositMoney());
this.view.getCancel().addActionListener(e -> view.dispose()); this.view.getCancel().addActionListener(e -> view.dispose());
this.view.getValue().getDocument().addDocumentListener(new DocumentListener() { this.view.getValue().getDocument().addDocumentListener(new DocumentListener() {
/**
* Validate the amount to deposit and update display
* variables.
*/
private void validateInputState() { private void validateInputState() {
var balance = account.getBalance(); var balance = account.getBalance();
try { try {
@ -55,6 +64,10 @@ public class DepositController {
this.view.showDialog(); this.view.showDialog();
} }
/**
* Deposit the last valid value to the account.
* This method may display error dialogs when no money can be deposited.
*/
private void depositMoney() { private void depositMoney() {
try { try {
var amount = view.getAmount(); var amount = view.getAmount();

View File

@ -1,14 +0,0 @@
package me.teridax.jcash.gui.deposit;
public class DepositData {
private double maxValue;
public DepositData(double maxValue) {
this.maxValue = maxValue;
}
public double getMaxValue() {
return maxValue;
}
}

View File

@ -11,12 +11,28 @@ import java.text.ParseException;
import static me.teridax.jcash.lang.Translator.translate; import static me.teridax.jcash.lang.Translator.translate;
/**
* View class for displaying a dialog prompting the user to
* enter a valid amount to deposit at their account
*/
public class DepositView { public class DepositView {
/**
* Window to use
*/
private JDialog dialog; private JDialog dialog;
private JButton cancel; private JButton cancel;
/**
* Button for applying the deposit operation
*/
private JButton deposit; private JButton deposit;
/**
* Displays the validated value to deposit
*/
private JLabel enteredValue; private JLabel enteredValue;
/**
* Displays the account balance after the deposit operation
*/
private JLabel balanceAfterDeposit; private JLabel balanceAfterDeposit;
private JFormattedTextField value; private JFormattedTextField value;
@ -119,6 +135,12 @@ public class DepositView {
this.dialog.setContentPane(new JPanel(new GridBagLayout())); this.dialog.setContentPane(new JPanel(new GridBagLayout()));
} }
/**
* Returns the amount of money that should be deposited
* This value derives from the input of the user.
* @return the value to deposit
* @throws InvalidInputException if the user entered something invalid
*/
public double getAmount() throws InvalidInputException { public double getAmount() throws InvalidInputException {
if (value.getText().isBlank()) if (value.getText().isBlank())
throw new InvalidInputException("currency value is blank or has been invalid whilst entered"); throw new InvalidInputException("currency value is blank or has been invalid whilst entered");
@ -147,6 +169,11 @@ public class DepositView {
this.dialog.dispose(); this.dialog.dispose();
} }
/**
* Sets the supplied amount to the preview GUI fields.
* @param amount the value to display for value to deposit
* @param after the value to display for balance after deposit
*/
public void setCommittedValue(double amount, double after) { public void setCommittedValue(double amount, double after) {
enteredValue.setText(StringDecoder.getNumberFormat().format(amount)); enteredValue.setText(StringDecoder.getNumberFormat().format(amount));
balanceAfterDeposit.setText(StringDecoder.getNumberFormat().format(after)); balanceAfterDeposit.setText(StringDecoder.getNumberFormat().format(after));

View File

@ -7,10 +7,6 @@ import me.teridax.jcash.gui.Utils;
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;
import static me.teridax.jcash.lang.Translator.translate;
public class LoginController { public class LoginController {
private final LoginView view; private final LoginView view;

View File

@ -46,10 +46,6 @@ public class LoginData {
return account; return account;
} }
public BankingManagementSystem getBms() {
return bms;
}
public void setBms(BankingManagementSystem bms) { public void setBms(BankingManagementSystem bms) {
this.bms = bms; this.bms = bms;
} }

View File

@ -1,6 +1,5 @@
package me.teridax.jcash.gui.login; package me.teridax.jcash.gui.login;
import me.teridax.jcash.decode.StringDecoder;
import me.teridax.jcash.gui.IconProvider; import me.teridax.jcash.gui.IconProvider;
import javax.swing.*; import javax.swing.*;
@ -11,6 +10,9 @@ import static me.teridax.jcash.gui.Utils.addGridBagRow;
import static me.teridax.jcash.gui.Utils.addHeading; import static me.teridax.jcash.gui.Utils.addHeading;
import static me.teridax.jcash.lang.Translator.translate; import static me.teridax.jcash.lang.Translator.translate;
/**
* GUI class for login into an account
*/
public class LoginView extends JPanel { public class LoginView extends JPanel {
/** /**
@ -18,6 +20,10 @@ public class LoginView extends JPanel {
* N = log10(2^32-1) = 9,632959861146281 * N = log10(2^32-1) = 9,632959861146281
*/ */
private static final int MAX_PIN_DECIMAL_DIGITS = 9; private static final int MAX_PIN_DECIMAL_DIGITS = 9;
/**
* Number of pixels the banner image should be in width
*/
public static final int BANNER_WIDTH = 400;
private JFormattedTextField blz; private JFormattedTextField blz;
private JFormattedTextField iban; private JFormattedTextField iban;
@ -37,7 +43,7 @@ public class LoginView extends JPanel {
var loginPane = new JPanel(new GridBagLayout()); var loginPane = new JPanel(new GridBagLayout());
loginPane.setOpaque(true); loginPane.setOpaque(true);
content.add(loginPane, BorderLayout.CENTER); content.add(loginPane, BorderLayout.CENTER);
content.add(Box.createHorizontalStrut(400), BorderLayout.WEST); content.add(Box.createHorizontalStrut(BANNER_WIDTH), BorderLayout.WEST);
this.setLayout(new BorderLayout(32, 32)); this.setLayout(new BorderLayout(32, 32));
this.add(new JScrollPane(content), BorderLayout.CENTER); this.add(new JScrollPane(content), BorderLayout.CENTER);
@ -66,6 +72,8 @@ public class LoginView extends JPanel {
this.pin = new JPasswordField(); this.pin = new JPasswordField();
this.login = new JButton(translate("Login")); this.login = new JButton(translate("Login"));
// customize login button
// this may not work with every swing look and feel
this.login.setFont(new Font("Circus", Font.PLAIN, 28)); this.login.setFont(new Font("Circus", Font.PLAIN, 28));
this.login.setBackground(Color.CYAN); this.login.setBackground(Color.CYAN);

View File

@ -10,22 +10,32 @@ import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
import java.text.ParseException; import java.text.ParseException;
/**
* Controller class for handling bank account take off.
*/
public class TakeoffController { public class TakeoffController {
/**
* Account to take off
*/
private final Account account; private final Account account;
/**
* GUI object
*/
private final TakeoffView view; private final TakeoffView view;
private final TakeoffData data;
public TakeoffController(Account account) { public TakeoffController(Account account) {
this.account = account; this.account = account;
// add overdraft on top of the maximum amount
// a user is allowed to take off
var overdraft = 0.0; var overdraft = 0.0;
if (account instanceof CurrentAccount) { if (account instanceof CurrentAccount) {
overdraft += ((CurrentAccount) account).getOverdraft(); overdraft += ((CurrentAccount) account).getOverdraft();
} }
this.data = new TakeoffData(account.getBalance()); TakeoffData data = new TakeoffData(account.getBalance());
this.view = new TakeoffView(this.data.getMaxValue() + overdraft); this.view = new TakeoffView(data.getMaxValue() + overdraft);
this.view.getTakeoff().addActionListener(e -> takeOff()); this.view.getTakeoff().addActionListener(e -> takeOff());
this.view.getCancel().addActionListener(e -> view.dispose()); this.view.getCancel().addActionListener(e -> view.dispose());
@ -61,6 +71,9 @@ public class TakeoffController {
this.view.showDialog(); this.view.showDialog();
} }
/**
* Attempts to take off some money from an account.
*/
private void takeOff() { private void takeOff() {
try { try {
account.takeoff(view.getAmount()); account.takeoff(view.getAmount());

View File

@ -1,7 +1,13 @@
package me.teridax.jcash.gui.takeoff; package me.teridax.jcash.gui.takeoff;
/**
* Data class for taking off value from a certain account
*/
public class TakeoffData { public class TakeoffData {
/**
* Maximum value a user is allowed to take off
*/
private final double maxValue; private final double maxValue;
public TakeoffData(double maxValue) { public TakeoffData(double maxValue) {

View File

@ -11,6 +11,9 @@ import java.text.ParseException;
import static me.teridax.jcash.lang.Translator.translate; import static me.teridax.jcash.lang.Translator.translate;
/**
* Dialog for taking off money of an account.
*/
public class TakeoffView { public class TakeoffView {
private JDialog dialog; private JDialog dialog;
@ -25,6 +28,9 @@ public class TakeoffView {
layoutComponents(); layoutComponents();
} }
/**
* Makes this dialog visible.
*/
public void showDialog() { public void showDialog() {
dialog.setIconImage(IconProvider.getWindowIcon()); dialog.setIconImage(IconProvider.getWindowIcon());
dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL); dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
@ -104,6 +110,10 @@ public class TakeoffView {
dialog.getContentPane().add(buttonPanel, c); dialog.getContentPane().add(buttonPanel, c);
} }
/**
* The createComponents function creates the components of the dialog.
* @param maxValue Set the maximum value of the jformattedtextfield
*/
private void createComponents(double maxValue) { private void createComponents(double maxValue) {
this.dialog = new JDialog(); this.dialog = new JDialog();
@ -116,6 +126,10 @@ public class TakeoffView {
this.dialog.setContentPane(new JPanel(new GridBagLayout())); this.dialog.setContentPane(new JPanel(new GridBagLayout()));
} }
/**
* The getAmount function is used to get the amount of currency that has been entered into the text field.
* @return A double value, which is the parsed amount from the text field
*/
public double getAmount() throws InvalidInputException { public double getAmount() throws InvalidInputException {
if (value.getText().isBlank()) if (value.getText().isBlank())
throw new InvalidInputException("currency value is blank or has been invalid whilst entered"); throw new InvalidInputException("currency value is blank or has been invalid whilst entered");
@ -128,6 +142,13 @@ public class TakeoffView {
} }
} }
/**
* The setCommittedValue function sets the text of the enteredValue and balanceAfterDeposit TextFields to
* a String representation of amount and after, respectively.
* @param amount Set the text of enteredvalue
* @param after Set the balance after deposit
*/
public void setCommittedValue(double amount, double after) { public void setCommittedValue(double amount, double after) {
enteredValue.setText(StringDecoder.getNumberFormat().format(amount)); enteredValue.setText(StringDecoder.getNumberFormat().format(amount));
balanceAfterDeposit.setText(StringDecoder.getNumberFormat().format(after)); balanceAfterDeposit.setText(StringDecoder.getNumberFormat().format(after));

View File

@ -33,20 +33,9 @@ public class TransferController {
this.transferData = new TransferData(bms); this.transferData = new TransferData(bms);
this.view.getTransfer().addActionListener(e -> transfer()); this.view.getTransfer().addActionListener(e -> transfer());
this.view.getCancel().addActionListener(e -> view.dispose()); 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);
}
}
// validates the users input
var validator = new DocumentListener() {
@Override @Override
public void insertUpdate(DocumentEvent documentEvent) { public void insertUpdate(DocumentEvent documentEvent) {
validateInputState(); validateInputState();
@ -61,10 +50,53 @@ public class TransferController {
public void changedUpdate(DocumentEvent documentEvent) { public void changedUpdate(DocumentEvent documentEvent) {
validateInputState(); validateInputState();
} }
}); };
this.view.getValue().getDocument().addDocumentListener(validator);
this.view.getIbanTextField().getDocument().addDocumentListener(validator);
this.view.getBlzTextField().getDocument().addDocumentListener(validator);
this.view.showDialog(); this.view.showDialog();
} }
/**
* Returns true if the target bank account is valid.
* @return true if the target bank account is valid false otherwise
*/
private boolean validateTargetAccount() {
if (transferData.validateBLZ(this.view.getBlz())) {
return transferData.validateIBAN(this.view.getBlz(), view.getIban());
}
return false;
}
/**
* Returns true if the entered value to transfer is valid.
* This method will also commit the valid value back to the text field.
* @return true if the value to transfer is valid, false otherwise
*/
private boolean validateTransferValue() {
var balance = account.getBalance();
try {
view.getValue().commitEdit();
var amount = view.getAmount();
view.setCommittedValue(amount, balance - amount);
return true;
} catch (InvalidInputException | ParseException ex) {
view.setCommittedValue(0, balance);
return false;
}
}
private void validateInputState() {
var valid = validateTargetAccount() && validateTransferValue();
view.getTransfer().setEnabled(valid);
}
/**
* Attempts to transfer the balance from one account to another
* This method will close the dialog.
*/
private void transfer() { private void transfer() {
try { try {
var amount = view.getAmount(); var amount = view.getAmount();

View File

@ -46,4 +46,32 @@ public class TransferData {
account.get().getPrimaryAccount().deposit(amount); account.get().getPrimaryAccount().deposit(amount);
} }
/**
* Validates the given BLZ. If no bank with the given BLZ can be found
* this method returns false. Otherwise, true is returned.
* @param blz the BLZ to validate
* @return true if the BLZ is valid and false otherwise
*/
public boolean validateBLZ(String blz) {
return bms.getBank(blz).isPresent();
}
/**
* Validates the given IBAN for the given BLZ. This method assumes the BLZ to be valid.
* If this is not the case, this function will throw an exception.
* Returns true if an account with the given IBAN was found for bank of the given BLZ.
* @param blz bank to search in
* @param ibanString account to search for
* @return true if the account was found false otherwise
*/
public boolean validateIBAN(String blz, String ibanString) {
var bank = bms.getBank(blz);
try {
var iban = StringDecoder.decodeUniqueIdentificationNumber(ibanString);
return bank.map(value -> value.getAccount(iban).isPresent()).orElse(false);
} catch (Exception e) {
return false;
}
}
} }

View File

@ -11,6 +11,24 @@ import java.text.ParseException;
import static me.teridax.jcash.lang.Translator.translate; import static me.teridax.jcash.lang.Translator.translate;
/**
* JDialog for displaying the GUI for a transfer dialog
* with the following crude layout:
* <pre>
BLZ IBAN
VALUE
Cancel Transfer
* </pre>
*/
public class TransferView { public class TransferView {
private JDialog dialog; private JDialog dialog;
@ -19,7 +37,6 @@ public class TransferView {
private JFormattedTextField iban; private JFormattedTextField iban;
private JFormattedTextField blz; private JFormattedTextField blz;
private JFormattedTextField value; private JFormattedTextField value;
private JLabel balanceAfterTransfer; private JLabel balanceAfterTransfer;
private JLabel enteredValue; private JLabel enteredValue;
@ -28,6 +45,9 @@ public class TransferView {
layoutComponents(); layoutComponents();
} }
/**
* Makes this dialog visible to the user
*/
public void showDialog() { public void showDialog() {
dialog.setIconImage(IconProvider.getWindowIcon()); dialog.setIconImage(IconProvider.getWindowIcon());
dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL); dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
@ -40,6 +60,9 @@ public class TransferView {
dialog.setVisible(true); dialog.setVisible(true);
} }
/**
* Layout all components of this dialog.
*/
private void layoutComponents() { private void layoutComponents() {
var c = new GridBagConstraints(); var c = new GridBagConstraints();
@ -142,6 +165,11 @@ public class TransferView {
this.dialog.setContentPane(new JPanel(new GridBagLayout())); this.dialog.setContentPane(new JPanel(new GridBagLayout()));
} }
/**
* Returns the entered amount parsed into a double value.
* @return the amount parsed into a double
* @throws InvalidInputException if the text in {@link #value} is not a valid double value.
*/
public double getAmount() throws InvalidInputException { public double getAmount() throws InvalidInputException {
if (value.getText().isBlank()) if (value.getText().isBlank())
throw new InvalidInputException("currency value is blank or has been invalid whilst entered"); throw new InvalidInputException("currency value is blank or has been invalid whilst entered");
@ -154,6 +182,11 @@ public class TransferView {
} }
} }
/**
* Sets the values to display in the dialog labels as overview information.
* @param amount the amount to transfer
* @param after balance after the transfer
*/
public void setCommittedValue(double amount, double after) { public void setCommittedValue(double amount, double after) {
enteredValue.setText(StringDecoder.getNumberFormat().format(amount)); enteredValue.setText(StringDecoder.getNumberFormat().format(amount));
balanceAfterTransfer.setText(StringDecoder.getNumberFormat().format(after)); balanceAfterTransfer.setText(StringDecoder.getNumberFormat().format(after));
@ -179,6 +212,14 @@ public class TransferView {
return blz.getText(); return blz.getText();
} }
public JFormattedTextField getIbanTextField() {
return iban;
}
public JFormattedTextField getBlzTextField() {
return blz;
}
public void dispose() { public void dispose() {
this.dialog.dispose(); this.dialog.dispose();
} }

View File

@ -10,6 +10,9 @@ import java.util.Locale;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class Locales { public class Locales {
/**
* Default locale initialized to the fallback locale used by this application.
*/
private static Locale defaultLocale = new Locale("en", "EN"); private static Locale defaultLocale = new Locale("en", "EN");
/** /**

View File

@ -6,6 +6,11 @@ import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/**
* Static translator class.
* This is a very simple translator able to translate base tokens from english into a variety of
* configured languages.
*/
public final class Translator { public final class Translator {
/** /**