/*
* The MIT License (MIT)
*
* Copyright (c) 2016 Lachlan Dowding
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package permafrost.tundra.id;
import java.util.concurrent.TimeUnit;
/**
* Provides a time-based algorithm for generating integer IDs. The IDs generated are lexically ordered by time,
* and fit within the bounds of a positive signed 32-bit integer.
*/
public class IntegerID {
/**
* The default time unit used to generate IDs.
*/
private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;
/**
* The previously generated ID's time component, used to ensure only one ID is generated per second.
*/
private long previous = -1;
/**
* The epoch from which time is measured.
*/
private long epoch;
/**
* The divisor used to calculate the time component of the ID (time % divisor).
*/
private int timeDivisor;
/**
* The number of bits used for partitioning the ID space.
*/
private int partitionBitLength;
/**
* The value used to partition the ID space.
*/
private int partition;
/**
* The unit of time used for calculating the time component of the ID.
*/
private TimeUnit timeUnit;
/**
* Constructs a new ID generator using 31 bits of time measured in seconds since the Unix epoch.
*/
public IntegerID() {
this(DEFAULT_TIME_UNIT);
}
/**
* Constructs a new ID generator using 31 bits of time measured in the given time unit since the Unix epoch.
*
* @param timeUnit The time unit used for generating IDs.
*/
public IntegerID(TimeUnit timeUnit) {
this(0, timeUnit);
}
/**
* Constructs a new ID generator using 31 bits of time since the given epoch in the given time unit.
*
* @param epoch The epoch from which time is measured.
* @param timeUnit The time unit used for generating IDs.
*/
public IntegerID(long epoch, TimeUnit timeUnit) {
this(epoch, timeUnit, 0, Integer.SIZE - 1);
}
/**
* Constructs a new ID generator using the given number of bits for the partition component, the remaining number
* of bits from 31 for the time component with time measured from Unix epoch in seconds.
*
* @param partitionBitLength The number of bits used for partitioning the ID space.
* @param partition The value to use to partition the ID space.
*/
public IntegerID(int partitionBitLength, int partition) {
this(DEFAULT_TIME_UNIT, partitionBitLength, partition);
}
/**
* Constructs a new ID generator using the given number of bits for the partition component, the remaining number
* of bits from 31 for the time component with time measured from Unix epoch in the given time unit.
*
* @param timeUnit The time unit used for generating IDs.
* @param partitionBitLength The number of bits used for partitioning the ID space.
* @param partition The value to use to partition the ID space.
*/
public IntegerID(TimeUnit timeUnit, int partitionBitLength, int partition) {
this(0, timeUnit, partitionBitLength, partition);
}
/**
* Constructs a new ID generator using the given number of bits for the partition component, the remaining number
* of bits from 31 for the time component with time measured from the given epoch in the given time unit.
*
* @param epoch The epoch from which time is measured.
* @param timeUnit The time unit used for generating IDs.
* @param partitionBitLength The number of bits used for partitioning the ID space.
* @param partition The value to use to partition the ID space.
*/
public IntegerID(long epoch, TimeUnit timeUnit, int partitionBitLength, int partition) {
if (epoch < 0) throw new IllegalArgumentException("epoch must be greater than or equal to zero");
if (partitionBitLength <= 0) throw new IllegalArgumentException("partitionBitLength must be greater than or equal to zero");
if (partition < 0) throw new IllegalArgumentException("partition must be greater than or equal to zero");
if (partitionBitLength > (Integer.SIZE - 1)) throw new IllegalArgumentException("partitionBitLength must be less than or equal to " + (Integer.SIZE - 1));
this.epoch = epoch;
this.partitionBitLength = partitionBitLength;
int partitionDivisor = (int)Math.pow(2, partitionBitLength);
this.partition = partitionDivisor == 0 ? 0 : partition % partitionDivisor;
this.timeDivisor = (int)Math.pow(2, ((Integer.SIZE - 1) - partitionBitLength)) - 1;
this.timeUnit = timeUnit;
}
/**
* Returns a newly generated ID.
*
* @return A newly generated ID.
*/
public synchronized int generate() {
long next;
do {
next = currentTime();
next = previous == -1 ? next : Math.min(previous + 1, next);
if (next == previous) {
try {
timeUnit.sleep(1);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
} while (next == previous);
previous = next;
return generate(next);
}
/**
* Returns a new ID given a time value.
*
* @param time The time to use to generate the new ID.
* @return A new ID.
*/
private int generate(long time) {
return (int)((time % timeDivisor) << partitionBitLength) | partition;
}
/**
* Returns the current time since this generator's epoch in this generator's time unit.
*
* @return the current time since this generator's epoch in this generator's time unit.
*/
private long currentTime() {
return timeUnit.convert(System.currentTimeMillis() - epoch, TimeUnit.MILLISECONDS);
}
}