Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
* Copyright (c) 2016-present, RxJava Contributors.
*
* 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 io.reactivex.observable.internal.operators;
import java.util.concurrent.atomic.*;
import io.reactivex.common.Disposable;
import io.reactivex.common.internal.disposables.SequentialDisposable;
import io.reactivex.common.internal.functions.ObjectHelper;
import io.reactivex.common.internal.utils.LinkedArrayList;
import io.reactivex.observable.*;
import io.reactivex.observable.internal.utils.NotificationLite;
/**
* An observable which auto-connects to another observable, caches the elements
* from that observable but allows terminating the connection and completing the cache.
*
* @param the source element type
*/
public final class ObservableCache extends AbstractObservableWithUpstream {
/** The cache and replay state. */
final CacheState state;
final AtomicBoolean once;
/**
* Creates a cached Observable with a default capacity hint of 16.
* @param the value type
* @param source the source Observable to cache
* @return the CachedObservable instance
*/
public static Observable from(Observable source) {
return from(source, 16);
}
/**
* Creates a cached Observable with the given capacity hint.
* @param the value type
* @param source the source Observable to cache
* @param capacityHint the hint for the internal buffer size
* @return the CachedObservable instance
*/
public static Observable from(Observable source, int capacityHint) {
ObjectHelper.verifyPositive(capacityHint, "capacityHint");
CacheState state = new CacheState(source, capacityHint);
return RxJavaObservablePlugins.onAssembly(new ObservableCache(source, state));
}
/**
* Private constructor because state needs to be shared between the Observable body and
* the onSubscribe function.
* @param source the source Observable to cache
* @param state the cache state object
*/
private ObservableCache(Observable source, CacheState state) {
super(source);
this.state = state;
this.once = new AtomicBoolean();
}
@Override
protected void subscribeActual(Observer super T> t) {
// we can connect first because we replay everything anyway
ReplayDisposable rp = new ReplayDisposable(t, state);
t.onSubscribe(rp);
state.addChild(rp);
// we ensure a single connection here to save an instance field of AtomicBoolean in state.
if (!once.get() && once.compareAndSet(false, true)) {
state.connect();
}
rp.replay();
}
/**
* Check if this cached observable is connected to its source.
* @return true if already connected
*/
/* public */boolean isConnected() {
return state.isConnected;
}
/**
* Returns true if there are observers subscribed to this observable.
* @return true if the cache has downstream Observers
*/
/* public */ boolean hasObservers() {
return state.observers.get().length != 0;
}
/**
* Returns the number of events currently cached.
* @return the current number of elements in the cache
*/
/* public */ int cachedEventCount() {
return state.size();
}
/**
* Contains the active child observers and the values to replay.
*
* @param
*/
static final class CacheState extends LinkedArrayList implements Observer {
/** The source observable to connect to. */
final Observable extends T> source;
/** Holds onto the subscriber connected to source. */
final SequentialDisposable connection;
/** Guarded by connection (not this). */
final AtomicReference[]> observers;
/** The default empty array of observers. */
@SuppressWarnings("rawtypes")
static final ReplayDisposable[] EMPTY = new ReplayDisposable[0];
/** The default empty array of observers. */
@SuppressWarnings("rawtypes")
static final ReplayDisposable[] TERMINATED = new ReplayDisposable[0];
/** Set to true after connection. */
volatile boolean isConnected;
/**
* Indicates that the source has completed emitting values or the
* Observable was forcefully terminated.
*/
boolean sourceDone;
@SuppressWarnings("unchecked")
CacheState(Observable extends T> source, int capacityHint) {
super(capacityHint);
this.source = source;
this.observers = new AtomicReference[]>(EMPTY);
this.connection = new SequentialDisposable();
}
/**
* Adds a ReplayDisposable to the observers array atomically.
* @param p the target ReplayDisposable wrapping a downstream Observer with additional state
* @return true if the disposable was added, false otherwise
*/
public boolean addChild(ReplayDisposable p) {
// guarding by connection to save on allocating another object
// thus there are two distinct locks guarding the value-addition and child come-and-go
for (;;) {
ReplayDisposable[] a = observers.get();
if (a == TERMINATED) {
return false;
}
int n = a.length;
@SuppressWarnings("unchecked")
ReplayDisposable[] b = new ReplayDisposable[n + 1];
System.arraycopy(a, 0, b, 0, n);
b[n] = p;
if (observers.compareAndSet(a, b)) {
return true;
}
}
}
/**
* Removes the ReplayDisposable (if present) from the observers array atomically.
* @param p the target ReplayDisposable wrapping a downstream Observer with additional state
*/
@SuppressWarnings("unchecked")
public void removeChild(ReplayDisposable p) {
for (;;) {
ReplayDisposable[] a = observers.get();
int n = a.length;
if (n == 0) {
return;
}
int j = -1;
for (int i = 0; i < n; i++) {
if (a[i].equals(p)) {
j = i;
break;
}
}
if (j < 0) {
return;
}
ReplayDisposable[] b;
if (n == 1) {
b = EMPTY;
} else {
b = new ReplayDisposable[n - 1];
System.arraycopy(a, 0, b, 0, j);
System.arraycopy(a, j + 1, b, j, n - j - 1);
}
if (observers.compareAndSet(a, b)) {
return;
}
}
}
@Override
public void onSubscribe(Disposable s) {
connection.update(s);
}
/**
* Connects the cache to the source.
* Make sure this is called only once.
*/
public void connect() {
source.subscribe(this);
isConnected = true;
}
@Override
public void onNext(T t) {
if (!sourceDone) {
Object o = NotificationLite.next(t);
add(o);
for (ReplayDisposable> rp : observers.get()) {
rp.replay();
}
}
}
@SuppressWarnings("unchecked")
@Override
public void onError(Throwable e) {
if (!sourceDone) {
sourceDone = true;
Object o = NotificationLite.error(e);
add(o);
connection.dispose();
for (ReplayDisposable> rp : observers.getAndSet(TERMINATED)) {
rp.replay();
}
}
}
@SuppressWarnings("unchecked")
@Override
public void onComplete() {
if (!sourceDone) {
sourceDone = true;
Object o = NotificationLite.complete();
add(o);
connection.dispose();
for (ReplayDisposable> rp : observers.getAndSet(TERMINATED)) {
rp.replay();
}
}
}
}
/**
* Keeps track of the current request amount and the replay position for a child Observer.
*
* @param
*/
static final class ReplayDisposable
extends AtomicInteger
implements Disposable {
private static final long serialVersionUID = 7058506693698832024L;
/** The actual child subscriber. */
final Observer super T> child;
/** The cache state object. */
final CacheState state;
/**
* Contains the reference to the buffer segment in replay.
* Accessed after reading state.size() and when emitting == true.
*/
Object[] currentBuffer;
/**
* Contains the index into the currentBuffer where the next value is expected.
* Accessed after reading state.size() and when emitting == true.
*/
int currentIndexInBuffer;
/**
* Contains the absolute index up until the values have been replayed so far.
*/
int index;
/** Set if the ReplayDisposable has been cancelled/disposed. */
volatile boolean cancelled;
ReplayDisposable(Observer super T> child, CacheState state) {
this.child = child;
this.state = state;
}
@Override
public boolean isDisposed() {
return cancelled;
}
@Override
public void dispose() {
if (!cancelled) {
cancelled = true;
state.removeChild(this);
}
}
/**
* Continue replaying available values if there are requests for them.
*/
public void replay() {
// make sure there is only a single thread emitting
if (getAndIncrement() != 0) {
return;
}
final Observer super T> child = this.child;
int missed = 1;
for (;;) {
if (cancelled) {
return;
}
// read the size, if it is non-zero, we can safely read the head and
// read values up to the given absolute index
int s = state.size();
if (s != 0) {
Object[] b = currentBuffer;
// latch onto the very first buffer now that it is available.
if (b == null) {
b = state.head();
currentBuffer = b;
}
final int n = b.length - 1;
int j = index;
int k = currentIndexInBuffer;
while (j < s) {
if (cancelled) {
return;
}
if (k == n) {
b = (Object[])b[n];
k = 0;
}
Object o = b[k];
if (NotificationLite.accept(o, child)) {
return;
}
k++;
j++;
}
if (cancelled) {
return;
}
index = j;
currentIndexInBuffer = k;
currentBuffer = b;
}
missed = addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
}
}