/*
* 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.google.common.base.Optional.fromNullable;
import static java.util.Objects.requireNonNull;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.TimeUnit;
/**
* @since 1.0.0
*/
final class WaitFunctionBuilderImpl<T, R> implements WaitFunctionBuilder<T, R> {
/**
* A grace period for the last poll.
*
* @since 1.0.0
*/
private static final long DEFAULT_GRACE_PERIOD_MS = 0L;
/**
* Initial delay to wait if we need to wait. Using 0L as base as a delay
* can never go below this value and for unit tests we do not want to have
* high delays.
*
* @since 1.0.0
*/
private static final long DEFAULT_INITIAL_DELAY_MS = 0L;
/**
* Factor by which the polling factor decelerates.
*
* @since 1.0.0
*/
private static final double DEFAULT_DECELERATION_FACTOR = 1.1;
@NotNull
private final Function<T, R> delegateFunction;
@NotNull
private Predicate<? super R> predicate = Predicates.alwaysTrue();
private long timeout;
@NotNull
private TimeUnit timeoutTimeUnit = TimeUnit.MILLISECONDS;
private long gracePeriod = DEFAULT_GRACE_PERIOD_MS;
@NotNull
private TimeUnit gracePeriodTimeUnit = TimeUnit.MILLISECONDS;
private long initialDelay = DEFAULT_INITIAL_DELAY_MS;
@NotNull
private TimeUnit initialDelayTimeUnit = TimeUnit.MILLISECONDS;
private double decelerationFactor = DEFAULT_DECELERATION_FACTOR;
@Nullable
private Function<WaitTimeoutEvent<T, R>, R> timeoutFunction;
public WaitFunctionBuilderImpl(@NotNull Function<T, R> delegateFunction) {
this.delegateFunction = requireNonNull(delegateFunction, "delegateFunction must not be null.");
}
@NotNull
@Override
public WaitFunctionBuilder<T, R> toFulfill(@NotNull Predicate<? super R> predicate) {
this.predicate = requireNonNull(predicate, "predicate must not be null.");
return this;
}
@NotNull
@Override
public WaitFunctionBuilder<T, R> onTimeout(
@NotNull Function<WaitTimeoutEvent<T, R>, R> timeoutFunction) {
this.timeoutFunction = requireNonNull(timeoutFunction, "timeoutFunction must not be null.");
return this;
}
@NotNull
@Override
public WaitFunctionBuilder<T, R> withinMs(long timeoutMs) {
within(timeoutMs, TimeUnit.MILLISECONDS);
return this;
}
@NotNull
@Override
public WaitFunctionBuilder<T, R> within(long timeout, @NotNull TimeUnit timeUnit) {
Preconditions.checkArgument(timeout >= 0L, "Timeout value must be positive.");
this.timeout = timeout;
timeoutTimeUnit = requireNonNull(timeUnit, "timeUnit must not be null.");
return this;
}
@NotNull
@Override
public WaitFunctionBuilder<T, R> withFinalGracePeriodMs(long gracePeriodMs) {
withFinalGracePeriod(gracePeriodMs, TimeUnit.MILLISECONDS);
return this;
}
@NotNull
@Override
public WaitFunctionBuilder<T, R> withFinalGracePeriod(long gracePeriod,
@NotNull TimeUnit timeUnit) {
Preconditions.checkArgument(gracePeriod >= 0, "Grace period value must be positive.");
this.gracePeriod = gracePeriod;
gracePeriodTimeUnit = requireNonNull(timeUnit, "timeUnit must not be null.");
return this;
}
@NotNull
@Override
public WaitFunctionBuilder<T, R> withInitialDelayMs(long initialDelayMs) {
withInitialDelay(initialDelayMs, TimeUnit.MILLISECONDS);
return this;
}
@NotNull
@Override
public WaitFunctionBuilder<T, R> withInitialDelay(long initialDelay,
@NotNull TimeUnit timeUnit) {
Preconditions.checkArgument(initialDelay >= 0, "Initial delay must be positive.");
this.initialDelay = initialDelay;
initialDelayTimeUnit = requireNonNull(timeUnit, "timeUnit must not be null.");
return this;
}
@NotNull
@Override
public WaitFunctionBuilder<T, R> deceleratePollingBy(double decelerationFactor) {
Preconditions
.checkArgument(decelerationFactor >= 1, "Factor must be greater than or equal to 1.");
this.decelerationFactor = decelerationFactor;
return this;
}
@NotNull
@Override
public WaitFunctionBuilder<T, R> and() {
return this;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("hash", Integer.toHexString(System.identityHashCode(this)))
.add("decelerationFactor", decelerationFactor)
.add("delegateFunction", delegateFunction)
.add("gracePeriod", gracePeriod)
.add("gracePeriodTimeUnit", gracePeriodTimeUnit)
.add("initialDelay", initialDelay)
.add("initialDelayTimeUnit", initialDelayTimeUnit)
.add("predicate", predicate)
.add("timeout", timeout)
.add("timeoutFunction", timeoutFunction)
.add("timeoutTimeUnit", timeoutTimeUnit)
.toString();
}
@Override
public Function<T, R> get() {
return new WaitFunction<>(
delegateFunction,
predicate,
fromNullable(timeoutFunction).or(new Supplier<Function<WaitTimeoutEvent<T, R>, R>>() {
@Override
public Function<WaitTimeoutEvent<T, R>, R> get() {
return new FailWithTimeoutExceptionFunction<>();
}
}),
timeout,
timeoutTimeUnit,
gracePeriod,
gracePeriodTimeUnit,
initialDelay,
initialDelayTimeUnit,
decelerationFactor
);
}
}