package cgeo.geocaching.utils;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import io.reactivex.Observable;
import io.reactivex.ObservableOperator;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Cancellable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.internal.disposables.CancellableDisposable;
import io.reactivex.schedulers.Schedulers;
public class RxUtils {
private RxUtils() {
// Utility class, not to be instantiated
}
public static<T> Observable<T> rememberLast(final Observable<T> observable, final T initialValue) {
final AtomicReference<T> lastValue = new AtomicReference<>(initialValue);
return observable.doOnNext(new Consumer<T>() {
@Override
public void accept(final T value) {
lastValue.set(value);
}
}).startWith(Observable.defer(new Callable<Observable<T>>() {
@Override
public Observable<T> call() {
final T last = lastValue.get();
return last != null ? Observable.just(last) : Observable.<T>empty();
}
})).replay(1).refCount();
}
/**
* Cache the last value of observables so that every key is associated to only one of them.
*
* @param <K> the type of the key
* @param <V> the type of the value
*/
public static class ObservableCache<K, V> {
private final Function<K, Observable<V>> func;
private final Map<K, Observable<V>> cached = new HashMap<>();
/**
* Create a new observables cache.
*
* @param func the function transforming a key into an observable
*/
public ObservableCache(final Function<K, Observable<V>> func) {
this.func = func;
}
/**
* Get the observable corresponding to a key. If the key has not already been
* seen, the function passed to the constructor will be called to build the observable
* <p/>
* If the observable has already emitted values, only the last one will be remembered.
* <p/>
* If the function throws an exception, it will be returned in the observable.
*
* @param key the key
* @return the observable corresponding to the key
*/
public synchronized Observable<V> get(final K key) {
if (cached.containsKey(key)) {
return cached.get(key);
}
try {
final Observable<V> value = func.apply(key).replay(1).refCount();
cached.put(key, value);
return value;
} catch (final Exception e) {
final Observable<V> error = Observable.error(e);
cached.put(key, error);
return error;
}
}
}
public static class DelayedUnsubscription<T> implements ObservableOperator<T, T> {
private final long time;
private final TimeUnit unit;
public DelayedUnsubscription(final long time, final TimeUnit unit) {
this.time = time;
this.unit = unit;
}
@Override
public Observer<? super T> apply(final Observer<? super T> observer) throws Exception {
final AtomicBoolean canceled = new AtomicBoolean();
return new Observer<T>() {
@Override
public void onSubscribe(final Disposable d) {
observer.onSubscribe(new CancellableDisposable(new Cancellable() {
@Override
public void cancel() throws Exception {
canceled.set(true);
Schedulers.computation().scheduleDirect(new Runnable() {
@Override
public void run() {
d.dispose();
}
}, time, unit);
}
}));
}
@Override
public void onComplete() {
if (!canceled.get()) {
observer.onComplete();
}
}
@Override
public void onError(final Throwable e) {
if (!canceled.get()) {
observer.onError(e);
}
}
@Override
public void onNext(final T t) {
if (!canceled.get()) {
observer.onNext(t);
}
}
};
}
}
}