Source: utils.js

/*
 * 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.
 */
'use strict';

/** @module odatajs/utils */


function inBrowser() {
    return typeof window !== 'undefined';
}

/** Creates a new ActiveXObject from the given progId.
 * @param {String} progId - ProgId string of the desired ActiveXObject.
 * @returns {Object} The ActiveXObject instance. Null if ActiveX is not supported by the browser.
 * This function throws whatever exception might occur during the creation
 * of the ActiveXObject.
*/
var activeXObject = function (progId) {
    
    if (window.ActiveXObject) {
        return new window.ActiveXObject(progId);
    }
    return null;
};

/** Checks whether the specified value is different from null and undefined.
 * @param [value] Value to check ( may be null)
 * @returns {Boolean} true if the value is assigned; false otherwise.
*/     
function assigned(value) {
    return value !== null && value !== undefined;
}

/** Checks whether the specified item is in the array.
 * @param {Array} [arr] Array to check in.
 * @param item - Item to look for.
 * @returns {Boolean} true if the item is contained, false otherwise.
*/
function contains(arr, item) {
    var i, len;
    for (i = 0, len = arr.length; i < len; i++) {
        if (arr[i] === item) {
            return true;
        }
    }
    return false;
}

/** Given two values, picks the first one that is not undefined.
 * @param a - First value.
 * @param b - Second value.
 * @returns a if it's a defined value; else b.
 */
function defined(a, b) {
    return (a !== undefined) ? a : b;
}

/** Delays the invocation of the specified function until execution unwinds.
 * @param {Function} callback - Callback function.
 */
function delay(callback) {

    if (arguments.length === 1) {
        window.setTimeout(callback, 0);
        return;
    }

    var args = Array.prototype.slice.call(arguments, 1);
    window.setTimeout(function () {
        callback.apply(this, args);
    }, 0);
}

/** Throws an exception in case that a condition evaluates to false.
 * @param {Boolean} condition - Condition to evaluate.
 * @param {String} message - Message explaining the assertion.
 * @param {Object} data - Additional data to be included in the exception.
 */
function djsassert(condition, message, data) {


    if (!condition) {
        throw { message: "Assert fired: " + message, data: data };
    }
}

/** Extends the target with the specified values.
 * @param {Object} target - Object to add properties to.
 * @param {Object} values - Object with properties to add into target.
 * @returns {Object} The target object.
*/
function extend(target, values) {
    for (var name in values) {
        target[name] = values[name];
    }

    return target;
}

function find(arr, callback) {
    /** Returns the first item in the array that makes the callback function true.
     * @param {Array} [arr] Array to check in. ( may be null)
     * @param {Function} callback - Callback function to invoke once per item in the array.
     * @returns The first item that makes the callback return true; null otherwise or if the array is null.
    */

    if (arr) {
        var i, len;
        for (i = 0, len = arr.length; i < len; i++) {
            if (callback(arr[i])) {
                return arr[i];
            }
        }
    }
    return null;
}

function isArray(value) {
    /** Checks whether the specified value is an array object.
     * @param value - Value to check.
     * @returns {Boolean} true if the value is an array object; false otherwise.
     */

    return Object.prototype.toString.call(value) === "[object Array]";
}

/** Checks whether the specified value is a Date object.
 * @param value - Value to check.
 * @returns {Boolean} true if the value is a Date object; false otherwise.
 */
function isDate(value) {
    return Object.prototype.toString.call(value) === "[object Date]";
}

/** Tests whether a value is an object.
 * @param value - Value to test.
 * @returns {Boolean} True is the value is an object; false otherwise.
 * Per javascript rules, null and array values are objects and will cause this function to return true.
 */
function isObject(value) {
    return typeof value === "object";
}

/** Parses a value in base 10.
 * @param {String} value - String value to parse.
 * @returns {Number} The parsed value, NaN if not a valid value.
*/   
function parseInt10(value) {
    return parseInt(value, 10);
}

