package examples;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.*;
import java.util.stream.Collectors;
public class DoThisInsteadOfJavaBeans {
static class Transaction{
//ONLY USING IMMUTABLES
final public int id;
final public int amount;
final public long date;
final public String desc;
public Transaction(int id, int amount, long date, String desc) {
this.id = id;
this.amount = amount;
this.date = date;
this.desc = desc;
}
public Transaction(int id, int amount, Date date, String desc) {
this(id, amount, date.getTime(), desc);
}
public Date getDate() {
return new Date(date);
}
@Override
public String toString() {
return "Transaction{" +
"id=" + id +
", amount=" + amount +
", date=" + getDate() +
", desc='" + desc + '\'' +
'}';
}
}
static class Account{
//ONLY IMMUTABLES
final public int accountId;
final public ImmutableList<Transaction> transactions;
public Account(int accountId){
this.accountId = accountId;
this.transactions = ImmutableList.of();
}
public Account(int accountId, List<Transaction> tList){
this.accountId = accountId;
transactions = ImmutableList.copyOf(tList);
}
public Account withTransaction(Transaction t){
return new Account(accountId, ImmutableList.<Transaction>builder().addAll(transactions).add(t).build());
}
public Account withTransactionReplaced(Transaction t){
return new Account(accountId,
ImmutableList.<Transaction>builder()
.addAll(transactions.stream().filter(x -> x.id != t.id).collect(Collectors.toList()))
.add(t)
.build());
}
@Override
public String toString() {
return "Account{" +
"accountId=" + accountId +
", transactions=" + transactions +
'}';
}
}
interface AccountChangedListener {
void accountEdited(Account a);
}
static class AccountEditor{
volatile private Account a;
final private ImmutableList<AccountChangedListener> listeners;
public AccountEditor(Account a, List<AccountChangedListener> listeners){
this.a = a;
this.listeners = ImmutableList.copyOf(listeners);
}
public void addTransaction(Transaction t){
a = a.withTransaction(t);
listeners.forEach(l -> l.accountEdited(a));
}
public void updateTransaction(Transaction t){
a = a.withTransactionReplaced(t);
listeners.forEach(l -> l.accountEdited(a));
}
}
static class PNLReport implements AccountChangedListener{
//WE CREATED THIS CLASS SO THAT WE CAN SWAP OUT BOTH ACCOUNTS AND TOTAL IN ONE GO WITHOUT SYNCHRONIZATION
static class PNLState{
public ImmutableMap<Integer, Account> accounts;
public Integer total;
public PNLState(ImmutableMap<Integer, Account> accounts) {
this.accounts = ImmutableMap.copyOf(accounts);
this.total = computeTotals(accounts);
}
private int computeTotals(ImmutableMap<Integer, Account> accounts){
final int[] totalFunds = new int[] {0};
accounts.values()
.forEach(v -> v.transactions
.forEach(t -> totalFunds[0] += t.amount));
return totalFunds[0];
}
}
volatile private PNLState pnlState;
public PNLReport(Map<Integer, Account> accounts){
this.pnlState = new PNLState(ImmutableMap.copyOf(accounts));
}
@Override
public void accountEdited(Account a) {
Map<Integer, Account> m = new HashMap(pnlState.accounts);
m.put(a.accountId, a);
this.pnlState = new PNLState(ImmutableMap.copyOf(m));
}
public void print(){
System.out.println("Accounts:" + pnlState.accounts);
System.out.println("Total:" + pnlState.total);
}
}
static class BalanceSheet implements AccountChangedListener{
public BalanceSheet(ImmutableMap<Integer, Account> accounts){}
//Another screen, similar implementation to PNLReport
@Override
public void accountEdited(Account a) {
//..
}
}
public static void main(String[] argc){
Account a = new Account(1);
Transaction t = new Transaction(1, 100, new Date(), "First Transaction");
Account accountWithTransaction = a.withTransaction(t);
ImmutableMap<Integer, Account> chartOfAccounts = ImmutableMap.of(accountWithTransaction.accountId, accountWithTransaction);
PNLReport pnl = new PNLReport(chartOfAccounts);
BalanceSheet balances = new BalanceSheet(chartOfAccounts);
AccountEditor editor = new AccountEditor(accountWithTransaction, ImmutableList.of(pnl, balances));
pnl.print();
editor.addTransaction(new Transaction(2, 200, new Date(), "Second Transaction"));
//ADD WORKED
pnl.print();
editor.updateTransaction(new Transaction(1, 50, new Date(), "Modified First Transaction"));
//UPDATE WORKED
pnl.print();
//EVERYTHING IS AUTOMATICALLY THREAD-SAFE WITHOUT ANY SYNCHRONIZATION
new Thread(new Runnable() {
@Override
public void run() {
pnl.print();
}
}).start();
//NOT POSSIBLE TO CORRUPT THE STATE ACCIDENTALLY
//t.setAmount(1000);
}
}