DoThisInsteadOfJavaBeans.java

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);

    }

}