UNPKG

@ui5/webcomponents-localization

Version:
483 lines (447 loc) 16.7 kB
/*! * OpenUI5 * (c) Copyright 2009-2024 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides class sap.ui.base.Metadata import ObjectPath from "../../base/util/ObjectPath.js"; import assert from "../../base/assert.js"; import Log from "../../base/Log.js"; import uniqueSort from "../../base/util/array/uniqueSort.js"; function isFunction(obj) { return typeof obj === "function"; } /** * Creates a new metadata object from the given static infos. * * <b>Note:</b> Throughout this class documentation, the described subclass of Object * is referenced as <i>the described class</i>. * * @param {string} sClassName Fully qualified name of the described class * @param {object} oClassInfo Info to construct the class and its metadata from * @param {sap.ui.base.Object.MetadataOptions} [oClassInfo.metadata] * The metadata object describing the class * * @class Metadata for a class. * @author Frank Weigel * @version 1.120.17 * @since 0.8.6 * @public * @alias sap.ui.base.Metadata */ var Metadata = function (sClassName, oClassInfo) { assert(typeof sClassName === "string" && sClassName, "Metadata: sClassName must be a non-empty string"); assert(typeof oClassInfo === "object", "Metadata: oClassInfo must be empty or an object"); // support for old usage of Metadata if (!oClassInfo || typeof oClassInfo.metadata !== "object") { oClassInfo = { metadata: oClassInfo || {}, // retrieve class by its name. Using a lookup costs time but avoids the need for redundant arguments to this function constructor: ObjectPath.get(sClassName) // legacy-relevant, code path not used by extend call }; oClassInfo.metadata.__version = 1.0; } oClassInfo.metadata.__version = oClassInfo.metadata.__version || 2.0; if (!isFunction(oClassInfo.constructor)) { throw Error("constructor for class " + sClassName + " must have been declared before creating metadata for it"); } // invariant: oClassInfo exists, oClassInfo.metadata exists, oClassInfo.constructor exists this._sClassName = sClassName; this._oClass = oClassInfo.constructor; this.extend(oClassInfo); }; /** * @private * @final */ Metadata.prototype.extend = function (oClassInfo) { this.applySettings(oClassInfo); this.afterApplySettings(); }; /** * @private * @since 1.3.1 */ Metadata.prototype.applySettings = function (oClassInfo) { var that = this, oStaticInfo = oClassInfo.metadata, oPrototype; if (oStaticInfo.baseType) { var oParentClass; if (isFunction(oStaticInfo.baseType)) { oParentClass = oStaticInfo.baseType; if (!isFunction(oParentClass.getMetadata)) { throw new TypeError("baseType must be a UI5 class with a static getMetadata function"); } } else { // lookup base class by its name - same reasoning as above oParentClass = ObjectPath.get(oStaticInfo.baseType); // legacy-relevant, code path not used by extend call if (!isFunction(oParentClass)) { Log.fatal("base class '" + oStaticInfo.baseType + "' does not exist"); } } // link metadata with base metadata if (oParentClass.getMetadata) { this._oParent = oParentClass.getMetadata(); assert(oParentClass === oParentClass.getMetadata().getClass(), "Metadata: oParentClass must match the class in the parent metadata"); } else { // fallback, if base class has no metadata - can only happen if baseType is a string this._oParent = new Metadata(oStaticInfo.baseType, {}); } } else { this._oParent = undefined; } this._bAbstract = !!oStaticInfo["abstract"]; this._bFinal = !!oStaticInfo["final"]; this._sStereotype = oStaticInfo.stereotype || (this._oParent ? this._oParent._sStereotype : "object"); this._bDeprecated = !!oStaticInfo["deprecated"]; // handle interfaces this._aInterfaces = oStaticInfo.interfaces || []; // take over metadata from static info this._aPublicMethods = oStaticInfo.publicMethods || []; // interfaces info possibly not unique this._bInterfacesUnique = false; // enrich prototype oPrototype = this._oClass.prototype; for (var n in oClassInfo) { if (n !== "metadata" && n !== "constructor") { oPrototype[n] = oClassInfo[n]; if (!n.match(/^_|^on|^init$|^exit$/)) { // TODO hard coded knowledge about event handlers ("on") and about init/exit hooks is not nice.... that._aPublicMethods.push(n); } } } }; /** * Called after new settings have been applied. * * Typically, this method is used to do some cleanup (e.g. uniqueness) * or to calculate an optimized version of some data. * @private * @since 1.3.1 */ Metadata.prototype.afterApplySettings = function () { // create the flattened "all" view if (this._oParent) { this._aAllPublicMethods = this._oParent._aAllPublicMethods.concat(this._aPublicMethods); this._bInterfacesUnique = false; } else { this._aAllPublicMethods = this._aPublicMethods; } }; /** * Stereotype of the described class. * * @experimental might be enhanced to a set of stereotypes * @private * @ui5-restricted */ Metadata.prototype.getStereotype = function () { return this._sStereotype; }; /** * Returns the fully qualified name of the described class * @return {string} name of the described class * @public */ Metadata.prototype.getName = function () { return this._sClassName; }; /** * Returns the (constructor of the) described class * @return {function(new:sap.ui.base.Object)} class described by this metadata * @public */ Metadata.prototype.getClass = function () { return this._oClass; }; /** * Returns the metadata object of the base class of the described class * or undefined if the class has no (documented) base class. * * @return {sap.ui.base.Metadata | undefined} metadata of the base class * @public */ Metadata.prototype.getParent = function () { return this._oParent; }; /** * Removes duplicate names in place from the interfaces and public methods members of this metadata object. * * @private */ Metadata.prototype._dedupInterfaces = function () { if (!this._bInterfacesUnique) { uniqueSort(this._aInterfaces); uniqueSort(this._aPublicMethods); uniqueSort(this._aAllPublicMethods); this._bInterfacesUnique = true; } }; /** * Returns an array with the names of the public methods declared by the described class, methods of * ancestors are not listed. * * @return {string[]} array with names of public methods declared by the described class * @deprecated As of 1.58, this method should not be used for productive code. The accuracy of the returned * information highly depends on the concrete class and is not actively monitored. There might be * more public methods or some of the returned methods might not really be intended for public use. * In general, pure visibility information should not be exposed in runtime metadata but be part of the * documentation. * Subclasses of <code>sap.ui.base.Object</code> might decide to provide runtime metadata describing * their public API, but this then should not be backed by this method. * See {@link sap.ui.core.mvc.ControllerMetadata#getAllMethods} for an example. * @public */ Metadata.prototype.getPublicMethods = function () { this._dedupInterfaces(); return this._aPublicMethods; }; /** * Returns an array with the names of all public methods declared by the described class * and all its ancestors classes. * * @return {string[]} array with names of all public methods provided by the described class and its ancestors * @deprecated As of 1.58, this method should not be used for productive code. The accuracy of the returned * information highly depends on the concrete class and is not actively monitored. There might be * more public methods or some of the returned methods might not really be intended for public use. * In general, pure visibility information should not be exposed in runtime metadata but be part of the * documentation. * Subclasses of <code>sap.ui.base.Object</code> might decide to provide runtime metadata describing * their public API, but this then should not be backed by this method. * See {@link sap.ui.core.mvc.ControllerMetadata#getAllMethods} for an example. * @public */ Metadata.prototype.getAllPublicMethods = function () { this._dedupInterfaces(); return this._aAllPublicMethods; }; /** * Returns the names of interfaces implemented by the described class. * As the representation of interfaces is not clear yet, this method is still private. * * @return {string} array of names of implemented interfaces * @private */ Metadata.prototype.getInterfaces = function () { this._dedupInterfaces(); return this._aInterfaces; }; /** * Checks whether the described class or one of its ancestor classes implements the given interface. * * @param {string} sInterface name of the interface to test for (in dot notation) * @return {boolean} whether this class implements the interface * @public */ Metadata.prototype.isInstanceOf = function (sInterface) { if (this._oParent) { if (this._oParent.isInstanceOf(sInterface)) { return true; } } var a = this._aInterfaces; for (var i = 0, l = a.length; i < l; i++) { // FIXME doesn't handle interface inheritance (requires object representation for interfaces) if (a[i] === sInterface) { return true; } } return false; }; /* * Lazy calculation of the set of implemented types. * * A calculation function is configured as getter for the <code>_mImplementedTypes</code> * on the prototype object. On first call for a metadata instance, it collects * the implemented types (classes, interfaces) from the described class and * any base classes and writes it to the property <code>_mImplementedTypes</code> of the * current instance of metadata. Future read access to the property will immediately * return the instance property and not call the calculation function again. */ Object.defineProperty(Metadata.prototype, "_mImplementedTypes", { get: function () { if (this === Metadata.prototype) { throw new Error("sap.ui.base.Metadata: The '_mImplementedTypes' property must not be accessed on the prototype"); } // create map of types, including inherited types // Note: to save processing time and memory, the inherited types are merged via the prototype chain of 'result' var result = Object.create(this._oParent ? this._oParent._mImplementedTypes : null); /* * Flat alternative: * var result = Object.create(null); * if ( this._oParent ) { * Object.assign(result, this._oParent._mImplementedTypes); * } */ // add own class result[this._sClassName] = true; // additionally collect interfaces var aInterfaces = this._aInterfaces, i = aInterfaces.length; while (i-- > 0) { if (!result[aInterfaces[i]]) { // take care to write property only if it hasn't been set already result[aInterfaces[i]] = true; } } // write instance property, hiding the getter on the prototype Object.defineProperty(this, "_mImplementedTypes", { value: Object.freeze(result), writable: false, configurable: false }); return result; }, configurable: true }); /** * Checks whether the class described by this metadata object is of the named type. * * This check is solely based on the type names as declared in the class metadata. * It compares the given <code>vTypeName</code> with the name of this class, with the * names of any base class of this class and with the names of all interfaces * implemented by any of the aforementioned classes. * * Instead of a single type name, an array of type names can be given and the method * will check if this class is of any of the listed types (logical or). * * Should the UI5 class system in future implement additional means of associating classes * with type names (e.g. by introducing mixins), then this method might detect matches * for those names as well. * * @param {string|string[]} vTypeName Type or types to check for * @returns {boolean} Whether this class is of the given type or of any of the given types * @public * @since 1.56 */ Metadata.prototype.isA = function (vTypeName) { var mTypes = this._mImplementedTypes; if (Array.isArray(vTypeName)) { for (var i = 0; i < vTypeName.length; i++) { if (vTypeName[i] in mTypes) { return true; } } return false; } // Note: the check with 'in' also finds inherited types via the prototype chain of mTypes return vTypeName in mTypes; }; /** * Returns whether the described class is abstract * @return {boolean} whether the class is abstract * @public */ Metadata.prototype.isAbstract = function () { return this._bAbstract; }; /** * Returns whether the described class is final * @return {boolean} whether the class is final * @public */ Metadata.prototype.isFinal = function () { return this._bFinal; }; /** * Whether the described class is deprecated and should not be used any more * * @return {boolean} whether the class is considered deprecated * @public * @since 1.26.4 */ Metadata.prototype.isDeprecated = function () { return this._bDeprecated; }; /** * Adds one or more new methods to the list of API methods. * * Can be used by contributer classes (like the EnabledPropagator) to enrich the declared set of methods. * The method can either be called with multiple names (strings) or with one array of strings. * * <b>Note</b>: the newly added method(s) will only be visible in {@link sap.ui.base.Interface interface} * objects that are created <i>after</i> this method has been called. * * @param {string|string[]} sMethod name(s) of the new method(s) */ Metadata.prototype.addPublicMethods = function (sMethod /* ... */) { var aNames = sMethod instanceof Array ? sMethod : arguments; Array.prototype.push.apply(this._aPublicMethods, aNames); Array.prototype.push.apply(this._aAllPublicMethods, aNames); this._bInterfacesUnique = false; }; /** * @since 1.3.1 * @private */ Metadata.createClass = function (fnBaseClass, sClassName, oClassInfo, FNMetaImpl) { if (typeof fnBaseClass === "string") { FNMetaImpl = oClassInfo; oClassInfo = sClassName; sClassName = fnBaseClass; fnBaseClass = null; } assert(!fnBaseClass || isFunction(fnBaseClass)); assert(typeof sClassName === "string" && !!sClassName); assert(!oClassInfo || typeof oClassInfo === "object"); assert(!FNMetaImpl || isFunction(FNMetaImpl)); // allow metadata class to preprocess FNMetaImpl = FNMetaImpl || Metadata; if (isFunction(FNMetaImpl.preprocessClassInfo)) { oClassInfo = FNMetaImpl.preprocessClassInfo(oClassInfo); } // normalize oClassInfo oClassInfo = oClassInfo || {}; oClassInfo.metadata = oClassInfo.metadata || {}; if (!oClassInfo.hasOwnProperty('constructor')) { oClassInfo.constructor = undefined; } var fnClass = oClassInfo.constructor; assert(!fnClass || isFunction(fnClass)); // ensure defaults if (fnBaseClass) { // default constructor just delegates to base class if (!fnClass) { if (oClassInfo.metadata.deprecated) { // create default factory with deprecation warning fnClass = function () { Log.warning("Usage of deprecated class: " + sClassName); fnBaseClass.apply(this, arguments); }; } else { // create default factory fnClass = function () { fnBaseClass.apply(this, arguments); }; } } // create prototype chain fnClass.prototype = Object.create(fnBaseClass.prototype); fnClass.prototype.constructor = fnClass; // enforce correct baseType oClassInfo.metadata.baseType = fnBaseClass; } else { // default constructor does nothing fnClass = fnClass || function () {}; // enforce correct baseType delete oClassInfo.metadata.baseType; } oClassInfo.constructor = fnClass; // make the class visible as JS Object ObjectPath.set(sClassName, fnClass); // add metadata var oMetadata = new FNMetaImpl(sClassName, oClassInfo); fnClass.getMetadata = fnClass.prototype.getMetadata = function () { return oMetadata; }; // enrich function if (!fnClass.getMetadata().isFinal()) { fnClass.extend = function (sSCName, oSCClassInfo, fnSCMetaImpl) { return Metadata.createClass(fnClass, sSCName, oSCClassInfo, fnSCMetaImpl || FNMetaImpl); }; } return fnClass; }; export default Metadata;