io.github.serpro69.kfaker.RandomService.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-faker Show documentation
Show all versions of kotlin-faker Show documentation
https://github.com/serpro69/kotlin-faker
package io.github.serpro69.kfaker
import com.ibm.icu.text.UnicodeSet
import com.ibm.icu.util.LocaleData
import com.ibm.icu.util.ULocale
import java.util.*
import kotlin.experimental.and
import kotlin.experimental.or
import kotlin.random.asKotlinRandom
/**
* Wrapper around [Random] that also contains some additional functions not covered by [Random].
*
* If two instances of this [RandomService] are created with the same seed,
* and the same sequence of method calls is made for each,
* then they will generate and return identical sequences of values.
*
* Instances of [RandomService] are not cryptographically secure by default.
* Consider passing [java.security.SecureRandom] to the constructor of this [RandomService]
* to get a cryptographically secure pseudo-random generator.
*/
class RandomService internal constructor(private val config: FakerConfig) {
private val random = config.random
private val alphabeticLowerCharset = ('a'..'z')
private val alphabeticUpperCharset = ('A'..'Z')
private val numericCharset = ('0'..'9')
/**
* Returns the next pseudorandom, uniformly distributed [Int] value from this [random] number generator's sequence.
*/
fun nextInt() = random.nextInt()
/**
* Returns a pseudorandom, uniformly distributed [Int] value between `0` (inclusive)
* and the specified [bound] (exclusive),
* drawn from this [random] number generator's sequence.
*/
fun nextInt(bound: Int) = random.nextInt(bound)
/**
* Returns a pseudorandom, uniformly distributed [Int] value within the specified int [range] (inclusive),
* drawn from this [random] number generator's sequence.
*/
fun nextInt(intRange: IntRange): Int {
val lowerBound = requireNotNull(intRange.minOrNull())
val upperBound = requireNotNull(intRange.maxOrNull())
return nextInt(lowerBound, upperBound)
}
/**
* Returns a pseudorandom, uniformly distributed [Int] value between [min] (inclusive) and [max] (inclusive),
* drawn from this [random] number generator's sequence.
*/
fun nextInt(min: Int, max: Int) = random.nextInt(max - min + 1) + min
/**
* Returns a pseudo-randomly selected value from the [list] of values.
*/
fun randomValue(list: List) = list[nextInt(list.size)]
/**
* Returns a pseudo-randomly selected value from the [array] of values.
*/
fun randomValue(array: Array) = array[nextInt(array.size)]
/**
* Returns the next pseudorandom, uniformly distributed [Char] value
* that corresponds to a letter in the English alphabet.
*
* @param upper returns the [Char] in upper-case if set to `true`, and in lower-case otherwise
*/
fun nextLetter(upper: Boolean): Char {
val source = if (upper) alphabeticUpperCharset else alphabeticLowerCharset
return source.random(config.random.asKotlinRandom())
}
/**
* Returns [String] with the specified [length] consisting of a pseudo-randomly generated
* English alphabet letters and optional [numericalChars],
* or an empty string for a `length < 1`.
*
* @param length the length of the resulting string.
* Default: `24`
* @param numericalChars add additional numerical chars from 0 to 9 to the resulting string.
* Default: `true`
*/
fun randomString(length: Int = 24, numericalChars: Boolean = true): String {
if (length < 1) return ""
val charset = if (numericalChars) {
alphabeticLowerCharset + alphabeticUpperCharset + numericCharset
} else alphabeticLowerCharset + alphabeticUpperCharset
return (1..length)
.map { charset.random(this.random.asKotlinRandom()) }
.joinToString("")
}
/**
* Returns the next pseudorandom, uniformly distributed [Boolean] value
* from this random number generator's sequence.
*
* The values `true` and `false` are produced with (approximately) equal probability.
*/
fun nextBoolean() = random.nextBoolean()
/**
* Returns the next pseudorandom, uniformly distributed [Long] value from this [random] number generator's sequence.
*/
fun nextLong() = random.nextLong()
/**
* Returns a pseudorandom, uniformly distributed [Long] value between `0` (inclusive),
* and the specified [bound] value (exclusive),
* drawn from this [random] number generator's sequence.
*
* @throws IllegalArgumentException if `bound < 0`
*/
fun nextLong(bound: Long): Long {
return if (bound > 0) {
var value: Long
do {
val bits = (nextLong().shl(1)).shr(1)
value = bits % bound
} while (bits - value + (bound - 1) < 0L)
value
} else throw IllegalArgumentException("Bound bound must be greater than 0")
}
/**
* Returns the next pseudorandom, uniformly distributed [Float] value between `0.0` and `1.0`
* from this [random] number generator's sequence.
*/
fun nextFloat() = random.nextFloat()
/**
* Returns the next pseudorandom, uniformly distributed [Double] value between `0.0` and `1.0`
* from this [random] number generator's sequence.
*/
fun nextDouble() = random.nextDouble()
/**
* Returns the next pseudorandom, uniformly distributed [Char] value,
* from this [random] number generator's sequence.
*/
fun nextChar() = nextInt().toChar()
/**
* Returns [String] with the specified [length] (or an empty string for a `length < 1`)
* consisting of pseudo-randomly generated characters
* in a given [locale] with optional [auxiliaryChars] and [numericalChars]
*
* @param length the length of the resulting string
* @param locale locale to use to generate the charset. Defaults to `locale` config value set for the `faker` instance
* @param auxiliaryChars add additional auxiliary chars to the resulting string as defined in [Character_Elements](https://www.unicode.org/reports/tr35/tr35-general.html#Character_Elements)
* @param numericalChars add additional numerical chars from 0 to 9 to the resulting string
*/
@Deprecated(
message = "This function is deprecated and will be removed in future releases.\n" +
"Note that default value for 'length' param has changed from '100' to '24' in the new 'randomString' function.",
replaceWith = ReplaceWith("randomString"),
level = DeprecationLevel.WARNING
)
fun nextString(
length: Int = 100,
locale: Locale = Locale.forLanguageTag(config.locale),
auxiliaryChars: Boolean = false,
numericalChars: Boolean = false
): String = randomString(length, locale, auxiliaryChars, numericalChars)
/**
* Returns [String] with the specified [length] (or an empty string for a `length < 1`)
* consisting of pseudo-randomly generated characters
* in a given [locale] with optional [auxiliaryChars] and [numericalChars]
*
* @param length the length of the resulting string.
* Default: `24`
* @param locale locale to use to generate the charset.
* Defaults to `locale` config value set for the `faker` instance.
* @param indexChars add additional index chars to the resulting string, as defined in
* [Character_Elements](https://www.unicode.org/reports/tr35/tr35-general.html#Character_Elements).
* Default: `true`
* @param auxiliaryChars add additional auxiliary chars to the resulting string as defined in
* [Character_Elements](https://www.unicode.org/reports/tr35/tr35-general.html#Character_Elements).
* Default: `false`
* @param numericalChars add additional numerical chars from 0 to 9 to the resulting string
* Default: `false`
*/
@JvmOverloads
fun randomString(
length: Int = 24,
locale: Locale = Locale.forLanguageTag(config.locale),
indexChars: Boolean = true,
auxiliaryChars: Boolean = false,
punctuationChars: Boolean = false,
numericalChars: Boolean = false,
): String {
if (length < 1) return "" // base case
val localeData = LocaleData.getInstance(ULocale.forLocale(locale))
val mainChars = localeData.getExemplarSet(UnicodeSet.MIN_VALUE, LocaleData.ES_STANDARD)
.ranges()
.flatMap { (it.codepoint..it.codepointEnd).map { code -> Char(code) } }
val auxChars = if (auxiliaryChars) {
localeData.getExemplarSet(UnicodeSet.MIN_VALUE, LocaleData.ES_AUXILIARY)
.ranges()
.flatMap { (it.codepoint..it.codepointEnd).map { code -> Char(code) } }
} else emptyList()
val idxChars = if (indexChars) {
localeData.getExemplarSet(UnicodeSet.MIN_VALUE, LocaleData.ES_INDEX)
.ranges()
.flatMap { (it.codepoint..it.codepointEnd).map { code -> Char(code) } }
} else emptyList()
val punctChars = if (punctuationChars) {
localeData.getExemplarSet(UnicodeSet.MIN_VALUE, LocaleData.ES_PUNCTUATION)
.ranges()
.flatMap { (it.codepoint..it.codepointEnd).map { code -> Char(code) } }
} else emptyList()
val numChars = if (numericalChars) numericCharset else emptyList()
val chars = (mainChars + auxChars + idxChars + punctChars + numChars)
return List(length) { chars.random(random.asKotlinRandom()) }.joinToString("")
}
/**
* Returns [String] with a randomLength withing the specified [min] and [max] boundaries
* (or an empty string for if `randomLength < 1`)
* consisting of pseudo-randomly generated characters
* in a given [locale] with optional [auxiliaryChars] and [numericalChars]
*
* @param min the minimum length of the resulting string.
* @param max the maximum length of the resulting string.
* @param locale locale to use to generate the charset.
* Defaults to `locale` config value set for the `faker` instance.
* @param indexChars add additional index chars to the resulting string, as defined in
* [Character_Elements](https://www.unicode.org/reports/tr35/tr35-general.html#Character_Elements).
* Default: `true`
* @param auxiliaryChars add additional auxiliary chars to the resulting string as defined in
* [Character_Elements](https://www.unicode.org/reports/tr35/tr35-general.html#Character_Elements).
* Default: `false`
* @param numericalChars add additional numerical chars from 0 to 9 to the resulting string
* Default: `false`
*/
@JvmOverloads
fun randomString(
min: Int,
max: Int,
locale: Locale = Locale.forLanguageTag(config.locale),
indexChars: Boolean = true,
auxiliaryChars: Boolean = false,
punctuationChars: Boolean = false,
numericalChars: Boolean = false,
): String {
val len = nextInt(min, max)
return randomString(
len,
locale,
indexChars = indexChars,
auxiliaryChars = auxiliaryChars,
punctuationChars = punctuationChars,
numericalChars = numericalChars,
)
}
/**
* Returns a randomly selected enum entry of type [E].
*/
inline fun > nextEnum(): E {
val x: Int = nextInt(enumValues().size)
return enumValues()[x]
}
/**
* Returns a pseudo-randomly selected enum entry of type [E].
*/
fun > nextEnum(enum: Class): E {
return randomValue(enum.enumConstants)
}
/**
* Returns a pseudo-randomly selected enum entry from an [Array] of [E] type [values].
*/
fun > nextEnum(values: Array): E {
return randomValue(values)
}
/**
* Returns a pseudo-randomly selected [enum] class of type [E] based on [predicate] for [E].
*/
tailrec fun > nextEnum(enum: Class, predicate: (E) -> Boolean): E {
val enumClass = nextEnum(enum)
return if (predicate(enumClass)) enumClass else nextEnum(enum, predicate)
}
/**
* Returns a randomly selected enum entry of type [E] excluding a particular class by it's className.
*/
inline fun > nextEnum(excludeName: String): E {
do {
val e: E = nextEnum()
if (e.name.lowercase() != excludeName.lowercase()) {
return e
}
} while (true)
}
/**
* Returns the next pseudorandom [UUID] as [String] taking the seed of this [random].
*/
fun nextUUID(): String {
val randomBytes = ByteArray(16)
random.nextBytes(randomBytes)
randomBytes[6] = randomBytes[6] and 0x0f // clear version
randomBytes[6] = randomBytes[6] or 0x40 // set to version
randomBytes[8] = randomBytes[8] and 0x3f // clear variant
randomBytes[8] = randomBytes[8] or 0x80.toByte() // set to IETF variant
return UUID.nameUUIDFromBytes(randomBytes).toString()
}
/**
* Returns a view of the portion of the [list]
* with pseudo-randomly generated `fromIndex` and (possibly) `toIndex` values.
*
* @param size the desired size of the resulting list.
* If `size <= 0` then `toIndex` will also be randomly-generated.
* @param shuffled if `true` the [list] will be shuffled before extracting the sublist
*/
@JvmOverloads
fun randomSublist(list: List, size: Int = 0, shuffled: Boolean = false): List {
val (from, to) = list.randomFromToIndices(size)
return list.ifEmpty { emptyList() }
.let { if (shuffled) it.shuffled(random) else it }
.subList(from, to)
}
/**
* Returns a view of the portion of the [list]
* with pseudo-randomly generated `fromIndex` and (possibly) `toIndex` values.
*
* @param sizeRange the desired size range of the resulting list.
* The `size` of the returned list is the result of calling [nextInt] with the [sizeRange].
* IF `size <= 0` then `toIndex` will also be randomly-generated.
* @param shuffled if `true` the [list] will be shuffled before extracting the sublist
*/
@JvmOverloads
fun randomSublist(list: List, sizeRange: IntRange, shuffled: Boolean = false): List {
return randomSublist(list, nextInt(sizeRange), shuffled)
}
/**
* Returns a portion of the [set]
* with pseudo-randomly generated `fromIndex` and (possibly) `toIndex` values.
*
* @param size the desired size of the resulting set.
* If `size <= 0` then `toIndex` will also be randomly-generated.
* @param shuffled if `true` the [set] will be shuffled before extracting the subset
*/
@JvmOverloads
fun randomSubset(set: Set, size: Int = 0, shuffled: Boolean = false): Set {
val (from, to) = set.randomFromToIndices(size)
return set.ifEmpty { emptyList() }
.let { if (shuffled) it.shuffled(random) else it }
.mapIndexedNotNull { i, v -> if (i in from until to) v else null }
.toSet()
}
/**
* Returns a portion of the [set]
* with pseudo-randomly generated `fromIndex` and (possibly) `toIndex` values.
*
* @param sizeRange the desired size range of the resulting list.
* The `size` of the returned list is the result of calling [nextInt] with the [sizeRange].
* IF `size <= 0` then `toIndex` will also be randomly-generated.
* @param shuffled if `true` the [set] will be shuffled before extracting the subset
*/
fun randomSubset(set: Set, sizeRange: IntRange, shuffled: Boolean = false): Set {
return randomSubset(set, nextInt(sizeRange), shuffled)
}
private fun Collection.randomFromToIndices(s: Int): Pair {
val fromIndex = if (s > 0) {
(0..size - s).random(random.asKotlinRandom())
} else {
(indices).random(random.asKotlinRandom())
}
val toIndex = if (s > 0) {
fromIndex + s
} else {
(fromIndex..size).random(random.asKotlinRandom())
}
return fromIndex to toIndex
}
}