Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# build output directory
target/

# IntelliJ generated files
.idea/
43 changes: 40 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,47 @@
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>ch.engenius</groupId>
<artifactId>accounts</artifactId>
<version>1.0-SNAPSHOT</version>


<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.1</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.18.0</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
66 changes: 53 additions & 13 deletions src/main/java/ch/engenius/bank/Account.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,69 @@
import java.math.BigDecimal;

public class Account {
private double money;
private BigDecimal money;

public void withdraw(double amount) {
if ((money - amount) < 0) {
throw new IllegalStateException("not enough credits on account");
/*
* Creates the account with zero credit.
*/
public Account() {
this(BigDecimal.ZERO);
}

/*
* Creates the account with given amount of credit.
*
* @param money Initial amount of credit for the account
*/
public Account(BigDecimal money) {
this.money = money;
}

/*
* Withdraws given amount of credit from the account.
*
* @param amount Amount to withdraw
*
* @throws IllegalArgumentException If given amount is bellow or equal zero
*
* @throws IllegalStateException If given amount is larger than credits on the account
*/
public void withdraw(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) < 1) {
throw new IllegalArgumentException("cannot withdraw zero or negative amount");
}
setMoney(money - amount);

synchronized (this) {
if (money.subtract(amount).compareTo(BigDecimal.ZERO) == -1) {
throw new IllegalStateException("not enough credit");
}

setMoney(money.subtract(amount));
}
}

public void deposit(double amount) {
setMoney(money + amount);
/*
* Deposits given amount of credit to the account.
*
* @param amount Amount to deposit
*
* @throws IllegalArgumentException If given amount is bellow or equal zero
*/
public void deposit(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) < 1) {
throw new IllegalArgumentException("cannot deposit zero or negative amount");
}

synchronized (this) {
setMoney(money.add(amount));
}
}

