/* * Copyright (C) 2015 Actor LLC. <https://actor.im> */ package im.actor.runtime.generic.storage; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import im.actor.runtime.bser.BserCreator; import im.actor.runtime.bser.BserObject; import im.actor.runtime.storage.ListEngineDisplayExt; import im.actor.runtime.storage.ListEngineDisplayListener; import im.actor.runtime.storage.ListEngineDisplayLoadCallback; import im.actor.runtime.storage.ListEngineItem; import im.actor.runtime.storage.ListStorageDisplayEx; import im.actor.runtime.storage.ObjectCache; // Disabling Bounds checks for speeding up calculations /*-[ #define J2OBJC_DISABLE_ARRAY_BOUND_CHECKS 1 ]-*/ public class AsyncListEngine<T extends BserObject & ListEngineItem> implements ListEngineDisplayExt<T> { private final AsyncStorageInt<T> asyncStorageInt; private final ObjectCache<Long, T> cache = new ObjectCache<>(); private final Object LOCK = new Object(); private CopyOnWriteArrayList<ListEngineDisplayListener<T>> listeners = new CopyOnWriteArrayList<>(); public AsyncListEngine(ListStorageDisplayEx storage, BserCreator<T> creator) { this.asyncStorageInt = new AsyncStorageInt<T>(storage, creator); } // Main List Engine @Override public void addOrUpdateItem(T item) { synchronized (LOCK) { // Update memory cache cache.onObjectUpdated(item.getEngineId(), item); List<T> items = new ArrayList<T>(); items.add(item); asyncStorageInt.addOrUpdateItems(items); for (ListEngineDisplayListener<T> l : listeners) { l.addOrUpdate(item); } } } @Override public void addOrUpdateItems(List<T> items) { synchronized (LOCK) { // Update memory cache for (T i : items) { cache.onObjectUpdated(i.getEngineId(), i); } asyncStorageInt.addOrUpdateItems(items); for (ListEngineDisplayListener<T> l : listeners) { l.addOrUpdate(items); } } } @Override public void replaceItems(List<T> items) { synchronized (LOCK) { // Update memory cache cache.clear(); for (T i : items) { cache.onObjectUpdated(i.getEngineId(), i); } asyncStorageInt.replaceItems(items); for (ListEngineDisplayListener<T> l : listeners) { l.onItemsReplaced(items); } } } @Override public void removeItem(long key) { synchronized (LOCK) { cache.removeObject(key); asyncStorageInt.remove(new long[]{key}); for (ListEngineDisplayListener<T> l : listeners) { l.onItemRemoved(key); } } } @Override public void removeItems(long[] keys) { synchronized (LOCK) { for (long key : keys) { cache.removeObject(key); } asyncStorageInt.remove(keys); for (ListEngineDisplayListener<T> l : listeners) { l.onItemsRemoved(keys); } } } @Override public void clear() { synchronized (LOCK) { cache.clear(); asyncStorageInt.clear(); for (ListEngineDisplayListener<T> l : listeners) { l.onListClear(); } } } @Override public T getValue(long key) { synchronized (LOCK) { T res = cache.lookup(key); if (res != null) { return res; } } T res = asyncStorageInt.getValue(key); if (res != null) { synchronized (LOCK) { cache.onObjectLoaded(key, res); } } return res; } @Override public T getHeadValue() { T res = asyncStorageInt.getHeadValue(); if (res != null) { synchronized (LOCK) { cache.onObjectLoaded(res.getEngineId(), res); } } return res; } @Override public boolean isEmpty() { // TODO: Correct implementation return getCount() == 0; } @Override public int getCount() { return asyncStorageInt.getCount(); } // Display extension @Override public void subscribe(ListEngineDisplayListener<T> listener) { if (!listeners.contains(listener)) { listeners.add(listener); } } @Override public void unsubscribe(ListEngineDisplayListener<T> listener) { listeners.remove(listener); } @Override public void loadForward(int limit, ListEngineDisplayLoadCallback<T> callback) { asyncStorageInt.loadForward(null, null, limit, cover(callback)); } @Override public void loadForward(long afterSortKey, int limit, ListEngineDisplayLoadCallback<T> callback) { asyncStorageInt.loadForward(null, afterSortKey, limit, cover(callback)); } @Override public void loadForward(String query, int limit, ListEngineDisplayLoadCallback<T> callback) { asyncStorageInt.loadForward(query, null, limit, cover(callback)); } @Override public void loadForward(String query, long afterSortKey, int limit, final ListEngineDisplayLoadCallback<T> callback) { asyncStorageInt.loadForward(query, afterSortKey, limit, cover(callback)); } @Override public void loadBackward(int limit, ListEngineDisplayLoadCallback<T> callback) { asyncStorageInt.loadBackward(null, null, limit, cover(callback)); } @Override public void loadBackward(long beforeSortKey, int limit, ListEngineDisplayLoadCallback<T> callback) { asyncStorageInt.loadBackward(null, beforeSortKey, limit, cover(callback)); } @Override public void loadBackward(String query, int limit, ListEngineDisplayLoadCallback<T> callback) { asyncStorageInt.loadBackward(query, null, limit, cover(callback)); } @Override public void loadBackward(String query, long beforeSortKey, int limit, ListEngineDisplayLoadCallback<T> callback) { asyncStorageInt.loadBackward(query, beforeSortKey, limit, cover(callback)); } @Override public void loadCenter(long centerSortKey, int limit, ListEngineDisplayLoadCallback<T> callback) { asyncStorageInt.loadCenter(centerSortKey, limit, cover(callback)); } private ListEngineDisplayLoadCallback<T> cover(final ListEngineDisplayLoadCallback<T> callback) { return (items, topSortKey, bottomSortKey) -> { synchronized (LOCK) { for (T i : items) { cache.onObjectLoaded(i.getEngineId(), i); } } callback.onLoaded(items, topSortKey, bottomSortKey); }; } }