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.
> This project was graded with 100,0 out of 100,0 points
## Overview
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.util.logging.*;
import static java.nio.file.Path.of;
/**
* 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
*/
private static void createConsoleLogger(Level level) {
var ch = new ConsoleHandler();
ConsoleHandler ch = new ConsoleHandler();
ch.setLevel(level);
LOGGER.addHandler(ch);
}
@ -60,15 +62,15 @@ public final class Logging {
*/
private static void createFileLogger(Level level) {
// setup log file name
var now = LocalDateTime.now();
var dateTime = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT).format(now);
var logFileName = LOG_FOLDER_NAME + dateTime + ".log";
LocalDateTime now = LocalDateTime.now();
String dateTime = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT).format(now);
String logFileName = LOG_FOLDER_NAME + dateTime + ".log";
// setup the folder for the logs
initializeLogFolder();
try {
var fh = new FileHandler(logFileName);
FileHandler fh = new FileHandler(logFileName);
fh.setLevel(level);
LOGGER.addHandler(fh);
} catch (Exception e) {
@ -81,7 +83,7 @@ public final class Logging {
* If the folder does not exist, the function will create a new folder.
*/
private static void initializeLogFolder() {
var folderPath = Path.of(LOG_FOLDER_NAME);
Path folderPath = of(LOG_FOLDER_NAME);
if (Files.isDirectory(folderPath))
return;

View File

@ -1,11 +1,9 @@
package me.teridax.jcash;
import me.teridax.jcash.banking.management.BankingManagementSystem;
import me.teridax.jcash.gui.IconProvider;
import me.teridax.jcash.gui.Loader;
import me.teridax.jcash.gui.MainFrame;
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 javax.swing.*;
@ -16,82 +14,38 @@ import java.util.logging.Level;
import static me.teridax.jcash.Logging.LOGGER;
import static me.teridax.jcash.Logging.initializeSystemLogger;
import static me.teridax.jcash.lang.Translator.translate;
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.
*/
private static Main instance;
/**
* Primary window of this program
* Primary class for controlling GUI of this application
*/
private final JFrame window;
private final CardLayout layout;
private BankingManagementSystem bms;
private LoginController loginMask;
private AccountController accountController;
private final MainFrame window;
private Main() {
// 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();
}
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);
this.window = new MainFrame();
}
/**
* 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() {
try {
this.bms = Loader.load();
var bms = Loader.load();
this.window.setBms(bms);
} catch (Exception e) {
LOGGER.severe("Failed to load database: " + e.getMessage());
Utils.error("Failed to load database");
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) {
@ -181,24 +135,16 @@ public final class Main {
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() {
this.layout.show(this.window.getContentPane(), LOGIN_SCREEN_STRING_IDENT);
public JFrame getWindow() {
return this.window.getWindow();
}
/**
* 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() {
this.loginMask.logout();
this.layout.show(this.window.getContentPane(), LOGIN_SCREEN_STRING_IDENT);
}
public JFrame getWindow() {
return this.window;
this.window.logout();
}
}

View File

@ -6,6 +6,8 @@ import me.teridax.jcash.banking.accounts.Owner;
import me.teridax.jcash.decode.StringDecoder;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
@ -23,6 +25,13 @@ public final class BankingManagementSystem {
* Separator used to separate columns of CSV files
*/
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
@ -76,7 +85,7 @@ public final class BankingManagementSystem {
LOGGER.fine("parsing banking management system from file: " + Objects.toString(file, "null"));
try {
var bms = new BankingManagementSystem();
var content = Files.readString(file);
var content = getSource(file);
// read line by line
// and skip the first line
@ -114,15 +123,33 @@ public final class BankingManagementSystem {
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) {
LOGGER.severe("Could not parse file: " + file + " due to: " + e.getMessage());
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.
*

View File

@ -18,6 +18,10 @@ public class StringDecoder {
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) {
var formatter = new NumberFormatter();
formatter.setValueClass(Double.class);
@ -29,7 +33,11 @@ public class StringDecoder {
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();
formatter.setValueClass(Integer.class);
formatter.setMinimum(0d);

View File

@ -7,14 +7,28 @@ import java.util.Objects;
import static me.teridax.jcash.Logging.LOGGER;
/**
* Static class for providing the capabilities to load images from file.
*/
public class IconProvider {
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() {
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) {
try {
var is = Objects.requireNonNull(IconProvider.class.getResourceAsStream(path));
@ -26,6 +40,10 @@ public class IconProvider {
return DEFAULT_IMAGE;
}
/**
* Fetches the background image used for the login screen
* @return login screen background image
*/
public static Image getBackground() {
return loadIcon("res/background.png");
}

View File

@ -1,7 +1,10 @@
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 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 {
/**
* 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) {
return String.format("<html><h1>%s</h1></html>", title);
}

View File

@ -25,6 +25,11 @@ public class AccountController {
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) {
this.profile = profile;
this.view.setProfile(profile);
@ -32,6 +37,9 @@ public class AccountController {
this.createListeners();
}
/**
* Create listeners for GUI components
*/
private void createListeners() {
this.view.getAccountSelection().addActionListener(e -> changeAccount());
this.view.getLogout().addActionListener(e -> logout());
@ -40,16 +48,25 @@ public class AccountController {
this.view.getTransfer().addActionListener(e -> transferMoney());
}
/**
* Open dialog to deposit money
*/
private void depositMoney() {
new DepositController(profile.getPrimaryAccount());
this.view.updateAccountVariables(profile);
}
/**
* Open dialog to transfer money
*/
private void transferMoney() {
new TransferController(profile.getPrimaryAccount(), data.getBms());
this.view.updateAccountVariables(profile);
}
/**
* Open dialog to take off money
*/
private void takeoffMoney() {
new TakeoffController(profile.getPrimaryAccount());
this.view.updateAccountVariables(profile);
@ -58,9 +75,11 @@ public class AccountController {
private void logout() {
Logging.LOGGER.fine("Logging out of account");
Main.getInstance().logout();
Main.getInstance().showLoginScreen();
}
/**
* Change the selected account.
*/
private void changeAccount() {
var description = ((String) this.view.getAccountSelection().getSelectedItem());
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;
/**
* Data storage class for account management
*/
public class AccountData {
private BankingManagementSystem bms;

View File

@ -40,6 +40,10 @@ public class AccountView extends JPanel {
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) {
this.updateAccountVariables(profile);
@ -137,8 +141,15 @@ public class AccountView extends JPanel {
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) {
Logging.LOGGER.finer("Updating account view");
// temporarily extract data
var bank = profile.getBank();
var account = profile.getPrimaryAccount();
var owner = profile.getOwner();
@ -152,6 +163,8 @@ public class AccountView extends JPanel {
this.balance.setText(StringDecoder.getNumberFormat().format(account.getBalance()) + "");
// update account type specific fields
this.type.setText(translate(account.getClass().getSimpleName()));
if (account instanceof CurrentAccount) {
this.typeSpecialLabel.setText(translate("Overdraft"));

View File

@ -9,21 +9,30 @@ import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.text.ParseException;
/**
* Class for controlling the deposit operation via a dialog.
*/
public class DepositController {
private final DepositView view;
/**
* Account to deposit money to.
*/
private final Account account;
private final DepositData data;
public DepositController(Account account) {
this.account = account;
this.data = new DepositData(account.getBalance());
this.view = new DepositView(this.data.getMaxValue());
this.view = new DepositView(account.getBalance());
this.view.getDeposit().addActionListener(e -> depositMoney());
this.view.getCancel().addActionListener(e -> view.dispose());
this.view.getValue().getDocument().addDocumentListener(new DocumentListener() {
/**
* Validate the amount to deposit and update display
* variables.
*/
private void validateInputState() {
var balance = account.getBalance();
try {
@ -55,6 +64,10 @@ public class DepositController {
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() {
try {
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;
/**
* View class for displaying a dialog prompting the user to
* enter a valid amount to deposit at their account
*/
public class DepositView {
/**
* Window to use
*/
private JDialog dialog;
private JButton cancel;
/**
* Button for applying the deposit operation
*/
private JButton deposit;
/**
* Displays the validated value to deposit
*/
private JLabel enteredValue;
/**
* Displays the account balance after the deposit operation
*/
private JLabel balanceAfterDeposit;
private JFormattedTextField value;
@ -119,6 +135,12 @@ public class DepositView {
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 {
if (value.getText().isBlank())
throw new InvalidInputException("currency value is blank or has been invalid whilst entered");
@ -147,6 +169,11 @@ public class DepositView {
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) {
enteredValue.setText(StringDecoder.getNumberFormat().format(amount));
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.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 {
private final LoginView view;

View File

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

View File

@ -1,6 +1,5 @@
package me.teridax.jcash.gui.login;
import me.teridax.jcash.decode.StringDecoder;
import me.teridax.jcash.gui.IconProvider;
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.lang.Translator.translate;
/**
* GUI class for login into an account
*/
public class LoginView extends JPanel {
/**
@ -18,6 +20,10 @@ public class LoginView extends JPanel {
* N = log10(2^32-1) = 9,632959861146281
*/
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 iban;
@ -37,7 +43,7 @@ public class LoginView extends JPanel {
var loginPane = new JPanel(new GridBagLayout());
loginPane.setOpaque(true);
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.add(new JScrollPane(content), BorderLayout.CENTER);
@ -66,6 +72,8 @@ public class LoginView extends JPanel {
this.pin = new JPasswordField();
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.setBackground(Color.CYAN);

View File

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

View File

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

View File

@ -11,6 +11,9 @@ import java.text.ParseException;
import static me.teridax.jcash.lang.Translator.translate;
/**
* Dialog for taking off money of an account.
*/
public class TakeoffView {
private JDialog dialog;
@ -25,6 +28,9 @@ public class TakeoffView {
layoutComponents();
}
/**
* Makes this dialog visible.
*/
public void showDialog() {
dialog.setIconImage(IconProvider.getWindowIcon());
dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
@ -104,6 +110,10 @@ public class TakeoffView {
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) {
this.dialog = new JDialog();
@ -116,6 +126,10 @@ public class TakeoffView {
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 {
if (value.getText().isBlank())
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) {
enteredValue.setText(StringDecoder.getNumberFormat().format(amount));
balanceAfterDeposit.setText(StringDecoder.getNumberFormat().format(after));

View File

@ -33,20 +33,9 @@ public class TransferController {
this.transferData = new TransferData(bms);
this.view.getTransfer().addActionListener(e -> transfer());
this.view.getCancel().addActionListener(e -> view.dispose());
this.view.getValue().getDocument().addDocumentListener(new DocumentListener() {
private void validateInputState() {
var balance = account.getBalance();
try {
view.getValue().commitEdit();
var amount = view.getAmount();
view.setCommittedValue(amount, balance - amount);
view.getTransfer().setEnabled(true);
} catch (InvalidInputException | ParseException ex) {
view.setCommittedValue(0, balance);
view.getTransfer().setEnabled(false);
}
}
// validates the users input
var validator = new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent documentEvent) {
validateInputState();
@ -61,10 +50,53 @@ public class TransferController {
public void changedUpdate(DocumentEvent documentEvent) {
validateInputState();
}
});
};
this.view.getValue().getDocument().addDocumentListener(validator);
this.view.getIbanTextField().getDocument().addDocumentListener(validator);
this.view.getBlzTextField().getDocument().addDocumentListener(validator);
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() {
try {
var amount = view.getAmount();

View File

@ -46,4 +46,32 @@ public class TransferData {
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;
/**
* JDialog for displaying the GUI for a transfer dialog
* with the following crude layout:
* <pre>
BLZ IBAN
VALUE
Cancel Transfer
* </pre>
*/
public class TransferView {
private JDialog dialog;
@ -19,7 +37,6 @@ public class TransferView {
private JFormattedTextField iban;
private JFormattedTextField blz;
private JFormattedTextField value;
private JLabel balanceAfterTransfer;
private JLabel enteredValue;
@ -28,6 +45,9 @@ public class TransferView {
layoutComponents();
}
/**
* Makes this dialog visible to the user
*/
public void showDialog() {
dialog.setIconImage(IconProvider.getWindowIcon());
dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
@ -40,6 +60,9 @@ public class TransferView {
dialog.setVisible(true);
}
/**
* Layout all components of this dialog.
*/
private void layoutComponents() {
var c = new GridBagConstraints();
@ -142,6 +165,11 @@ public class TransferView {
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 {
if (value.getText().isBlank())
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) {
enteredValue.setText(StringDecoder.getNumberFormat().format(amount));
balanceAfterTransfer.setText(StringDecoder.getNumberFormat().format(after));
@ -179,6 +212,14 @@ public class TransferView {
return blz.getText();
}
public JFormattedTextField getIbanTextField() {
return iban;
}
public JFormattedTextField getBlzTextField() {
return blz;
}
public void dispose() {
this.dialog.dispose();
}

View File

@ -10,6 +10,9 @@ import java.util.Locale;
@SuppressWarnings("unused")
public class Locales {
/**
* Default locale initialized to the fallback locale used by this application.
*/
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.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 {
/**