Please wait. This can take some minutes ...
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.
hu.akarnokd.rxjava2.subjects.ReplaySubject Maven / Gradle / Ivy
/**
* Copyright 2015 David Karnok and 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 hu.akarnokd.rxjava2.subjects;
import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.*;
import org.reactivestreams.*;
import hu.akarnokd.rxjava2.Scheduler;
import hu.akarnokd.rxjava2.internal.functions.Objects;
import hu.akarnokd.rxjava2.internal.subscriptions.SubscriptionHelper;
import hu.akarnokd.rxjava2.internal.util.*;
import hu.akarnokd.rxjava2.plugins.RxJavaPlugins;
import hu.akarnokd.rxjava2.schedulers.Schedulers;
/**
* Replays events to Subscribers.
*
* This Subject respects the backpressure behavior of its Subscribers (individually).
*
* @param the value type
*/
public final class ReplaySubject extends Subject {
public static ReplaySubject create() {
return create(16);
}
public static ReplaySubject create(int capacityHint) {
if (capacityHint <= 0) {
throw new IllegalArgumentException("capacityHint > 0 required but it was " + capacityHint);
}
ReplayBuffer buffer = new UnboundedReplayBuffer(capacityHint);
return createWithBuffer(buffer);
}
public static ReplaySubject createWithSize(int size) {
if (size <= 0) {
throw new IllegalArgumentException("size > 0 required but it was " + size);
}
SizeBoundReplayBuffer buffer = new SizeBoundReplayBuffer(size);
return createWithBuffer(buffer);
}
/* test */ static ReplaySubject createUnbounded() {
SizeBoundReplayBuffer buffer = new SizeBoundReplayBuffer(Integer.MAX_VALUE);
return createWithBuffer(buffer);
}
public static ReplaySubject createWithTime(long maxAge, TimeUnit unit) {
return createWithTime(maxAge, unit, Schedulers.trampoline());
}
public static ReplaySubject createWithTime(long maxAge, TimeUnit unit, Scheduler scheduler) {
return createWithTimeAndSize(maxAge, unit, scheduler, Integer.MAX_VALUE);
}
public static ReplaySubject createWithTimeAndSize(long maxAge, TimeUnit unit, Scheduler scheduler, int size) {
Objects.requireNonNull(unit, "unit is null");
Objects.requireNonNull(scheduler, "scheduler is null");
if (size <= 0) {
throw new IllegalArgumentException("size > 0 required but it was " + size);
}
SizeAndTimeBoundReplayBuffer buffer = new SizeAndTimeBoundReplayBuffer(size, maxAge, unit, scheduler);
return createWithBuffer(buffer);
}
static ReplaySubject createWithBuffer(ReplayBuffer buffer) {
State state = new State(buffer);
return new ReplaySubject(state);
}
final State state;
protected ReplaySubject(State state) {
super(state);
this.state = state;
}
@Override
public void onSubscribe(Subscription s) {
state.onSubscribe(s);
}
@Override
public void onNext(T t) {
if (t == null) {
onError(new NullPointerException());
return;
}
state.onNext(t);
}
@Override
public void onError(Throwable t) {
if (t == null) {
t = new NullPointerException();
}
state.onError(t);
}
@Override
public void onComplete() {
state.onComplete();
}
@Override
public boolean hasSubscribers() {
return state.subscribers.length != 0;
}
/* test */ int subscriberCount() {
return state.subscribers.length;
}
@Override
public Throwable getThrowable() {
Object o = state.get();
if (NotificationLite.isError(o)) {
return NotificationLite.getError(o);
}
return null;
}
@Override
public T getValue() {
return state.buffer.getValue();
}
@Override
public T[] getValues(T[] array) {
return state.buffer.getValues(array);
}
@Override
public boolean hasComplete() {
Object o = state.get();
return NotificationLite.isComplete(o);
}
@Override
public boolean hasThrowable() {
Object o = state.get();
return NotificationLite.isError(o);
}
@Override
public boolean hasValue() {
return state.buffer.size() != 0;
}
/* test*/ int size() {
return state.buffer.size();
}
static final class State extends AtomicReference implements Publisher, Subscriber {
/** */
private static final long serialVersionUID = -4673197222000219014L;
final ReplayBuffer buffer;
boolean done;
volatile ReplaySubscription[] subscribers;
@SuppressWarnings("rawtypes")
static final AtomicReferenceFieldUpdater SUBSCRIBERS =
AtomicReferenceFieldUpdater.newUpdater(State.class, ReplaySubscription[].class, "subscribers");
@SuppressWarnings("rawtypes")
static final ReplaySubscription[] EMPTY = new ReplaySubscription[0];
@SuppressWarnings("rawtypes")
static final ReplaySubscription[] TERMINATED = new ReplaySubscription[0];
public State(ReplayBuffer buffer) {
this.buffer = buffer;
SUBSCRIBERS.lazySet(this, EMPTY);
}
@Override
public void subscribe(Subscriber super T> s) {
ReplaySubscription rs = new ReplaySubscription(s, this);
s.onSubscribe(rs);
if (!rs.cancelled) {
if (add(rs)) {
if (rs.cancelled) {
remove(rs);
return;
}
}
buffer.replay(rs);
}
}
public boolean add(ReplaySubscription rs) {
for (;;) {
ReplaySubscription[] a = subscribers;
if (a == TERMINATED) {
return false;
}
int len = a.length;
@SuppressWarnings("unchecked")
ReplaySubscription[] b = new ReplaySubscription[len + 1];
System.arraycopy(a, 0, b, 0, len);
b[len] = rs;
if (SUBSCRIBERS.compareAndSet(this, a, b)) {
return true;
}
}
}
@SuppressWarnings("unchecked")
public void remove(ReplaySubscription rs) {
for (;;) {
ReplaySubscription[] a = subscribers;
if (a == TERMINATED || a == EMPTY) {
return;
}
int len = a.length;
int j = -1;
for (int i = 0; i < len; i++) {
if (a[i] == rs) {
j = i;
break;
}
}
if (j < 0) {
return;
}
ReplaySubscription[] b;
if (len == 1) {
b = EMPTY;
} else {
b = new ReplaySubscription[len - 1];
System.arraycopy(a, 0, b, 0, j);
System.arraycopy(a, j + 1, b, j, len - j - 1);
}
if (SUBSCRIBERS.compareAndSet(this, a, b)) {
return;
}
}
}
@SuppressWarnings("unchecked")
public ReplaySubscription[] terminate(Object terminalValue) {
if (compareAndSet(null, terminalValue)) {
return SUBSCRIBERS.getAndSet(this, TERMINATED);
}
return TERMINATED;
}
@Override
public void onSubscribe(Subscription s) {
if (done) {
s.cancel();
return;
}
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(T t) {
if (done) {
return;
}
ReplayBuffer b = buffer;
b.add(t);
for (ReplaySubscription rs : subscribers) {
b.replay(rs);
}
}
@Override
public void onError(Throwable t) {
if (done) {
RxJavaPlugins.onError(t);
return;
}
done = true;
Object o = NotificationLite.error(t);
ReplayBuffer b = buffer;
b.addFinal(o);
for (ReplaySubscription rs : terminate(o)) {
b.replay(rs);
}
}
@Override
public void onComplete() {
if (done) {
return;
}
done = true;
Object o = NotificationLite.complete();
ReplayBuffer b = buffer;
b.addFinal(o);
for (ReplaySubscription rs : terminate(o)) {
b.replay(rs);
}
}
}
interface ReplayBuffer {
void add(T value);
void addFinal(Object notificationLite);
void replay(ReplaySubscription rs);
int size();
T getValue();
T[] getValues(T[] array);
}
static final class ReplaySubscription extends AtomicInteger implements Subscription {
/** */
private static final long serialVersionUID = 466549804534799122L;
final Subscriber super T> actual;
final State state;
Object index;
volatile long requested;
@SuppressWarnings("rawtypes")
static final AtomicLongFieldUpdater REQUESTED =
AtomicLongFieldUpdater.newUpdater(ReplaySubscription.class, "requested");
volatile boolean cancelled;
public ReplaySubscription(Subscriber super T> actual, State state) {
this.actual = actual;
this.state = state;
}
@Override
public void request(long n) {
if (SubscriptionHelper.validateRequest(n)) {
return;
}
BackpressureHelper.add(REQUESTED, this, n);
state.buffer.replay(this);
}
@Override
public void cancel() {
if (!cancelled) {
cancelled = true;
state.remove(this);
}
}
}
static final class UnboundedReplayBuffer implements ReplayBuffer {
final List buffer;
volatile boolean done;
volatile int size;
public UnboundedReplayBuffer(int capacityHint) {
this.buffer = new ArrayList(capacityHint);
}
@Override
public void add(T value) {
buffer.add(value);
size++;
}
@Override
public void addFinal(Object notificationLite) {
buffer.add(notificationLite);
size++;
done = true;
}
@Override
@SuppressWarnings("unchecked")
public T getValue() {
int s = size;
if (s != 0) {
List b = buffer;
Object o = b.get(s - 1);
if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) {
if (s == 1) {
return null;
}
return (T)b.get(s - 2);
}
return (T)o;
}
return null;
}
@Override
@SuppressWarnings("unchecked")
public T[] getValues(T[] array) {
int s = size;
if (s == 0) {
if (array.length != 0) {
array[0] = null;
}
return array;
}
List b = buffer;
Object o = b.get(s - 1);
if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) {
s--;
if (s == 0) {
if (array.length != 0) {
array[0] = null;
}
return array;
}
}
if (array.length < s) {
array = (T[])Array.newInstance(array.getClass().getComponentType(), s);
}
for (int i = 0; i < s; i++) {
array[i] = (T)b.get(i);
}
if (array.length > s) {
array[s] = null;
}
return array;
}
@Override
@SuppressWarnings("unchecked")
public void replay(ReplaySubscription rs) {
if (rs.getAndIncrement() != 0) {
return;
}
int missed = 1;
final List b = buffer;
final Subscriber super T> a = rs.actual;
Integer indexObject = (Integer)rs.index;
int index = 0;
if (indexObject != null) {
index = indexObject;
} else {
index = 0;
rs.index = 0;
}
for (;;) {
if (rs.cancelled) {
rs.index = null;
return;
}
int s = size;
long r = rs.requested;
boolean unbounded = r == Long.MAX_VALUE;
long e = 0L;
while (s != index) {
if (rs.cancelled) {
rs.index = null;
return;
}
Object o = b.get(index);
if (done) {
if (index + 1 == s) {
s = size;
if (index + 1 == s) {
if (NotificationLite.isComplete(o)) {
a.onComplete();
} else {
a.onError(NotificationLite.getError(o));
}
rs.index = null;
rs.cancelled = true;
return;
}
}
}
if (r == 0) {
r = rs.requested + e;
if (r == 0) {
break;
}
}
a.onNext((T)o);
r--;
e--;
index++;
}
if (e != 0L) {
if (!unbounded) {
r = ReplaySubscription.REQUESTED.addAndGet(rs, e);
}
}
if (index != size && r != 0L) {
continue;
}
rs.index = index;
missed = rs.addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
@Override
public int size() {
int s = size;
if (s != 0) {
Object o = buffer.get(s - 1);
if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) {
return s - 1;
}
return s;
}
return 0;
}
}
static final class Node extends AtomicReference> {
/** */
private static final long serialVersionUID = 6404226426336033100L;
final T value;
public Node(T value) {
this.value = value;
}
}
static final class TimedNode extends AtomicReference> {
/** */
private static final long serialVersionUID = 6404226426336033100L;
final T value;
final long time;
public TimedNode(T value, long time) {
this.value = value;
this.time = time;
}
}
static final class SizeBoundReplayBuffer implements ReplayBuffer {
final int maxSize;
int size;
volatile Node head;
Node tail;
volatile boolean done;
public SizeBoundReplayBuffer(int maxSize) {
this.maxSize = maxSize;
Node h = new Node(null);
this.tail = h;
this.head = h;
}
void trim() {
if (size > maxSize) {
size--;
Node h = head;
head = h.get();
}
}
@Override
public void add(T value) {
Object o = value;
Node n = new Node(o);
Node t = tail;
tail = n;
size++;
/*
* FIXME not sure why lazySet doesn't work here
* (testReplaySubjectEmissionSubscriptionRace hangs)
* must be the lack of StoreLoad barrier?
*/
t.set(n); // releases both the tail and size
trim();
}
@Override
public void addFinal(Object notificationLite) {
Object o = notificationLite;
Node n = new Node(o);
Node t = tail;
tail = n;
size++;
t.lazySet(n); // releases both the tail and size
done = true;
}
@Override
@SuppressWarnings("unchecked")
public T getValue() {
Node prev = null;
Node h = head;
for (;;) {
Node next = h.get();
if (next == null) {
break;
}
prev = h;
h = next;
}
Object v = h.value;
if (v == null) {
return null;
}
if (NotificationLite.isComplete(v) || NotificationLite.isError(v)) {
return (T)prev.value;
}
return (T)v;
}
@Override
@SuppressWarnings("unchecked")
public T[] getValues(T[] array) {
Node h = head;
int s = size();
if (s == 0) {
if (array.length != 0) {
array[0] = null;
}
} else {
if (array.length < s) {
array = (T[])Array.newInstance(array.getClass().getComponentType(), s);
}
int i = 0;
while (i != s) {
Node next = h.get();
array[i] = (T)next.value;
i++;
h = next;
}
if (array.length > s) {
array[s] = null;
}
}
return array;
}
@Override
@SuppressWarnings("unchecked")
public void replay(ReplaySubscription rs) {
if (rs.getAndIncrement() != 0) {
return;
}
int missed = 1;
final Subscriber super T> a = rs.actual;
Node index = (Node)rs.index;
if (index == null) {
index = head;
}
for (;;) {
if (rs.cancelled) {
rs.index = null;
return;
}
long r = rs.requested;
boolean unbounded = r == Long.MAX_VALUE;
long e = 0;
for (;;) {
if (rs.cancelled) {
rs.index = null;
return;
}
Node n = index.get();
if (n == null) {
break;
}
Object o = n.value;
if (done) {
if (n.get() == null) {
if (NotificationLite.isComplete(o)) {
a.onComplete();
} else {
a.onError(NotificationLite.getError(o));
}
rs.index = null;
rs.cancelled = true;
return;
}
}
if (r == 0) {
r = rs.requested + e;
if (r == 0) {
break;
}
}
a.onNext((T)o);
r--;
e--;
index = n;
}
if (e != 0L) {
if (!unbounded) {
r = ReplaySubscription.REQUESTED.addAndGet(rs, e);
}
}
if (index.get() != null && r != 0L) {
continue;
}
rs.index = index;
missed = rs.addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
@Override
public int size() {
int s = 0;
Node h = head;
while (s != Integer.MAX_VALUE) {
Node next = h.get();
if (next == null) {
Object o = h.value;
if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) {
s--;
}
break;
}
s++;
h = next;
}
return s;
}
}
static final class SizeAndTimeBoundReplayBuffer implements ReplayBuffer {
final int maxSize;
final long maxAge;
final TimeUnit unit;
final Scheduler scheduler;
int size;
volatile TimedNode head;
TimedNode tail;
volatile boolean done;
public SizeAndTimeBoundReplayBuffer(int maxSize, long maxAge, TimeUnit unit, Scheduler scheduler) {
this.maxSize = maxSize;
this.maxAge = maxAge;
this.unit = unit;
this.scheduler = scheduler;
TimedNode h = new TimedNode(null, 0L);
this.tail = h;
this.head = h;
}
void trim() {
if (size > maxSize) {
size--;
TimedNode h = head;
head = h.get();
}
long limit = scheduler.now(unit) - maxAge;
TimedNode h = head;
for (;;) {
TimedNode next = h.get();
if (next == null) {
head = h;
break;
}
if (next.time > limit) {
head = h;
break;
}
h = next;
}
}
void trimFinal() {
long limit = scheduler.now(unit) - maxAge;
TimedNode h = head;
for (;;) {
TimedNode next = h.get();
if (next.get() == null) {
head = h;
break;
}
if (next.time > limit) {
head = h;
break;
}
h = next;
}
}
@Override
public void add(T value) {
Object o = value;
TimedNode n = new TimedNode(o, scheduler.now(unit));
TimedNode t = tail;
tail = n;
size++;
/*
* FIXME not sure why lazySet doesn't work here
* (testReplaySubjectEmissionSubscriptionRace hangs)
* must be the lack of StoreLoad barrier?
*/
t.set(n); // releases both the tail and size
trim();
}
@Override
public void addFinal(Object notificationLite) {
Object o = notificationLite;
TimedNode n = new TimedNode(o, Long.MAX_VALUE);
TimedNode t = tail;
tail = n;
size++;
t.lazySet(n); // releases both the tail and size
trimFinal();
done = true;
}
@Override
@SuppressWarnings("unchecked")
public T getValue() {
TimedNode prev = null;
TimedNode h = head;
for (;;) {
TimedNode next = h.get();
if (next == null) {
break;
}
prev = h;
h = next;
}
Object v = h.value;
if (v == null) {
return null;
}
if (NotificationLite.isComplete(v) || NotificationLite.isError(v)) {
return (T)prev.value;
}
return (T)v;
}
@Override
@SuppressWarnings("unchecked")
public T[] getValues(T[] array) {
TimedNode h = head;
int s = size();
if (s == 0) {
if (array.length != 0) {
array[0] = null;
}
} else {
if (array.length < s) {
array = (T[])Array.newInstance(array.getClass().getComponentType(), s);
}
int i = 0;
while (i != s) {
TimedNode next = h.get();
array[i] = (T)next.value;
i++;
h = next;
}
if (array.length > s) {
array[s] = null;
}
}
return array;
}
@Override
@SuppressWarnings("unchecked")
public void replay(ReplaySubscription rs) {
if (rs.getAndIncrement() != 0) {
return;
}
int missed = 1;
final Subscriber super T> a = rs.actual;
TimedNode index = (TimedNode)rs.index;
if (index == null) {
index = head;
if (!done) {
// skip old entries
long limit = scheduler.now(unit) - maxAge;
TimedNode next = index.get();
while (next != null) {
long ts = next.time;
if (ts > limit) {
break;
}
index = next;
next = index.get();
}
}
}
for (;;) {
if (rs.cancelled) {
rs.index = null;
return;
}
long r = rs.requested;
boolean unbounded = r == Long.MAX_VALUE;
long e = 0;
for (;;) {
if (rs.cancelled) {
rs.index = null;
return;
}
TimedNode n = index.get();
if (n == null) {
break;
}
Object o = n.value;
if (done) {
if (n.get() == null) {
if (NotificationLite.isComplete(o)) {
a.onComplete();
} else {
a.onError(NotificationLite.getError(o));
}
rs.index = null;
rs.cancelled = true;
return;
}
}
if (r == 0) {
r = rs.requested + e;
if (r == 0) {
break;
}
}
a.onNext((T)o);
r--;
e--;
index = n;
}
if (e != 0L) {
if (!unbounded) {
r = ReplaySubscription.REQUESTED.addAndGet(rs, e);
}
}
if (index.get() != null && r != 0L) {
continue;
}
rs.index = index;
missed = rs.addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
@Override
public int size() {
int s = 0;
TimedNode h = head;
while (s != Integer.MAX_VALUE) {
TimedNode next = h.get();
if (next == null) {
Object o = h.value;
if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) {
s--;
}
break;
}
s++;
h = next;
}
return s;
}
}
}