@accordproject/concerto-core
Version:
Core Implementation for the Concerto Modeling Language
403 lines (364 loc) • 12.4 kB
JavaScript
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
;
const { loremIpsum } = require('lorem-ipsum');
const RandExp = require('randexp');
const dayjs = require('dayjs');
const utc = require('dayjs/plugin/utc');
dayjs.extend(utc);
/**
* Generate a random number within a given range with
* a prescribed precision and inside a global range
* @param {*} userMin - Lower bound on the range, inclusive. Defaults to systemMin
* @param {*} userMax - Upper bound on the range, inclusive. Defaults to systemMax
* @param {*} precision - The precision of values returned, e.g. a value of `1` returns only whole numbers
* @param {*} systemMin - Global minimum on the range, takes precidence over the userMin
* @param {*} systemMax - Global maximum on the range, takes precidence over the userMax
* @return {number} a number
*/
const randomNumberInRangeWithPrecision = function (userMin, userMax, precision, systemMin, systemMax) {
if (userMin === null) {
userMin = systemMin;
}
userMin = Math.min(Math.max(userMin, systemMin), systemMax);
if (userMax === null || userMax > systemMax) {
userMax = systemMax;
}
userMax = Math.max(Math.min(userMax, systemMax), systemMin);
userMax += precision;
userMax = userMax / precision;
userMin = userMin / precision;
let randomNumber = (Math.random() * (userMax - userMin) + userMin);
return randomNumber / (1 / precision);
};
/**
* Get a random value from the range.
* @param {number} lowerBound the lower bound on the range, inclusive.
* @param {number} upperBound the upper bound on the range, inclusive.
* @param {string} type the number type for the range,
* `'Long'`, `'Double'`, or `'Integer'`
* @return {number} a number.
* @private
*/
const getRange = (lowerBound, upperBound, type) => {
let min = lowerBound;
let max = upperBound;
if (max !== null && min !== null && max < min) {
min = upperBound;
max = lowerBound;
}
switch(type){
case 'Long':
return Math.floor(
randomNumberInRangeWithPrecision(min, max, 1, -Math.pow(2, 32), Math.pow(2, 32))
);
case 'Integer': {
return Math.floor(
randomNumberInRangeWithPrecision(min, max, 1, -Math.pow(2, 16), Math.pow(2, 16))
);
}
case 'Double': {
// IEEE 754 numbers can be larger,
// but we don't need the whole range when generating a sample random number
return Number(
randomNumberInRangeWithPrecision(min, max, 0.0001, -Math.pow(2, 8), Math.pow(2, 8))
.toFixed(3)
);
}
default:
return 0;
}
};
/**
* Get a randomly generated sample String value with lower and upper bound.
* @param {string} seedString a String value.
* @param {number} minLength the lower bound on the range, inclusive.
* @param {number} maxLength the upper bound on the range, inclusive.
* @param {Function} stringGenFunc String gen function
* @return {string} a String value.
* @private
*/
const generateString = (seedString, minLength, maxLength, stringGenFunc) => {
minLength ??= 0; //set to 0 if null or underfined
maxLength ??= null; //set to null if null or undefined.
const stringLength = getRange(minLength, maxLength, 'Integer');
while(seedString.length < stringLength) {
seedString += stringGenFunc();
}
return seedString.substring(0, stringLength);
};
/**
* Get a randomly generated sample String value with lower and upper bound.
* @param {number} minLength the lower bound on the range, inclusive.
* @param {number} maxLength the upper bound on the range, inclusive.
* @return {string} a String value.
* @private
*/
const getString = (minLength, maxLength) => {
const lower = 1;
const upper = 5;
let stringValue = loremIpsum({
count: 1 // Number of words, sentences, or paragraphs to generate.
, units: 'sentences' // Generate words, sentences, or paragraphs.
, sentenceLowerBound: lower // Minimum words per sentence.
, sentenceUpperBound: upper // Maximum words per sentence.
});
if (minLength || maxLength) {
stringValue = generateString(stringValue, minLength, maxLength, () => loremIpsum({
count: 1 // Number of words, sentences, or paragraphs to generate.
, units: 'sentences' // Generate words, sentences, or paragraphs.
, sentenceLowerBound: lower // Minimum words per sentence.
, sentenceUpperBound: upper // Maximum words per sentence.
}));
}
return stringValue;
};
/**
* Get a randomly generated sample regex String value with lower and upper bound.
* @param {RegExp} regex A regular expression.
* @param {number} minLength the lower bound on the range, inclusive.
* @param {number} maxLength the upper bound on the range, inclusive.
* @return {string} a String value.
* @private
*/
const getRegexString = (regex, minLength, maxLength) => {
if (!regex) {
return '';
}
const randexp = new RandExp(regex.source, regex.flags);
let stringValue = randexp.gen();
if (minLength || maxLength) {
stringValue = generateString(stringValue, minLength, maxLength, () => randexp.gen());
}
return stringValue;
};
/**
* Empty value generator.
* @private
*/
class EmptyValueGenerator {
/**
* This constructor should not be called directly.
* @private
*/
constructor() {
this.currentDate = dayjs.utc();
}
/**
* Get a default DateTime value.
* @return {object} a date value.
*/
getDateTime() {
return this.currentDate;
}
/**
* Get a default Integer value.
* @return {number} an Integer value.
*/
getInteger() {
return 0;
}
/**
* Get a default Long value.
* @return {number} a Long value.
*/
getLong() {
return 0;
}
/**
* Get a default Double value.
* @return {number} a Double value.
*/
getDouble() {
return 0.000;
}
/**
* Get a default Boolean value.
* @return {boolean} a Boolean value.
*/
getBoolean() {
return false;
}
/**
* Get a randomly generated sample String value with lower and upper bound.
* @param {number} minLength the lower bound on the range, inclusive.
* @param {number} maxLength the upper bound on the range, inclusive.
* @return {string} a String value.
*/
getString(minLength, maxLength) {
if (minLength || maxLength) {
return getString(minLength, maxLength);
}
return '';
}
/**
* Get the first enum value from the supplied array.
* @param {Array} enumValues Array of possible enum values.
* @return {*} an enum value.
*/
getEnum(enumValues) {
return enumValues[0];
}
/**
* Get an instance of an empty map.
* @return {*} an map value.
*/
getMap() {
return new Map();
}
/**
* Get an array using the supplied callback to obtain array values.
* @param {Function} valueSupplier - callback to obtain values.
* @return {Array} an array
*/
getArray(valueSupplier) {
return [];
}
/**
* Get a randomly generated sample regex String value with lower and upper bound.
* @param {RegExp} regex A regular expression.
* @param {number} minLength the lower bound on the range, inclusive.
* @param {number} maxLength the upper bound on the range, inclusive.
* @return {string} a String value.
*/
getRegex(regex, minLength, maxLength) {
return getRegexString(regex, minLength, maxLength);
}
/**
* Get a random value from the range.
* @param {number} lowerBound the lower bound on the range, inclusive.
* @param {number} upperBound the upper bound on the range, inclusive.
* @param {string} type the number type for the range,
* `'Long'`, `'Double'`, or `'Integer'`
* @return {number} a number.
*/
getRange(lowerBound, upperBound, type) {
return getRange(lowerBound, upperBound, type);
}
}
/**
* Sample data value generator.
* @private
*/
class SampleValueGenerator extends EmptyValueGenerator {
/**
* This constructor should not be called directly.
* @private
*/
constructor() {
super();
}
/**
* Get a randomly generated sample Integer value.
* @return {number} an Integer value.
*/
getInteger() {
return Math.round(Math.random() * Math.pow(2, 16));
}
/**
* Get a randomly generated sample Long value.
* @return {number} a Long value.
*/
getLong() {
return Math.round(Math.random() * Math.pow(2, 32));
}
/**
* Get a randomly generated sample Double value.
* @return {number} a Double value.
*/
getDouble() {
return Number((Math.random() * Math.pow(2, 8)).toFixed(3));
}
/**
* Get a randomly generated sample Boolean value.
* @return {boolean} a Boolean value.
*/
getBoolean() {
return Math.round(Math.random()) === 1;
}
/**
* Get a randomly generated sample String value with lower and upper bound.
* @param {number} minLength the lower bound on the range, inclusive.
* @param {number} maxLength the upper bound on the range, inclusive.
* @return {string} a String value.
*/
getString(minLength, maxLength) {
return getString(minLength, maxLength);
}
/**
* Get a randomly selected enum value from the supplied array.
* @param {Array} enumValues Array of possible enum values.
* @return {*} an enum value.
*/
getEnum(enumValues) {
return enumValues[Math.floor(Math.random() * enumValues.length)];
}
/**
* Get a map instance with randomly generated values for key & value.
* @return {*} a map value.
*/
getMap() {
return new Map([[this.getString(1,10), this.getString(1,10)]]);
}
/**
* Get an array using the supplied callback to obtain array values.
* @param {Function} valueSupplier - callback to obtain values.
* @return {Array} an array
*/
getArray(valueSupplier) {
return [valueSupplier()];
}
/**
* Get a randomly generated sample regex String value with lower and upper bound.
* @param {RegExp} regex A regular expression.
* @param {number} minLength the lower bound on the range, inclusive.
* @param {number} maxLength the upper bound on the range, inclusive.
* @return {string} a String value.
*/
getRegex(regex, minLength, maxLength) {
return getRegexString(regex, minLength, maxLength);
}
/**
* Get a random value from the range.
* @param {number} lowerBound the lower bound on the range, inclusive.
* @param {number} upperBound the upper bound on the range, inclusive.
* @param {string} type the number type for the range,
* `'Long'`, `'Double'`, or `'Integer'`
* @return {number} a number.
*/
getRange(lowerBound, upperBound, type) {
return getRange(lowerBound, upperBound, type);
}
}
/**
* Factory providing static methods to create ValueGenerator instances.
* @private
*/
class ValueGeneratorFactory {
/**
* Create a value generator that supplies empty values.
* @return {ValueGenerator} a value generator.
*/
static empty() {
return new EmptyValueGenerator();
}
/**
* Create a value generator that supplies randomly generated sample values.
* @return {ValueGenerator} a value generator.
*/
static sample() {
return new SampleValueGenerator();
}
}
module.exports = ValueGeneratorFactory;