/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Gareth Johnson
*/
/**
* Defines {@link baja.UnitDatabase}.
* @module baja/obj/UnitDatabase
*/
define([ "bajaScript/sys",
"bajaScript/baja/comm/Callback",
"bajaScript/baja/obj/Dimension",
"bajaScript/baja/obj/Unit" ], function (
baja,
Callback,
Dimension,
Unit) {
'use strict';
var callbackify = baja.callbackify,
STORAGE_KEY = 'bsUnitDatabase',
retrievePromise;
////////////////////////////////////////////////////////////////
// Utility functions
////////////////////////////////////////////////////////////////
function decodeDimension(str) {
return Dimension.DEFAULT.decodeFromString(str);
}
/**
* Clear the saved unit database JSON from local storage.
*
* @inner
*/
function clearJsonFromStorage() {
try {
return baja.storage.removeItem(STORAGE_KEY);
} catch (ignore) {
//what to do?
}
}
/**
* Retrieve the saved unit database JSON from local storage.
*
* @inner
* @returns {Object} retrieved and parsed object, or null if not found
*/
function getJsonFromStorage() {
try {
var str = baja.storage.getItem(STORAGE_KEY);
return str && JSON.parse(str);
} catch (ignore) {
clearJsonFromStorage();
}
}
/**
* Persist the unit database JSON retrieved from the station into local
* storage.
*
* @param {Object} json
*/
function saveJsonToStorage(json) {
try {
return baja.storage.setItem(STORAGE_KEY, JSON.stringify(json));
} catch (ignore) {
//what to do?
}
}
/**
* Retrieve the unit database from the station.
*
* It will make a network call to the `UnitChannel` on the BOX service,
* passing along the last time the unit database was retrieved. If the unit
* database has been updated, it will be returned from the BOX service and
* stored in local storage. If the database has not been updated since the
* last time it was retrieved, the `UnitChannel` will not send it down
* and the copy in local storage will be used instead.
*
* @inner
* @param {baja.comm.Batch} [batch] optional batch to use to retrieve the
* unit database
* @returns {Promise}
*/
function retrieveJson(batch) {
var cb = new Callback(baja.ok, baja.fail, batch),
fromStorage = getJsonFromStorage(),
lastKnownBuildTime = (fromStorage && fromStorage.buildTime) || 0;
cb.addOk(function (ok, fail, resp) {
baja.runAsync(function () {
var json;
if (fromStorage && !resp.db) {
//we had the database saved locally, and it hasn't been updated since
//we last retrieved it. use the existing db.
json = fromStorage;
} else {
//new unit database from the station.
json = resp;
saveJsonToStorage(json);
}
ok(json);
});
});
cb.addReq('unit', 'getUnitDatabase', {
ifModifiedSince: lastKnownBuildTime
});
cb.autoCommit();
return cb.promise();
}
////////////////////////////////////////////////////////////////
// Quantities
////////////////////////////////////////////////////////////////
/**
* Denotes one particular quantity from the unit database, and all the units
* it contains.
*
* @private
* @memberOf baja.UnitDatabase
* @class
* @param {String} name quantity display name
* @param {baja.Dimension} dimension quantity dimension
* @param {Array.<baja.Unit>} units the units this quantity contains
*/
var Quantity = function (name, dimension, units) {
this.$dimension = dimension;
this.$name = name;
this.$units = units;
};
/**
* Returns the display name of this quantity.
* @returns {String}
*/
Quantity.prototype.getName = function () {
return this.$name;
};
/**
* Returns the units this quantity contains.
*
* @returns {Array.<baja.Unit>}
*/
Quantity.prototype.getUnits = function () {
return this.$units.slice();
};
/**
* Returns the dimension of this quantity
* @returns {baja.Dimension}
* @since Niagara 4.13
*/
Quantity.prototype.getDimension = function () {
return this.$dimension;
};
/**
* Returns the quantity name and dimension
* @returns {String}
*/
Quantity.prototype.toString = function () {
return this.getName() + ' (' + this.getDimension().toString() + ')';
};
/**
* JSON structure:
*
* {
* "buildTime": {long} db last build time,
* "db": [
* {
* "n": {string} quantity name,
* "d": {baja.dimension} dimension,
* "u": [
* {
* "n": {string} unit name,
* "s": {string} unit symbol,
* "d": {string} unit dimension (BDimension#decodeFromString),
* "sc": {double} unit scale,
* "o": {double} unit offset,
* "p": {boolean} unit is prefix
* }
* ]
* }
* ],
* "v": {long} version number
* }
*
* @inner
* @param json
* @returns {Array}
*/
function toQuantityArray(json) {
var quantities = [],
db = json.db;
for (var i = 0; i < db.length; i++) {
var quantity = db[i],
u = quantity.u,
units = [];
for (var j = 0; j < u.length; j++) {
var unitObj = u[j];
units.push(Unit.make(unitObj.n, unitObj.s,
decodeDimension(unitObj.d), unitObj.sc, unitObj.o, unitObj.p));
}
quantities.push(new Quantity(quantity.n, decodeDimension(quantity.d), units));
}
return quantities;
}
////////////////////////////////////////////////////////////////
// UnitDatabase implementation
////////////////////////////////////////////////////////////////
/**
* Queries the unit database from the station.
*
* There is no reason to call this constructor directly; rather use the
* static accessor functions.
*
* @class
* @alias baja.UnitDatabase
*/
var UnitDatabase = function UnitDatabase(json) {
this.$quantities = toQuantityArray(json);
this.$byName = {};
var conversions = json.cdb || [],
byMetric = {},
byEnglish = {};
for (var i = 0; i < conversions.length; i++) {
var conv = conversions[i];
byMetric[conv.metric] = this.getUnit(conv.english);
byEnglish[conv.english] = this.getUnit(conv.metric);
}
this.$conversions = conversions;
this.$byMetric = byMetric;
this.$byEnglish = byEnglish;
};
UnitDatabase.Quantity = Quantity;
/**
* Asynchronously retrieve the unit database from the station. The network
* call to the station will only happen once: the same database instance will
* be resolved no matter how many times this function is called.
*
* @param {Object} [callbacks]
* @param {Function} [callbacks.ok] (Deprecated: use Promise) ok callback,
* will receive a `UnitDatabase` instance populated with data retrieved from
* the station
* @param {Function} [callbacks.fail] (Deprecated: use Promise) fail callback
* @param {baja.comm.Batch} [callbacks.batch] batch to use for the network
* request
* @returns {Promise.<baja.UnitDatabase>}
*/
UnitDatabase.get = function (callbacks) {
callbacks = callbackify(callbacks);
if (!retrievePromise) {
retrievePromise = retrieveJson(callbacks.batch)
.then(function (json) {
return new UnitDatabase(json);
});
}
retrievePromise.then(callbacks.ok, callbacks.fail);
return retrievePromise;
};
/**
* Get all quantities contained in the unit database.
*
* @returns {Array.<baja.UnitDatabase.Quantity>}
*/
UnitDatabase.prototype.getQuantities = function () {
return this.$quantities.slice();
};
/**
* Get an array of all raw entries from `unitConversion.xml`.
* @private
* @returns {Array.<Object>} array where each object has `english` and
* `metric` properties - each are unit names
*/
UnitDatabase.prototype.$getUnitConversions = function () {
return this.$conversions.slice();
};
/**
* Retrieve the unit instance with the given name.
*
* @param {String|baja.Unit} name the desired unit name. If a baja.Unit is
* passed, it will just be returned back directly.
* @returns {baja.Unit|null} the unit, or `null` if not found
*/
UnitDatabase.prototype.getUnit = function (name) {
if (name instanceof baja.Unit) { return name; }
var byName = this.$byName, quantities = this.$quantities, units, i, j;
if (byName[name] !== undefined) {
return byName[name];
}
for (i = 0; i < quantities.length; i++) {
units = quantities[i].getUnits();
for (j = 0; j < units.length; j++) {
if (units[j].getUnitName() === name) {
return (byName[name] = units[j]);
}
}
}
return (byName[name] = null);
};
/**
* Convert a unit from metric to English or vice versa.
*
* @param {String|number|baja.FrozenEnum} unitConversion `metric` to convert
* from English to metric; `english` to convert from metric to English. Also
* accepts baja:UnitConversion instances or ordinals.
* @param {baja.Unit} unit the unit to convert
* @returns {baja.Unit|null} the converted unit. If the unit cannot be
* converted, the same unit will be returned directly. If no unit given,
* returns null.
*/
UnitDatabase.prototype.convertUnit = function (unitConversion, unit) {
if (!unit || !unitConversion) {
return unit || null;
}
var name = unit.getUnitName();
switch (unitConversion.valueOf()) {
case 1: case 'metric': return this.$byEnglish[name] || unit;
case 2: case 'english': return this.$byMetric[name] || unit;
default: return unit;
}
};
/**
* Get the Quantity which contains the specified unit or return undefined if the unit is not
* contained by the database.
* @since Niagara 4.14
* @param {baja.Unit} unit the unit we want the quantity for
* @returns {baja.UnitDatabase.Quantity|undefined}
*/
UnitDatabase.prototype.getQuantity = function (unit) {
const quantities = this.getQuantities();
for (let index1 = 0; index1 < quantities.length; index1++) {
const quantity = quantities[index1];
const units = quantity.getUnits();
for (let index2 = 0; index2 < units.length; index2++) {
if (units[index2].equivalent(unit)) {
return quantity;
}
}
}
return undefined;
};
return UnitDatabase;
});