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

reactor.core.publisher.FluxConcatArray Maven / Gradle / Ivy

Go to download

Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC

There is a newer version: 3.40.2
Show newest version
/*
 * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved.
 *
 * 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
 *
 *   https://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 reactor.core.publisher;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import reactor.core.CoreSubscriber;
import reactor.core.Exceptions;
import reactor.util.annotation.Nullable;

/**
 * Concatenates a fixed array of Publishers' values.
 *
 * @param  the value type
 * @see Reactive-Streams-Commons
 */
final class FluxConcatArray extends Flux implements SourceProducer {

	static final Object WORKING = new Object();
	static final Object DONE = new Object();

	final Publisher[] array;
	
	final boolean delayError;

	@SafeVarargs
	FluxConcatArray(boolean delayError, Publisher... array) {
		this.array = Objects.requireNonNull(array, "array");
		this.delayError = delayError;
	}

	@Override
	public void subscribe(CoreSubscriber actual) {
		Publisher[] a = array;

		if (a.length == 0) {
			Operators.complete(actual);
			return;
		}
		if (a.length == 1) {
			Publisher p = a[0];

			if (p == null) {
				Operators.error(actual, new NullPointerException("The single source Publisher is null"));
			} else {
				p = Operators.toFluxOrMono(p);
				p.subscribe(actual);
			}
			return;
		}

		if (delayError) {
			ConcatArrayDelayErrorSubscriber parent = new
					ConcatArrayDelayErrorSubscriber<>(actual, a);

			parent.onComplete();
			return;
		}
		ConcatArraySubscriber parent = new ConcatArraySubscriber<>(actual, a);

		parent.onComplete();
	}


	@Override
	public Object scanUnsafe(Attr key) {
		if (key == Attr.DELAY_ERROR) return delayError;
		if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC;
		return SourceProducer.super.scanUnsafe(key);
	}

