package database.wallet; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NavigableSet; import java.util.TreeMap; import org.mapdb.BTreeKeySerializer; import org.mapdb.DB; import org.mapdb.Fun; import org.mapdb.Fun.Tuple2; import org.mapdb.BTreeMap; import qora.account.Account; import qora.transaction.Transaction; import utils.ObserverMessage; import utils.Pair; import utils.ReverseComparator; import database.DBMap; import database.serializer.TransactionSerializer; public class TransactionMap extends DBMap<Tuple2<String, String>, Transaction> { public static final int TIMESTAMP_INDEX = 1; public static final int ADDRESS_INDEX = 2; public static final int AMOUNT_INDEX = 3; private Map<Integer, Integer> observableData = new HashMap<Integer, Integer>(); public TransactionMap(WalletDatabase walletDatabase, DB database) { super(walletDatabase, database); this.observableData.put(DBMap.NOTIFY_ADD, ObserverMessage.ADD_TRANSACTION_TYPE); this.observableData.put(DBMap.NOTIFY_REMOVE, ObserverMessage.REMOVE_TRANSACTION_TYPE); this.observableData.put(DBMap.NOTIFY_LIST, ObserverMessage.LIST_TRANSACTION_TYPE); } public TransactionMap(TransactionMap parent) { super(parent); } @SuppressWarnings({ "unchecked", "rawtypes" }) protected void createIndexes(DB database) { //TIMESTAMP INDEX NavigableSet<Tuple2<Long, Tuple2<String, String>>> timestampIndex = database.createTreeSet("transactions_index_timestamp") .comparator(Fun.COMPARATOR) .makeOrGet(); NavigableSet<Tuple2<Long, Tuple2<String, String>>> descendingTimestampIndex = database.createTreeSet("transactions_index_timestamp_descending") .comparator(new ReverseComparator(Fun.COMPARATOR)) .makeOrGet(); createIndex(TIMESTAMP_INDEX, timestampIndex, descendingTimestampIndex, new Fun.Function2<Long, Tuple2<String, String>, Transaction>() { @Override public Long run(Tuple2<String, String> key, Transaction value) { return value.getTimestamp(); } }); //ADDRESS INDEX NavigableSet<Tuple2<String, Tuple2<String, String>>> addressIndex = database.createTreeSet("transactions_index_address") .comparator(Fun.COMPARATOR) .makeOrGet(); NavigableSet<Tuple2<String, Tuple2<String, String>>> descendingAddressIndex = database.createTreeSet("transactions_index_address_descending") .comparator(new ReverseComparator(Fun.COMPARATOR)) .makeOrGet(); createIndex(ADDRESS_INDEX, addressIndex, descendingAddressIndex, new Fun.Function2<String, Tuple2<String, String>, Transaction>() { @Override public String run(Tuple2<String, String> key, Transaction value) { return key.a; } }); //AMOUNT INDEX NavigableSet<Tuple2<BigDecimal, Tuple2<String, String>>> amountIndex = database.createTreeSet("transactions_index_amount") .comparator(Fun.COMPARATOR) .makeOrGet(); NavigableSet<Tuple2<BigDecimal, Tuple2<String, String>>> descendingAmountIndex = database.createTreeSet("transactions_index_amount_descending") .comparator(new ReverseComparator(Fun.COMPARATOR)) .makeOrGet(); createIndex(AMOUNT_INDEX, amountIndex, descendingAmountIndex, new Fun.Function2<BigDecimal, Tuple2<String, String>, Transaction>() { @Override public BigDecimal run(Tuple2<String, String> key, Transaction value) { Account account = new Account(key.a); return value.getAmount(account); } }); } @Override protected Map<Tuple2<String, String>, Transaction> getMap(DB database) { //OPEN MAP return database.createTreeMap("transactions") .keySerializer(BTreeKeySerializer.TUPLE2) .valueSerializer(new TransactionSerializer()) .counterEnable() .makeOrGet(); } @Override protected Map<Tuple2<String, String>, Transaction> getMemoryMap() { return new TreeMap<Tuple2<String, String>, Transaction>(Fun.TUPLE2_COMPARATOR); } @Override protected Transaction getDefaultValue() { return null; } @Override protected Map<Integer, Integer> getObservableData() { return this.observableData; } @SuppressWarnings({ "unchecked", "rawtypes" }) public List<Transaction> get(Account account, int limit) { List<Transaction> transactions = new ArrayList<Transaction>(); try { //GET ALL TRANSACTIONS THAT BELONG TO THAT ADDRESS /*Map<Tuple2<String, String>, Transaction> accountTransactions = ((BTreeMap) this.map).subMap( Fun.t2(null, account.getAddress()), Fun.t2(Fun.HI(), account.getAddress()));*/ Map<Tuple2<String, String>, Transaction> accountTransactions = ((BTreeMap) this.map).subMap( Fun.t2(account.getAddress(), null), Fun.t2(account.getAddress(), Fun.HI())); //GET ITERATOR Iterator<Transaction> iterator = accountTransactions.values().iterator(); //RETURN {LIMIT} TRANSACTIONS int counter = 0; while(iterator.hasNext() && counter < limit) { transactions.add(iterator.next()); counter++; } } catch(Exception e) { //ERROR e.printStackTrace(); } return transactions; } public List<Pair<Account, Transaction>> get(List<Account> accounts, int limit) { List<Pair<Account, Transaction>> transactions = new ArrayList<Pair<Account, Transaction>>(); try { //FOR EACH ACCOUNTS synchronized(accounts) { for(Account account: accounts) { List<Transaction> accountTransactions = get(account, limit); for(Transaction transaction: accountTransactions) { transactions.add(new Pair<Account, Transaction>(account, transaction)); } } } } catch(Exception e) { //ERROR e.printStackTrace(); } return transactions; } @SuppressWarnings({ "unchecked", "rawtypes" }) public void delete(Account account) { //GET ALL TRANSACTIONS THAT BELONG TO THAT ADDRESS Map<Tuple2<String, String>, Transaction> accountTransactions = ((BTreeMap) this.map).subMap( Fun.t2(account.getAddress(), null), Fun.t2(account.getAddress(), Fun.HI())); //DELETE TRANSACTIONS for(Tuple2<String, String> key: accountTransactions.keySet()) { this.delete(key); } } public void delete(Account account, Transaction transaction) { this.delete(new Tuple2<String, String>(account.getAddress(), new String(transaction.getSignature()))); } public void deleteAll(List<Account> accounts) { for(Account account: accounts) { this.delete(account); } } public boolean add(Account account, Transaction transaction) { return this.set(new Tuple2<String, String>(account.getAddress(), new String(transaction.getSignature())), transaction); } public void addAll(Map<Account, List<Transaction>> transactions) { //FOR EACH ACCOUNT for(Account account: transactions.keySet()) { //FOR EACH TRANSACTION for(Transaction transaction: transactions.get(account)) { this.add(account, transaction); } } } }