/* * Copyright 2015 Mark Michaelis * * 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 com.github.mmichaelis.hamcrest.nextdeed.concurrent; import static com.github.mmichaelis.hamcrest.nextdeed.glue.HamcrestGlue.asPredicate; import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.MoreObjects; import com.github.mmichaelis.hamcrest.nextdeed.glue.Consumer; import org.hamcrest.Matcher; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.TimeUnit; /** * Implementation of {@link ProbeBuilder}. * * @since 1.0.0 */ final class ProbeBuilderImpl<T, R> implements ProbeBuilder<T, R> { /** * Builder for delegate builder for {@link WaitFunction}. * * @since 1.0.0 */ @NotNull private final WaitFunctionBuilder<T, R> waitFunctionBuilder; /** * Target, typically the system under test, which will be probed. * * @since 1.0.0 */ @NotNull private final T target; /** * Consumers which will be called upon timeout. * * @since 1.0.0 */ @NotNull private final Collection<Consumer<WaitTimeoutEvent<T, R>>> onTimeoutConsumers = new ArrayList<>(); /** * Function to retrieve the actual value from the system under test. * * @since 1.0.0 */ private Function<T, R> actualFunction; private Function<Function<T, R>, Function<T, R>> waitFunctionPreProcessor = Functions.identity(); ProbeBuilderImpl(@NotNull T target) { this.target = requireNonNull(target, "target must not be null."); waitFunctionBuilder = WaitFunction.waitFor(new Function<T, R>() { @Override public R apply(@Nullable T input) { return getActualFunction().apply(input); } }); } @NotNull @Override public ProbeBuilder<T, R> withinMs(long timeoutMs) { waitFunctionBuilder.withinMs(timeoutMs); return this; } @NotNull @Override public ProbeBuilder<T, R> within(long timeout, @NotNull TimeUnit timeUnit) { waitFunctionBuilder.within(timeout, timeUnit); return this; } @NotNull @Override public ProbeBuilder<T, R> withFinalGracePeriodMs(long gracePeriodMs) { waitFunctionBuilder.withFinalGracePeriodMs(gracePeriodMs); return this; } @NotNull @Override public ProbeBuilder<T, R> withFinalGracePeriod(long gracePeriod, @NotNull TimeUnit timeUnit) { waitFunctionBuilder.withFinalGracePeriod(gracePeriod, timeUnit); return this; } @NotNull @Override public ProbeBuilder<T, R> withInitialDelayMs(long initialDelayMs) { waitFunctionBuilder.withInitialDelayMs(initialDelayMs); return this; } @NotNull @Override public ProbeBuilder<T, R> withInitialDelay(long initialDelay, @NotNull TimeUnit timeUnit) { waitFunctionBuilder.withInitialDelay(initialDelay, timeUnit); return this; } @NotNull @Override public ProbeBuilder<T, R> deceleratePollingBy(double decelerationFactor) { waitFunctionBuilder.deceleratePollingBy(decelerationFactor); return this; } @NotNull @Override public ProbeBuilder<T, R> and() { waitFunctionBuilder.and(); return this; } @NotNull @Override public ProbeBuilder<T, R> onTimeout( @NotNull Consumer<WaitTimeoutEvent<T, R>> waitTimeoutEventConsumer ) { onTimeoutConsumers.add(waitTimeoutEventConsumer); return this; } @Override public void assertThat(@NotNull Function<T, R> actualFunction, @NotNull Matcher<? super R> matcher) { assertThat(null, actualFunction, matcher); } @Override public void assertThat(@Nullable String reason, @NotNull Function<T, R> actualFunction, @NotNull Matcher<? super R> matcher) { checkThat( actualFunction, matcher, new ThrowAssertionError<T, R>(reason, matcher)); } @Override public void assumeThat(@NotNull Function<T, R> actualFunction, @NotNull Matcher<? super R> matcher) { assumeThat(null, actualFunction, matcher); } @Override public void assumeThat(@Nullable String reason, @NotNull Function<T, R> actualFunction, @NotNull Matcher<? super R> matcher) { checkThat( actualFunction, matcher, new ThrowAssumptionViolatedException<T, R>(reason, matcher)); } @Override public void requireThat(@NotNull Function<T, R> actualFunction, @NotNull Matcher<? super R> matcher) { requireThat(null, actualFunction, matcher); } @Override public void requireThat(@Nullable String reason, @NotNull Function<T, R> actualFunction, @NotNull Matcher<? super R> matcher) { checkThat( actualFunction, matcher, new ThrowWaitTimeoutException<T, R>(reason, matcher)); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("hash", Integer.toHexString(System.identityHashCode(this))) .add("actualFunction", actualFunction) .add("target", target) .add("waitFunctionBuilder", waitFunctionBuilder) .toString(); } /** * <p> * Pre-process wait function. This is only meant for testing to possibly mock the wait * function. The default pre-processor is identity, which means that the function is * taken as is. * </p> * <p> * The function will retrieve the wait function as argument and may decide to return * a completely different function. * </p> * * @param waitFunctionPreProcessor function to map wait function * @return self-reference * @since 1.0.0 */ @VisibleForTesting @NotNull ProbeBuilder<T, R> preProcessWaitFunction( @NotNull Function<Function<T, R>, Function<T, R>> waitFunctionPreProcessor) { this.waitFunctionPreProcessor = waitFunctionPreProcessor; return this; } /** * Validate and get the actual function (function to retrieve the actual value). * * @return function */ @NotNull private Function<T, R> getActualFunction() { assert actualFunction != null : "actualFunction must be set."; return actualFunction; } private void checkThat(@NotNull Function<T, R> actualFunction, @NotNull Matcher<? super R> matcher, @NotNull final Function<WaitTimeoutEvent<T, R>, R> timeoutFunction) { this.actualFunction = actualFunction; Function<T, R> waitFunction = waitFunctionBuilder .toFulfill(asPredicate(matcher)) .onTimeout(new Function<WaitTimeoutEvent<T, R>, R>() { @Override public R apply(@Nullable WaitTimeoutEvent<T, R> input) { for (Consumer<WaitTimeoutEvent<T, R>> onTimeoutConsumer : onTimeoutConsumers) { onTimeoutConsumer.accept(input); } return timeoutFunction.apply(input); } }) .get(); Function<T, R> preProcessedWaitFunction = waitFunctionPreProcessor.apply(waitFunction); assert preProcessedWaitFunction != null : "Wait function should not have been preprocessed to null."; preProcessedWaitFunction.apply(target); } }