JavaBeansAreDangerousDontDoThis.java

package examples;


import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import java.util.*;

public class JavaBeansAreDangerousDontgDoThis {
    static class Transaction{
        //USES SOME MUTABLES!
        int id;
        int amount = 0;
        Date date = new Date();
        String desc = "";

        public Transaction(int id, int amount, Date date, String desc) {
            this.id = id;
            this.amount = amount;
            this.date = date;
            this.desc = desc;
        }

        public int getId() { return id; }

        public void setId(int id) { this.id = id; }

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }

        public Date getDate() {
            return date;
        }

        public void setDate(Date date) {
            this.date = date;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        @Override
        public String toString() {
            return "Transaction{" +
                    "amount=" + amount +
                    ", date=" + date +
                    ", desc='" + desc + '\'' +
                    '}';
        }
    }
    static class Account{
        //VULNARABLE BECAUSE IT USES MUTABLES
        private Map<Integer, Transaction> transactions = new HashMap<>();

        public void addOrUpdateTransaction(Transaction t) {
            this.transactions.put(t.id, t);
        }

        public Collection<Transaction> getTransactions() {
            return transactions.values();
        }

        public String toString(){
            return transactions.toString();
        }
        //..
    }
    interface AccountChangedListener {
        void accountEdited(Account a);
    }
    static class AccountEditor {
        private Account a;
        private List<AccountChangedListener> listeners;
        public AccountEditor(Account a, List<AccountChangedListener> listeners){
            this.a = a;
            this.listeners = listeners;
        }
        public void addTransaction(Transaction t){
            a.addOrUpdateTransaction(t);
            listeners.forEach(l -> l.accountEdited(a));
        }
        public void updateTransaction(Transaction t){
            a.addOrUpdateTransaction(t);
            listeners.forEach(l -> l.accountEdited(a));
        }

    }
    static class PNLReport implements AccountChangedListener{
        //VULNARABLE BECAUSE IT USES MUTABLES
        private List<Account> accounts;
        private int totalFunds = 0;
        public PNLReport(List<Account> accounts){
            this.accounts = accounts;
            computeTotals();
        }
        public void computeTotals(){
            totalFunds = 0;
            for(Account a : accounts){
                for(Transaction t: a.getTransactions()){
                    totalFunds += t.getAmount();
                }
            }
        }
        public void print(){
            System.out.println("Accounts:" + accounts);
            System.out.println("Total:" + totalFunds);
        }
        @Override
        public void accountEdited(Account a) {
            computeTotals();
        }
    }
    static class BalanceSheet implements AccountChangedListener{
        //VULNARABLE BECAUSE IT USES MUTABLES
        private List<Account> accounts;
        public BalanceSheet(List<Account> a){
            this.accounts = a;
        }
        public void computeTotals(){
            //...
        }
        public void print(){
            //..
        }
        @Override
        public void accountEdited(Account a) {
            computeTotals();
        }
    }

    public static void main(String[] argc){
        //First you initialize everyhing
        Account a = new Account();
        List<Account> chartOfAccounts = Arrays.asList(new Account[]{a});
        PNLReport pnl = new PNLReport(chartOfAccounts);
        BalanceSheet balances = new BalanceSheet(chartOfAccounts);
        AccountEditor editor = new AccountEditor(a, Arrays.asList(pnl, balances));
        editor.addTransaction(new Transaction(1, 100, new Date(), "First Transaction"));
        pnl.print();

        //Now you edit some transactions. This would already break things if done from another thread
        Transaction t2 = new Transaction(2, 100, new Date(), "Second Transaction");
        editor.addTransaction(t2);
        editor.updateTransaction(new Transaction(1, 50, new Date(), "Modified First Transaction"));
        //This worked because listeners were called
        pnl.print();

        //This will break things even from the same thread because listeners were not called so totals didn't get recalculated
        t2.setAmount(1000);
        pnl.print();

        //This really won't be reliable because mutability makes it thread-unsafe
        new Thread(new Runnable() {
            @Override
            public void run() {
                pnl.print();
            }
        }).start();

    }

}