/** Renames a property in an object.
 * @param {Object} obj - Object in which the property will be renamed.
 * @param {String} oldName - Name of the property that will be renamed.
 * @param {String} newName - New name of the property.
 * This function will not do anything if the object doesn't own a property with the specified old name.
 */
function renameProperty(obj, oldName, newName) {
    if (obj.hasOwnProperty(oldName)) {
        obj[newName] = obj[oldName];
        delete obj[oldName];
    }
}

/** Default error handler.
 * @param {Object} error - Error to handle.
 */
function throwErrorCallback(error) {
    throw error;
}

/** Removes leading and trailing whitespaces from a string.
 * @param {String} str String to trim
 * @returns {String} The string with no leading or trailing whitespace.
 */
function trimString(str) {
    if (str.trim) {
        return str.trim();
    }

    return str.replace(/^\s+|\s+$/g, '');
}

/** Returns a default value in place of undefined.
 * @param [value] Value to check (may be null)
 * @param defaultValue - Value to return if value is undefined.
 * @returns value if it's defined; defaultValue otherwise.
 * This should only be used for cases where falsy values are valid;
 * otherwise the pattern should be 'x = (value) ? value : defaultValue;'.
 */
function undefinedDefault(value, defaultValue) {
    return (value !== undefined) ? value : defaultValue;
}

// Regular expression that splits a uri into its components:
// 0 - is the matched string.
// 1 - is the scheme.
// 2 - is the authority.
// 3 - is the path.
// 4 - is the query.
// 5 - is the fragment.
var uriRegEx = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#:]+)?(\?[^#]*)?(#.*)?/;
var uriPartNames = ["scheme", "authority", "path", "query", "fragment"];

/** Gets information about the components of the specified URI.
 * @param {String} uri - URI to get information from.
 * @return  {Object} An object with an isAbsolute flag and part names (scheme, authority, etc.) if available.
 */
function getURIInfo(uri) {
    var result = { isAbsolute: false };

    if (uri) {
        var matches = uriRegEx.exec(uri);
        if (matches) {
            var i, len;
            for (i = 0, len = uriPartNames.length; i < len; i++) {
                if (matches[i + 1]) {
                    result[uriPartNames[i]] = matches[i + 1];
                }
            }
        }
        if (result.scheme) {
            result.isAbsolute = true;
        }
    }

    return result;
}

/** Builds a URI string from its components.
 * @param {Object} uriInfo -  An object with uri parts (scheme, authority, etc.).
 * @returns {String} URI string.
 */
function getURIFromInfo(uriInfo) {
    return "".concat(
        uriInfo.scheme || "",
        uriInfo.authority || "",
        uriInfo.path || "",
        uriInfo.query || "",
        uriInfo.fragment || "");
}

// Regular expression that splits a uri authority into its subcomponents:
// 0 - is the matched string.
// 1 - is the userinfo subcomponent.
// 2 - is the host subcomponent.
// 3 - is the port component.
var uriAuthorityRegEx = /^\/{0,2}(?:([^@]*)@)?([^:]+)(?::{1}(\d+))?/;

// Regular expression that matches percentage enconded octects (i.e %20 or %3A);
var pctEncodingRegEx = /%[0-9A-F]{2}/ig;

/** Normalizes the casing of a URI.
 * @param {String} uri - URI to normalize, absolute or relative.
 * @returns {String} The URI normalized to lower case.
*/
function normalizeURICase(uri) {
    var uriInfo = getURIInfo(uri);
    var scheme = uriInfo.scheme;
    var authority = uriInfo.authority;

    if (scheme) {
        uriInfo.scheme = scheme.toLowerCase();
        if (authority) {
            var matches = uriAuthorityRegEx.exec(authority);
            if (matches) {
                uriInfo.authority = "//" +
                (matches[1] ? matches[1] + "@" : "") +
                (matches[2].toLowerCase()) +
                (matches[3] ? ":" + matches[3] : "");
            }
        }
    }

    uri = getURIFromInfo(uriInfo);

    return uri.replace(pctEncodingRegEx, function (str) {
        return str.toLowerCase();
    });
}

