package org.magenta.random;
import java.util.Random;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
/**
* Helper class that generates random longs.
* @author ngagnon
*
*/
public class RandomLong {
private Random random;
private Range<Long> constraint;
private long resolution;
private static final Range<Long> ALL = Range.all();
private static final Range<Long> EVERY_POSITIVE_BUT_ZERO = Range.greaterThan(0L);
private static final Range<Long> EVERY_POSITIVE_INCLUDING_ZERO = Range.atLeast(0L);
private static final Range<Long> EVERY_NEGATIVE_BUT_ZERO = Range.lessThan(0L);
private static final Range<Long> EVERY_NEGATIVE_INCLUDING_ZERO = Range.atMost(0L);
/**
* Default constructor which defines no constraint and use a resolution of 1.
*
* @param random the random to use
*/
public RandomLong(Random random) {
this(random, ALL, 1L);
}
/**
* Constructor for custom <code>RandomLong</code> instance. This constructor allow you to specify default values for <code>constraint</code> and
* <code>resolution</code>.
*
* @param random the random to use
* @param constraint the range from which to generate a <code>long</code>.
* @param resolution the resolution
*/
public RandomLong(Random random, Range<Long> constraint, long resolution) {
this.random = random;
this.constraint = constraint;
this.resolution = resolution;
}
/**
* Generate a {@code long} within the intersection formed by the specified {@code range} and this instance own range.
*
* @param range the specfied range
* @return a generated {@code long}
*/
public long any(final Range<Long> range) {
Range<Long> r = range.intersection(constraint);
if (r.isEmpty()) {
throw new IllegalStateException(String.format(
"The intersection of the passed in range %s and this class constrained range %s result in a empty range", range, constraint));
}
long upperBound = r.hasUpperBound() ? r.upperEndpoint() : Long.MAX_VALUE;
long lowerBound = r.hasLowerBound() ? r.lowerEndpoint() : Long.MIN_VALUE;
if (r.hasUpperBound() && BoundType.CLOSED == r.upperBoundType()) {
upperBound++;
}
if (r.hasLowerBound() && BoundType.OPEN == r.lowerBoundType()) {
lowerBound++;
}
long randomLong;
if (lowerBound < 0 && upperBound > 0) {
// special case
long lowerRandomLong = (((this.random.nextLong() % ((-lowerBound) / resolution))) * resolution) + lowerBound;
long upperRandomLong = (((this.random.nextLong() % ((upperBound) / resolution))) * resolution);
randomLong = lowerRandomLong + upperRandomLong;
} else {
long delta = Math.abs(upperBound - lowerBound);
randomLong = (((this.random.nextLong() % (delta / resolution))) * resolution) + lowerBound;
}
return randomLong;
}
/**
* Generate a {@code long} within this instance own range.
*
* @return a generated {@code long}
*/
public long any() {
return any(ALL);
}
/**
* Generate a positive {@code long} within this instance own range.
*
* @return a generated {@code long}
*/
public long anyPositive() {
return any(EVERY_POSITIVE_INCLUDING_ZERO);
}
/**
* Generate a positive {@code long} (excluding zero) within this instance own range.
*
* @return a generated {@code long}
*/
public long anyPositiveButZero() {
return any(EVERY_POSITIVE_BUT_ZERO);
}
/**
* Generate a positive {@code long} within this instance own range and having a maximum value of {@code max}.
*
* @param max the maximum possible value
* @return a generated {@code long}
*/
public long anyPositive(long max) {
return any(Range.closedOpen(0L, max));
}
/**
* Generate a positive {@code long} (excluding zero) within this instance own range and having a maximum value of {@code max}.
*
* @param max the maximum possible value
* @return a generated {@code long}
*/
public long anyPositiveButZero(long max) {
return any(Range.open(0L, max));
}
/**
* Generate a negative {@code long} within this instance own range.
*
* @return a generated {@code long}
*/
public long anyNegative() {
return any(EVERY_NEGATIVE_INCLUDING_ZERO);
}
/**
* Generate a negative {@code long} (excluding zero) within this instance own range.
*
* @return a generated {@code long}
*/
public long anyNegativeButZero() {
return any(EVERY_NEGATIVE_BUT_ZERO);
}
/**
* Generate a negative {@code long} within this instance own range and having a minimum value of {@code max}.
*
* @param min the minimum possible value
* @return a generated {@code long}
*/
public long anyNegative(long min) {
return any(Range.openClosed(min, 0L));
}
/**
* Generate a negative {@code long} (excluding zero) within this instance own range and having a minimum value of {@code max}.
*
* @param min the minimum possible value
* @return a generated {@code long}
*/
public long anyNegativeButZero(long min) {
return any(Range.open(min, 0L));
}
/**
* Return a new copy of this instance having the specified {@code resolution}.
*
* @param resolution the resolution
* @return a new {@code RandomLong}
*/
public RandomLong resolution(long resolution) {
return new RandomLong(random, constraint, resolution);
}
/**
* Return a new copy of this instance having the specified {@code constraint}.
*
* @param constraint the constraint
* @return a new {@code RandomLong}
*/
public RandomLong constraint(Range<Long> constraint) {
return new RandomLong(random, this.constraint.intersection(constraint), resolution);
}
/**
* Return a new copy of this instance having the range specified by the
* interval <code>minIncluded</code> and <code>maxIncluded</code>.
*
* @param minIncluded
* lower bound
* @param maxIncluded
* upper bound
* @return a new instance
*/
public RandomLong constraint(long minIncluded, long maxIncluded) {
return constraint(Range.closed(minIncluded, maxIncluded));
}
}