/*
* ModeShape (http://www.modeshape.org)
*
* 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 org.modeshape.common.util;
import org.modeshape.common.annotation.GuardedBy;
import org.modeshape.common.annotation.ThreadSafe;
/**
* This is a generator of simple unique and (non-monotonically) increasing long-value keys that incorporate the time in which they
* were generated. A single generator instance will never return two identical keys, and all keys are unique and naturally sorted
* in time. The generator is safe to be called concurrently from multiple threads.
* <p>
* Each long key value contains the time (in milliseconds) at which the key is generated, left-shifted by 16 bits. Those 16 bits
* are then used to store a counter value that is unique for each millisecond. When a new key is needed, the current time is
* compared to the last time for which a key was generated. If the times are different, then the counter is reset to 0; otherwise,
* the counter is incremented.
* </p>
* <p>
* Use a single generator instance for each sequence of keys.
* </p>
* <p>
* To obtain a unique value, simply call:
*
* <pre>
* TimeBasedKeys keys = TimeBasedKeys.create();
* ...
* long key = keys.nextKey();
* </pre>
*
* Because the keys are time-based, the generator can also identify the range of keys that were created before or after a given
* instant in time, or within a range of times. For example, all keys obtained after January 10, 2014 at 12:12:41.845-06:00 (which
* has a <code>System.currentTimeMillis()</code> value of {@code 1389378406}) will be greater than or equal to the following
* value:
*
* <pre>
* long timeInMillis = 1389378406L;
* long minKey = keys.getKeyStartingAt(timeInMillis);
* </pre>
*
* Using this and similar methods, one can obtain all counter values that might have been generated, for example, sometime during
* 2012:
*
* <pre>
* long janFirst2012 = 1325397600L; // Jan 1 2012 at 00:00.000 UTC
* long janFirst2013 = 1357020000L; // Jan 1 2013 at 00:00.000 UTC
* long smallestKey = keys.getKeyStartingAt(janFirst2012);
* long justLargerKey = keys.getKeyStartingAt(janFirst2013);
* </pre>
*
* Then all keys generated during 2012 will therefore satisfy:
*
* <pre>
* smallestKey >= key && key < justLargerKey
* </pre>
*
* </p>
*
* @author Randall Hauch (rhauch@redhat.com)
*/
@ThreadSafe
public final class TimeBasedKeys {
/**
* By default each {@link TimeBasedKeys} instance will use 16 bits for the counter. That results in a maximum of of 65535
* distinct values per millisecond (which is likely sufficient), while also leaving enough bits in the long to store a value
* well past the year 10,000.
*/
public static final short DEFAULT_BITS_IN_COUNTER = 16;
/**
* Create a new generator that uses 16 bits for the counter portion of the keys.
*
* @return the generator instance; never null
*/
public static TimeBasedKeys create() {
return new TimeBasedKeys(DEFAULT_BITS_IN_COUNTER);
}
/**
* Create a new generator that uses the specified number of bits for the counter portion of the keys.
*
* @param bitsUsedInCounter the number of bits in the counter portion of the keys; must be a positive number for which theere
* is enough space to left shift without overflowing.
* @return the generator instance; never null
*/
public static TimeBasedKeys create( int bitsUsedInCounter ) {
CheckArg.isPositive(bitsUsedInCounter, "bitsUsedInCounter");
int maxAvailableBitsToShift = Long.numberOfLeadingZeros(System.currentTimeMillis());
CheckArg.isLessThan(bitsUsedInCounter, maxAvailableBitsToShift, "bitsUsedInCounter");
return new TimeBasedKeys((short)bitsUsedInCounter);
}
/**
* The number of bits used in the counter portion of the key.
*/
private final short counterBits;
/**
* The maximum counter portion of the key given the number of {@link #counterBits}.
*/
private final long maximumCounterValue;
/**
* The last millis in UTC that was seen. This is only accessed and modified within the synchronized {@link #counterFor(long)}
* method.
*/
@GuardedBy( "this" )
private volatile long lastMillis;
/**
* The last counter that was used with the current value of {@link #lastMillis}. This is only accessed and modified within the
* synchronized {@link #counterFor(long)} method.
*/
@GuardedBy( "this" )
private volatile int counter;
/**
* Create a new key generator.
*
* @param bitsUsedInCounter the number of bits to be used in the counter.
*/
protected TimeBasedKeys( short bitsUsedInCounter ) {
this.counterBits = bitsUsedInCounter;
this.maximumCounterValue = (1L << bitsUsedInCounter) - 1L;
}
/**
* Get the next key for the current time in UTC.
*
* @return a long that is determined by the current time in UTC and a unique counter value for the current time.
*/
public long nextKey() {
// Note that per Oracle the currentTimeMillis is the current number of seconds past the epoch
// in UTC (not in local time). Therefore, processes with exactly synchronized clocks will
// always get the same value regardless of their timezone ...
final long timestamp = System.currentTimeMillis();
final int increment = counterFor(timestamp);
if (increment <= maximumCounterValue) {
return (timestamp << counterBits) + increment;
}
// The counter is surprisingly too high, so try again (repeatedly) until we get to the next millisecond ...
return this.nextKey();
}
/**
* Obtain the first (earliest) key that would have been generated <em>at</em> the specified UTC time. The resulting key is
* equal to or smaller than all keys generated at or since that time.
*
* @param millisInUtc the number of milliseconds (in UTC) past epoch, and the time at which {@link #nextKey()} might have been
* called
* @return a long value that is the earliest possible key for the given time
*/
public long getCounterStartingAt( long millisInUtc ) {
return (millisInUtc << counterBits);
}
/**
* Obtain the largest (latest) key that would have been generated <em>at</em> the specified UTC time. The resulting key is
* equal to or greater than all keys generated at or before that time.
*
* @param millisInUtc the number of milliseconds (in UTC) past epoch, and the time at which {@link #nextKey()} might have been
* called
* @return a long value that is the latest possible key for the given time
*/
public long getCounterEndingAt( long millisInUtc ) {
return (millisInUtc << counterBits) + maximumCounterValue;
}
/**
* Obtain the first (earliest) key that would have been generated <em>after</em> the specified UTC time. The resulting key is
* greater than all keys generated at or before that time.
*
* @param millisInUtc the number of milliseconds (in UTC) past epoch, and the time at which {@link #nextKey()} might have been
* called
* @return a long value that is the latest possible key for the given time
*/
public long getCounterEndingAfter( long millisInUtc ) {
return (millisInUtc + 1) << counterBits;
}
/**
* Obtain the milliseconds since epoch in UTC that the supplied key was generated. The value is the same value that would have
* been returned by {@link System#currentTimeMillis()} when the key was generated.
*
* @param key the key
* @return the generated time, in millseconds past epoch
*/
public long getTimeGenerated( long key ) {
return key < maximumCounterValue ? 0 : key >> counterBits;
}
/**
* Determine the counter portion of the key given the supplied time. This method is synchronized to ensure that the
* {@link #counter} and {@link #lastMillis} are updated atomically.
*
* @param timestamp the current timestamp, and the new value for the {@link #lastMillis} field
* @return the next available counter for the given timestamp
*/
private synchronized int counterFor( long timestamp ) {
if (timestamp == lastMillis) {
// Just increment the counter...
counter += 1;
} else {
// Otherwise, the timestamp has changed, so set it and reset the counter ...
lastMillis = timestamp;
counter = 0;
}
return counter;
}
}