/** Normalizes a possibly relative URI with a base URI.
 * @param {String} uri - URI to normalize, absolute or relative
 * @param {String} base - Base URI to compose with (may be null)
 * @returns {String} The composed URI if relative; the original one if absolute.
 */
function normalizeURI(uri, base) {
    if (!base) {
        return uri;
    }

    var uriInfo = getURIInfo(uri);
    if (uriInfo.isAbsolute) {
        return uri;
    }

    var baseInfo = getURIInfo(base);
    var normInfo = {};
    var path;

    if (uriInfo.authority) {
        normInfo.authority = uriInfo.authority;
        path = uriInfo.path;
        normInfo.query = uriInfo.query;
    } else {
        if (!uriInfo.path) {
            path = baseInfo.path;
            normInfo.query = uriInfo.query || baseInfo.query;
        } else {
            if (uriInfo.path.charAt(0) === '/') {
                path = uriInfo.path;
            } else {
                path = mergeUriPathWithBase(uriInfo.path, baseInfo.path);
            }
            normInfo.query = uriInfo.query;
        }
        normInfo.authority = baseInfo.authority;
    }

    normInfo.path = removeDotsFromPath(path);

    normInfo.scheme = baseInfo.scheme;
    normInfo.fragment = uriInfo.fragment;

    return getURIFromInfo(normInfo);
}

/** Merges the path of a relative URI and a base URI.
 * @param {String} uriPath - Relative URI path.
 * @param {String} basePath - Base URI path.
 * @returns {String} A string with the merged path.
 */
function mergeUriPathWithBase(uriPath, basePath) {
    var path = "/";
    var end;

    if (basePath) {
        end = basePath.lastIndexOf("/");
        path = basePath.substring(0, end);

        if (path.charAt(path.length - 1) !== "/") {
            path = path + "/";
        }
    }

    return path + uriPath;
}

/** Removes the special folders . and .. from a URI's path.
 * @param {string} path - URI path component.
 * @returns {String} Path without any . and .. folders.
 */
function removeDotsFromPath(path) {
    var result = "";
    var segment = "";
    var end;

    while (path) {
        if (path.indexOf("..") === 0 || path.indexOf(".") === 0) {
            path = path.replace(/^\.\.?\/?/g, "");
        } else if (path.indexOf("/..") === 0) {
            path = path.replace(/^\/\..\/?/g, "/");
            end = result.lastIndexOf("/");
            if (end === -1) {
                result = "";
            } else {
                result = result.substring(0, end);
            }
        } else if (path.indexOf("/.") === 0) {
            path = path.replace(/^\/\.\/?/g, "/");
        } else {
            segment = path;
            end = path.indexOf("/", 1);
            if (end !== -1) {
                segment = path.substring(0, end);
            }
            result = result + segment;
            path = path.replace(segment, "");
        }
    }
    return result;
}

function convertByteArrayToHexString(str) {
    var arr = [];
    if (window.atob === undefined) {
        arr = decodeBase64(str);
    } else {
        var binaryStr = window.atob(str);
        for (var i = 0; i < binaryStr.length; i++) {
            arr.push(binaryStr.charCodeAt(i));
        }
    }
    var hexValue = "";
    var hexValues = "0123456789ABCDEF";
    for (var j = 0; j < arr.length; j++) {
        var t = arr[j];
        hexValue += hexValues[t >> 4];
        hexValue += hexValues[t & 0x0F];
    }
    return hexValue;
}

function decodeBase64(str) {
    var binaryString = "";
    for (var i = 0; i < str.length; i++) {
        var base65IndexValue = getBase64IndexValue(str[i]);
        var binaryValue = "";
        if (base65IndexValue !== null) {
            binaryValue = base65IndexValue.toString(2);
            binaryString += addBase64Padding(binaryValue);
        }
    }
    var byteArray = [];
    var numberOfBytes = parseInt(binaryString.length / 8, 10);
    for (i = 0; i < numberOfBytes; i++) {
        var intValue = parseInt(binaryString.substring(i * 8, (i + 1) * 8), 2);
        byteArray.push(intValue);
    }
    return byteArray;
}

