rx.operators.OperationTakeLast Maven / Gradle / Ivy
Show all versions of rxjava-core Show documentation
/**
* Copyright 2014 Netflix, Inc.
*
* 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 rx.operators;
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import rx.Observable;
import rx.Observable.OnSubscribeFunc;
import rx.Observer;
import rx.Scheduler;
import rx.Subscription;
import rx.schedulers.Timestamped;
/**
* Returns an Observable that emits the last count
items emitted by the source
* Observable.
*
*
*/
public final class OperationTakeLast {
public static OnSubscribeFunc takeLast(final Observable extends T> items, final int count) {
return new OnSubscribeFunc() {
@Override
public Subscription onSubscribe(Observer super T> observer) {
return new TakeLast(items, count).onSubscribe(observer);
}
};
}
private static class TakeLast implements OnSubscribeFunc {
private final int count;
private final Observable extends T> items;
private final SafeObservableSubscription subscription = new SafeObservableSubscription();
TakeLast(final Observable extends T> items, final int count) {
this.count = count;
this.items = items;
}
public Subscription onSubscribe(Observer super T> observer) {
if (count < 0) {
throw new IndexOutOfBoundsException(
"count could not be negative");
}
return subscription.wrap(items.subscribe(new ItemObserver(observer)));
}
private class ItemObserver implements Observer {
/**
* Store the last count elements until now.
*/
private Deque deque = new LinkedList();
private final Observer super T> observer;
private final ReentrantLock lock = new ReentrantLock();
public ItemObserver(Observer super T> observer) {
this.observer = observer;
}
@Override
public void onCompleted() {
try {
for (T value : deque) {
observer.onNext(value);
}
observer.onCompleted();
} catch (Throwable e) {
observer.onError(e);
}
}
@Override
public void onError(Throwable e) {
observer.onError(e);
}
@Override
public void onNext(T value) {
if (count == 0) {
// If count == 0, we do not need to put value into deque and
// remove it at once. We can ignore the value directly.
return;
}
lock.lock();
try {
deque.offerLast(value);
if (deque.size() > count) {
// Now deque has count + 1 elements, so the first
// element in the deque definitely does not belong
// to the last count elements of the source
// sequence. We can drop it now.
deque.removeFirst();
}
} catch (Throwable e) {
observer.onError(e);
subscription.unsubscribe();
} finally {
lock.unlock();
}
}
}
}
/**
* Returns the items emitted by source whose arrived in the time window
* before the source completed.
*/
public static OnSubscribeFunc takeLast(Observable extends T> source, long time, TimeUnit unit, Scheduler scheduler) {
return new TakeLastTimed(source, -1, time, unit, scheduler);
}
/**
* Returns the items emitted by source whose arrived in the time window
* before the source completed and at most count values.
*/
public static OnSubscribeFunc takeLast(Observable extends T> source, int count, long time, TimeUnit unit, Scheduler scheduler) {
return new TakeLastTimed(source, count, time, unit, scheduler);
}
/** Take only the values which appeared some time before the completion. */
static final class TakeLastTimed implements OnSubscribeFunc {
final Observable extends T> source;
final long ageMillis;
final Scheduler scheduler;
final int count;
public TakeLastTimed(Observable extends T> source, int count, long time, TimeUnit unit, Scheduler scheduler) {
this.source = source;
this.ageMillis = unit.toMillis(time);
this.scheduler = scheduler;
this.count = count;
}
@Override
public Subscription onSubscribe(Observer super T> t1) {
SafeObservableSubscription sas = new SafeObservableSubscription();
sas.wrap(source.subscribe(new TakeLastTimedObserver(t1, sas, count, ageMillis, scheduler)));
return sas;
}
}
/** Observes source values and keeps the most recent items. */
static final class TakeLastTimedObserver implements Observer {
final Observer super T> observer;
final Subscription cancel;
final long ageMillis;
final Scheduler scheduler;
/** -1 indicates unlimited buffer. */
final int count;
final Deque> buffer = new LinkedList>();
public TakeLastTimedObserver(Observer super T> observer, Subscription cancel,
int count, long ageMillis, Scheduler scheduler) {
this.observer = observer;
this.cancel = cancel;
this.ageMillis = ageMillis;
this.scheduler = scheduler;
this.count = count;
}
protected void runEvictionPolicy(long now) {
// trim size
while (count >= 0 && buffer.size() > count) {
buffer.pollFirst();
}
// remove old entries
while (!buffer.isEmpty()) {
Timestamped v = buffer.peekFirst();
if (v.getTimestampMillis() < now - ageMillis) {
buffer.pollFirst();
} else {
break;
}
}
}
@Override
public void onNext(T args) {
long t = scheduler.now();
buffer.add(new Timestamped(t, args));
runEvictionPolicy(t);
}
@Override
public void onError(Throwable e) {
buffer.clear();
observer.onError(e);
cancel.unsubscribe();
}
/**
* Emit the contents of the buffer.
*
* @return true if no exception was raised in the process
*/
protected boolean emitBuffer() {
for (Timestamped v : buffer) {
try {
observer.onNext(v.getValue());
} catch (Throwable t) {
buffer.clear();
observer.onError(t);
return false;
}
}
buffer.clear();
return true;
}
@Override
public void onCompleted() {
runEvictionPolicy(scheduler.now());
if (emitBuffer()) {
observer.onCompleted();
}
cancel.unsubscribe();
}
}
}