public double getMoney() {
public BigDecimal getMoney() {
return money;
}

public void setMoney(double money) {
private void setMoney(BigDecimal money) {
this.money = money;
}

public BigDecimal getMoneyAsBigDecimal() {
return BigDecimal.valueOf(money);
}
}
34 changes: 29 additions & 5 deletions src/main/java/ch/engenius/bank/Bank.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,42 @@
package ch.engenius.bank;

import java.math.BigDecimal;
import java.util.HashMap;

public class Bank {
private HashMap<Integer, Account> accounts = new HashMap<>();

public Account registerAccount(int accountNumber, int amount) {
Account account = new Account();
account.setMoney(amount);
/*
* Creates an account with specified account number and initial amount of credits.
*
* @param accountNumber Number of the account
*
* @param amount Initial credit for the account
*
* @thorws IllegalStateException If account with given number has already been registered
*/
public Account registerAccount(int accountNumber, BigDecimal amount) {
if (accounts.containsKey(accountNumber)) {
throw new IllegalStateException("account already exists");
}

Account account = new Account(amount);
accounts.put(accountNumber, account);
return account;
}

public Account getAccount( int number) {
return accounts.get(number);
/*
* Retrieve account by its number
*
* @param accountNumber Number of the account to retrieve
*
* @throws IllegalStateException If account with given number is not registered
*/
public Account getAccount(int accountNumber) {
if (!accounts.containsKey(accountNumber)) {
throw new IllegalStateException("account does not exist");
}

return accounts.get(accountNumber);
}
}
50 changes: 25 additions & 25 deletions src/main/java/ch/engenius/bank/BankRunner.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ch.engenius.bank;

import java.math.BigDecimal;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
Expand All @@ -14,58 +15,57 @@ public class BankRunner {
private final Random random = new Random(43);
private final Bank bank = new Bank();


public static void main(String[] args) {
BankRunner runner = new BankRunner();
int accounts = 100;
int defaultDeposit = 1000;
int iterations = 10000;
int defaultDeposit = 1000;
int iterations = 10000;
runner.registerAccounts(accounts, defaultDeposit);
runner.sanityCheck(accounts, accounts*defaultDeposit);
runner.sanityCheck(accounts, accounts * defaultDeposit);
runner.runBank(iterations, accounts);
runner.sanityCheck(accounts, accounts*defaultDeposit);

runner.sanityCheck(accounts, accounts * defaultDeposit);
}

private void runBank(int iterations, int maxAccount) {
for (int i =0; i< iterations; i++ ) {
executor.submit( ()-> runRandomOperation(maxAccount));
for (int i = 0; i < iterations; i++) {
executor.submit(() -> runRandomOperation(maxAccount));
}
try {
executor.shutdown();
executor.awaitTermination(100,TimeUnit.SECONDS);
executor.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (!executor.isTerminated()) {
List<Runnable> operations = executor.shutdownNow();
System.err.println("# of operations that failed to complete: " + operations.size());
}
}
}

private void runRandomOperation(int maxAccount) {
double transfer = random.nextDouble()*100.0;
BigDecimal transfer = BigDecimal.valueOf(random.nextDouble()).multiply(BigDecimal.valueOf(100));
int accountInNumber = random.nextInt(maxAccount);
int accountOutNumber = random.nextInt(maxAccount);
Account accIn =bank.getAccount(accountInNumber);
Account accOut =bank.getAccount(accountOutNumber);
accIn.deposit(transfer);
Account accIn = bank.getAccount(accountInNumber);
Account accOut = bank.getAccount(accountOutNumber);
accOut.withdraw(transfer);
accIn.deposit(transfer);
}

private void registerAccounts(int number, int defaultMoney) {
for ( int i = 0; i < number; i++) {
bank.registerAccount(i, defaultMoney);
private void registerAccounts(int number, int defaultMoney) {
for (int i = 0; i < number; i++) {
bank.registerAccount(i, BigDecimal.valueOf(defaultMoney));
}
}

private void sanityCheck( int accountMaxNumber, int totalExpectedMoney) {
BigDecimal sum = IntStream.range(0, accountMaxNumber)
.mapToObj( bank::getAccount)
.map ( Account::getMoneyAsBigDecimal)
.reduce( BigDecimal.ZERO, BigDecimal::add);
private void sanityCheck(int accountMaxNumber, int totalExpectedMoney) {
BigDecimal sum = IntStream.range(0, accountMaxNumber).mapToObj(bank::getAccount).map(Account::getMoney)
.reduce(BigDecimal.ZERO, BigDecimal::add);

if ( sum.intValue() != totalExpectedMoney) {
throw new IllegalStateException("we got "+ sum + " != " + totalExpectedMoney +" (expected)");
if (sum.intValue() != totalExpectedMoney) {
throw new IllegalStateException("we got " + sum + " != " + totalExpectedMoney + " (expected)");
}
System.out.println("sanity check OK");
}


}
80 changes: 80 additions & 0 deletions src/test/java/ch/engenius/bank/AccountTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package ch.engenius.bank;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class AccountTest {

private Account account;
private final BigDecimal initialMoney = BigDecimal.valueOf(123.456);

@BeforeEach
public void setUp() {
account = new Account(initialMoney);
}

@Test
public void shouldDepositMoney() {
final BigDecimal depositAmount = BigDecimal.valueOf(11);

account.deposit(depositAmount);

assertEquals(initialMoney.add(depositAmount), account.getMoney());
}

@Test
public void shouldWithdrawMoney() {
final BigDecimal withdrawalAmount = BigDecimal.valueOf(22);

account.withdraw(withdrawalAmount);

assertEquals(initialMoney.subtract(withdrawalAmount), account.getMoney());
}

@Test
public void shouldFailToDepositNothing() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
() -> account.deposit(BigDecimal.ZERO));
assertEquals("cannot deposit zero or negative amount", exception.getMessage());
}

@Test
public void shouldFailToDepositNegativeAmount() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
() -> account.deposit(BigDecimal.valueOf(-100)));
assertEquals("cannot deposit zero or negative amount", exception.getMessage());
}

@Test
public void shouldWithdrawAllMoney() {
account.withdraw(initialMoney);

assertEquals(BigDecimal.ZERO, account.getMoney().setScale(0));
}

@Test
public void shouldFailToWithdrawNothing() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
() -> account.withdraw(BigDecimal.ZERO));
assertEquals("cannot withdraw zero or negative amount", exception.getMessage());
}

@Test
public void shouldFailToWithdrawNegativeAmount() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
() -> account.withdraw(BigDecimal.valueOf(-100)));
assertEquals("cannot withdraw zero or negative amount", exception.getMessage());
}

@Test
public void shouldFailToWithdrawWhenMissingMoney() {
final BigDecimal withdrawalAmount = BigDecimal.valueOf(321);

IllegalStateException exception = assertThrows(IllegalStateException.class,
() -> account.withdraw(BigDecimal.valueOf(321)));
assertEquals("not enough credit", exception.getMessage());
}
}
Loading