/*!
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you 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.
*/
import {Optional} from "./Monad";
import {Es2019ArrayFrom} from "./Es2019Array";
/**
* Lang helpers crossported from the apache myfaces project
*/
export namespace Lang {
//should be in lang, but for now here to avoid recursive imports, not sure if typescript still has a problem with those
/**
* helper function to safely resolve anything
* this is not an elvis operator, it resolves
* a value without exception in a tree and if
* it is not resolvable then an optional of
* a default value is restored or Optional.empty
* if none is given
*
* usage
*
* let var: Optiona = saveResolve(() => a.b.c.d.e, "foobaz")
*
*
* @param resolverProducer a lambda which can produce the value
* @param defaultValue an optional default value if the producer fails to produce anything
* @returns an Optional of the produced value
*/
export function saveResolve(resolverProducer: () => T, defaultValue: T | null = null): Optional {
try {
let result = resolverProducer();
return Optional.fromNullable(result ?? defaultValue);
} catch (e) {
return Optional.absent;
}
}
/**
* lazy resolve... aka the function is called on resolve and a default value also
* is a producing function (called only if the original producer does not produce any result)
* @param resolverProducer the producer for the resolve
* @param defaultValue the default value producer function
*/
export function saveResolveLazy(resolverProducer: () => T, defaultValue: (() => T) | null = null): Optional {
try {
let result = resolverProducer();
return Optional.fromNullable(result ?? defaultValue!());
} catch (e) {
return Optional.absent;
}
}
/**
* String to array function performs a string to array transformation
* @param {String} it the string which has to be changed into an array
* @param {RegExp} splitter our splitter reglar expression
* @return a trimmed array of the splitted string
*/
export function strToArray(it: string, splitter: string | RegExp = /\./gi): Array {
let ret: string[] = [];
it.split(splitter).forEach((element => {
ret.push(trim(element));
}));
return ret;
}
/**
* hyperfast trim
* http://blog.stevenlevithan.com/archives/faster-trim-javascript
* crossported from dojo
*/
export function trim(str: string): string {
str = str.replace(/^\s\s*/, '');
let ws = /\s/, i = str.length;
while (ws.test(str.charAt(--i))) {
//do nothing
}
return str.slice(0, i + 1);
}
/**
* generic object arrays like dom definitions to array conversion method which
* transforms any object to something array like
* @param obj
* @param offset
* @param pack
* @returns an array converted from the object
*/
export function objToArray(obj: any, offset: number = 0, pack: Array = []): Array {
if ((obj ?? "__undefined__") == "__undefined__") {
return pack ?? null;
}
//since offset is numeric we cannot use the shortcut due to 0 being false
//special condition array delivered no offset no pack
if ((obj) instanceof Array && !offset && !pack as any) return obj;
return Es2019ArrayFrom(pack.concat(Array.prototype.slice.call(obj, offset)));
}
/**
* equalsIgnoreCase, case-insensitive comparison of two strings
*
* @param source
* @param destination
*/
export function equalsIgnoreCase(source?: string | null, destination?: string | null): boolean {
let finalSource = source ?? "___no_value__";
let finalDest = destination ?? "___no_value__";
//in any other case we do a strong string comparison
return finalSource.toLowerCase() === finalDest.toLowerCase();
}
/**
* runtime type assertion
*
* @param probe the probe to be tested for a type
* @param theType the type to be tested for
*/
export function assertType(probe: any, theType: any): boolean {
return isString(theType) ? typeof probe == theType : probe instanceof theType;
}
/**
* Back ported from Dojo
* a failsafe string determination method
* (since in javascript String != "" typeof alone fails!)
* @param it {|Object|} the object to be checked for being a string
* @return true in case of being a string false otherwise
*/
export function isString(it?: any): boolean {
// summary:
// Return true if it is a String
return !!arguments.length && it != null && (typeof it == "string" || it instanceof String); // Boolean
}
/**
* Back-ported, a failsafe determination code for checking whether an object is a function
* @param it the object to check for being a function
*/
export function isFunc(it: any): boolean {
return it instanceof Function || typeof it === "function";
}
export function objAssign(target: any, ...theArgs: any[]) { // .length of function is 2
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
let to = Object(target);
if((Object).assign as any) {
theArgs.forEach(item => (Object).assign(to, item) as any);
return to;
}
theArgs.filter(item => item != null).forEach(item => {
let nextSource = item;
const stringKeys = Object.keys(nextSource);
const symbolKeys = Object.getOwnPropertySymbols(nextSource)
.filter(sym => Object.prototype.propertyIsEnumerable.call(nextSource, sym));
[...stringKeys, ...symbolKeys]
.forEach(key => to[key] = nextSource[key]);
});
return to;
}
}