package org.ovirt.engine.core.utils; import java.math.BigInteger; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Random; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.commons.lang.ArrayUtils; /** * {@code RandomUtils} is a singleton class with more powerful random generating methods. * <P> * Useful methods include: * <UL> * <LI>{@code nextXXX()} for the {@code byte} and {@code short} types. * <LI>{@code nextXXX(mod)} for all types. * <LI>{@code nextXXX(min, max)} for all types. * <LI>{@code pickRandom()} - picks a random element from a given {@code Collection} or array. * </UL> * * @see java.util.Random */ @SuppressWarnings("serial") public final class RandomUtils extends Random { /* --- Class constants --- */ /** Error message for a case when the min is larger than max, */ private static final String MIN_MAX_ERROR = "min must be less than or equal to max"; /** The first printable character. */ private static final char FIRST_PRINTABLE_CHAR = ' '; /** The last printable character. */ private static final char LAST_PRINTABLE_CHAR = '~'; /** The first XML printable character. */ private static final char FIRST_XML_PRINTABLE_CHAR = 'A'; /** The last XML printable character. */ private static final char LAST_XML_PRINTABLE_CHAR = 'Z'; /** The legal characters for an entity name. */ private static final char[] LEGAL_PROPERTY_CHARS = (fillRange('a', 'z') + fillRange('A', 'Z') + fillRange('0', '9') + "_.").toCharArray(); /* --- Class Fields --- */ /** The single(ton) instance. */ private static RandomUtils instance = new RandomUtils(); /* --- Instance Fields --- */ /** The seed that was last set. */ private long seed; /* --- Constructor --- */ /** * Private constructor so that only we can instantiate the instance. */ private RandomUtils() { } /* --- Singleton-related Methods --- */ /** * Returns the single(ton) instance. */ public static RandomUtils instance() { return instance; } /** * Returns the single(ton) instance set with the given seed. */ public static RandomUtils instance(long seed) { instance.setSeed(seed); return instance; } /* --- Seed-related Methods --- */ /** * The last seed is saved, so it is possible to {@link #getSeed()} it later. Since {@link Random}'s * seed is private, I am obliged to save my own copy. * * See {@link Random#setSeed(long)}. */ @Override public synchronized void setSeed(long seed) { super.setSeed(seed); this.seed = seed; } /** * Returns the seed that was set last. */ public synchronized long getSeed() { return seed; } /* --- Byte-related Methods --- */ /** * Randomizes a {@code byte} value. */ public byte nextByte() { return (byte) nextInt(); } /** * Randomize a {@code byte} value between 0 (inclusive) and the specified value (exclusive). */ public byte nextByte(byte b) { return (byte) nextInt(b); } /** * Randomize a {@code byte} value in the given range [min, max]. */ public byte nextByte(byte min, byte max) { if (min > max) { throw new IllegalArgumentException(MIN_MAX_ERROR); } return (byte) (min + nextByte((byte) (max - min + 1))); } /* --- Short-related Methods --- */ /** * Randomizes a {@code short} value. */ public short nextShort() { return (short) nextInt(); } /** * Randomize a {@code short} value between 0 (inclusive) and the specified value (exclusive). */ public short nextShort(short s) { return (short) nextInt(s); } /** * Randomize a {@code short} value in the given range [min, max]. */ public short nextShort(short min, short max) { if (min > max) { throw new IllegalArgumentException(MIN_MAX_ERROR); } return (short) (min + nextShort((short) (max - min + 1))); } /* --- Integer-related Methods --- */ /** * Randomize an {@code int} value in the given range [min, max]. */ public int nextInt(int min, int max) { if (min > max) { throw new IllegalArgumentException(MIN_MAX_ERROR); } return min + nextInt(max - min + 1); } /* --- Long-related Methods --- */ /** * Randomize a {@code long} value between 0 (inclusive) and the specified value (exclusive). */ public long nextLong(long l) { if (l <= 0) { throw new IllegalArgumentException("l must be greater than 0!"); } long rand = nextLong(); if (rand == Long.MIN_VALUE) { rand++; } return Math.abs(rand) % l; } /** * Randomize a {@code long} value in the given range [min, max]. */ public long nextLong(long min, long max) { if (min > max) { throw new IllegalArgumentException(MIN_MAX_ERROR); } return min + nextLong(max - min + 1); } /* --- Float-related Methods --- */ /** * Randomize a {@code float} value between 0.0 (inclusive) and the specified value (exclusive). */ public float nextFloat(float f) { return nextFloat(f, false); } /** * Randomize a {@code float} value between 0.0 (inclusive) and the specified value (inclusive or exclusive as * required). * * @param inclusive * Whether or not, the returned value should include the given one. */ public float nextFloat(float f, boolean inclusive) { if (f <= 0.0F) { throw new IllegalArgumentException("f must be greater than 0!"); } // Randomize a float float rand = nextFloat(); // If the returned value should not include the given one, // make sure that the randomized float is not exactly 1.0 if (!inclusive) { while (rand == 1.0F) { rand = nextFloat(); } } return rand * f; } /** * Randomize a {@code float} value in the given range [min, max]. */ public float nextFloat(float min, float max) { if (min > max) { throw new IllegalArgumentException(MIN_MAX_ERROR); } return min + nextFloat(max - min, true); } /* --- Double-related Methods --- */ /** * Randomize a {@code double} value between 0.0 (inclusive) and the specified value (exclusive). */ public double nextDouble(double d) { return nextDouble(d, false); } /** * Randomize a {@code double} value between 0.0 (inclusive) and the specified value (inclusive or exclusive as * required). * * @param inclusive * Whether or not, the returned value should include the given one. */ public double nextDouble(double d, boolean inclusive) { if (d <= 0.0D) { throw new IllegalArgumentException("d must be greater than 0!"); } // Randomize a double double rand = nextDouble(); // If the returned value should not include the given one, // make sure that the randomized float is not exactly 1.0 if (!inclusive) { while (rand == 1.0D) { rand = nextDouble(); } } return rand * d; } /* --- Collections-related Methods --- */ /** * Picks a random element from the given {@code Collection}. */ public <T> T pickRandom(Collection<T> c) { int elementIndex = nextInt(c.size()); Iterator<T> iter = c.iterator(); for (int i = 0; i < elementIndex; ++i) { iter.next(); } return iter.next(); } /* --- Array-related Methods --- */ /** * Picks a random element from the given array. */ public <T> T pickRandom(T[] o) { return pickRandom(Arrays.asList(o)); } /* --- String-related Methods --- */ /** * Randomize a {@code String}. * * @param length * The requested length of the string. * @param printable * Whether or not, the string should contain only printable characters. */ public String nextString(int length, boolean printable) { if (printable) { byte[] data = new byte[length]; for (int i = 0; i < length; ++i) { data[i] = (byte) nextInt(FIRST_PRINTABLE_CHAR, LAST_PRINTABLE_CHAR); } return new String(data); } return new String(nextBytes(length)); } /** * Randomize a valid numeric string. * * @param length * The requested length of the string. */ public String nextNumericString(int length) { return Long.toString(nextLong( (long) Math.pow(10, length - 1), (long) (Math.pow(10, length) - 1))); } /** * Randomize a valid XML Element name. * * @param length * The requested length of the string. */ public String nextXmlString(int length) { byte[] data = new byte[length]; for (int i = 0; i < length; ++i) { data[i] = (byte) nextInt(FIRST_XML_PRINTABLE_CHAR, LAST_XML_PRINTABLE_CHAR); } return new String(data); } /** * Randomize a valid entity name. * * @param length * The requested length of the string. */ public String nextPropertyString(int length) { return nextString(length, LEGAL_PROPERTY_CHARS); } /** * Randomize a printable {@code String}. * * @param length * The requested length of the string. */ public String nextString(int length) { return nextString(length, true); } /** * Randomize a {@code String} made up of the given characters * * @param length * The requested length of the string. */ public String nextString(int length, char[] chars) { Character[] characters = ArrayUtils.toObject(chars); StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; ++i) { sb.append(pickRandom(characters)); } return sb.toString(); } /** * Randomize a {@code String} of a length in the given range [min, max]. * * @param printable * Whether or not, the string should contain only printable characters. */ public String nextString(int min, int max, boolean printable) { return nextString(nextInt(min, max), printable); } /** * Randomize a printable {@code String} of a length in the given range [min, max]. */ public String nextString(int min, int max) { return nextString(nextInt(min, max), true); } /* --- General Utility Methods --- */ /** * Creates a {@code byte} array of the specified size, initialized with random values. */ public byte[] nextBytes(int size) { byte[] data = new byte[size]; nextBytes(data); return data; } /* -- Big Integer related methods -- */ /** * generates a new big integer with the desired number of bits the generated number will always be positive. * * @param numOfBits * the number of bits of the Big Integer * @return the randomized big integer. */ public BigInteger nextBigInt(int numOfBits) { return new BigInteger(numOfBits, this); } /** * Returns a random value from an enum. * * @param <T> * The enum type. * @param enumClass * The enum class to randomize. * * @return A random enum from the given enum, or null if got null. */ public <T extends Enum<?>> T nextEnum(Class<T> enumClass) { if (enumClass == null) { return null; } return pickRandom(enumClass.getEnumConstants()); } /** * @return A random MAC address. */ public String nextMacAddress() { return IntStream.range(0, 6).mapToObj(i -> Integer.toHexString(nextInt(0x10, 0x100))).collect(Collectors.joining(":")); } /* -- Utility methods -- */ private static String fillRange(char first, char last) { StringBuilder sb = new StringBuilder((int)last - (int)first + 1); for (char c = first; c <= last; ++ c) { sb.append(c); } return sb.toString(); } }