/** * Provides a set of static utility functions. * * @module josm/util */ /* global Java */ // -- imports const MessageFormat = Java.type('java.text.MessageFormat') /** * Checks whether a value is null or undefined. * * @param {object} value the value to check * @return {boolean} false, if <code>value</code> is null or undefined; true, otherwise * @summary Checks whether a value is null or undefined. * @function * @name isNothing */ export function isNothing(value) { return value === null || value === undefined } /** * Checks whether a value is neither null nor undefined. * * @param {object} value the value to check * @return {boolean} false, if <code>value</code> is null or undefined; true, otherwise * @summary Checks whether a value is neither null nor undefined. * @function * @name isSomething */ export function isSomething(val) { return !isNothing(val) } /** * Trims leading and trailing whitespace from <code>s</code>. * <p> * * Replies s, if s is null or undefined. Any other value is converted to a * string, then leading and trailing white space is removed. * * @param {string} s the string to be trimmed * @return {string} * @summary Trims leading and trailing whitespace from <code>s</code>. * @function * @name trim */ export function trim(s) { if (isNothing(s)) { return s } return (s + '').replace(/^\s+/, '').replace(/\s+$/, '') } /** * Assert a condition and throw an Error if the condition isn't met. * * <p><strong>Usage:</strong> * <dl> * <dt><code>assert()</code></dt> * <dd class="param-desc">Does nothing</dd> * * <dt><code>assert(cond)</code></dt> * <dd class="param-desc">Checks the condition <code>cond</code>. If it is false, throws an * Error.</dd> * * <dt><code>assert(cond, msg)</code></dt> * <dd class="param-desc">Checks the condition <code>cond</code>. If it is false, throws an * Error, whose <code>description</code> property * is set to <code>msg</code>.</dd> * * <dt><code>assert(cond, msg, objs...)</code></dt> * <dd class="param-desc">Checks the condition <code>cond</code>. If it is false, throws an * Error, whose <code>description</code> property * is set to the formatted message <code>msg</code>. Internally uses * <code>java.text.MessageFormat</code> to format the message.</dd> * * </dl> * * @example * import {assert} from 'josm/util' * // throws an Error * assert(false) * * // throws an Error e, with e.description == "My message" * assert(false, "My message") * * // throws an Error e, with e.description == "My message: test" * assert(false, "My message: {0}", "test") * * @summary Assert a condition and throw an Error if the condition isn't met. * @function * @name assert * @static * @param {boolean} condition * @param {string} [message] the message * @param {...object} [values] an optional list of values */ export function assert() { let message, name switch (arguments.length) { case 0: return case 1: if (arguments[0]) return name = 'AssertionError' message = 'An assertion failed' break case 2: if (arguments[0]) return name = 'AssertionError' message = arguments[1] break default: { if (arguments[0]) return name = 'AssertionError' const args = Array.prototype.slice.call(arguments, 0) message = MessageFormat.format( args[1], args.slice(2) ) break } } const error = new Error(message) error.name = name throw error } /** * Asserts that <code>val</code> is defined and non-null. * * @example * import {assertSomething} from 'josm/util' * * assertSomething(null) // -> throws an exception * assertSomething(void 0) // -> throws an exception * * assertSomting("test") // -> OK * assertSomething(5) // -> OK * * @param {any} val the value to check * @param {string} [msg] message if the assertion fails * @param {...object} [values] additional values used in <code>msg</code> * placeholders * @summary Asserts that <code>val</code> is defined and non-null. * @function * @static * @name assertSomething */ export function assertSomething(val) { let args if (arguments.length <= 1) { args = [isSomething(val), 'Expected a defined non-null value, got {0}', val] } else { args = [isSomething(val)].concat( Array.prototype.slice.call(arguments, 1)) } assert.apply(args) } /** * Asserts that <code>val</code> is a number. * * @param {Anything} value the value to check * @param {String} [msg] message if the assertion fails * @param {...object} [values] values used in <code>msg</code> placeholders * @summary Asserts that <code>val</code> is a number. * @function * @name assertNumber * @static */ export function assertNumber(val) { let args = [] if (arguments.length <= 1) { args = [isSomething(val), 'Expected a number, got {0}', val] } else { args = [isSomething(val)] .concat(Array.prototype.slice.call(arguments, 1)) } assert.apply(args) } /** * Returns true if <code>val</code> is defined. * * @param {any} value the value to check * @summary Returns true if <code>val</code> is defined. * @return {boolean} true if <code>val</code> is defined * @function * @name isDef * @static */ export function isDef(val) { return val !== undefined } /** * Returns true if <code>val</code> is a number. * * @param {any} value the value to check * @summary Returns true if <code>val</code> is a number. * @return {boolean} true if <code>val</code> is a number * @function * @name isNumber * @static */ export function isNumber(val) { return typeof val === 'number' } /** * Returns true if <code>val</code> is a string. * * @param {any} value the value to check * @return {boolean} true, if val is a string or a String object * @summary Returns true if <code>val</code> is a string. * @function * @name isString * @static */ export function isString(val) { return isDef(val) && (typeof val === 'string' || val instanceof String) } /** * Replies true if <code>val</code> is an array. * * @param {anything} value the value to check * @return {boolean} true, if val is an array * @summary Replies true if <code>val</code> is an array. * @function * @name isArray * @static */ export function isArray(val) { return Object.prototype.toString.call(val) === '[object Array]' } /** * Replies true if <code>val</code> is a list of arguments. * * @param {anything} value the value to check * @return {boolean} true, if val is a list of arguments * @summary Replies true if <code>val</code> is a list of arguments. * @function * @name isArguments * @static */ export function isArguments(val) { return Object.prototype.toString.call(val) === '[object Arguments]' } /** * Replies the number of properties owned by <code>o</code>. * * @example * import {countProperties} from 'josm/util' * * let o = {p1: "v1", p2: "v2"} * let c = countProperties(o) // -> 2 * * o = {} * c = countProperties(o) // -> 0 * * o = undefined * c = countProperties(o) // -> undefined * * @param {any} o the object * @summary Replies the number of properties owned by <code>o</code>. * @return {number} the number of properties owned by <code>o</code>. * @function * @name countProperties * @static */ export function countProperties(o) { if (isNothing(o)) return undefined if (!(typeof o === 'object')) return undefined let count = 0 for (const p in o) { if (hasProp(o, p)) count++ } return count } /** * Replies true, if <code>o</code> owns at least one property. * * @example * import {hasProperties} from 'josm/util' * * let o = {p1: "v1", p2: "v2"} * let c = hasProperties(o) // -> true * * o = {} * c = hasProperties(o) // -> false * * o = undefined * c = hasProperties(o) // -> false * * @param {any} o the object * @summary Replies true, if <code>o</code> owns at least one property. * @return {boolean} true, if <code>o</code> owns at least one property. * @function * @name hasProperties * @static */ export function hasProperties(o) { const count = countProperties(o) if (count === undefined) return false return count > 0 } /** * Replies true, if f is a function. * * @param {any} f the object * @summary Replies true, if f is a function. * @return {boolean} true, if f is a function. * @function * @name isFunction * @static */ export function isFunction(f) { return typeof f === 'function' } /** * Mixes the properties of a list of objects into one object. * * @return a new object which includes the combined properties of the * argument objects * @return {object} * @summary Mixes the properties of a list of objects into one object. * @function * @name mix * @static */ export function mix() { const mixin = {} function copyProperties (other) { for (const p in other) { if (!hasProp(other, p)) continue mixin[p] = other[p] } } for (let i = 0; i < arguments.length; i++) { const template = arguments[i] if (isNothing(template)) continue if (!(typeof template === 'object')) continue copyProperties(template) } return mixin } /** * Prints a message to stdout (including newline). * * Supports the same string templates as {@class java.text.MessageFormat}. * * @example * import * as util from 'josm/util' * * const myname = '...' * util.println('Hello world! My name is {0}', myname); * // escape meta characters like {, } or ' with a leading apostrophe * util.println(" a pair of curly braces '{'}"); * * @summary Prints a message to stdout (including newline). * @param {string} message * @param {...object} [args] * @function * @name println * @static */ export function println() { const args = Array.prototype.slice.call(arguments, 0) if (args.length === 0) return '' args[0] = args[0] + '' // make sure first argument is a string const System = Java.type('java.lang.System') System.out.println(MessageFormat.format(args[0], args.slice(1))) } /** * Prints a message to stdout (without newline). * * Supports the same string templates as {@class java.text.MessageFormat} * * @example * import * as util from 'josm/util' * * const myname = "..." * util.print('Hello world! My name is {0}', myname) * // escape meta characters like {, } or ' with a leading apostrophe * util.print(" a pair of curly braces '{'}") * * @summary Prints a message to stdout (without newline). * @param {string} message * @param {...object} [args] * @function * @name print * @static */ export function print() { const args = Array.prototype.slice.call(arguments, 0) if (args.length === 0) return '' args[0] = args[0] + '' // make sure first argument is a string const System = Java.type('java.lang.System') System.out.print(MessageFormat.format(args[0], args.slice(1))) } /** * Checks whether two java objects are either both null or equal by calling * o1.equals(o2). * * @param {object} o1 a java object or null * @param {object} o2 a java object or null * @summary Are two java objects equal. * @return {boolean} * @function * @name javaEquals * @static */ export function javaEquals(o1, o2) { assert(typeof o1 === 'object' && typeof o1.equals === 'function') assert(typeof o2 === 'object' && typeof o2.equals === 'function') if (o1 === null && o2 === null) return true if (o1 === null && o2 !== null) return false return o1.equals(o2) } /** * Iterates over the elements in <code>collection</code> and invokes * <code>delegate()</code> on each element. * * @param {array|arguments|java.util.Collection} collection the collection of * elements * @param {function} delegate the function to call on each elemennt * @summary Iteraties over the elements of a collection * @function * @name each * @static */ export function each(collection, delegate) { const Collection = Java.type('java.util.Collection') if (isNothing(collection) || isNothing(delegate)) return if (isArray(collection) || isArguments(collection)) { const len = collection.length for (let i = 0; i < len; i++) delegate(collection[i]) } else if (collection instanceof Collection) { for (let it = collection.iterator(); it.hasNext();) delegate(it.next()) } else { assert(false, 'collection: unexpected type of value, got {0}"', collection) } } /** * Replies true, if a value is an array, an arguments list or a Java * collection. * * @param {object} value the value to check * @summary Is a value a collection? * @return {boolean} true, if <code>value</code> is a collection * @function * @name isCollection * @static */ export function isCollection(value) { const Collection = Java.type('java.util.Collection') return isArray(value) || isArguments(value) || value instanceof Collection } export function hasProp(o, name) { return Object.prototype.hasOwnProperty.call(o, name) }