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

rx.internal.operators.OperatorScan Maven / Gradle / Ivy

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

import java.util.Queue;
import java.util.concurrent.atomic.AtomicLong;

import rx.*;
import rx.Observable.Operator;
import rx.exceptions.Exceptions;
import rx.functions.*;
import rx.internal.util.atomic.SpscLinkedAtomicQueue;
import rx.internal.util.unsafe.*;

/**
 * Returns an Observable that applies a function to the first item emitted by a source Observable, then feeds
 * the result of that function along with the second item emitted by an Observable into the same function, and
 * so on until all items have been emitted by the source Observable, emitting the result of each of these
 * iterations.
 * 

* *

* This sort of function is sometimes called an accumulator. *

* Note that when you pass a seed to {@code scan} the resulting Observable will emit that seed as its * first emitted item. * * @param the aggregate and output type * @param the input value type */ public final class OperatorScan implements Operator { private final Func0 initialValueFactory; final Func2 accumulator; // sentinel if we don't receive an initial value private static final Object NO_INITIAL_VALUE = new Object(); /** * Applies an accumulator function over an observable sequence and returns each intermediate result with the * specified source and accumulator. * * @param initialValue * the initial (seed) accumulator value * @param accumulator * an accumulator function to be invoked on each element from the sequence * @see Observable.Scan(TSource, TAccumulate) Method (IObservable(TSource), TAccumulate, Func(TAccumulate, TSource, * TAccumulate)) */ public OperatorScan(final R initialValue, Func2 accumulator) { this(new Func0() { @Override public R call() { return initialValue; } }, accumulator); } public OperatorScan(Func0 initialValueFactory, Func2 accumulator) { this.initialValueFactory = initialValueFactory; this.accumulator = accumulator; } /** * Applies an accumulator function over an observable sequence and returns each intermediate result with the * specified source and accumulator. * * @param accumulator * an accumulator function to be invoked on each element from the sequence * @see Observable.Scan(TSource) Method (IObservable(TSource), Func(TSource, TSource, TSource)) */ @SuppressWarnings("unchecked") public OperatorScan(final Func2 accumulator) { this((R) NO_INITIAL_VALUE, accumulator); } @Override public Subscriber call(final Subscriber child) { final R initialValue = initialValueFactory.call(); if (initialValue == NO_INITIAL_VALUE) { return new Subscriber(child) { boolean once; R value; @SuppressWarnings("unchecked") @Override public void onNext(T t) { R v; if (!once) { once = true; v = (R)t; } else { v = value; try { v = accumulator.call(v, t); } catch (Throwable e) { Exceptions.throwOrReport(e, child, t); return; } } value = v; child.onNext(v); } @Override public void onError(Throwable e) { child.onError(e); } @Override public void onCompleted() { child.onCompleted(); } }; } final InitialProducer ip = new InitialProducer(initialValue, child); Subscriber parent = new Subscriber() { private R value = initialValue; @Override public void onNext(T currentValue) { R v = value; try { v = accumulator.call(v, currentValue); } catch (Throwable e) { Exceptions.throwOrReport(e, this, currentValue); return; } value = v; ip.onNext(v); } @Override public void onError(Throwable e) { ip.onError(e); } @Override public void onCompleted() { ip.onCompleted(); } @Override public void setProducer(final Producer producer) { ip.setProducer(producer); } }; child.add(parent); child.setProducer(ip); return parent; } static final class InitialProducer implements Producer, Observer { final Subscriber child; final Queue queue; boolean emitting; /** Missed a terminal event. */ boolean missed; /** Missed a request. */ long missedRequested; /** The current requested amount. */ final AtomicLong requested; /** The current producer. */ volatile Producer producer; volatile boolean done; Throwable error; public InitialProducer(R initialValue, Subscriber child) { this.child = child; Queue q; // TODO switch to the linked-array based queue once available if (UnsafeAccess.isUnsafeAvailable()) { q = new SpscLinkedQueue(); // new SpscUnboundedArrayQueue(8); } else { q = new SpscLinkedAtomicQueue(); // new SpscUnboundedAtomicArrayQueue(8); } this.queue = q; q.offer(NotificationLite.instance().next(initialValue)); this.requested = new AtomicLong(); } @Override public void onNext(R t) { queue.offer(NotificationLite.instance().next(t)); emit(); } boolean checkTerminated(boolean d, boolean empty, Subscriber child) { if (child.isUnsubscribed()) { return true; } if (d) { Throwable err = error; if (err != null) { child.onError(err); return true; } else if (empty) { child.onCompleted(); return true; } } return false; } @Override public void onError(Throwable e) { error = e; done = true; emit(); } @Override public void onCompleted() { done = true; emit(); } @Override public void request(long n) { if (n < 0L) { throw new IllegalArgumentException("n >= required but it was " + n); } else if (n != 0L) { BackpressureUtils.getAndAddRequest(requested, n); Producer p = producer; if (p == null) { // not synchronizing on this to avoid clash with emit() synchronized (requested) { p = producer; if (p == null) { long mr = missedRequested; missedRequested = BackpressureUtils.addCap(mr, n); } } } if (p != null) { p.request(n); } emit(); } } public void setProducer(Producer p) { if (p == null) { throw new NullPointerException(); } long mr; // not synchronizing on this to avoid clash with emit() synchronized (requested) { if (producer != null) { throw new IllegalStateException("Can't set more than one Producer!"); } mr = missedRequested; // request one less because of the initial value, this happens once // and is performed only if the request is not at MAX_VALUE already if (mr != Long.MAX_VALUE) { mr -= 1; } missedRequested = 0L; producer = p; } if (mr > 0L) { p.request(mr); } emit(); } void emit() { synchronized (this) { if (emitting) { missed = true; return; } emitting = true; } emitLoop(); } void emitLoop() { final Subscriber child = this.child; final Queue queue = this.queue; final NotificationLite nl = NotificationLite.instance(); AtomicLong requested = this.requested; long r = requested.get(); for (;;) { boolean d = done; boolean empty = queue.isEmpty(); if (checkTerminated(d, empty, child)) { return; } long e = 0L; while (e != r) { d = done; Object o = queue.poll(); empty = o == null; if (checkTerminated(d, empty, child)) { return; } if (empty) { break; } R v = nl.getValue(o); try { child.onNext(v); } catch (Throwable ex) { Exceptions.throwOrReport(ex, child, v); return; } e++; } if (e != 0 && r != Long.MAX_VALUE) { r = BackpressureUtils.produced(requested, e); } synchronized (this) { if (!missed) { emitting = false; return; } missed = false; } } } } }