	/**
	 * Returns a new instance which has the additional source to be merged together with
	 * the current array of sources.
	 * 

* This operation doesn't change the current FluxMerge instance. * * @param source the new source to merge with the others * @return the new FluxConcatArray instance */ FluxConcatArray concatAdditionalSourceLast(Publisher source) { int n = array.length; @SuppressWarnings("unchecked") Publisher[] newArray = new Publisher[n + 1]; System.arraycopy(array, 0, newArray, 0, n); newArray[n] = source; return new FluxConcatArray<>(delayError, newArray); } /** * Returns a new instance which has the additional source to be merged together with * the current array of sources. *

* This operation doesn't change the current FluxMerge instance. * * @param source the new source to merge with the others * @return the new FluxConcatArray instance */ @SuppressWarnings("unchecked") FluxConcatArray concatAdditionalIgnoredLast(Publisher source) { int n = array.length; Publisher[] newArray = new Publisher[n + 1]; //noinspection SuspiciousSystemArraycopy System.arraycopy(array, 0, newArray, 0, n); newArray[n - 1] = Mono.ignoreElements(newArray[n - 1]); newArray[n] = source; return new FluxConcatArray<>(delayError, newArray); } /** * Returns a new instance which has the additional first source to be concatenated together with * the current array of sources. *

* This operation doesn't change the current FluxConcatArray instance. * * @param source the new source to merge with the others * @return the new FluxConcatArray instance */ FluxConcatArray concatAdditionalSourceFirst(Publisher source) { int n = array.length; @SuppressWarnings("unchecked") Publisher[] newArray = new Publisher[n + 1]; System.arraycopy(array, 0, newArray, 1, n); newArray[0] = source; return new FluxConcatArray<>(delayError, newArray); } interface SubscriptionAware { Subscription upstream(); } static final class ConcatArraySubscriber extends ThreadLocal implements InnerOperator, SubscriptionAware { final CoreSubscriber actual; final Publisher[] sources; int index; long produced; Subscription s; volatile long requested; @SuppressWarnings("rawtypes") static final AtomicLongFieldUpdater REQUESTED = AtomicLongFieldUpdater.newUpdater(ConcatArraySubscriber.class, "requested"); volatile boolean cancelled; ConcatArraySubscriber(CoreSubscriber actual, Publisher[] sources) { this.actual = actual; this.sources = sources; } @Override public void onSubscribe(Subscription s) { if (this.cancelled) { this.remove(); s.cancel(); return; } final Subscription previousSubscription = this.s; this.s = s; if (previousSubscription == null) { this.actual.onSubscribe(this); return; } final long actualRequested = activateAndGetRequested(REQUESTED, this); if (actualRequested > 0) { s.request(actualRequested); } } @Override public void onNext(T t) { this.produced++; this.actual.onNext(t); } @Override public void onError(Throwable t) { this.remove(); this.actual.onError(t); } @Override public void onComplete() { if (this.get() == WORKING) { this.set(DONE); return; } final Publisher[] a = this.sources; for (;;) { this.set(WORKING); int i = this.index; if (i == a.length) { this.remove(); if (this.cancelled) { return; } this.actual.onComplete(); return; } Publisher p = a[i]; if (p == null) { this.remove(); if (this.cancelled) { return; } this.actual.onError(new NullPointerException("Source Publisher at index " + i + " is null")); return; } long c = this.produced; if (c != 0L) { this.produced = 0L; deactivateAndProduce(c, REQUESTED, this); } this.index = ++i; if (this.cancelled) { return; } p = Operators.toFluxOrMono(p); p.subscribe(this); final Object state = this.get(); if (state != DONE) { this.remove(); return; } } } @Override public void request(long n) { final Subscription subscription = addCapAndGetSubscription(n, REQUESTED, this); if (subscription == null) { return; } subscription.request(n); } @Override public void cancel() { this.remove(); this.cancelled = true; if ((this.requested & Long.MIN_VALUE) != Long.MIN_VALUE) { this.s.cancel(); } } @Override public Subscription upstream() { return this.s; } @Override public CoreSubscriber actual() { return this.actual; } @Override public Object scanUnsafe(Attr key) { if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; if (key == Attr.PARENT) return this.s; if (key == Attr.CANCELLED) return cancelled; if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; return InnerOperator.super.scanUnsafe(key); } } static final class ConcatArrayDelayErrorSubscriber extends ThreadLocal implements InnerOperator, SubscriptionAware { final CoreSubscriber actual; final Publisher[] sources; int index; long produced; Subscription s; volatile long requested; @SuppressWarnings("rawtypes") static final AtomicLongFieldUpdater REQUESTED = AtomicLongFieldUpdater.newUpdater(ConcatArrayDelayErrorSubscriber.class, "requested"); volatile Throwable error; @SuppressWarnings("rawtypes") static final AtomicReferenceFieldUpdater ERROR = AtomicReferenceFieldUpdater.newUpdater(ConcatArrayDelayErrorSubscriber.class, Throwable.class, "error"); volatile boolean cancelled; ConcatArrayDelayErrorSubscriber(CoreSubscriber actual, Publisher[] sources) { this.actual = actual; this.sources = sources; } @Override public void onSubscribe(Subscription s) { if (this.cancelled) { this.remove(); s.cancel(); return; } final Subscription previousSubscription = this.s; this.s = s; if (previousSubscription == null) { this.actual.onSubscribe(this); return; } final long actualRequested = activateAndGetRequested(REQUESTED, this); if (actualRequested > 0) { s.request(actualRequested); } } @Override public void onNext(T t) { this.produced++; this.actual.onNext(t); } @Override public void onError(Throwable t) { if (!Exceptions.addThrowable(ERROR, this, t)) { this.remove(); Operators.onErrorDropped(t, this.actual.currentContext()); return; } onComplete(); } @Override public void onComplete() { if (this.get() == WORKING) { this.set(DONE); return; } final Publisher[] a = this.sources; for (;;) { this.set(WORKING); int i = this.index; if (i == a.length) { this.remove(); final Throwable e = Exceptions.terminate(ERROR, this); if (e == Exceptions.TERMINATED) { return; } if (e != null) { this.actual.onError(e); } else { this.actual.onComplete(); } return; } Publisher p = a[i]; if (p == null) { this.remove(); if (this.cancelled) { return; } final NullPointerException npe = new NullPointerException("Source Publisher at index " + i + " is null"); if (!Exceptions.addThrowable(ERROR, this, npe)) { Operators.onErrorDropped(npe, this.actual.currentContext()); return; } final Throwable throwable = Exceptions.terminate(ERROR, this); if (throwable == Exceptions.TERMINATED) { return; } this.actual.onError(throwable); return; } long c = this.produced; if (c != 0L) { this.produced = 0L; deactivateAndProduce(c, REQUESTED, this); } this.index = ++i; if (this.cancelled) { return; } p = Operators.toFluxOrMono(p); p.subscribe(this); final Object state = this.get(); if (state != DONE) { this.remove(); return; } } } @Override public void request(long n) { final Subscription subscription = addCapAndGetSubscription(n, REQUESTED, this); if (subscription == null) { return; } subscription.request(n); } @Override public void cancel() { this.remove(); this.cancelled = true; if ((this.requested & Long.MIN_VALUE) != Long.MIN_VALUE) { this.s.cancel(); } final Throwable throwable = Exceptions.terminate(ERROR, this); if (throwable != null) { Operators.onErrorDropped(throwable, this.actual.currentContext()); } } @Override public Subscription upstream() { return this.s; } @Override public CoreSubscriber actual() { return this.actual; } @Override @Nullable public Object scanUnsafe(Attr key) { if (key == Attr.DELAY_ERROR) return true; if (key == Attr.TERMINATED) return this.error == Exceptions.TERMINATED; if (key == Attr.ERROR) return this.error != Exceptions.TERMINATED ? this.error : null; if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; if (key == Attr.PARENT) return this.s; if (key == Attr.CANCELLED) return this.cancelled; if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return this.requested; return InnerOperator.super.scanUnsafe(key); } } static long activateAndGetRequested(AtomicLongFieldUpdater updater, T instance) { for (;;) { final long deactivatedRequested = updater.get(instance); final long actualRequested = deactivatedRequested & Long.MAX_VALUE; if (updater.compareAndSet(instance, deactivatedRequested, actualRequested)) { return actualRequested; } } } static void deactivateAndProduce(long produced, AtomicLongFieldUpdater updater, T instance) { for (;;) { final long actualRequested = updater.get(instance); final long deactivatedRequested = actualRequested == Long.MAX_VALUE ? Long.MAX_VALUE | Long.MIN_VALUE : (actualRequested - produced) | Long.MIN_VALUE; if (updater.compareAndSet(instance, actualRequested, deactivatedRequested)) { return; } } } @Nullable static Subscription addCapAndGetSubscription(long n, AtomicLongFieldUpdater updater, T instance) { for (;;) { final long state = updater.get(instance); final Subscription s = instance.upstream(); final long actualRequested = state & Long.MAX_VALUE; final long status = state & Long.MIN_VALUE; if (actualRequested == Long.MAX_VALUE) { return status == Long.MIN_VALUE ? null : s; } if (updater.compareAndSet(instance, state, Operators.addCap(actualRequested , n) | status)) { return status == Long.MIN_VALUE ? null : s; } } } }