All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hu.akarnokd.rxjava2.subjects.nbp.NbpBehaviorSubject Maven / Gradle / Ivy

There is a newer version: 2.0.0-RC3
Show newest version
/**
 * 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.nbp;

import java.lang.reflect.Array;
import java.util.concurrent.atomic.*;
import java.util.concurrent.locks.*;

import hu.akarnokd.rxjava2.disposables.Disposable;
import hu.akarnokd.rxjava2.functions.Predicate;
import hu.akarnokd.rxjava2.internal.functions.Objects;
import hu.akarnokd.rxjava2.internal.util.*;
import hu.akarnokd.rxjava2.plugins.RxJavaPlugins;

public final class NbpBehaviorSubject extends NbpSubject {

    public static  NbpBehaviorSubject create() {
        State state = new State();
        return new NbpBehaviorSubject(state);
    }
    
    // TODO a plain create() would create a method ambiguity with Observable.create with javac
    public static  NbpBehaviorSubject createDefault(T defaultValue) {
        Objects.requireNonNull(defaultValue, "defaultValue is null");
        State state = new State();
        state.lazySet(defaultValue);
        return new NbpBehaviorSubject(state);
    }
    
    final State state;
    protected NbpBehaviorSubject(State state) {
        super(state);
        this.state = state;
    }
    
    @Override
    public void onSubscribe(Disposable 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 support*/ 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() {
        Object o = state.get();
        if (NotificationLite.isComplete(o) || NotificationLite.isError(o)) {
            return null;
        }
        return NotificationLite.getValue(o);
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public T[] getValues(T[] array) {
        Object o = state.get();
        if (o == null || NotificationLite.isComplete(o) || NotificationLite.isError(o)) {
            if (array.length != 0) {
                array[0] = null;
            }
            return array;
        }
        T v = NotificationLite.getValue(o);
        if (array.length != 0) {
            array[0] = v;
            if (array.length != 1) {
                array[1] = null;
            }
        } else {
            array = (T[])Array.newInstance(array.getClass().getComponentType(), 1);
            array[0] = v;
        }
        return 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() {
        Object o = state.get();
        return o != null && !NotificationLite.isComplete(o) && !NotificationLite.isError(o);
    }
    
    static final class State extends AtomicReference implements NbpOnSubscribe, NbpSubscriber {
        /** */
        private static final long serialVersionUID = -4311717003288339429L;

        boolean done;
        
        volatile BehaviorDisposable[] subscribers;
        
        @SuppressWarnings("rawtypes")
        static final AtomicReferenceFieldUpdater SUBSCRIBERS =
                AtomicReferenceFieldUpdater.newUpdater(State.class, BehaviorDisposable[].class, "subscribers");
        
        @SuppressWarnings("rawtypes")
        static final BehaviorDisposable[] EMPTY = new BehaviorDisposable[0];

        @SuppressWarnings("rawtypes")
        static final BehaviorDisposable[] TERMINATED = new BehaviorDisposable[0];

        long index;
        
        final ReadWriteLock lock;
        final Lock readLock;
        final Lock writeLock;
        
        public State() {
            this.lock = new ReentrantReadWriteLock();
            this.readLock = lock.readLock();
            this.writeLock = lock.writeLock();
            SUBSCRIBERS.lazySet(this, EMPTY);
        }
        
        public boolean add(BehaviorDisposable rs) {
            for (;;) {
                BehaviorDisposable[] a = subscribers;
                if (a == TERMINATED) {
                    return false;
                }
                int len = a.length;
                @SuppressWarnings("unchecked")
                BehaviorDisposable[] b = new BehaviorDisposable[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(BehaviorDisposable rs) {
            for (;;) {
                BehaviorDisposable[] 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;
                }
                BehaviorDisposable[] b;
                if (len == 1) {
                    b = EMPTY;
                } else {
                    b = new BehaviorDisposable[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 BehaviorDisposable[] terminate(Object terminalValue) {
            
            BehaviorDisposable[] a = subscribers;
            if (a != TERMINATED) {
                a = SUBSCRIBERS.getAndSet(this, TERMINATED);
                if (a != TERMINATED) {
                    // either this or atomics with lots of allocation
                    setCurrent(terminalValue);
                }
            }
            
            return a;
        }
        
        @Override
        public void accept(NbpSubscriber s) {
            BehaviorDisposable bs = new BehaviorDisposable(s, this);
            s.onSubscribe(bs);
            if (!bs.cancelled) {
                if (add(bs)) {
                    if (bs.cancelled) {
                        remove(bs);
                    } else {
                        bs.emitFirst();
                    }
                } else {
                    Object o = get();
                    if (NotificationLite.isComplete(o)) {
                        s.onComplete();
                    } else {
                        s.onError(NotificationLite.getError(o));
                    }
                }
            }
        }
        
        @Override
        public void onSubscribe(Disposable s) {
            if (done) {
                s.dispose();
                return;
            }
        }
        
        void setCurrent(Object o) {
            writeLock.lock();
            try {
                index++;
                lazySet(o);
            } finally {
                writeLock.unlock();
            }
        }
        
        @Override
        public void onNext(T t) {
            if (done) {
                return;
            }
            Object o = NotificationLite.next(t);
            setCurrent(o);
            for (BehaviorDisposable bs : subscribers) {
                bs.emitNext(o, index);
            }
        }
        
        @Override
        public void onError(Throwable t) {
            if (done) {
                RxJavaPlugins.onError(t);
                return;
            }
            done = true;
            Object o = NotificationLite.error(t);
            for (BehaviorDisposable bs : terminate(o)) {
                bs.emitNext(o, index);
            }
        }
        
        @Override
        public void onComplete() {
            if (done) {
                return;
            }
            done = true;
            Object o = NotificationLite.complete();
            for (BehaviorDisposable bs : terminate(o)) {
                bs.emitNext(o, index);  // relaxed read okay since this is the only mutator thread
            }
        }
    }
    
    static final class BehaviorDisposable implements Disposable, Predicate {
        
        final NbpSubscriber actual;
        final State state;
        
        boolean next;
        boolean emitting;
        AppendOnlyLinkedArrayList queue;
        
        boolean fastPath;
        
        volatile boolean cancelled;
        
        long index;

        public BehaviorDisposable(NbpSubscriber actual, State state) {
            this.actual = actual;
            this.state = state;
        }
        
        @Override
        public void dispose() {
            if (!cancelled) {
                cancelled = true;
                
                state.remove(this);
            }
        }

        void emitFirst() {
            if (cancelled) {
                return;
            }
            Object o;
            synchronized (this) {
                if (cancelled) {
                    return;
                }
                if (next) {
                    return;
                }
                
                State s = state;
                Lock lock = s.readLock;
                
                lock.lock();
                try {
                    index = s.index;
                    o = s.get();
                } finally {
                    lock.unlock();
                }
                
                emitting = o != null;
                next = true;
            }
            
            if (o != null) {
                if (test(o)) {
                    return;
                }
            
                emitLoop();
            }
        }
        
        void emitNext(Object value, long stateIndex) {
            if (cancelled) {
                return;
            }
            if (!fastPath) {
                synchronized (this) {
                    if (cancelled) {
                        return;
                    }
                    if (index == stateIndex) {
                        return;
                    }
                    if (emitting) {
                        AppendOnlyLinkedArrayList q = queue;
                        if (q == null) {
                            q = new AppendOnlyLinkedArrayList(4);
                            queue = q;
                        }
                        q.add(value);
                        return;
                    }
                    next = true;
                }
                fastPath = true;
            }

            test(value);
        }

        @Override
        public boolean test(Object o) {
            if (cancelled) {
                return true;
            }
            return NotificationLite.accept(o, actual);
        }
        
        void emitLoop() {
            for (;;) {
                if (cancelled) {
                    return;
                }
                AppendOnlyLinkedArrayList q;
                synchronized (this) {
                    q = queue;
                    if (q == null) {
                        emitting = false;
                        return;
                    }
                    queue = null;
                }
                
                q.forEachWhile(this);
            }
        }
    }
}