Rename 'CT326_Programming_III' to 'CT326: Programming III'
This commit is contained in:
38
third/semester1/CT326: Programming III/assignments/assignment5/CT326-Assignment-5/.gitignore
vendored
Normal file
38
third/semester1/CT326: Programming III/assignments/assignment5/CT326-Assignment-5/.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
3
third/semester1/CT326: Programming III/assignments/assignment5/CT326-Assignment-5/.idea/.gitignore
generated
vendored
Normal file
3
third/semester1/CT326: Programming III/assignments/assignment5/CT326-Assignment-5/.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
13
third/semester1/CT326: Programming III/assignments/assignment5/CT326-Assignment-5/.idea/misc.xml
generated
Normal file
13
third/semester1/CT326: Programming III/assignments/assignment5/CT326-Assignment-5/.idea/misc.xml
generated
Normal file
@ -0,0 +1,13 @@
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>CT326-Assignment-5</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.joda</groupId>
|
||||
<artifactId>joda-money</artifactId>
|
||||
<version>1.0.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,117 @@
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
|
||||
/**
|
||||
* Account class for CT326 Assignment 5 (23/24)
|
||||
* @author Adrian Clear
|
||||
*/
|
||||
public class Account implements Serializable {
|
||||
|
||||
// Define serialVersionUID which is used during object deserialization to verify that the sender and receiver of a serialized
|
||||
// object have loaded classes for that object that are compatible - will throw InvalidClassException if not compatible
|
||||
// IDEs will often auto generate this value for you when creating new Java classes
|
||||
|
||||
private static final long serialVersionUID = 202010191519L;
|
||||
|
||||
private int accnum;
|
||||
private Money balance;
|
||||
|
||||
// Date and Time account object was first created or loaded from storage
|
||||
private LocalDateTime activated;
|
||||
|
||||
/**
|
||||
* Creates an account with a given account number and balance.
|
||||
* @param accnum the account number
|
||||
* @param balance the initial balance of the account
|
||||
* @throws NegativeBalanceException if the initial balance is negative
|
||||
*/
|
||||
public Account (int accnum, Money balance) throws NegativeBalanceException {
|
||||
setBalance(balance);
|
||||
this.accnum = accnum;
|
||||
activated = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a deposit to the account of the given amount
|
||||
* @param amount the amount to deposit
|
||||
*/
|
||||
public synchronized void makeDeposit(Money amount) throws NegativeBalanceException { // should never throw this Exception, only attempt to setBalance if amount is greater than 0
|
||||
if(amount.isGreaterThan(Money.of(CurrencyUnit.EUR, 0)) ) {
|
||||
setBalance(balance.plus(amount));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a withdrawal from the account of the given amount
|
||||
* @param amount the amount to withdraw
|
||||
* @throws InsufficientFundsException if the amount to withdraw is greater than the current balance
|
||||
*/
|
||||
public synchronized void makeWithdrawal(Money amount) throws InsufficientFundsException {
|
||||
try {
|
||||
setBalance(balance.minus(amount));
|
||||
} catch (NegativeBalanceException e) {
|
||||
throw new InsufficientFundsException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the balance of the account to the given amount.
|
||||
* @param balance the new balance of the account
|
||||
* @throws NegativeBalanceException if the balance is negative
|
||||
*/
|
||||
private synchronized void setBalance(Money balance) throws NegativeBalanceException {
|
||||
if (balance.isLessThan(Money.of(CurrencyUnit.EUR, 0))) throw new NegativeBalanceException("Negative Balance Not Allowed!");
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the balance of the account
|
||||
* @return the balance of the account
|
||||
*/
|
||||
public Money getBalance() {
|
||||
return balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account number of the account
|
||||
* @return the account number of the account
|
||||
*/
|
||||
public int getAccountNumber() {
|
||||
return accnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the activation date of the account
|
||||
* @return the activation date of the account
|
||||
*/
|
||||
public LocalDateTime getActivated() {
|
||||
return activated;
|
||||
}
|
||||
|
||||
// This method is called when we are deserializing an instance of an Account object
|
||||
private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException,IOException {
|
||||
balance = (Money) aInputStream.readObject();
|
||||
accnum = aInputStream.readInt();
|
||||
// Reinitialise value for the LocalDateTime activated variable
|
||||
activated = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// This method is called when we serialize an instance of an Account object
|
||||
private void writeObject(ObjectOutputStream aOutputStream) throws IOException {
|
||||
aOutputStream.writeObject(balance);
|
||||
aOutputStream.writeInt(accnum);
|
||||
// We don't write value for the LocalDateTime activated variable as we will reinitialise this when reloading the object
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Account %d has a balance of %s.", accnum, balance);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
// Name: Andrew Hayes
|
||||
// ID: 21321503
|
||||
import org.joda.money.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* Class to represent a Bank and its Accounts
|
||||
* @author andrew
|
||||
*/
|
||||
public class Bank {
|
||||
private static Map<Integer, Account> accounts = new HashMap<>(); // key is the account's number
|
||||
private LinkedBlockingQueue<Transaction> transactions = new LinkedBlockingQueue<>();
|
||||
|
||||
/**
|
||||
* method to add an Account to the Bank
|
||||
* @param account the Account object to be added to the Bank
|
||||
*/
|
||||
public void addAccount(Account account) {
|
||||
accounts.put(account.getAccountNumber(), account);
|
||||
}
|
||||
|
||||
/**
|
||||
* method to return the Account identified by the supplied account number
|
||||
* @param accountNumber the number of the account to be returned
|
||||
* @return the account with the supplied accountNumber
|
||||
*/
|
||||
public Account getAccountByNumber(int accountNumber) {
|
||||
return accounts.get(accountNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* method to submit a Transaction to the queue to be processed
|
||||
* @param transaction the Transaction to be submitted to the queue
|
||||
*/
|
||||
public void submitTransaction(Transaction transaction) {
|
||||
try {
|
||||
transactions.put(transaction);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt(); // restore interrupted status
|
||||
e.printStackTrace(); // if a transaction fails to be submitted, fail gracefully so that the transaction can be tried again later
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* method to get the next Transaction from the queue for processing
|
||||
* @return the next Transaction in the queue
|
||||
*/
|
||||
public Transaction getNextTransaction() throws InterruptedException {
|
||||
// retrieve the head of the queue, waiting up to 5 seconds for an element to become available, otherwise returning null
|
||||
return transactions.poll(5, TimeUnit.SECONDS); // not catching InterruptedExceptions here, should be caught in the calling method
|
||||
}
|
||||
|
||||
/**
|
||||
* method to print out the details of each account in the bank (account number and balance)
|
||||
*/
|
||||
public static void printAccountsDetails() {
|
||||
for (Account account : accounts.values()) {
|
||||
System.out.println(account); // using given toString() method of the Account class
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* method to return a collection of all the Account numbers in the Bank
|
||||
* @return a Set of all the Integer account numbers in the Bank
|
||||
*/
|
||||
public ArrayList<Integer> getAccountNumbers() {
|
||||
return new ArrayList<>(accounts.keySet());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* main method of the Bank class
|
||||
* @param args No arguments used
|
||||
*/
|
||||
public static void main(String[] args) throws NegativeBalanceException {
|
||||
// declare and instantiate a bank object
|
||||
Bank bank = new Bank();
|
||||
|
||||
// create and add three account instances to the bank with different starting balances
|
||||
bank.addAccount(new Account(1, Money.of(CurrencyUnit.EUR, 20_000.00)));
|
||||
bank.addAccount(new Account(2, Money.of(CurrencyUnit.EUR, 25_000.00)));
|
||||
bank.addAccount(new Account(3, Money.of(CurrencyUnit.EUR, 33_333.33)));
|
||||
|
||||
// declare and instantiate two TransactionProcessors
|
||||
TransactionProcessor TP1 = new TransactionProcessor("TP1", bank);
|
||||
TransactionProcessor TP2 = new TransactionProcessor("TP2", bank);
|
||||
|
||||
// declare and instantiate one RandomTransactionGenerator
|
||||
RandomTransactionGenerator rtg = new RandomTransactionGenerator(bank);
|
||||
|
||||
// execute the threads using a thread pool
|
||||
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(4);
|
||||
executorService.execute(rtg);
|
||||
executorService.execute(TP1);
|
||||
executorService.execute(TP2);
|
||||
|
||||
// shut down the transaction generator after 10 seconds
|
||||
executorService.schedule(
|
||||
() -> {
|
||||
rtg.stopGenerating();
|
||||
executorService.shutdown();
|
||||
}, 10, TimeUnit.SECONDS
|
||||
);
|
||||
|
||||
// waiting until all threads have finished to print account details
|
||||
try {
|
||||
if (executorService.awaitTermination(10, TimeUnit.MINUTES)) { // giving some arbitrarily long timeout to wait for threads to finish
|
||||
printAccountsDetails();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// just printing stacktrace if exception thrown, no handling to be done
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* An exception representing the fact that an account has insufficient funds
|
||||
* for a given transaction.
|
||||
* @author Adrian Clear
|
||||
*/
|
||||
public class InsufficientFundsException extends Exception {
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* An exception representing an instance where a negative balance is set to an account
|
||||
* @author Adrian Clear
|
||||
*/
|
||||
public class NegativeBalanceException extends Exception {
|
||||
public NegativeBalanceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
// Name: Andrew Hayes
|
||||
// ID: 21321503
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Thread to randomly generate deposit and withdrawal transactions for random accounts
|
||||
* @author andrew
|
||||
*/
|
||||
public class RandomTransactionGenerator extends Thread {
|
||||
private Bank bank;
|
||||
private ArrayList<Integer> accountNumbers;
|
||||
public static final Transaction poisonPill = new Transaction(-1, 0);
|
||||
private boolean keepGenerating = true; // flag used to stop the thread
|
||||
|
||||
/**
|
||||
* Constructor for RandomTransactionGenerator objects
|
||||
* @param bank the Bank object with which the RandomTransactionGenerator is associated
|
||||
*/
|
||||
public RandomTransactionGenerator(Bank bank) {
|
||||
this.bank = bank;
|
||||
accountNumbers = bank.getAccountNumbers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread's run() method
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
// loop until stopped
|
||||
while (keepGenerating) {
|
||||
// sleep for a random amount of time between 0 and 1 seconds
|
||||
try {
|
||||
sleep((long) (Math.random() * 1000));
|
||||
} catch (InterruptedException e) {
|
||||
// if thread is interrupted, just break out of loop
|
||||
break;
|
||||
}
|
||||
|
||||
// pick account and transaction amount randomly
|
||||
int accountNumber = accountNumbers.get((int) (Math.random() * accountNumbers.size()));
|
||||
float amount = (float) ((Math.random() * 20_000) - 10_000);
|
||||
|
||||
// submit random transaction
|
||||
bank.submitTransaction(new Transaction(accountNumber, amount));
|
||||
}
|
||||
|
||||
// submit poison pill before exiting
|
||||
bank.submitTransaction(poisonPill);
|
||||
System.out.println("Transaction generator terminated");
|
||||
}
|
||||
|
||||
/**
|
||||
* method to stop the thread
|
||||
*/
|
||||
public void stopGenerating() {
|
||||
keepGenerating = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* A class representing an account transaction for the CT326 Assignment 5 (23/24)
|
||||
* @author Adrian Clear
|
||||
*/
|
||||
public class Transaction {
|
||||
private float amount;
|
||||
private int accountNumber;
|
||||
|
||||
/**
|
||||
* Create a transaction for the Account with the given account number, of the given amount.
|
||||
* @param accNumber the account number of the transaction account
|
||||
* @param amount the amount to withdraw/deposit. A positive value represents a deposit, a negative value represents a withdrawal
|
||||
*/
|
||||
public Transaction(int accNumber, float amount) {
|
||||
this.accountNumber = accNumber;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of this transaction
|
||||
* @return the amount of the transaction
|
||||
*/
|
||||
public float getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the account number of the transaction
|
||||
* @return the account number of the transaction
|
||||
*/
|
||||
public int getAccountNumber() {
|
||||
return accountNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if(amount >= 0)
|
||||
return String.format("a deposit of %f to %d", amount, accountNumber);
|
||||
else
|
||||
return String.format("a withdrawal of %f from %d", amount, accountNumber);
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
// Name: Andrew Hayes
|
||||
// ID: 21321503
|
||||
import org.joda.money.Money;
|
||||
|
||||
/**
|
||||
* Thread class to process Transactions of a Bank
|
||||
* @author andrew
|
||||
*/
|
||||
public class TransactionProcessor extends Thread {
|
||||
private String name;
|
||||
private Bank bank;
|
||||
|
||||
// explicitly setting these values to 0 for readability, although they would default to 0 regardless
|
||||
// variables to keep a tally of how many withdrawals and deposits the object has made
|
||||
private int numWithdrawals = 0;
|
||||
private int numDeposits = 0;
|
||||
|
||||
/**
|
||||
* TransactionProcessor Constructor
|
||||
* @param name
|
||||
* @param bank
|
||||
*/
|
||||
public TransactionProcessor(String name, Bank bank) {
|
||||
this.name = name;
|
||||
this.bank = bank;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread's run() method
|
||||
*/
|
||||
public void run() {
|
||||
// loop infinitely
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
// sleep for a random amount of time between 0 and 1 seconds before processing the next transaction
|
||||
sleep((long) (Math.random() * 1000));
|
||||
|
||||
// get transaction to process
|
||||
Transaction transaction = bank.getNextTransaction();
|
||||
|
||||
// finish executing if the thread has been closed (poison pill received) or 5 seconds has elapsed without a new transaction being received
|
||||
// if the transaction is `null` this is because bank.getNextTransaction() waited for 5 seconds without receiving a new transaction
|
||||
if (transaction == null || transaction.equals(RandomTransactionGenerator.poisonPill)) {
|
||||
break;
|
||||
}
|
||||
// otherwise, process the transaction
|
||||
else {
|
||||
Account account = bank.getAccountByNumber(transaction.getAccountNumber());
|
||||
float amount = transaction.getAmount();
|
||||
|
||||
// if the amount is positive, then it is a deposit
|
||||
// made the design choice to treat a transaction of amount 0 as a deposit because it still needs to be processed and 0 is a positive number
|
||||
if (amount >= 0) {
|
||||
System.out.printf("%s is processing a deposit of EUR %.2f to %d\n", name, amount, transaction.getAccountNumber());
|
||||
// only dealing in euros as specified
|
||||
account.makeDeposit(Money.parse(String.format("EUR %.2f", amount))); // ignoring decimals after the second place
|
||||
numDeposits++;
|
||||
}
|
||||
// if the amount is negative, then it is a deposit
|
||||
else {
|
||||
try {
|
||||
System.out.printf("%s is processing a withdrawal of EUR %.2f from %d\n", name, amount, transaction.getAccountNumber());
|
||||
account.makeWithdrawal(Money.parse(String.format("EUR %.2f", -amount)));
|
||||
|
||||
// if the above line fails due to an exception, the control flow will jump to the catch block and the following line will not be executed, and the failed withdrawal will not be counted
|
||||
numWithdrawals++;
|
||||
}
|
||||
catch (InsufficientFundsException e) { // abort transaction if funds are insufficient
|
||||
System.out.println("Transaction could not be processed due to insufficient funds");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
// if the thread has been interrupted, just exit loop and return
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// print the TransactionProcessor's name and number of deposits and withdrawals processed
|
||||
System.out.printf("%s finished processing %d transactions, including %d deposits, and %d withdrawals\n", name, numWithdrawals + numDeposits, numDeposits, numWithdrawals);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user