diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5eac309
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 2ca15ec..65724af 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,6 +7,37 @@
ch.engenius
accounts
1.0-SNAPSHOT
-
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 11
+ 11
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.8.2
+ test
+
+
+ org.mockito
+ mockito-core
+ 4.0.0
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 4.0.0
+ 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
deleted file mode 100644
index b9979cb..0000000
--- a/src/main/java/ch/engenius/bank/Account.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package ch.engenius.bank;
-
-import java.math.BigDecimal;
-
-public class Account {
- private double money;
-
- public void withdraw(double amount) {
- if ((money - amount) < 0) {
- throw new IllegalStateException("not enough credits on account");
- }
- setMoney(money - amount);
-
- }
-
- public void deposit(double amount) {
- setMoney(money + amount);
- }
-
- public double getMoney() {
- return money;
- }
-
- public void setMoney(double 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
deleted file mode 100644
index 571ebc7..0000000
--- a/src/main/java/ch/engenius/bank/Bank.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package ch.engenius.bank;
-
-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);
- accounts.put(accountNumber, account);
- return account;
- }
-
- public Account getAccount( int number) {
- return accounts.get(number);
- }
-}
diff --git a/src/main/java/ch/engenius/bank/BankRunner.java b/src/main/java/ch/engenius/bank/BankRunner.java
index 10b30fd..73eb8fe 100644
--- a/src/main/java/ch/engenius/bank/BankRunner.java
+++ b/src/main/java/ch/engenius/bank/BankRunner.java
@@ -1,10 +1,12 @@
package ch.engenius.bank;
+import ch.engenius.bank.domain.Account;
+import ch.engenius.bank.service.BankService;
import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Random;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
import java.util.stream.IntStream;
public class BankRunner {
@@ -12,60 +14,63 @@ public class BankRunner {
private static final ExecutorService executor = Executors.newFixedThreadPool(8);
private final Random random = new Random(43);
- private final Bank bank = new Bank();
+ private final BankService bankService = new BankService();
public static void main(String[] args) {
BankRunner runner = new BankRunner();
int accounts = 100;
- int defaultDeposit = 1000;
+ BigDecimal defaultDeposit = BigDecimal.valueOf(1000);
int iterations = 10000;
- runner.registerAccounts(accounts, defaultDeposit);
- runner.sanityCheck(accounts, accounts*defaultDeposit);
- runner.runBank(iterations, accounts);
- runner.sanityCheck(accounts, accounts*defaultDeposit);
-
+ try {
+ runner.registerAccounts(accounts, defaultDeposit);
+ runner.sanityCheck(accounts, defaultDeposit.multiply(BigDecimal.valueOf(accounts)));
+ runner.runBank(iterations, accounts);
+ runner.sanityCheck(accounts, defaultDeposit.multiply(BigDecimal.valueOf(accounts)));
+ } catch (IllegalStateException e) {
+ System.out.println(e.getMessage());
+ }
}
private void runBank(int iterations, int maxAccount) {
- for (int i =0; i< iterations; i++ ) {
- executor.submit( ()-> runRandomOperation(maxAccount));
+ List> callableTasks = new ArrayList<>();
+
+ for (int i = 0; i < iterations; i++) {
+ Callable callableTask = () -> runRandomOperation(maxAccount);
+ callableTasks.add(callableTask);
}
+
try {
- executor.shutdown();
- executor.awaitTermination(100,TimeUnit.SECONDS);
+ executor.invokeAll(callableTasks);
} catch (InterruptedException e) {
e.printStackTrace();
}
+ executor.shutdown();
}
- private void runRandomOperation(int maxAccount) {
- double transfer = random.nextDouble()*100.0;
+ private Void runRandomOperation(int maxAccount) {
+ BigDecimal transfer = BigDecimal.valueOf(random.nextDouble() * 100.0);
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);
+ bankService.transferMoney(transfer, accountInNumber, accountOutNumber);
+ return null;
}
- private void registerAccounts(int number, int defaultMoney) {
- for ( int i = 0; i < number; i++) {
- bank.registerAccount(i, defaultMoney);
+ private void registerAccounts(int number, BigDecimal defaultMoney) {
+ for (int i = 0; i < number; i++) {
+ bankService.registerAccount(i, defaultMoney);
}
}
- private void sanityCheck( int accountMaxNumber, int totalExpectedMoney) {
+ private void sanityCheck(int accountMaxNumber, BigDecimal totalExpectedMoney) {
BigDecimal sum = IntStream.range(0, accountMaxNumber)
- .mapToObj( bank::getAccount)
- .map ( Account::getMoneyAsBigDecimal)
- .reduce( BigDecimal.ZERO, BigDecimal::add);
+ .mapToObj(bankService::getAccount)
+ .map (Account::getMoney)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
- if ( sum.intValue() != totalExpectedMoney) {
- throw new IllegalStateException("we got "+ sum + " != " + totalExpectedMoney +" (expected)");
+ if (sum.compareTo(totalExpectedMoney) != 0) {
+ throw new IllegalStateException("We got "+ sum + " != " + totalExpectedMoney +" (expected)");
}
- System.out.println("sanity check OK");
+ System.out.println("Sanity check OK");
}
-
-
}
diff --git a/src/main/java/ch/engenius/bank/domain/Account.java b/src/main/java/ch/engenius/bank/domain/Account.java
new file mode 100644
index 0000000..0031a92
--- /dev/null
+++ b/src/main/java/ch/engenius/bank/domain/Account.java
@@ -0,0 +1,62 @@
+package ch.engenius.bank.domain;
+
+import java.math.BigDecimal;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class Account {
+
+ private BigDecimal money;
+ private ReentrantLock lock;
+
+ public Account() {
+ this.money = BigDecimal.ZERO;
+ this.lock = new ReentrantLock();
+ }
+
+ public Account(BigDecimal money) {
+ this.lock = new ReentrantLock();
+ try {
+ validAmount(money);
+ this.money = money;
+ } catch (IllegalArgumentException e) {
+ this.money = BigDecimal.ZERO;
+ System.out.println(e.getMessage());
+ }
+ }
+
+ public BigDecimal getMoney() {
+ return money;
+ }
+
+ public void withdraw(BigDecimal amount) {
+ lock.lock();
+ try {
+ validAmount(amount);
+ if (money.subtract(amount).compareTo(BigDecimal.ZERO) < 0)
+ throw new IllegalStateException("Not enough credits on account");
+
+ money = money.subtract(amount);
+ } catch (IllegalArgumentException e) {
+ System.out.println(e.getMessage());
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public void deposit(BigDecimal amount) {
+ lock.lock();
+ try {
+ validAmount(amount);
+ money = money.add(amount);
+ } catch (IllegalArgumentException e) {
+ System.out.println(e.getMessage());
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private void validAmount(BigDecimal amount) {
+ if (amount.compareTo(BigDecimal.ZERO) <= 0)
+ throw new IllegalArgumentException("Amount should have positive value");
+ }
+}
diff --git a/src/main/java/ch/engenius/bank/domain/Bank.java b/src/main/java/ch/engenius/bank/domain/Bank.java
new file mode 100644
index 0000000..5c9266c
--- /dev/null
+++ b/src/main/java/ch/engenius/bank/domain/Bank.java
@@ -0,0 +1,16 @@
+package ch.engenius.bank.domain;
+
+import java.util.HashMap;
+
+public class Bank {
+
+ private HashMap accounts;
+
+ public Bank() {
+ this.accounts = new HashMap<>();
+ }
+
+ public HashMap getAccounts() {
+ return accounts;
+ }
+}
diff --git a/src/main/java/ch/engenius/bank/service/BankService.java b/src/main/java/ch/engenius/bank/service/BankService.java
new file mode 100644
index 0000000..db77f91
--- /dev/null
+++ b/src/main/java/ch/engenius/bank/service/BankService.java
@@ -0,0 +1,40 @@
+package ch.engenius.bank.service;
+
+import ch.engenius.bank.domain.Account;
+import ch.engenius.bank.domain.Bank;
+import java.math.BigDecimal;
+import java.util.NoSuchElementException;
+
+public class BankService {
+
+ private Bank bank;
+
+ public BankService() {
+ this.bank = new Bank();
+ }
+
+ public Account registerAccount(int accountNumber, BigDecimal amount) {
+ Account account = new Account(amount);
+ bank.getAccounts().put(accountNumber, account);
+ return account;
+ }
+
+ public Account getAccount(int number) {
+ if (!bank.getAccounts().containsKey(number))
+ throw new NoSuchElementException("Account with number: " + number + " does not exist");
+
+ return bank.getAccounts().get(number);
+ }
+
+ public void transferMoney(BigDecimal money, int accountInNumber, int accountOutNumber) {
+ try {
+ Account accountIn = getAccount(accountInNumber);
+ Account accountOut = getAccount(accountOutNumber);
+
+ accountOut.withdraw(money);
+ accountIn.deposit(money);
+ } catch (NoSuchElementException e) {
+ System.out.println(e.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/ch/engenius/bank/domain/AccountTest.java b/src/test/java/ch/engenius/bank/domain/AccountTest.java
new file mode 100644
index 0000000..1e5b1b7
--- /dev/null
+++ b/src/test/java/ch/engenius/bank/domain/AccountTest.java
@@ -0,0 +1,41 @@
+package ch.engenius.bank.domain;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+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;
+
+ @BeforeEach
+ public void setUp() {
+ account = new Account(BigDecimal.valueOf(1000));
+ }
+
+ @Test
+ public void shouldCreateAccount() {
+ assertEquals(BigDecimal.valueOf(1000), account.getMoney());
+ }
+
+ @Test
+ public void shouldAddMoney() {
+ account.deposit(BigDecimal.valueOf(100));
+ assertEquals(BigDecimal.valueOf(1100), account.getMoney());
+ }
+
+ @Test
+ public void shouldSubtractMoney() {
+ account.withdraw(BigDecimal.valueOf(100));
+ assertEquals(BigDecimal.valueOf(900), account.getMoney());
+ }
+
+ @Test
+ public void shouldThrowException_whenThereIsNotEnoughMoney() {
+ IllegalStateException exception = assertThrows(IllegalStateException.class, () -> account.withdraw(BigDecimal.valueOf(2000)));
+ assertEquals("Not enough credits on account", exception.getMessage());
+ assertEquals(BigDecimal.valueOf(1000), account.getMoney());
+ }
+}
diff --git a/src/test/java/ch/engenius/bank/service/BankServiceIntTest.java b/src/test/java/ch/engenius/bank/service/BankServiceIntTest.java
new file mode 100644
index 0000000..5febd41
--- /dev/null
+++ b/src/test/java/ch/engenius/bank/service/BankServiceIntTest.java
@@ -0,0 +1,59 @@
+package ch.engenius.bank.service;
+
+import ch.engenius.bank.domain.Account;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import java.math.BigDecimal;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class BankServiceIntTest {
+
+ private BankService bankService;
+
+ @BeforeAll
+ public void setUp() {
+ bankService = new BankService();
+ }
+
+ @Test
+ public void shouldTransferMoney() {
+ Account accountIn = bankService.registerAccount(1, BigDecimal.valueOf(1000));
+ Account accountOut = bankService.registerAccount(2, BigDecimal.valueOf(2000));
+
+ bankService.transferMoney(BigDecimal.valueOf(100), 1, 2);
+ assertEquals(BigDecimal.valueOf(1900), accountOut.getMoney());
+ assertEquals(BigDecimal.valueOf(1100), accountIn.getMoney());
+ }
+
+ @Test
+ public void shouldCreateAccountWithZeroAmount_whenAmountIsNegative() {
+ Account account = bankService.registerAccount(1, BigDecimal.valueOf(-1000));
+ assertEquals(BigDecimal.ZERO, account.getMoney());
+ }
+
+ @Test
+ public void shouldThrowException_whenThereIsNotEnoughMoneyOnAccount() {
+ Account accountIn = bankService.registerAccount(1, BigDecimal.valueOf(1000));
+ Account accountOut = bankService.registerAccount(2, BigDecimal.valueOf(2000));
+
+ IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
+ bankService.transferMoney(BigDecimal.valueOf(2500), 1, 2);
+ });
+ assertEquals("Not enough credits on account", exception.getMessage());
+ assertEquals(BigDecimal.valueOf(1000), accountIn.getMoney());
+ assertEquals(BigDecimal.valueOf(2000), accountOut.getMoney());
+ }
+
+ @Test
+ public void shouldNotTransferMoney_whenTransferAmountIsNegative() {
+ Account accountIn = bankService.registerAccount(1, BigDecimal.valueOf(1000));
+ Account accountOut = bankService.registerAccount(2, BigDecimal.valueOf(2000));
+
+ bankService.transferMoney(BigDecimal.valueOf(-100), 1, 2);
+ assertEquals(BigDecimal.valueOf(1000), accountIn.getMoney());
+ assertEquals(BigDecimal.valueOf(2000), accountOut.getMoney());
+ }
+}
diff --git a/src/test/java/ch/engenius/bank/service/BankServiceTest.java b/src/test/java/ch/engenius/bank/service/BankServiceTest.java
new file mode 100644
index 0000000..10218c8
--- /dev/null
+++ b/src/test/java/ch/engenius/bank/service/BankServiceTest.java
@@ -0,0 +1,59 @@
+package ch.engenius.bank.service;
+
+import ch.engenius.bank.domain.Account;
+import ch.engenius.bank.domain.Bank;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+public class BankServiceTest {
+
+ @Mock
+ private HashMap accounts;
+
+ @Mock
+ private Account account;
+
+ @Mock
+ private Bank bank;
+
+ @InjectMocks
+ private BankService bankService = new BankService();
+
+ @Test
+ public void shouldReturnAccountByProvidedNumber() {
+ Mockito.when(bank.getAccounts()).thenReturn(accounts);
+ Mockito.when(accounts.containsKey(1)).thenReturn(true);
+ Mockito.when(accounts.get(1)).thenReturn(account);
+
+ assertEquals(account, bankService.getAccount(1));
+ }
+
+ @Test
+ public void shouldTestTransferMoney() {
+ Account accountIn = Mockito.mock(Account.class);
+ Account accountOut = Mockito.mock(Account.class);
+
+ Mockito.when(bank.getAccounts()).thenReturn(accounts);
+ Mockito.when(accounts.containsKey(1)).thenReturn(true);
+ Mockito.when(accounts.containsKey(2)).thenReturn(true);
+ Mockito.when(accounts.get(1)).thenReturn(accountIn);
+ Mockito.when(accounts.get(2)).thenReturn(accountOut);
+
+ Mockito.doNothing().when(accountOut).withdraw(BigDecimal.valueOf(100));
+ Mockito.doNothing().when(accountIn).deposit(BigDecimal.valueOf(100));
+
+ bankService.transferMoney(BigDecimal.valueOf(100), 1, 2);
+ verify(accountOut, times(1)).withdraw(BigDecimal.valueOf(100));
+ verify(accountIn, times(1)).deposit(BigDecimal.valueOf(100));
+ }
+}