/*
* 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);
};
}
}