package org.jruby;
import java.math.BigInteger;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import static org.jruby.runtime.Visibility.PRIVATE;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.Random;
import org.jruby.util.TypeConverter;
import static org.jruby.util.TypeConverter.toFloat;
@JRubyClass(name = "Random")
public class RubyRandom extends RubyObject {
public static final class RandomType {
private final RubyInteger seed;
private final Random impl;
RandomType(IRubyObject seed) {
this.seed = seed.convertToInteger();
if (this.seed instanceof RubyFixnum) {
this.impl = randomFromFixnum((RubyFixnum) this.seed);
} else if (this.seed instanceof RubyBignum) {
this.impl = randomFromBignum((RubyBignum) this.seed);
} else {
throw seed.getRuntime().newTypeError(
String.format("failed to convert %s into Integer", seed.getMetaClass().getName()));
}
}
public static Random randomFromFixnum(RubyFixnum seed) {
return randomFromLong(RubyNumeric.num2long(seed));
}
public static Random randomFromLong(long seed) {
long v = Math.abs(seed);
if (v == (v & 0xffffffffL)) {
return new Random((int) v);
} else {
int[] ints = new int[2];
ints[0] = (int) v;
ints[1] = (int) (v >> 32);
return new Random(ints);
}
}
public static Random randomFromBignum(RubyBignum seed) {
BigInteger big = seed.getBigIntegerValue();
return randomFromBigInteger(big);
}
public static Random randomFromBigInteger(BigInteger big) {
if (big.signum() < 0) {
big = big.abs();
}
byte[] buf = big.toByteArray();
int buflen = buf.length;
if (buf[0] == 0) {
buflen -= 1;
}
int len = Math.min((buflen + 3) / 4, Random.N);
int[] ints = bigEndianToInts(buf, len);
if (len <= 1) {
return new Random(ints[0]);
} else {
return new Random(ints);
}
}
RandomType(IRubyObject vseed, RubyBignum state, int left) {
this.seed = vseed.convertToInteger();
byte[] bytes = state.getBigIntegerValue().toByteArray();
int[] ints = new int[bytes.length / 4];
for (int i = 0; i < ints.length; ++i) {
ints[i] = getIntBigIntegerBuffer(bytes, i);
}
this.impl = new Random(ints, left);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof RandomType)) return false;
RandomType rhs = (RandomType) obj;
return seed.op_equal(seed.getRuntime().getCurrentContext(), rhs.seed).isTrue() && impl.equals(rhs.impl);
}
@Override
public int hashCode() {
return (629 + seed.hashCode()) * 37 + impl.hashCode();
}
RandomType(RandomType orig) {
this.seed = orig.seed;
this.impl = new Random(orig.impl);
}
int genrandInt32() {
return impl.genrandInt32();
}
double genrandReal() {
return impl.genrandReal();
}
double genrandReal2() {
return impl.genrandReal2();
}
RubyInteger getSeed() {
return seed;
}
RubyBignum getState() {
int[] ints = impl.getState();
byte[] bytes = new byte[ints.length * 4];
for (int idx = 0; idx < ints.length; ++idx) {
setIntBigIntegerBuffer(bytes, idx, ints[idx]);
}
return RubyBignum.newBignum(seed.getRuntime(), new BigInteger(bytes));
}
int getLeft() {
return impl.getLeft();
}
private static int[] bigEndianToInts(byte[] buf, int initKeyLen) {
int[] initKey = new int[initKeyLen];
for (int idx = 0; idx < initKey.length; ++idx) {
initKey[idx] = getIntBigIntegerBuffer(buf, idx);
}
return initKey;
}
}
static int getIntBigIntegerBuffer(byte[] src, int loc) {
int v = 0;
int idx = src.length - loc * 4 - 1;
if (idx >= 0) {
v |= (src[idx--] & 0xff);
if (idx >= 0) {
v |= (src[idx--] & 0xff) << 8;
if (idx >= 0) {
v |= (src[idx--] & 0xff) << 16;
if (idx >= 0) {
v |= (src[idx--] & 0xff) << 24;
}
}
}
}
return v;
}
static void setIntBigIntegerBuffer(byte[] dest, int loc, int value) {
int idx = dest.length - loc * 4 - 1;
if (idx >= 0) {
dest[idx--] = (byte) (value & 0xff);
if (idx >= 0) {
dest[idx--] = (byte) ((value >> 8) & 0xff);
if (idx >= 0) {
dest[idx--] = (byte) ((value >> 16) & 0xff);
if (idx >= 0) {
dest[idx--] = (byte) ((value >> 24) & 0xff);
}
}
}
}
}
private static final int DEFAULT_SEED_CNT = 4;
public static BigInteger randomSeedBigInteger(java.util.Random random) {
byte[] seed = new byte[DEFAULT_SEED_CNT * 4];
random.nextBytes(seed);
return new BigInteger(seed).abs();
}
public static RubyBignum randomSeed(Ruby runtime) {
return RubyBignum.newBignum(runtime, randomSeedBigInteger(runtime.random));
}
@SuppressWarnings("deprecation")
public static RubyClass createRandomClass(Ruby runtime) {
RubyClass randomClass = runtime
.defineClass("Random", runtime.getObject(), RANDOM_ALLOCATOR);
randomClass.defineAnnotatedMethods(RubyRandom.class);
RubyRandom defaultRand = new RubyRandom(runtime, randomClass);
defaultRand.random = new RandomType(randomSeed(runtime));
randomClass.setConstant("DEFAULT", defaultRand);
runtime.setDefaultRand(defaultRand.random);
runtime.setRandomClass(randomClass);
return randomClass;
}
private static ObjectAllocator RANDOM_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyRandom(runtime, klass);
}
};
private RandomType random = null;
RubyRandom(Ruby runtime, RubyClass rubyClass) {
super(runtime, rubyClass);
}
@JRubyMethod(visibility = PRIVATE, optional = 1)
public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
random = new RandomType((args.length == 0) ? randomSeed(context.runtime) : args[0]);
return this;
}
@JRubyMethod
public IRubyObject seed(ThreadContext context) {
return random.getSeed();
}
@JRubyMethod(name = "initialize_copy", required = 1, visibility = PRIVATE)
@Override
public IRubyObject initialize_copy(IRubyObject orig) {
if (!(orig instanceof RubyRandom)) {
throw getRuntime().newTypeError(String.format(
"wrong argument type %s (expected %s)", orig.getMetaClass().getName(), getMetaClass().getName())
);
}
checkFrozen();
random = new RandomType(((RubyRandom) orig).random);
return this;
}
@JRubyMethod(name = "rand", meta = true, optional = 1)
public static IRubyObject rand(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
RandomType random = getDefaultRand(context);
if (args.length == 0) return randFloat(context, random);
return randomRand(context, args[0], random);
}
@Deprecated
public static IRubyObject randCommon19(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
return randKernel(context, args);
}
static IRubyObject randKernel(ThreadContext context, IRubyObject[] args) {
RandomType random = getDefaultRand(context);
if (args.length == 0) {
return randFloat(context, random);
}
IRubyObject arg = args[0];
if (arg == context.nil) {
return randFloat(context, random);
}
if (arg instanceof RubyRange) {
return randomRand(context, arg, random);
}
RubyInteger max = arg.convertToInteger();
if (max.isZero()) {
return randFloat(context, random);
}
IRubyObject r = randInt(context, random, max, false);
return (r == context.nil) ? randFloat(context, random) : r;
}
@Deprecated
public IRubyObject randObj(ThreadContext context, IRubyObject[] args) {
return (args.length == 0) ? rand(context) : rand(context, args[0]);
}
@JRubyMethod(name = "rand")
public IRubyObject rand(ThreadContext context) {
return randFloat(context, random);
}
@JRubyMethod(name = "rand")
public IRubyObject rand(ThreadContext context, IRubyObject arg) {
return randomRand(context, arg, random);
}
private static IRubyObject randInt(ThreadContext context, RandomType random, RubyInteger vmax,
boolean restrictive) {
if (vmax instanceof RubyFixnum) {
long max = RubyNumeric.fix2long(vmax);
if (max == 0) return context.nil;
if (max < 0) {
if (restrictive) return context.nil;
max = -max;
}
return randLimitedFixnum(context, random, max - 1);
} else {
BigInteger big = vmax.getBigIntegerValue();
if (big.equals(BigInteger.ZERO)) {
return context.nil;
}
if (big.signum() < 0) {
if (restrictive) return context.nil;
big = big.abs();
}
big = big.subtract(BigInteger.ONE);
return randLimitedBignum(context, random, big);
}
}
public static RubyFloat randFloat(ThreadContext context) {
return randFloat(context, getDefaultRand(context));
}
public static RubyFloat randFloat(ThreadContext context, RandomType random) {
return context.runtime.newFloat(random.genrandReal());
}
public static RubyInteger randLimited(ThreadContext context, long limit) {
return randLimitedFixnum(context, getDefaultRand(context), limit);
}
private static RubyInteger randLimitedFixnum(ThreadContext context, RandomType random, long limit) {
return RubyFixnum.newFixnum(context.runtime, randLimitedFixnumInner(random.impl, limit));
}
public static long randLimitedFixnumInner(Random random, long limit) {
long val;
if (limit == 0) {
val = 0;
} else {
long mask = makeMask(limit);
retry: while (true) {
val = 0;
for (int i = 1; 0 <= i; --i) {
if (((mask >>> (i * 32)) & 0xffffffffL) != 0) {
val |= (random.genrandInt32() & 0xffffffffL) << (i * 32);
val &= mask;
}
if (limit < val) {
continue retry;
}
}
break;
}
}
return val;
}
public static RubyInteger randLimited(ThreadContext context, BigInteger limit) {
return randLimitedBignum(context, getDefaultRand(context), limit);
}
private static RubyInteger randLimitedBignum(ThreadContext context, RandomType random, BigInteger limit) {
byte[] buf = limit.toByteArray();
byte[] bytes = new byte[buf.length];
int len = (buf.length + 3) / 4;
retry: while (true) {
long mask = 0;
boolean boundary = true;
for (int idx = len - 1; 0 <= idx; --idx) {
long lim = getIntBigIntegerBuffer(buf, idx) & 0xffffffffL;
mask = (mask != 0) ? 0xffffffffL : makeMask(lim);
long rnd;
if (mask != 0) {
rnd = (random.genrandInt32() & 0xffffffffL) & mask;
if (boundary) {
if (lim < rnd) {
continue retry;
}
if (rnd < lim) {
boundary = false;
}
}
} else {
rnd = 0;
}
setIntBigIntegerBuffer(bytes, idx, (int) rnd);
}
break;
}
return RubyBignum.newBignum(context.runtime, new BigInteger(bytes));
}
private static long makeMask(long x) {
x = x | x >>> 1;
x = x | x >>> 2;
x = x | x >>> 4;
x = x | x >>> 8;
x = x | x >>> 16;
x = x | x >>> 32;
return x;
}
private static RandomType getDefaultRand(ThreadContext context) {
return context.runtime.defaultRand;
}
private static IRubyObject randomRand(ThreadContext context, IRubyObject vmax, RandomType random) {
IRubyObject v;
RangeLike range = null;
final IRubyObject nil = context.nil;
if (vmax == nil) {
v = nil;
} else if ((v = checkMaxInt(context, vmax)) != null) {
v = randInt(context, random, (RubyInteger) v, true);
} else if ((v = TypeConverter.checkFloatType(context.runtime, vmax)) != nil) {
double max = ((RubyFloat) v).value;
if (max > 0.0) {
v = context.runtime.newFloat(max * random.genrandReal());
} else {
v = nil;
}
} else if ((range = rangeValues(context, vmax)) != null) {
if ((v = checkMaxInt(context, range.range)) != null) {
if (v instanceof RubyFixnum) {
long max = ((RubyFixnum) v).value;
if (range.excl) {
max -= 1;
}
if (max >= 0) {
v = randLimitedFixnum(context, random, max);
} else {
v = nil;
}
} else if (v instanceof RubyBignum) {
BigInteger big = ((RubyBignum) v).value;
if (big.signum() > 0) {
if (range.excl) {
big = big.subtract(BigInteger.ONE);
}
v = randLimitedBignum(context, random, big);
} else {
v = nil;
}
} else {
v = nil;
}
} else if ((v = TypeConverter.checkFloatType(context.runtime, range.range)) != nil) {
int scale = 1;
double max = ((RubyFloat) v).value;
double mid = 0.5;
double r;
if (Double.isInfinite(max)) {
double min = floatValue(context, toFloat(context.runtime, range.begin)) / 2.0;
max = floatValue(context, toFloat(context.runtime, range.end)) / 2.0;
scale = 2;
mid = max + min;
max -= min;
} else {
checkFloatValue(context, max);
}
v = context.nil;
if (max > 0.0) {
if (range.excl) {
r = random.genrandReal();
} else {
r = random.genrandReal2();
}
if (scale > 1) {
return context.runtime.newFloat(+(+(+(r - 0.5) * max) * scale) + mid);
}
v = context.runtime.newFloat(r * max);
} else if (max == 0.0 && !range.excl) {
v = context.runtime.newFloat(0.0);
}
}
} else {
v = nil;
RubyNumeric.num2long(vmax);
}
if (v == nil) {
throw context.runtime.newArgumentError("invalid argument - " + vmax);
}
if (range == null) return v;
if (range.begin instanceof RubyFixnum && v instanceof RubyFixnum) {
long x = ((RubyFixnum) range.begin).getLongValue() + ((RubyFixnum) v).getLongValue();
return context.runtime.newFixnum(x);
}
if (v instanceof RubyBignum) {
return ((RubyBignum) v).op_plus(context, range.begin);
}
if (v instanceof RubyFloat) {
IRubyObject f = TypeConverter.checkFloatType(context.runtime, range.begin);
if (f != nil) return ((RubyFloat) v).op_plus(context, f);
}
return Helpers.invoke(context, range.begin, "+", v);
}
private static double floatValue(ThreadContext context, RubyFloat v) {
final double x = v.value;
checkFloatValue(context, x);
return x;
}
private static void checkFloatValue(ThreadContext context, double x) {
if (Double.isInfinite(x) || Double.isNaN(x)) {
throw context.runtime.newErrnoEDOMError("Numerical argument out of domain");
}
}
private static IRubyObject checkMaxInt(ThreadContext context, IRubyObject vmax) {
if (!(vmax instanceof RubyFloat)) {
IRubyObject v = TypeConverter.checkIntegerType(context, vmax);
if (v != context.nil) return v;
}
return null;
}
static class RangeLike {
public IRubyObject begin = null;
public IRubyObject end = null;
boolean excl = false;
public IRubyObject range = null;
}
private static RangeLike rangeValues(ThreadContext context, IRubyObject range) {
RangeLike like = new RangeLike();
if (range instanceof RubyRange) {
RubyRange vrange = (RubyRange) range;
like.begin = vrange.first(context);
like.end = vrange.last(context);
like.excl = vrange.isExcludeEnd();
} else {
if (!range.respondsTo("begin") || !range.respondsTo("end")
|| !range.respondsTo("exclude_end?")) {
return null;
}
like.begin = Helpers.invoke(context, range, "begin");
like.end = Helpers.invoke(context, range, "end");
like.excl = Helpers.invoke(context, range, "exclude_end?").isTrue();
}
like.range = Helpers.invoke(context, like.end, "-", like.begin);
return like;
}
@JRubyMethod(meta = true)
public static IRubyObject srand(ThreadContext context, IRubyObject recv) {
return srandCommon(context, recv);
}
@JRubyMethod(meta = true)
public static IRubyObject srand(ThreadContext context, IRubyObject recv, IRubyObject seed) {
return srandCommon(context, recv, seed);
}
public static IRubyObject srandCommon(ThreadContext context, IRubyObject recv) {
return srandCommon(context, recv, randomSeed(context.runtime));
}
@SuppressWarnings("deprecation")
public static IRubyObject srandCommon(ThreadContext context, IRubyObject recv, IRubyObject newSeed) {
RandomType defaultRand = getDefaultRand(context);
IRubyObject previousSeed = defaultRand.getSeed();
defaultRand = new RandomType(newSeed);
context.runtime.setDefaultRand(defaultRand);
((RubyRandom) (context.runtime.getRandomClass()).getConstant("DEFAULT")).setRandomType(defaultRand);
return previousSeed;
}
@Deprecated
public IRubyObject op_equal_19(ThreadContext context, IRubyObject obj) {
return op_equal(context, obj);
}
@Override
@JRubyMethod(name = "==", required = 1)
public IRubyObject op_equal(ThreadContext context, IRubyObject obj) {
if (!getType().equals(obj.getType())) {
return context.fals;
}
return context.runtime.newBoolean(random.equals(((RubyRandom) obj).random));
}
@JRubyMethod(name = "state", visibility = PRIVATE)
public IRubyObject stateObj(ThreadContext context) {
return random.getState();
}
@JRubyMethod(name = "left", visibility = PRIVATE)
public IRubyObject leftObj(ThreadContext context) {
return RubyNumeric.int2fix(context.runtime, random.getLeft());
}
@JRubyMethod(name = "state", meta = true, visibility = PRIVATE)
public static IRubyObject state(ThreadContext context, IRubyObject recv) {
return getDefaultRand(context).getState();
}
@JRubyMethod(name = "left", meta = true, visibility = PRIVATE)
public static IRubyObject left(ThreadContext context, IRubyObject recv) {
return RubyNumeric.int2fix(context.runtime, getDefaultRand(context).getLeft());
}
@JRubyMethod(name = "marshal_dump")
public IRubyObject marshal_dump(ThreadContext context) {
RubyBignum state = random.getState();
RubyInteger left = RubyFixnum.newFixnum(context.runtime, (long) random.getLeft());
RubyArray dump = RubyArray.newArray(context.runtime, state, left, random.getSeed());
if (hasVariables()) {
dump.syncVariables(this);
}
return dump;
}
@JRubyMethod()
public IRubyObject marshal_load(ThreadContext context, IRubyObject arg) {
RubyArray load = arg.convertToArray();
if (load.size() != 3) {
throw context.runtime.newArgumentError("wrong dump data");
}
if (!(load.eltInternal(0) instanceof RubyBignum)) {
throw context.runtime.newTypeError(load.eltInternal(0), context.runtime.getBignum());
}
RubyBignum state = (RubyBignum) load.eltInternal(0);
int left = RubyNumeric.num2int(load.eltInternal(1));
IRubyObject seed = load.eltInternal(2);
random = new RandomType(seed, state, left);
if (load.hasVariables()) {
syncVariables((IRubyObject) load);
}
return this;
}
@JRubyMethod(name = "bytes")
public IRubyObject bytes(ThreadContext context, IRubyObject arg) {
int n = RubyNumeric.num2int(arg);
byte[] bytes = new byte[n];
int idx = 0;
for (; n >= 4; n -= 4) {
int r = random.genrandInt32();
for (int i = 0; i < 4; ++i) {
bytes[idx++] = (byte) (r & 0xff);
r >>>= 8;
}
}
if (n > 0) {
int r = random.genrandInt32();
for (int i = 0; i < n; ++i) {
bytes[idx++] = (byte) (r & 0xff);
r >>>= 8;
}
}
return context.runtime.newString(new ByteList(bytes));
}
private static RandomType tryGetRandomType(ThreadContext context, IRubyObject obj) {
if (obj.equals(context.runtime.getRandomClass())) return getDefaultRand(context);
if (obj instanceof RubyRandom) return ((RubyRandom) obj).random;
return null;
}
public static long randomLongLimited(ThreadContext context, IRubyObject obj, long limit) {
RandomType rnd = tryGetRandomType(context, obj);
if (rnd == null) {
RubyInteger v = Helpers.invokePublic(context, obj, "rand", context.runtime.newFixnum(limit + 1)).convertToInteger();
long r = RubyNumeric.num2long(v);
if (r < 0) throw context.runtime.newRangeError("random number too small " + r);
if (r > limit) throw context.runtime.newRangeError("random number too big " + r);
return r;
}
return randLimitedFixnumInner(rnd.impl, limit);
}
public static double randomReal(ThreadContext context, IRubyObject obj) {
RandomType random = tryGetRandomType(context, obj);
if (random != null) return random.genrandReal();
double d = RubyNumeric.num2dbl(context, Helpers.invoke(context, obj, "rand"));
if (d < 0.0 || d >= 1.0) throw context.runtime.newRangeError("random number too big: " + d);
return d;
}
@JRubyMethod(name = "new_seed", meta = true)
public static IRubyObject newSeed(ThreadContext context, IRubyObject recv) {
return randomSeed(context.runtime);
}
@JRubyMethod(name = "urandom", meta = true)
public static IRubyObject urandom(ThreadContext context, IRubyObject recv, IRubyObject num) {
Ruby runtime = context.runtime;
int n = num.convertToInteger().getIntValue();
if (n < 0) throw runtime.newArgumentError("negative string size (or size too big)");
if (n == 0) return runtime.newString();
byte[] seed = new byte[n];
runtime.random.nextBytes(seed);
return RubyString.newString(runtime, seed);
}
private void setRandomType(RandomType random) {
this.random = random;
}
}