DoThisInsteadOfJavaBeans.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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);
 
    }
 
}