/*
* Copyright 2016 Google 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 com.example.time.testing;
import com.example.time.Clock;
import org.joda.time.Instant;
import org.joda.time.ReadableDuration;
import org.joda.time.ReadableInstant;
import java.util.concurrent.atomic.AtomicLong;
/**
* A Clock that returns a fixed Instant value as the current clock time. The
* fixed Instant is settable for testing. Test code should hold a reference to
* the FakeClock, while code under test should hold a Clock reference.
*
* <p>The clock time can be incremented/decremented manually, with
* {@link #incrementTime} and {@link #decrementTime} respectively.
*
* <p>The clock can also be configured so that the time is incremented whenever
* {@link #now()} is called: see {@link #setAutoIncrementStep}.
*/
public class FakeClock implements Clock {
private static final Instant DEFAULT_TIME = new Instant(1000000000L);
private final long baseTimeMs;
private final AtomicLong fakeNowMs;
private volatile long autoIncrementStepMs;
/**
* Creates a FakeClock instance initialized to an arbitrary constant.
*/
public FakeClock() {
this(DEFAULT_TIME);
}
/**
* Creates a FakeClock instance initialized to the given time.
*/
public FakeClock(ReadableInstant now) {
baseTimeMs = now.getMillis();
fakeNowMs = new AtomicLong(baseTimeMs);
}
/**
* Sets the value of the underlying instance for testing purposes.
*
* @return this
*/
public FakeClock setNow(ReadableInstant now) {
fakeNowMs.set(now.getMillis());
return this;
}
@Override
public Instant now() {
return getAndAdd(autoIncrementStepMs);
}
/**
* Returns the current time without applying an auto increment, if configured.
* The default behavior of {@link #now()} is the same as this method.
*/
public Instant peek() {
return new Instant(fakeNowMs.get());
}
/**
* Reset the given clock back to the base time with which the FakeClock was
* initially constructed.
*
* @return this
*/
public FakeClock resetTime() {
fakeNowMs.set(baseTimeMs);
return this;
}
/**
* Increments the clock time by the given duration.
*
* @param duration the duration to increment the clock time by
* @return this
*/
public FakeClock incrementTime(ReadableDuration duration) {
incrementTime(duration.getMillis());
return this;
}
/**
* Increments the clock time by the given duration.
*
* @param durationMs the duration to increment the clock time by,
* in milliseconds
* @return this
*/
public FakeClock incrementTime(long durationMs) {
fakeNowMs.addAndGet(durationMs);
return this;
}
/**
* Decrements the clock time by the given duration.
*
* @param duration the duration to decrement the clock time by
* @return this
*/
public FakeClock decrementTime(ReadableDuration duration) {
incrementTime(-duration.getMillis());
return this;
}
/**
* Decrements the clock time by the given duration.
*
* @param durationMs the duration to decrement the clock time by,
* in milliseconds
* @return this
*/
public FakeClock decrementTime(long durationMs) {
incrementTime(-durationMs);
return this;
}
/**
* Sets the increment applied to the clock whenever it is queried.
* The increment is zero by default: the clock is left unchanged when queried.
*
* @param autoIncrementStep the new auto increment duration
* @return this
*/
public FakeClock setAutoIncrementStep(ReadableDuration autoIncrementStep) {
setAutoIncrementStep(autoIncrementStep.getMillis());
return this;
}
/**
* Sets the increment applied to the clock whenever it is queried.
* The increment is zero by default: the clock is left unchanged when queried.
*
* @param autoIncrementStepMs the new auto increment duration, in milliseconds
* @return this
*/
public FakeClock setAutoIncrementStep(long autoIncrementStepMs) {
this.autoIncrementStepMs = autoIncrementStepMs;
return this;
}
/**
* Atomically adds the given value to the current time.
*
* @see AtomicLong#addAndGet
*
* @param durationMs the duration to add, in milliseconds
* @return the updated current time
*/
protected final Instant addAndGet(long durationMs) {
return new Instant(fakeNowMs.addAndGet(durationMs));
}
/**
* Atomically adds the given value to the current time.
*
* @see AtomicLong#getAndAdd
*
* @param durationMs the duration to add, in milliseconds
* @return the previous time
*/
protected final Instant getAndAdd(long durationMs) {
return new Instant(fakeNowMs.getAndAdd(durationMs));
}
}