[Framework] Add extension sorter

Add priority ordering to loaded extensions in each category;
this allows control over the resulting order of extensions
acquired and used within the application. WTD-590
This commit is contained in:
Victor Woeltjen 2015-01-07 16:39:57 -08:00
parent 2bcb6a1a6e
commit 9d8885d48f
4 changed files with 129 additions and 18 deletions

View File

@ -16,5 +16,13 @@ define({
"tests": "test",
"configuration": {},
"extensions": {}
}
},
PRIORITY_LEVELS: {
"fallback": Number.NEGATIVE_INFINITY,
"default": 100,
"optional": 200,
"preferred": 400,
"mandatory": Number.POSITIVE_INFINITY
},
DEFAULT_PRIORITY: 0
});

View File

@ -26,23 +26,26 @@ define(
'./resolve/RequireConfigurator',
'./register/CustomRegistrars',
'./register/ExtensionRegistrar',
'./register/ExtensionSorter',
'./bootstrap/ApplicationBootstrapper'
],
function (require,
es6promise,
angular,
angularRoute,
Constants,
FrameworkInitializer,
BundleLoader,
ImplementationLoader,
ExtensionResolver,
BundleResolver,
RequireConfigurator,
CustomRegistrars,
ExtensionRegistrar,
ApplicationBootstrapper
) {
function (
require,
es6promise,
angular,
angularRoute,
Constants,
FrameworkInitializer,
BundleLoader,
ImplementationLoader,
ExtensionResolver,
BundleResolver,
RequireConfigurator,
CustomRegistrars,
ExtensionRegistrar,
ExtensionSorter,
ApplicationBootstrapper
) {
"use strict";
// Get a reference to Angular's injector, so we can get $http and $log
@ -68,6 +71,7 @@ define(
registrar = new ExtensionRegistrar(
app,
new CustomRegistrars(app, $log),
new ExtensionSorter($log),
$log
),
bootstrapper = new ApplicationBootstrapper(

View File

@ -17,9 +17,11 @@ define(
* @param {Object.<string,function>} customRegistrars an object
* containing custom registration functions, primarily for
* Angular built-ins.
* @param {ExtensionSorter} sorter the sorter which will impose
* priority ordering upon extensions
* @param {*} $log Angular's logging service
*/
function ExtensionRegistrar(app, customRegistrars, $log) {
function ExtensionRegistrar(app, customRegistrars, sorter, $log) {
// Track which extension categories have already been registered.
// Exceptions will be thrown if the same extension category is
// registered twice.
@ -163,7 +165,7 @@ define(
Object.keys(extensionGroup).forEach(function (category) {
registerExtensionsForCategory(
category,
extensionGroup[category]
sorter.sort(extensionGroup[category])
);
});

View File

@ -0,0 +1,97 @@
/*global define*/
define(
["../Constants"],
function (Constants) {
"use strict";
/**
* Responsible for applying priority order to extensions in a
* given category. This will sort in reverse order of the numeric
* priority given for extensions in the `priority` priority (such
* that large values are registered first.) Extensions may also
* specify symbolic properties as strings (instead of numbers),
* which will be looked up from the table `Constants.PRIORITY_LEVELS`.
* @param $log Angular's logging service
* @constructor
*/
function ExtensionSorter($log) {
// Handle unknown or malformed priorities specified by extensions
function unrecognizedPriority(extension) {
// Issue a warning
$log.warn([
"Unrecognized priority '",
(extension || {}).priority,
"' specified for extension from ",
((extension || {}).bundle || {}).path,
"; defaulting to ",
Constants.DEFAULT_PRIORITY
].join(''));
// Provide a return value (default priority) to make this
// useful in an expression.
return Constants.DEFAULT_PRIORITY;
}
function getPriority(extension) {
var priority =
(extension || {}).priority || Constants.DEFAULT_PRIORITY;
// If it's a symbolic priority, look it up
if (typeof priority === 'string') {
priority = Constants.PRIORITY_LEVELS[priority];
}
// Should be a number; otherwise, issue a warning and
// fall back to default priority level.
return (typeof priority === 'number') ?
priority : unrecognizedPriority(extension);
}
// Attach a numeric priority to an extension; this is done in
// one pass outside of the comparator, mainly because getPriority
// may log warnings, and we only want this to happen once
// (instead of the many times that might occur during a sort.)
function prioritize(extension, index) {
return {
// The extension itself, for later unwrapping
extension: extension,
// The index, to provide a stable sort (see compare)
index: index,
// The numeric priority of the extension
priority: getPriority(extension)
};
}
// Unwrap the original extension
// (for use after ordering has been applied)
function deprioritize(prioritized) {
return prioritized.extension;
}
// Compare two prioritized extensions
function compare(a, b) {
// Reverse order by numeric priority; or, original order.
return (b.priority - a.priority) || (a.index - b.index);
}
return {
/**
* Sort extensions according to priority.
*
* @param {object[]} extensions array of resolved extensions
* @returns {object[]} the same extensions, in priority order
*/
sort: function (extensions) {
return (extensions || [])
.map(prioritize)
.sort(compare)
.map(deprioritize);
}
};
}
return ExtensionSorter;
}
);