/*
* Copyright 2015 Jacek Marchwicki <jacek.marchwicki@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.appunite.rx.operators;
import com.appunite.rx.ResponseOrError;
import com.appunite.rx.observables.NetworkObservableProvider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nonnull;
import rx.Notification;
import rx.Observable;
import rx.Observer;
import rx.Producer;
import rx.Scheduler;
import rx.Subscriber;
import rx.Subscription;
import rx.exceptions.Exceptions;
import rx.exceptions.OnErrorThrowable;
import rx.functions.Action0;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.functions.Func2;
import rx.functions.Func3;
import rx.functions.FuncN;
import rx.internal.util.RxRingBuffer;
import rx.subscriptions.Subscriptions;
public class MoreOperators {
@Nonnull
public static <T> Observable.Transformer<T, T> refresh(
@Nonnull final Observable<Object> refreshSubject) {
return new Observable.Transformer<T, T>() {
@Override
public Observable<T> call(final Observable<T> observable) {
return refresh(refreshSubject, observable);
}
};
}
@Nonnull
private static <T> Observable<T> refresh(@Nonnull Observable<Object> refreshSubject,
@Nonnull final Observable<T> toRefresh) {
return refreshSubject.startWith((Object)null).switchMap(new Func1<Object, Observable<T>>() {
@Override
public Observable<T> call(final Object o) {
return toRefresh;
}
});
}
@Nonnull
public static <T> Observable.Transformer<T, T> cacheWithTimeout(
@Nonnull final Scheduler scheduler) {
return cacheWithTimeout(scheduler, 5L, TimeUnit.SECONDS);
}
@Nonnull
public static <T> Observable.Transformer<T, T> cacheWithTimeout(
@Nonnull final Scheduler scheduler,
final long keepTime,
@Nonnull final TimeUnit timeUnit) {
return new Observable.Transformer<T, T>() {
@Override
public Observable<T> call(final Observable<T> observable) {
return cacheWithTimeout(observable, scheduler, keepTime, timeUnit);
}
};
}
@Nonnull
private static <T> Observable<T> cacheWithTimeout(
@Nonnull Observable<T> observable,
@Nonnull Scheduler scheduler,
long keepTime,
@Nonnull TimeUnit timeUnit) {
return OnSubscribeRefCountDelayed.create(
observable.replay(1), keepTime, timeUnit, scheduler);
}
@Nonnull
public static <T> Observable.Transformer<ResponseOrError<T>, ResponseOrError<T>> repeatOnError(
@Nonnull final Scheduler scheduler) {
return
new Observable.Transformer<ResponseOrError<T>, ResponseOrError<T>>() {
@Override
public Observable<ResponseOrError<T>> call(final Observable<ResponseOrError<T>> responseOrErrorObservable) {
return repeatOnError(responseOrErrorObservable, scheduler);
}
};
}
@Nonnull
public static <T> Observable.Transformer<ResponseOrError<T>, ResponseOrError<T>> repeatOnErrorOrNetwork(
@Nonnull final NetworkObservableProvider networkObservableProvider,
@Nonnull final Scheduler scheduler) {
return
new Observable.Transformer<ResponseOrError<T>, ResponseOrError<T>>() {
@Override
public Observable<ResponseOrError<T>> call(final Observable<ResponseOrError<T>> responseOrErrorObservable) {
return repeatOnErrorOrNetwork(responseOrErrorObservable, networkObservableProvider, scheduler);
}
};
}
@Nonnull
public static <T1, T2, R> Observable.Transformer<T1, R> combineWith(@Nonnull final Observable<T2> observable,
@Nonnull final Func2<T1, T2, R> func) {
return new Observable.Transformer<T1, R>() {
@Override
public Observable<R> call(Observable<T1> t1Observable) {
return Observable.combineLatest(t1Observable, observable, func);
}
};
}
@Nonnull
public static <T1, T2, T3, R> Observable.Transformer<T1, R> combineWith(@Nonnull final Observable<T2> observable2,
@Nonnull final Observable<T3> observable3,
@Nonnull final Func3<T1, T2, T3, R> func) {
return new Observable.Transformer<T1, R>() {
@Override
public Observable<R> call(Observable<T1> t1Observable) {
return Observable.combineLatest(t1Observable, observable2, observable3, func);
}
};
}
@Nonnull
private static <T> Observable<ResponseOrError<T>> repeatOnError(
@Nonnull final Observable<ResponseOrError<T>> from,
@Nonnull final Scheduler scheduler) {
return OnSubscribeRedoWithNext.repeat(from, new Func1<Observable<? extends Notification<ResponseOrError<T>>>, Observable<?>>() {
@Override
public Observable<?> call(Observable<? extends Notification<ResponseOrError<T>>> observable) {
return observable
.filter(new Func1<Notification<ResponseOrError<T>>, Boolean>() {
@Override
public Boolean call(Notification<ResponseOrError<T>> responseOrErrorNotification) {
return responseOrErrorNotification.isOnNext();
}
})
.map(new Func1<Notification<ResponseOrError<T>>, ResponseOrError<T>>() {
@Override
public ResponseOrError<T> call(Notification<ResponseOrError<T>> responseOrErrorNotification) {
return responseOrErrorNotification.getValue();
}
})
.scan(0, new Func2<Integer, ResponseOrError<T>, Integer>() {
@Override
public Integer call(Integer integer, ResponseOrError<T> tResponseOrError) {
if (tResponseOrError.isData()) {
return 0;
} else {
if (integer == 0) {
return 1;
} else {
return integer * 2;
}
}
}
})
.switchMap(new Func1<Integer, Observable<?>>() {
@Override
public Observable<?> call(Integer integer) {
if (integer == 0) {
return Observable.never();
}
return Observable.timer(integer, TimeUnit.SECONDS, scheduler);
}
});
}
});
}
@Nonnull
private static <T> Observable<ResponseOrError<T>> repeatOnErrorOrNetwork(
@Nonnull final Observable<ResponseOrError<T>> from,
@Nonnull final NetworkObservableProvider networkObservableProvider,
@Nonnull final Scheduler scheduler) {
return OnSubscribeRedoWithNext.repeat(from, new Func1<Observable<? extends Notification<ResponseOrError<T>>>, Observable<?>>() {
@Override
public Observable<?> call(Observable<? extends Notification<ResponseOrError<T>>> observable) {
return observable
.filter(new Func1<Notification<ResponseOrError<T>>, Boolean>() {
@Override
public Boolean call(Notification<ResponseOrError<T>> responseOrErrorNotification) {
return responseOrErrorNotification.isOnNext();
}
})
.map(new Func1<Notification<ResponseOrError<T>>, ResponseOrError<T>>() {
@Override
public ResponseOrError<T> call(Notification<ResponseOrError<T>> responseOrErrorNotification) {
return responseOrErrorNotification.getValue();
}
})
.scan(0, new Func2<Integer, ResponseOrError<T>, Integer>() {
@Override
public Integer call(Integer integer, ResponseOrError<T> tResponseOrError) {
if (tResponseOrError.isData()) {
return 0;
} else {
if (integer == 0) {
return 1;
} else {
return integer * 2;
}
}
}
})
.switchMap(new Func1<Integer, Observable<?>>() {
@Override
public Observable<?> call(Integer integer) {
if (integer == 0) {
return Observable.never();
}
final Observable<NetworkObservableProvider.NetworkStatus> networkBecomeActive = networkObservableProvider
.networkObservable()
.skip(1)
.filter(new Func1<NetworkObservableProvider.NetworkStatus, Boolean>() {
@Override
public Boolean call(NetworkObservableProvider.NetworkStatus networkStatus) {
return networkStatus.isNetwork();
}
});
final Observable<Long> timeout = Observable.timer(integer, TimeUnit.SECONDS, scheduler);
return Observable.amb(networkBecomeActive, timeout);
}
});
}
});
}
@Nonnull
public static <T> Observable.Operator<T, T> callOnNext(@Nonnull Observer<? super T> events) {
return new OperatorCallOnNext<>(events);
}
/**
* @deprecated use {@link Observable#ignoreElements()} instead.
*/
@Deprecated
@Nonnull
public static <T> Observable.Operator<T, T> ignoreNext() {
return new Observable.Operator<T, T>() {
@Override
public Subscriber<? super T> call(final Subscriber<? super T> subscriber) {
return new Subscriber<T>(subscriber) {
@Override
public void onCompleted() {
subscriber.onCompleted();
}
@Override
public void onError(Throwable e) {
subscriber.onError(e);
}
@Override
public void onNext(T obj) {}
};
}
};
}
@Nonnull
public static <T> Observable.Transformer<Object, T> filterAndMap(@Nonnull final Class<T> clazz) {
return new Observable.Transformer<Object, T>() {
@Override
public Observable<T> call(Observable<Object> observable) {
return observable
.filter(new Func1<Object, Boolean>() {
@Override
public Boolean call(Object o) {
return o != null && clazz.isInstance(o);
}
})
.map(new Func1<Object, T>() {
@Override
public T call(Object o) {
//noinspection unchecked
return (T) o;
}
});
}
};
}
@Nonnull
public static Func1<Throwable, Object> throwableToIgnoreError() {
return new Func1<Throwable, Object>() {
@Override
public Object call(Throwable throwable) {
return new Object();
}
};
}
@Nonnull
public static <T> Observable.OnSubscribe<T> fromAction(@Nonnull final Func0<T> call) {
return new Observable.OnSubscribe<T>() {
@Override
public void call(final Subscriber<? super T> child) {
final AtomicBoolean produce = new AtomicBoolean(true);
child.setProducer(new Producer() {
@Override
public void request(long n) {
if (n <= 0) {
return;
}
if (produce.getAndSet(false)) {
produceValue(child);
}
}
});
}
private void produceValue(Subscriber<? super T> child) {
try {
if (child.isUnsubscribed()) {
return;
}
final T result = call.call();
if (child.isUnsubscribed()) {
return;
}
child.onNext(result);
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
child.onError(OnErrorThrowable.addValueAsLastCause(e, call));
}
if (child.isUnsubscribed()) {
return;
}
child.onCompleted();
}
};
}
@Nonnull
public static <T> Observable.Transformer<List<Observable<T>>, List<T>> newCombineAll() {
return new Observable.Transformer<List<Observable<T>>, List<T>>() {
@Override
public Observable<List<T>> call(Observable<List<Observable<T>>> listObservable) {
return listObservable.switchMap(new Func1<List<Observable<T>>, Observable<List<T>>>() {
@Override
public Observable<List<T>> call(List<Observable<T>> observables) {
return newCombineAll(observables);
}
});
}
};
}
private static int divCailing(int x, int y) {
return (x+y-1)/y;
}
@Nonnull
public static <T> Observable<List<T>> newCombineAll(@Nonnull List<Observable<T>> observables) {
if (observables.isEmpty()) {
return Observable.just(Collections.<T>emptyList());
}
if (observables.size() > RxRingBuffer.SIZE) {
// RxJava has some limitation that can only handle up to 128 arguments in combineLast
// Additionally on android there is a bug so this limit is cut to 16 arguments - on
// android we will not get any throw so rxjava fail sailent
final int size = observables.size();
// we divide to multiple buckets
// i.e. for RxRingBuffer.SIZE = 3 and observables.size() == 10
// 10
// 3 3 4
// 1 1 1 1 1 1 2 2
// 1 1 1 1
final int buckets = Math.min(RxRingBuffer.SIZE, divCailing(size, RxRingBuffer.SIZE));
final List<Observable<List<T>>> observableList = new ArrayList<>();
final int bucketSize = divCailing(size, buckets);
for (int bucket = 0; bucket < buckets; bucket++) {
int start = bucket * bucketSize;
int end = Math.min(size, start + bucketSize);
final Observable<List<T>> observable = newCombineAll(observables.subList(start, end))
// use onBackpressureLatest to be safe if we are not fast enough consuming events
.onBackpressureLatest();
observableList.add(observable);
}
return Observable.combineLatest(observableList, new FuncN<List<T>>() {
@Override
public List<T> call(Object... args) {
final ArrayList<T> ret = new ArrayList<>(args.length);
for (Object arg : args) {
//noinspection unchecked
ret.addAll((List<T>) arg);
}
return Collections.unmodifiableList(ret);
}
});
}
return Observable.combineLatest(observables, new FuncN<List<T>>() {
@Override
public List<T> call(final Object... args) {
final ArrayList<T> ret = new ArrayList<>(args.length);
for (Object arg : args) {
//noinspection unchecked
ret.add((T) arg);
}
return Collections.unmodifiableList(ret);
}
});
}
public static final int FRAME_PERIOD = 16;
@Nonnull
public static <T> Observable.Transformer<T, T> animatorCompose(
@Nonnull final Scheduler scheduler,
final long period, final TimeUnit timeUnit,
@Nonnull final TypeEvaluator<T> evaluator) {
return new Observable.Transformer<T, T>() {
@Override
public Observable<T> call(final Observable<T> integerObservable) {
final long periodInMillis = timeUnit.toMillis(period);
return Observable.create(new Observable.OnSubscribe<T>() {
T prevValue;
@Override
public void call(final Subscriber<? super T> child) {
final Scheduler.Worker worker = scheduler.createWorker();
child.add(worker);
final Subscription sub = integerObservable.subscribe(new Observer<T>() {
private Subscription subscription;
@Override
public void onCompleted() {
}
@Override
public void onError(final Throwable e) {
child.onError(e);
}
@Override
public void onNext(final T endValue) {
if (subscription != null) {
subscription.unsubscribe();
subscription = null;
}
if (prevValue == null) {
prevValue = endValue;
child.onNext(endValue);
} else if (!prevValue.equals(endValue)) {
subscription = worker.schedulePeriodically(new Action0() {
long startTime = scheduler.now();
final T startValue = prevValue;
@Override
public void call() {
float percent = (scheduler.now() - startTime) / (float)periodInMillis;
prevValue = evaluator.evaluate(Math.min(percent, 1.0f), startValue, endValue);
child.onNext(prevValue);
if (percent >= 1.0f) {
if (subscription != null) {
subscription.unsubscribe();
subscription = null;
}
}
}
}, FRAME_PERIOD, FRAME_PERIOD, TimeUnit.MILLISECONDS);
}
}
});
child.add(Subscriptions.create(new Action0() {
@Override
public void call() {
sub.unsubscribe();
}
}));
}
});
}
};
}
private static class State<T, R> {
@Nonnull
private final Map<T, Observable<R>> items;
@Nonnull
private final List<Observable<R>> observables;
State(
@Nonnull Map<T, Observable<R>> items,
@Nonnull List<Observable<R>> observables) {
this.items = items;
this.observables = observables;
}
State() {
this(Collections.<T, Observable<R>>emptyMap(), Collections.<Observable<R>>emptyList());
}
}
/**
* Operator use func to covert T element in to list of R by applying Observable
* @param func fuc that convert T to Observable of R
* @param <T> input type
* @param <R> output type
* @return transformer
*/
public static <T, R> Observable.Transformer<? super List<T>, List<R>> observableSwitch(final Func1<T, Observable<R>> func) {
return new Observable.Transformer<List<T>, List<R>>() {
@Override
public Observable<List<R>> call(Observable<List<T>> listObservable) {
return listObservable
.serialize()
.scan(new State<T, R>(), new Func2<State<T, R>, List<T>, State<T, R>>() {
@Override
public State<T, R> call(State<T, R> state, List<T> ts) {
final HashMap<T, Observable<R>> items = new HashMap<>();
final ArrayList<Observable<R>> observables = new ArrayList<>(ts.size());
for (T t : ts) {
final Observable<R> fromMap = state.items.get(t);
final Observable<R> observable = fromMap != null ? fromMap : func.call(t).replay(1).refCount();
observables.add(observable);
items.put(t, observable);
}
return new State<T, R>(items, observables);
}
})
.skip(1)
.map(new Func1<State<T, R>, Observable<List<R>>>() {
@Override
public Observable<List<R>> call(State<T, R> rState) {
return MoreOperators.newCombineAll(rState.observables);
}
})
.lift(OperatorSwitchThenUnsubscribe.<List<R>>instance());
}
};
}
}