diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8acb57b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+# build output directory
+target/
+
+# IntelliJ generated files
+.idea/
diff --git a/pom.xml b/pom.xml
index 2ca15ec..5344dd2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -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">
4.0.0
-
ch.engenius
accounts
1.0-SNAPSHOT
-
-
+
+ 11
+ 11
+
+
+
+
+ maven-surefire-plugin
+ 2.19.1
+
+
+ org.junit.platform
+ junit-platform-surefire-provider
+ 1.0.3
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.8.1
+
+
+
+
+ net.revelc.code.formatter
+ formatter-maven-plugin
+ 2.18.0
+
+ UTF-8
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.8.1
+ test
+
+
\ No newline at end of file
diff --git a/src/main/java/ch/engenius/bank/Account.java b/src/main/java/ch/engenius/bank/Account.java
index b9979cb..74549f6 100644
--- a/src/main/java/ch/engenius/bank/Account.java
+++ b/src/main/java/ch/engenius/bank/Account.java
@@ -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);
- }
}
diff --git a/src/main/java/ch/engenius/bank/Bank.java b/src/main/java/ch/engenius/bank/Bank.java
index 571ebc7..2eccf04 100644
--- a/src/main/java/ch/engenius/bank/Bank.java
+++ b/src/main/java/ch/engenius/bank/Bank.java
@@ -1,18 +1,42 @@
package ch.engenius.bank;
+import java.math.BigDecimal;
import java.util.HashMap;
public class Bank {
private HashMap 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);
}
}
diff --git a/src/main/java/ch/engenius/bank/BankRunner.java b/src/main/java/ch/engenius/bank/BankRunner.java
index 10b30fd..093e857 100644
--- a/src/main/java/ch/engenius/bank/BankRunner.java
+++ b/src/main/java/ch/engenius/bank/BankRunner.java
@@ -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;
@@ -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 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");
}
-
-
}
diff --git a/src/test/java/ch/engenius/bank/AccountTest.java b/src/test/java/ch/engenius/bank/AccountTest.java
new file mode 100644
index 0000000..86d0c80
--- /dev/null
+++ b/src/test/java/ch/engenius/bank/AccountTest.java
@@ -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());
+ }
+}
diff --git a/src/test/java/ch/engenius/bank/BankTest.java b/src/test/java/ch/engenius/bank/BankTest.java
new file mode 100644
index 0000000..d2d8c7d
--- /dev/null
+++ b/src/test/java/ch/engenius/bank/BankTest.java
@@ -0,0 +1,48 @@
+package ch.engenius.bank;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import java.math.BigDecimal;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class BankTest {
+
+ private static Bank bank;
+ private final int accountNumber = 3;
+ private final BigDecimal amount = BigDecimal.valueOf(123.456);
+
+ @BeforeAll
+ public static void setUp() {
+ bank = new Bank();
+ }
+
+ @Test
+ @Order(1)
+ public void shouldRegisterAccount() {
+ bank.registerAccount(accountNumber, amount);
+
+ Account account = bank.getAccount(accountNumber);
+ assertNotNull(account);
+ }
+
+ @Test
+ public void shouldFailToRegisterAccountIfAlreadyExists() {
+ IllegalStateException exception = assertThrows(IllegalStateException.class,
+ () -> bank.registerAccount(accountNumber, BigDecimal.ZERO));
+ assertEquals("account already exists", exception.getMessage());
+ }
+
+ @Test
+ public void shouldRegisterAccountWithEnoughMoney() {
+ Account account = bank.getAccount(accountNumber);
+
+ assertEquals(amount, account.getMoney());
+ }
+
+ @Test
+ public void shouldFailToGetNonExistingAccount() {
+ IllegalStateException exception = assertThrows(IllegalStateException.class, () -> bank.getAccount(550));
+ assertEquals("account does not exist", exception.getMessage());
+ }
+}