function getBase64IndexValue(character) {
    var asciiCode = character.charCodeAt(0);
    var asciiOfA = 65;
    var differenceBetweenZanda = 6;
    if (asciiCode >= 65 && asciiCode <= 90) {           // between "A" and "Z" inclusive
        return asciiCode - asciiOfA;
    } else if (asciiCode >= 97 && asciiCode <= 122) {   // between 'a' and 'z' inclusive
        return asciiCode - asciiOfA - differenceBetweenZanda;
    } else if (asciiCode >= 48 && asciiCode <= 57) {    // between '0' and '9' inclusive
        return asciiCode + 4;
    } else if (character == "+") {
        return 62;
    } else if (character == "/") {
        return 63;
    } else {
        return null;
    }
}

function addBase64Padding(binaryString) {
    while (binaryString.length < 6) {
        binaryString = "0" + binaryString;
    }
    return binaryString;

}

function getJsonValueArraryLength(data) {
    if (data && data.value) {
        return data.value.length;
    }

    return 0;
}

function sliceJsonValueArray(data, start, end) {
    if (data === undefined || data.value === undefined) {
        return data;
    }

    if (start < 0) {
        start = 0;
    }

    var length = getJsonValueArraryLength(data);
    if (length < end) {
        end = length;
    }

    var newdata = {};
    for (var property in data) {
        if (property == "value") {
            newdata[property] = data[property].slice(start, end);
        } else {
            newdata[property] = data[property];
        }
    }

    return newdata;
}

function concatJsonValueArray(data, concatData) {
    if (concatData === undefined || concatData.value === undefined) {
        return data;
    }

    if (data === undefined || Object.keys(data).length === 0) {
        return concatData;
    }

    if (data.value === undefined) {
        data.value = concatData.value;
        return data;
    }

    data.value = data.value.concat(concatData.value);

    return data;
}

function endsWith(input, search) {
    return input.indexOf(search, input.length - search.length) !== -1;
}

function startsWith (input, search) {
    return input.indexOf(search) === 0;
}

function getFormatKind(format, defaultFormatKind) {
    var formatKind = defaultFormatKind;
    if (!assigned(format)) {
        return formatKind;
    }

    var normalizedFormat = format.toLowerCase();
    switch (normalizedFormat) {
        case "none":
            formatKind = 0;
            break;
        case "minimal":
            formatKind = 1;
            break;
        case "full":
            formatKind = 2;
            break;
        default:
            break;
    }

    return formatKind;
}


    
    
exports.inBrowser = inBrowser;
exports.activeXObject = activeXObject;
exports.assigned = assigned;
exports.contains = contains;
exports.defined = defined;
exports.delay = delay;
exports.djsassert = djsassert;
exports.extend = extend;
exports.find = find;
exports.getURIInfo = getURIInfo;
exports.isArray = isArray;
exports.isDate = isDate;
exports.isObject = isObject;
exports.normalizeURI = normalizeURI;
exports.normalizeURICase = normalizeURICase;
exports.parseInt10 = parseInt10;
exports.renameProperty = renameProperty;
exports.throwErrorCallback = throwErrorCallback;
exports.trimString = trimString;
exports.undefinedDefault = undefinedDefault;
exports.decodeBase64 = decodeBase64;
exports.convertByteArrayToHexString = convertByteArrayToHexString;
exports.getJsonValueArraryLength = getJsonValueArraryLength;
exports.sliceJsonValueArray = sliceJsonValueArray;
exports.concatJsonValueArray = concatJsonValueArray;
exports.startsWith = startsWith;
exports.endsWith = endsWith;
exports.getFormatKind = getFormatKind;