/*
* Copyright (c) 2011-2017 Pivotal Software Inc, 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
*
* 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 reactor.test.publisher;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import org.reactivestreams.Publisher;
import reactor.test.StepVerifier;
import static reactor.core.Fuseable.NONE;
/**
* @author Stephane Maldini
*/
public class OperatorScenario<I, PI extends Publisher<? extends I>, O, PO extends Publisher<? extends O>> {
final Function<PI, ? extends PO> body;
final Exception stack;
RuntimeException producerError = null;
RuntimeException droppedError = null;
I droppedItem = null;
int fusionMode = NONE;
int fusionModeThreadBarrier = NONE;
int prefetch = -1;
boolean shouldHitDropNextHookAfterTerminate = true;
boolean shouldHitDropErrorHookAfterTerminate = true;
boolean shouldAssertPostTerminateState = true;
int producing = -1;
int demand = -1;
IntFunction<? extends I> producingMapper;
Consumer<? super O>[] receivers = null;
O[] receiverValues = null;
String description = null;
Consumer<StepVerifier.Step<O>> verifier = null;
OperatorScenario(Function<PI, ? extends PO> body, Exception stack) {
this.body = body;
this.stack = stack;
}
public OperatorScenario<I, PI, O, PO> applyAllOptions(OperatorScenario<I, PI, O, PO> source) {
if (source == null) {
return this;
}
this.description = source.description;
this.fusionMode = source.fusionMode;
this.receivers = source.receivers;
this.receiverValues = source.receiverValues;
this.producerError = source.producerError;
this.droppedItem = source.droppedItem;
this.droppedError = source.droppedError;
this.demand = source.demand;
this.producing = source.producing;
this.producingMapper = source.producingMapper;
this.fusionModeThreadBarrier = source.fusionModeThreadBarrier;
this.prefetch = source.prefetch;
this.shouldHitDropNextHookAfterTerminate =
source.shouldHitDropNextHookAfterTerminate;
this.shouldHitDropErrorHookAfterTerminate =
source.shouldHitDropErrorHookAfterTerminate;
this.shouldAssertPostTerminateState = source.shouldAssertPostTerminateState;
this.verifier = source.verifier;
return this;
}
public OperatorScenario<I, PI, O, PO> description(String description) {
this.description = description;
return this;
}
public OperatorScenario<I, PI, O, PO> fusionMode(int fusionMode) {
this.fusionMode = fusionMode;
return this;
}
public OperatorScenario<I, PI, O, PO> fusionModeThreadBarrier(int fusionModeThreadBarrier) {
this.fusionModeThreadBarrier = fusionModeThreadBarrier;
return this;
}
public OperatorScenario<I, PI, O, PO> prefetch(int prefetch) {
this.prefetch = prefetch;
return this;
}
public OperatorScenario<I, PI, O, PO> producer(int n,
IntFunction<? extends I> producer) {
if (n < 0) {
throw new IllegalArgumentException("negative number of produced data");
}
this.producingMapper = Objects.requireNonNull(producer, "producer");
this.producing = n;
return this;
}
public OperatorScenario<I, PI, O, PO> producerEmpty() {
this.producing = 0;
return this;
}
public OperatorScenario<I, PI, O, PO> producerNever() {
this.producing = -1;
return this;
}
public OperatorScenario<I, PI, O, PO> producerError(RuntimeException producerError) {
this.producerError = Objects.requireNonNull(producerError, "producerError");
return this;
}
public OperatorScenario<I, PI, O, PO> droppedError(RuntimeException producerError) {
this.droppedError = Objects.requireNonNull(producerError, "producerError");
return this;
}
public OperatorScenario<I, PI, O, PO> droppedItem(I item) {
this.droppedItem = Objects.requireNonNull(item, "item");
return this;
}
public OperatorScenario<I, PI, O, PO> receive(Consumer<? super O>... receivers) {
this.receivers = Objects.requireNonNull(receivers, "receivers");
if (this.demand != 0 && this.demand < receivers.length) {
this.demand = receivers.length;
}
this.receiverValues = null;
return this;
}
public OperatorScenario<I, PI, O, PO> receive(int n,
IntFunction<? extends O> receiverMapper) {
if (n < 1) {
throw new IllegalArgumentException("Minimum 1 receiver expectation");
}
@SuppressWarnings("unchecked") O[] receivers = (O[]) new Object[n];
for (int i = 0; i < n; i++) {
receivers[i] = receiverMapper.apply(i);
}
return receiveValues(receivers);
}
public OperatorScenario<I, PI, O, PO> receiveValues(O... receivers) {
this.receiverValues = Objects.requireNonNull(receivers, "receivers");
if (this.demand != 0 && this.demand < receivers.length) {
this.demand = receivers.length;
}
this.receivers = null;
return this;
}
public OperatorScenario<I, PI, O, PO> receiverDemand(long d) {
if (d < 0) {
throw new IllegalArgumentException("demand must be positive, was: " + d);
}
this.demand = (int) Math.min(Integer.MAX_VALUE, d);
return this;
}
public OperatorScenario<I, PI, O, PO> receiverEmpty() {
this.receiverValues = null;
this.receivers = null;
return this;
}
public OperatorScenario<I, PI, O, PO> shouldAssertPostTerminateState(boolean shouldAssertPostTerminateState) {
this.shouldAssertPostTerminateState = shouldAssertPostTerminateState;
return this;
}
public OperatorScenario<I, PI, O, PO> shouldHitDropErrorHookAfterTerminate(boolean shouldHitDropErrorHookAfterTerminate) {
this.shouldHitDropErrorHookAfterTerminate = shouldHitDropErrorHookAfterTerminate;
return this;
}
public OperatorScenario<I, PI, O, PO> shouldHitDropNextHookAfterTerminate(boolean shouldHitDropNextHookAfterTerminate) {
this.shouldHitDropNextHookAfterTerminate = shouldHitDropNextHookAfterTerminate;
return this;
}
public OperatorScenario<I, PI, O, PO> verifier(Consumer<StepVerifier.Step<O>> verifier) {
this.verifier = verifier;
return this;
}
final Consumer<StepVerifier.Step<O>> verifier() {
return verifier;
}
final StepVerifier.Step<O> applySteps(StepVerifier.Step<O> step) {
return applySteps(0, Integer.MAX_VALUE, step);
}
StepVerifier.Step<O> applySteps(int initial, StepVerifier.Step<O> step) {
if (initial < 0) {
throw new IllegalArgumentException("initial demand cannot be negative");
}
step = applySteps(0, initial, step);
if (initial == Integer.MAX_VALUE) {
return step;
}
int toRequest = demand == -1 ? producing : demand;
toRequest = Math.max(toRequest - initial, 0);
if (toRequest == 0) {
return step;
}
step = step.thenRequest(toRequest);
if (receiverValues == null && receivers == null) {
return step;
}
return applySteps(initial, Integer.MAX_VALUE, step);
}
final StepVerifier.Step<O> applySteps(int start,
int stop,
StepVerifier.Step<O> step) {
if (stop <= start) {
return step;
}
if (receivers != null) {
for (int i = start; i < receivers.length; i++) {
step = step.assertNext(receivers[i]);
if (i == stop - 1) {
return step;
}
}
}
else if (receiverValues != null) {
for (int i = start; i < receiverValues.length; i++) {
step = step.expectNext(receiverValues[i]);
if (i == stop - 1) {
return step;
}
}
}
return step;
}
final Function<PI, ? extends PO> body() {
return body;
}
final String description() {
return description;
}
OperatorScenario<I, PI, O, PO> duplicate() {
return new OperatorScenario<I, PI, O, PO>(body, stack).applyAllOptions(this);
}
final int fusionMode() {
return fusionMode;
}
final int prefetch() {
return prefetch;
}
final int producerCount() {
return producing;
}
final int receiverCount() {
return demand;
}
final boolean shouldAssertPostTerminateState() {
return shouldAssertPostTerminateState;
}
final boolean shouldHitDropErrorHookAfterTerminate() {
return shouldHitDropErrorHookAfterTerminate;
}
final boolean shouldHitDropNextHookAfterTerminate() {
return shouldHitDropNextHookAfterTerminate;
}
}