/* Copyright (c) 2015-present The Open Source Geospatial Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* A utility class providing methods to check for symbols of OpenLayers we
* depend upon.
*
* This class can be mixed into classes to check if the dependencies to external
* symbols are fulfilled. An example:
*
* Ext.define('MyNewClass.DependingOnOpenLayersClasses', {
* mixins: ['GeoExt.mixin.SymbolCheck'],
* // the contents of the `symbols` property will be checked
* symbols: [
* 'ol.Map', // checking a class
* 'ol.View.prototype.constrainResolution', // an instance method
* 'ol.control.ScaleLine#getUnits', // other way for instance method
* 'ol.color.asArray', // one way to reference a static method
* 'ol.color::asString' // other way to reference a static method
* ]
* // … your configuration and methods …
* });
*
* Since this sort of checking usually only makes sense in debug mode, you can
* additionally wrap the `symbols`-configuration in these `<debug>`-line
* comments:
*
* Ext.define('MyNewClass.DependingOnOpenLayersClasses', {
* mixins: ['GeoExt.mixin.SymbolCheck'],
* // <debug>
* symbols: []
* // </debug>
* });
*
* This means that the array of symbols is not defined in production builds
* as the wrapped lines are simply removed from the final JavaScript.
*
* If one of the symbols cannot be found, a warning will be printed to the
* developer console (via `Ext.log.warn`, which will only print in a debug
* build):
*
* [W] The class "MyNewClass.DependingOnOpenLayersClasses" depends on the
* external symbol "ol.color.notExisting", which does not seem to exist.
*
* @class GeoExt.mixin.SymbolCheck
*/
Ext.define('GeoExt.mixin.SymbolCheck', {
extend: 'Ext.Mixin',
inheritableStatics: {
/**
* An object that we will use to store already looked up references in.
*
* The key will be a symbol (after it has been normalized by the
* method #normalizeSymbol), and the value will be a boolean indicating
* if the symbol was found to be defined when it was checked.
*
* @private
*/
_checked: {
// will be filled while we are checking stuff for existence
},
/**
* Checks whether the required symbols of the given class are defined
* in the global context. Will log to the console if a symbol cannot be
* found.
*
* @param {Ext.Base} cls An ext class defining a property `symbols` that
* that this method will check.
*/
check: function (cls) {
// <debug>
const me = this;
const proto = cls.prototype;
const olSymbols = proto && proto.symbols;
const clsName = proto && proto['$className'];
if (!olSymbols) {
return;
}
Ext.each(olSymbols, function (olSymbol) {
olSymbol = me.normalizeSymbol(olSymbol);
me.checkSymbol(olSymbol, clsName);
});
// </debug>
},
/**
* Normalizes a short form of a symbol to a canonical one we use to
* store the results of the #isDefinedSymbol method. The following two
* normalizations take place:
*
* * A `#` in the symbol is being replaced with `.prototype.` so that
* e.g. the symbol `'ol.Class#methodName'` turns into the symbol
* `'ol.Class.prototype.methodName'`
* * A `::` in the symbol is being replaced with `.` so that
* e.g. the symbol `'ol.Class::staticMethodName'` turns into the
* symbol `'ol.Class.staticMethodName'`
*
* @param {string} symbolStr A string to normalize.
* @return {string} The normalized string.
* @private
*/
normalizeSymbol: (function () {
// <debug>
const hashRegEx = /#/;
const colonRegEx = /::/;
// </debug>
const normalizeFunction = function (symbolStr) {
// <debug>
if (hashRegEx.test(symbolStr)) {
symbolStr = symbolStr.replace(hashRegEx, '.prototype.');
} else if (colonRegEx.test(symbolStr)) {
symbolStr = symbolStr.replace(colonRegEx, '.');
}
return symbolStr;
// </debug>
};
return normalizeFunction;
})(),
/**
* Checks the passed symbolStr and raises a warning if it cannot be
* found.
*
* @param {string} symbolStr A string to check. Usually this string has
* been {@link #normalizeSymbol normalized} already.
* @param {string} [clsName] The optional name of the class that
* requires the passed openlayers symbol.
* @private
*/
checkSymbol: function (symbolStr, clsName) {
// <debug>
const isDefined = this.isDefinedSymbol(symbolStr);
if (!isDefined) {
Ext.log.warn(
'The class "' +
(clsName || 'unknown') +
'" ' +
'depends on the external symbol "' +
symbolStr +
'", ' +
'which does not seem to exist.',
);
}
// </debug>
},
/**
* Checks if the passed symbolStr is defined.
*
* @param {string} symbolStr A string to check. Usually this string has
* been {@link #normalizeSymbol normalized} already.
* @return {boolean} Whether the symbol is defined or not.
* @private
*/
isDefinedSymbol: function (symbolStr) {
// <debug>
const checkedCache = this._checked;
if (Ext.isDefined(checkedCache[symbolStr])) {
return checkedCache[symbolStr];
}
const parts = symbolStr.split('.');
const lastIdx = parts.length - 1;
let curSymbol = Ext.getWin().dom;
let isDefined = false;
let intermediateSymb = '';
Ext.each(parts, function (part, idx) {
if (intermediateSymb !== '') {
intermediateSymb += '.';
}
intermediateSymb += part;
// Check the current symbol's property or method
if (curSymbol[part]) {
checkedCache[intermediateSymb] = true;
curSymbol = curSymbol[part];
if (lastIdx === idx) {
isDefined = true;
}
} else if (lastIdx === idx) {
// Special handling for instance-bound methods
try {
const parentObj = Ext.Object.chain(curSymbol);
const instance = new parentObj.constructor();
if (typeof instance[part] === 'function') {
checkedCache[intermediateSymb] = true;
isDefined = true;
}
} catch (e) {
// Handle errors such as constructors requiring arguments
Ext.log.warn(
`Unable to create instance or access method: ${intermediateSymb}`,
e,
);
checkedCache[intermediateSymb] = false;
}
} else {
// Method or property is not defined
checkedCache[intermediateSymb] = false;
return false; // break early
}
});
checkedCache[symbolStr] = isDefined;
return isDefined;
// </debug>
},
},
/**
* @property {Array<string>} symbols The symbols to check.
*/
/**
* Whenever a class mixes in GeoExt.mixin.SymbolCheck, this method will be
* called and it actually runs the checks for all the defined #symbols.
*
* @param {Ext.Class} cls The class that this mixin is mixed into.
* @private
*/
onClassMixedIn: function (cls) {
// <debug>
GeoExt.mixin.SymbolCheck.check(cls);
// </debug>
},
});