From 2181356d1f19f74e7cbe707df299465dd828a21f Mon Sep 17 00:00:00 2001 From: Milos Petrusic Date: Thu, 14 Apr 2022 15:40:47 +0200 Subject: [PATCH 1/3] Not bad bank --- .gitignore | 5 ++ pom.xml | 32 ++++++++ src/main/java/ch/engenius/bank/Account.java | 42 +++++++--- src/main/java/ch/engenius/bank/Bank.java | 18 +++-- .../java/ch/engenius/bank/BankRunner.java | 17 ++-- .../java/ch/engenius/bank/AccountTest.java | 80 +++++++++++++++++++ src/test/java/ch/engenius/bank/BankTest.java | 49 ++++++++++++ 7 files changed, 220 insertions(+), 23 deletions(-) create mode 100644 .gitignore create mode 100644 src/test/java/ch/engenius/bank/AccountTest.java create mode 100644 src/test/java/ch/engenius/bank/BankTest.java 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..2ff7b9e 100644 --- a/pom.xml +++ b/pom.xml @@ -7,6 +7,38 @@ 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 + + + + + + + + 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..42e5ede 100644 --- a/src/main/java/ch/engenius/bank/Account.java +++ b/src/main/java/ch/engenius/bank/Account.java @@ -3,29 +3,45 @@ 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"); + public Account() { + this(BigDecimal.ZERO); + } + + public Account(BigDecimal money) { + this.money = money; + } + + 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); + 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..adc5e72 100644 --- a/src/main/java/ch/engenius/bank/Bank.java +++ b/src/main/java/ch/engenius/bank/Bank.java @@ -1,18 +1,26 @@ 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); + 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); + 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..b8115c8 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; @@ -33,32 +34,38 @@ private void runBank(int iterations, int 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); accOut.withdraw(transfer); + accIn.deposit(transfer); } private void registerAccounts(int number, int defaultMoney) { for ( int i = 0; i < number; i++) { - bank.registerAccount(i, defaultMoney); + 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) + .map ( Account::getMoney) .reduce( BigDecimal.ZERO, BigDecimal::add); if ( sum.intValue() != totalExpectedMoney) { 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..de4d1b6 --- /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..0a5508e --- /dev/null +++ b/src/test/java/ch/engenius/bank/BankTest.java @@ -0,0 +1,49 @@ +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()); + } +} From 88918700543dc709d5f86f2e3e0e2b6a10c47cec Mon Sep 17 00:00:00 2001 From: Milos Petrusic Date: Thu, 14 Apr 2022 15:41:36 +0200 Subject: [PATCH 2/3] Add docs --- src/main/java/ch/engenius/bank/Account.java | 21 +++++++++++++++++++++ src/main/java/ch/engenius/bank/Bank.java | 13 +++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/main/java/ch/engenius/bank/Account.java b/src/main/java/ch/engenius/bank/Account.java index 42e5ede..49425ce 100644 --- a/src/main/java/ch/engenius/bank/Account.java +++ b/src/main/java/ch/engenius/bank/Account.java @@ -5,14 +5,29 @@ public class Account { private BigDecimal money; + /* + * 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"); @@ -27,6 +42,12 @@ public void withdraw(BigDecimal 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"); diff --git a/src/main/java/ch/engenius/bank/Bank.java b/src/main/java/ch/engenius/bank/Bank.java index adc5e72..24a8c7a 100644 --- a/src/main/java/ch/engenius/bank/Bank.java +++ b/src/main/java/ch/engenius/bank/Bank.java @@ -6,6 +6,13 @@ public class Bank { private HashMap accounts = new HashMap<>(); + /* + * 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"); @@ -16,6 +23,12 @@ public Account registerAccount(int accountNumber, BigDecimal amount) { return account; } + /* + * 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"); From 33fd837bf1deefc0fc501383f945d9ef80592198 Mon Sep 17 00:00:00 2001 From: Milos Petrusic Date: Thu, 14 Apr 2022 15:43:40 +0200 Subject: [PATCH 3/3] Add formatter plugin --- pom.xml | 11 ++++-- src/main/java/ch/engenius/bank/Account.java | 37 ++++++++++-------- src/main/java/ch/engenius/bank/Bank.java | 15 ++++--- .../java/ch/engenius/bank/BankRunner.java | 39 ++++++++----------- .../java/ch/engenius/bank/AccountTest.java | 8 ++-- src/test/java/ch/engenius/bank/BankTest.java | 5 +-- 6 files changed, 59 insertions(+), 56 deletions(-) diff --git a/pom.xml b/pom.xml index 2ff7b9e..5344dd2 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,6 @@ 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 @@ -29,6 +28,14 @@ + + net.revelc.code.formatter + formatter-maven-plugin + 2.18.0 + + UTF-8 + + @@ -39,6 +46,4 @@ 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 49425ce..74549f6 100644 --- a/src/main/java/ch/engenius/bank/Account.java +++ b/src/main/java/ch/engenius/bank/Account.java @@ -6,28 +6,30 @@ public class Account { private BigDecimal money; /* - * Creates the account with zero credit. - * */ + * 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 - * */ + * 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 - * */ + * 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"); @@ -43,11 +45,12 @@ public void withdraw(BigDecimal amount) { } /* - * Deposits given amount of credit to the account. - * - * @param amount Amount to deposit - * @throws IllegalArgumentException If given amount is bellow or equal zero - * */ + * 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"); diff --git a/src/main/java/ch/engenius/bank/Bank.java b/src/main/java/ch/engenius/bank/Bank.java index 24a8c7a..2eccf04 100644 --- a/src/main/java/ch/engenius/bank/Bank.java +++ b/src/main/java/ch/engenius/bank/Bank.java @@ -9,10 +9,12 @@ public class Bank { /* * 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 + * @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"); @@ -26,9 +28,10 @@ public Account registerAccount(int accountNumber, BigDecimal amount) { /* * Retrieve account by its number * - * @param accountNumber Number of the account to retrieve - * @throws IllegalStateException If account with given number is not registered - * */ + * @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"); diff --git a/src/main/java/ch/engenius/bank/BankRunner.java b/src/main/java/ch/engenius/bank/BankRunner.java index b8115c8..093e857 100644 --- a/src/main/java/ch/engenius/bank/BankRunner.java +++ b/src/main/java/ch/engenius/bank/BankRunner.java @@ -15,22 +15,20 @@ 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(); @@ -46,33 +44,28 @@ private void runBank(int iterations, int maxAccount) { } private void runRandomOperation(int maxAccount) { - BigDecimal transfer = BigDecimal.valueOf(random.nextDouble()) - .multiply(BigDecimal.valueOf(100)); + 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); + 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++) { + 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::getMoney) - .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 index de4d1b6..86d0c80 100644 --- a/src/test/java/ch/engenius/bank/AccountTest.java +++ b/src/test/java/ch/engenius/bank/AccountTest.java @@ -37,7 +37,7 @@ public void shouldWithdrawMoney() { @Test public void shouldFailToDepositNothing() { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, - () -> account.deposit(BigDecimal.ZERO)); + () -> account.deposit(BigDecimal.ZERO)); assertEquals("cannot deposit zero or negative amount", exception.getMessage()); } @@ -58,14 +58,14 @@ public void shouldWithdrawAllMoney() { @Test public void shouldFailToWithdrawNothing() { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, - () -> account.withdraw(BigDecimal.ZERO)); + () -> 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))); + () -> account.withdraw(BigDecimal.valueOf(-100))); assertEquals("cannot withdraw zero or negative amount", exception.getMessage()); } @@ -74,7 +74,7 @@ public void shouldFailToWithdrawWhenMissingMoney() { final BigDecimal withdrawalAmount = BigDecimal.valueOf(321); IllegalStateException exception = assertThrows(IllegalStateException.class, - () -> account.withdraw(BigDecimal.valueOf(321))); + () -> 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 index 0a5508e..d2d8c7d 100644 --- a/src/test/java/ch/engenius/bank/BankTest.java +++ b/src/test/java/ch/engenius/bank/BankTest.java @@ -29,7 +29,7 @@ public void shouldRegisterAccount() { @Test public void shouldFailToRegisterAccountIfAlreadyExists() { IllegalStateException exception = assertThrows(IllegalStateException.class, - () -> bank.registerAccount(accountNumber, BigDecimal.ZERO)); + () -> bank.registerAccount(accountNumber, BigDecimal.ZERO)); assertEquals("account already exists", exception.getMessage()); } @@ -42,8 +42,7 @@ public void shouldRegisterAccountWithEnoughMoney() { @Test public void shouldFailToGetNonExistingAccount() { - IllegalStateException exception = assertThrows(IllegalStateException.class, - () -> bank.getAccount(550)); + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> bank.getAccount(550)); assertEquals("account does not exist", exception.getMessage()); } }