package com.badoo.barf.data.repo;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import rx.Observable;
/**
* Implementation of {@link Repository} which manages in progress queries. For this to work correctly there are two constraints, firstly
* the query must correctly implemented the equals method such that it can be compared to in progress queries. Secondly the Observable
*
* returned should be able to handle multiple subscription without causing long running operations such as network calls to restart.
*/
public abstract class BaseRepository<T> implements Repository<T> {
private static final String TAG = BaseRepository.class.getSimpleName();
private static final boolean DEBUG = true;
private Map<Query<?>, Pending> mInProcessQueries = new ConcurrentHashMap<>();
@NonNull
@Override
public <Result> Observable<Result> query(@NonNull Query<Result> query) {
Log.d(TAG, "Starting query: " + query + " with pending: " + mInProcessQueries.size());
if (DEBUG) {
assertQueryOverridesEqualsAndHashcode(query.getClass());
}
Pending pending = mInProcessQueries.get(query);
if (pending != null) {
Log.d(TAG, "Query: " + query + " already in progress for " + (System.currentTimeMillis() - pending.mStartTime) + " ms, ignoring");
//noinspection unchecked
return (Observable<Result>) pending.mObservable;
}
final Observable<Result> queryObservable = createObservable(query)
.doOnSubscribe(() -> {
Pending current = mInProcessQueries.get(query);
if (current != null) {
current.mSubscriptionCount++;
if (DEBUG) {
Log.d(TAG, "Query: " + query + " subscribe, count: " + current.mSubscriptionCount);
}
}
})
.doOnUnsubscribe(() -> {
Pending current = mInProcessQueries.get(query);
if (current != null) {
current.mSubscriptionCount--;
if (current.mSubscriptionCount == 0) {
terminateQuery(query);
if (DEBUG) {
Log.d(TAG, "Query: " + query + " unsubscribe, count: " + current.mSubscriptionCount);
}
}
}
})
.doOnTerminate(() -> {
terminateQuery(query);
});
mInProcessQueries.put(query, new Pending(queryObservable, System.currentTimeMillis()));
return queryObservable;
}
private <Result> void terminateQuery(@NonNull Query<Result> query) {
Pending terminated = mInProcessQueries.get(query);
if (terminated != null) {
Log.d(TAG, "Query: " + query + " terminated after " + (System.currentTimeMillis() - terminated.mStartTime) + "ms");
mInProcessQueries.remove(query);
}
}
/**
* In the case an query isn't in progress, this method will be called to perform the query.
*/
@NonNull
protected abstract <Result> Observable<Result> createObservable(@NonNull Query<Result> query);
@VisibleForTesting
static boolean assertQueryOverridesEqualsAndHashcode(Class<?> clazz) {
try {
final Method hashCode = clazz.getMethod("hashCode");
final Method equals = clazz.getMethod("equals", Object.class);
return hashCode.getDeclaringClass() == clazz && equals.getDeclaringClass() == clazz;
}
catch (NoSuchMethodException e) {
// Should be impossible.
throw new RuntimeException();
}
}
private static class Pending {
final Observable<?> mObservable;
final long mStartTime;
int mSubscriptionCount;
private Pending(Observable<?> observable, long startTime) {
mObservable = observable;
mStartTime = startTime;
}
}
}