/**
* 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.internal.util;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import rx.Subscription;
import rx.functions.Func1;
/**
* Similar to CompositeSubscription but giving extra access to internals so we can reuse a datastructure.
* <p>
* NOTE: This purposefully is leaking the internal data structure through the API for efficiency reasons to avoid extra object allocations.
*/
public final class SubscriptionIndexedRingBuffer<T extends Subscription> implements Subscription {
@SuppressWarnings("unchecked")
private volatile IndexedRingBuffer<T> subscriptions = IndexedRingBuffer.getInstance();
private volatile int unsubscribed = 0;
@SuppressWarnings("rawtypes")
private final static AtomicIntegerFieldUpdater<SubscriptionIndexedRingBuffer> UNSUBSCRIBED = AtomicIntegerFieldUpdater.newUpdater(SubscriptionIndexedRingBuffer.class, "unsubscribed");
public SubscriptionIndexedRingBuffer() {
}
public SubscriptionIndexedRingBuffer(final T... subscriptions) {
for (T t : subscriptions) {
this.subscriptions.add(t);
}
}
@Override
public boolean isUnsubscribed() {
return unsubscribed == 1;
}
/**
* Adds a new {@link Subscription} to this {@code CompositeSubscription} if the {@code CompositeSubscription} is not yet unsubscribed. If the {@code CompositeSubscription} <em>is</em>
* unsubscribed, {@code add} will indicate this by explicitly unsubscribing the new {@code Subscription} as
* well.
*
* @param s
* the {@link Subscription} to add
*
* @return int index that can be used to remove a Subscription
*/
public synchronized int add(final T s) {
// TODO figure out how to remove synchronized here. See https://github.com/Netflix/RxJava/issues/1420
if (unsubscribed == 1 || subscriptions == null) {
s.unsubscribe();
return -1;
} else {
int n = subscriptions.add(s);
// double check for race condition
if (unsubscribed == 1) {
s.unsubscribe();
}
return n;
}
}
/**
* Uses the Node received from `add` to remove this Subscription.
* <p>
* Unsubscribes the Subscription after removal
*/
public void remove(final int n) {
if (unsubscribed == 1 || subscriptions == null || n < 0) {
return;
}
Subscription t = subscriptions.remove(n);
if (t != null) {
// if we removed successfully we then need to call unsubscribe on it
if (t != null) {
t.unsubscribe();
}
}
}
/**
* Uses the Node received from `add` to remove this Subscription.
* <p>
* Does not unsubscribe the Subscription after removal.
*/
public void removeSilently(final int n) {
if (unsubscribed == 1 || subscriptions == null || n < 0) {
return;
}
subscriptions.remove(n);
}
@Override
public void unsubscribe() {
if (UNSUBSCRIBED.compareAndSet(this, 0, 1) && subscriptions != null) {
// we will only get here once
unsubscribeFromAll(subscriptions);
IndexedRingBuffer<T> s = subscriptions;
subscriptions = null;
s.unsubscribe();
}
}
public int forEach(Func1<T, Boolean> action) {
return forEach(action, 0);
}
/**
*
* @param action
* @return int of last index seen if forEach exited early
*/
public synchronized int forEach(Func1<T, Boolean> action, int startIndex) {
// TODO figure out how to remove synchronized here. See https://github.com/Netflix/RxJava/issues/1420
if (unsubscribed == 1 || subscriptions == null) {
return 0;
}
return subscriptions.forEach(action, startIndex);
}
private static void unsubscribeFromAll(IndexedRingBuffer<? extends Subscription> subscriptions) {
if (subscriptions == null) {
return;
}
// TODO migrate to drain (remove while we're doing this) so we don't have to immediately clear it in IndexedRingBuffer.releaseToPool?
subscriptions.forEach(UNSUBSCRIBE);
}
private final static Func1<Subscription, Boolean> UNSUBSCRIBE = new Func1<Subscription, Boolean>() {
@Override
public Boolean call(Subscription s) {
s.unsubscribe();
return Boolean.TRUE;
}
};
}