package sqlancer;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
public final class Randomly {
private static StringGenerationStrategy stringGenerationStrategy = StringGenerationStrategy.SOPHISTICATED;
private static int maxStringLength = 10;
private static boolean useCaching = true;
private static int cacheSize = 100;
private final List cachedLongs = new ArrayList<>();
private final List cachedStrings = new ArrayList<>();
private final List cachedDoubles = new ArrayList<>();
private final List cachedBytes = new ArrayList<>();
private Supplier provider;
private static final ThreadLocal THREAD_RANDOM = new ThreadLocal<>();
private long seed;
private void addToCache(long val) {
if (useCaching && cachedLongs.size() < cacheSize && !cachedLongs.contains(val)) {
cachedLongs.add(val);
}
}
private void addToCache(double val) {
if (useCaching && cachedDoubles.size() < cacheSize && !cachedDoubles.contains(val)) {
cachedDoubles.add(val);
}
}
private void addToCache(String val) {
if (useCaching && cachedStrings.size() < cacheSize && !cachedStrings.contains(val)) {
cachedStrings.add(val);
}
}
private Long getFromLongCache() {
if (!useCaching || cachedLongs.isEmpty()) {
return null;
} else {
return Randomly.fromList(cachedLongs);
}
}
private Double getFromDoubleCache() {
if (!useCaching) {
return null;
}
if (Randomly.getBoolean() && !cachedLongs.isEmpty()) {
return (double) Randomly.fromList(cachedLongs);
} else if (!cachedDoubles.isEmpty()) {
return Randomly.fromList(cachedDoubles);
} else {
return null;
}
}
private String getFromStringCache() {
if (!useCaching) {
return null;
}
if (Randomly.getBoolean() && !cachedLongs.isEmpty()) {
return String.valueOf(Randomly.fromList(cachedLongs));
} else if (Randomly.getBoolean() && !cachedDoubles.isEmpty()) {
return String.valueOf(Randomly.fromList(cachedDoubles));
} else if (Randomly.getBoolean() && !cachedBytes.isEmpty()
&& stringGenerationStrategy == StringGenerationStrategy.SOPHISTICATED) {
return new String(Randomly.fromList(cachedBytes));
} else if (!cachedStrings.isEmpty()) {
String randomString = Randomly.fromList(cachedStrings);
if (Randomly.getBoolean()) {
return randomString;
} else {
return stringGenerationStrategy.transformCachedString(this, randomString);
}
} else {
return null;
}
}
private static boolean cacheProbability() {
return useCaching && getNextLong(0, 3) == 1;
}
// CACHING END
public static T fromList(List list) {
return list.get((int) getNextLong(0, list.size()));
}
@SafeVarargs
public static T fromOptions(T... options) {
return options[getNextInt(0, options.length)];
}
@SafeVarargs
public static List nonEmptySubset(T... options) {
int nr = 1 + getNextInt(0, options.length);
return extractNrRandomColumns(Arrays.asList(options), nr);
}
public static List nonEmptySubset(List columns) {
int nr = 1 + getNextInt(0, columns.size());
return nonEmptySubset(columns, nr);
}
public static List nonEmptySubset(List columns, int nr) {
if (nr > columns.size()) {
throw new AssertionError(columns + " " + nr);
}
return extractNrRandomColumns(columns, nr);
}
public static List nonEmptySubsetPotentialDuplicates(List columns) {
List arr = new ArrayList<>();
for (int i = 0; i < Randomly.smallNumber() + 1; i++) {
arr.add(Randomly.fromList(columns));
}
return arr;
}
public static List subset(List columns) {
int nr = getNextInt(0, columns.size() + 1);
return extractNrRandomColumns(columns, nr);
}
public static List subset(int nr, @SuppressWarnings("unchecked") T... values) {
List list = new ArrayList<>();
for (T val : values) {
list.add(val);
}
return extractNrRandomColumns(list, nr);
}
public static List subset(@SuppressWarnings("unchecked") T... values) {
List list = new ArrayList<>();
for (T val : values) {
list.add(val);
}
return subset(list);
}
public static List extractNrRandomColumns(List columns, int nr) {
assert nr >= 0;
List selectedColumns = new ArrayList<>();
List remainingColumns = new ArrayList<>(columns);
for (int i = 0; i < nr; i++) {
selectedColumns.add(remainingColumns.remove(getNextInt(0, remainingColumns.size())));
}
return selectedColumns;
}
public static int smallNumber() {
// no need to cache for small numbers
return (int) (Math.abs(getThreadRandom().get().nextGaussian())) * 2;
}
public static boolean getBoolean() {
return getThreadRandom().get().nextBoolean();
}
public static double getPercentage() {
return getThreadRandom().get().nextDouble();
}
private static ThreadLocal getThreadRandom() {
if (THREAD_RANDOM.get() == null) {
// a static method has been called, before Randomly was instantiated
THREAD_RANDOM.set(new Random());
}
return THREAD_RANDOM;
}
public long getInteger() {
if (smallBiasProbability()) {
return Randomly.fromOptions(-1L, Long.MAX_VALUE, Long.MIN_VALUE, 1L, 0L);
} else {
if (cacheProbability()) {
Long l = getFromLongCache();
if (l != null) {
return l;
}
}
long nextLong = getThreadRandom().get().nextInt();
addToCache(nextLong);
return nextLong;
}
}
public enum StringGenerationStrategy {
NUMERIC {
@Override
public String getString(Randomly r) {
return getStringOfAlphabet(r, NUMERIC_ALPHABET);
}
},
ALPHANUMERIC {
@Override
public String getString(Randomly r) {
return getStringOfAlphabet(r, ALPHANUMERIC_ALPHABET);
}
},
ALPHANUMERIC_SPECIALCHAR {
@Override
public String getString(Randomly r) {
return getStringOfAlphabet(r, ALPHANUMERIC_SPECIALCHAR_ALPHABET);
}
},
SOPHISTICATED {
private static final String ALPHABET = ALPHANUMERIC_SPECIALCHAR_ALPHABET;
@Override
public String getString(Randomly r) {
if (smallBiasProbability()) {
return Randomly.fromOptions("TRUE", "FALSE", "0.0", "-0.0", "1e500", "-1e500");
}
if (cacheProbability()) {
String s = r.getFromStringCache();
if (s != null) {
return s;
}
}
int n = ALPHABET.length();
StringBuilder sb = new StringBuilder();
int chars = getStringLength(r);
for (int i = 0; i < chars; i++) {
if (Randomly.getBooleanWithRatherLowProbability()) {
char val = (char) r.getInteger();
if (val != 0) {
sb.append(val);
}
} else {
sb.append(ALPHABET.charAt(getNextInt(0, n)));
}
}
while (Randomly.getBooleanWithSmallProbability()) {
String[][] pairs = { { "{", "}" }, { "[", "]" }, { "(", ")" } };
int idx = (int) Randomly.getNotCachedInteger(0, pairs.length);
int left = (int) Randomly.getNotCachedInteger(0, sb.length() + 1);
sb.insert(left, pairs[idx][0]);
int right = (int) Randomly.getNotCachedInteger(left + 1, sb.length() + 1);
sb.insert(right, pairs[idx][1]);
}
if (r.provider != null) {
while (Randomly.getBooleanWithSmallProbability()) {
if (sb.length() == 0) {
sb.append(r.provider.get());
} else {
sb.insert((int) Randomly.getNotCachedInteger(0, sb.length()), r.provider.get());
}
}
}
String s = sb.toString();
r.addToCache(s);
return s;
}
public String transformCachedString(Randomly r, String randomString) {
if (Randomly.getBoolean()) {
return randomString.toLowerCase();
} else if (Randomly.getBoolean()) {
return randomString.toUpperCase();
} else {
char[] chars = randomString.toCharArray();
if (chars.length != 0) {
for (int i = 0; i < Randomly.smallNumber(); i++) {
chars[r.getInteger(0, chars.length)] = ALPHABET.charAt(r.getInteger(0, ALPHABET.length()));
}
}
return new String(chars);
}
}
};
private static final String ALPHANUMERIC_SPECIALCHAR_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#<>/.,~-+'*()[]{} ^*?%_\t\n\r|&\\";
private static final String ALPHANUMERIC_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final String NUMERIC_ALPHABET = "0123456789";
private static int getStringLength(Randomly r) {
int chars;
if (Randomly.getBoolean()) {
chars = Randomly.smallNumber();
} else {
chars = r.getInteger(0, maxStringLength);
}
return chars;
}
private static String getStringOfAlphabet(Randomly r, String alphabet) {
int chars = getStringLength(r);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < chars; i++) {
sb.append(alphabet.charAt(getNextInt(0, alphabet.length())));
}
return sb.toString();
}
public abstract String getString(Randomly r);
public String transformCachedString(Randomly r, String s) {
return s;
}
}
public String getString() {
return stringGenerationStrategy.getString(this);
}
public byte[] getBytes() {
int size = Randomly.smallNumber();
byte[] arr = new byte[size];
getThreadRandom().get().nextBytes(arr);
return arr;
}
public long getNonZeroInteger() {
long value;
if (smallBiasProbability()) {
return Randomly.fromOptions(-1L, Long.MAX_VALUE, Long.MIN_VALUE, 1L);
}
if (cacheProbability()) {
Long l = getFromLongCache();
if (l != null && l != 0) {
return l;
}
}
do {
value = getInteger();
} while (value == 0);
assert value != 0;
addToCache(value);
return value;
}
public long getPositiveInteger() {
if (cacheProbability()) {
Long value = getFromLongCache();
if (value != null && value >= 0) {
return value;
}
}
long value;
if (smallBiasProbability()) {
value = Randomly.fromOptions(0L, Long.MAX_VALUE, 1L);
} else {
value = getNextLong(0, Long.MAX_VALUE);
}
addToCache(value);
assert value >= 0;
return value;
}
public double getFiniteDouble() {
while (true) {
double val = getDouble();
if (Double.isFinite(val)) {
return val;
}
}
}
public double getDouble() {
if (smallBiasProbability()) {
return Randomly.fromOptions(0.0, -0.0, Double.MAX_VALUE, -Double.MAX_VALUE, Double.POSITIVE_INFINITY,
Double.NEGATIVE_INFINITY);
} else if (cacheProbability()) {
Double d = getFromDoubleCache();
if (d != null) {
return d;
}
}
double value = getThreadRandom().get().nextDouble();
addToCache(value);
return value;
}
private static boolean smallBiasProbability() {
return getThreadRandom().get().nextInt(100) == 1;
}
public static boolean getBooleanWithRatherLowProbability() {
return getThreadRandom().get().nextInt(10) == 1;
}
public static boolean getBooleanWithSmallProbability() {
return smallBiasProbability();
}
public int getInteger(int left, int right) {
if (left == right) {
return left;
}
return (int) getLong(left, right);
}
// TODO redundant?
public long getLong(long left, long right) {
if (left == right) {
return left;
}
return getNextLong(left, right);
}
public BigDecimal getRandomBigDecimal() {
return new BigDecimal(getThreadRandom().get().nextDouble());
}
public long getPositiveIntegerNotNull() {
while (true) {
long val = getPositiveInteger();
if (val != 0) {
return val;
}
}
}
public static long getNonCachedInteger() {
return getThreadRandom().get().nextLong();
}
public static long getPositiveOrZeroNonCachedInteger() {
return getNextLong(0, Long.MAX_VALUE);
}
public static long getNotCachedInteger(int lower, int upper) {
return getNextLong(lower, upper);
}
public Randomly(Supplier provider) {
this.provider = provider;
}
public Randomly() {
THREAD_RANDOM.set(new Random());
}
public Randomly(long seed) {
this.seed = seed;
THREAD_RANDOM.set(new Random(seed));
}
public static double getUncachedDouble() {
return getThreadRandom().get().nextDouble();
}
public String getChar() {
while (true) {
String s = getString();
if (!s.isEmpty()) {
return s.substring(0, 1);
}
}
}
public String getAlphabeticChar() {
while (true) {
String s = getChar();
if (Character.isAlphabetic(s.charAt(0))) {
return s;
}
}
}
// see https://stackoverflow.com/a/2546158
// uniformity does not seem to be important for us
// SQLancer previously used ThreadLocalRandom.current().nextLong(lower, upper)
private static long getNextLong(long lower, long upper) {
if (lower > upper) {
throw new IllegalArgumentException(lower + " " + upper);
}
if (lower == upper) {
return lower;
}
return (long) (getThreadRandom().get().longs(lower, upper).findFirst().getAsLong());
}
private static int getNextInt(int lower, int upper) {
return (int) getNextLong(lower, upper);
}
public long getSeed() {
return seed;
}
public static void initialize(MainOptions options) {
stringGenerationStrategy = options.getRandomStringGenerationStrategy();
maxStringLength = options.getMaxStringConstantLength();
useCaching = options.useConstantCaching();
cacheSize = options.getConstantCacheSize();
}
}