Skip to content

Commit

Permalink
refactor: add proxies for ioc
Browse files Browse the repository at this point in the history
  • Loading branch information
ThorstenSuckow committed Dec 1, 2022
1 parent 4a861bd commit a023a87
Show file tree
Hide file tree
Showing 10 changed files with 1,047 additions and 0 deletions.
102 changes: 102 additions & 0 deletions src/ioc/Bindings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* coon.js
* extjs-lib-core
* Copyright (C) 2022 Thorsten Suckow-Homberg https://github.com/coon-js/extjs-lib-core
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/


/**
* Object for managing bindings used by the ioc-container.
* This class should not be used directly. Instead, the owning ioc-container should manage it.
*
* @example
* const bindings = Ext.create("coon.core.ioc.Bindings", {});
* const data = bindings.getData(); // {}
*
* bindigs.merge({"com.acme": {requestConfigurator: "coon.core.data.request.Configurator"}});
* bindings.getData(); // {"com.acme": {requestConfigurator: "coon.core.data.request.Configurator"}}
*
* bindigs.merge({"com.acme.Class": {requestConfigurator: "com.acme.RequestConfigurator"}});
* bindings.getData(); // {
* // "com.acme.Class": {requestConfigurator: "com.acme.RequestConfigurator"},
* // "com.acme": {requestConfigurator: "coon.core.data.request.Configurator"}
* // }
*
*/
Ext.define("coon.core.ioc.Bindings", {


/**
* @type {Object} data
*/
constructor (data) {
this.data = {};
this.merge(data || {});
},


getData () {
return this.data;
},


/**
* Merge bindings.
* Existing data is being overwritten with new data.
* Will sort the entries after data has been merged to make sure entries with more
* namespace information appear first, and more general entries are found at the end ot the
* data-container.
*
* @param {Object} data An object containing key-value pairs, with keys representing class-names/namespaced,
* and values representing types or type configuration that need to get resolved by the implemention ioc.container
*/
merge (data) {

const me = this;

me.data = Object.assign(me.data, data);

me.data = Object.fromEntries(Object.entries(me.data).sort((lft, rt) => {

lft = lft[0];
rt = rt[0];

let lftParts = lft.split(".");
let rtParts = rt.split(".");

if(lftParts.length < rtParts.length) {
return 1;
}

if(lftParts.length > rtParts.length) {
return -1;
}

return 0;


}));

return me.data;
}

});
86 changes: 86 additions & 0 deletions src/ioc/Proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* coon.js
* extjs-lib-core
* Copyright (C) 2022 Thorsten Suckow-Homberg https://github.com/coon-js/extjs-lib-core
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/


/**
*
*
*/
Ext.define("coon.core.ioc.Proxy", {


requires: [
// @define "l8"
"l8",
"Ext.Factory",
"coon.core.ioc.sencha.FactoryProxy",
"coon.core.ioc.sencha.CreateProxy"
],

/**
* @type {coon.core.ioc.sencha.FactoryProxy} FactoryProxy
* @private
*/

/**
* @type {coon.core.ioc.sencha.CreateProxy} CreateProxy
* @private
*/

/**
* @type {Boolean} booted
* @private
*/


/**
*
* @param {coon.core.ioc.Bindings} bindings
*/
constructor ({bindings}) {
const me = this;

me.boot(bindings || {});
},


/**
* @private
*/
boot (bindings) {
const me = this;

if (!me.booted) {

me.factoryProxy = Ext.create("coon.core.ioc.sencha.FactoryProxy", {bindings});
me.createProxy = Ext.create("coon.core.ioc.sencha.CreateProxy", {bindings});

Ext.Factory = new Proxy(Ext.Factory, me.factoryProxy);
Ext.create = new Proxy(Ext.create, me.createProxy);
me.booted = true;
}
}

});
175 changes: 175 additions & 0 deletions src/ioc/sencha/AbstractProxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/**
* coon.js
* extjs-lib-core
* Copyright (C) 2022 Thorsten Suckow-Homberg https://github.com/coon-js/extjs-lib-core
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*/


/**
* AbstractProxy providing functionality for working with Sencha Ext JS specific classes.
* Implementing classes can delegate calls that involve resolving dependencies to resolveDependencies()
*
* @abstract
*/
Ext.define("coon.core.ioc.sencha.AbstractProxy", {

requires: [
// @define "l8"
"l8",
"coon.core.ioc.Bindings"
],


/**
* @type {coon.core.ioc.Bindings} bindings
* @private
*/


/**
* Constructor.
*
* @param {coon.core.ioc.Bindings} bindings
*
* @throws if bindings is not an instance of coon.core.ioc.Bindings
*/
constructor (bindings) {
if (!(bindings instanceof coon.core.ioc.Bindings)) {
throw new Error("\"bindings\" must be an instance of coon.core.ioc.Bindings");
}
this.bindings = bindings;
},


/**
* Tries to find a binding configuration for "targetClass" by matching it against
* a list of available namespaces. If an entry is found, the entry must be configured
* as {[requiredType]: [specific]}. If such an entry exists, "specific" will be
* returned. If no entry is found, "requiredType" is returned.
*
* @example
*
* // available bindings
* // {
* // "com.acme.data": {
* // "org.project.impl.IClass": "org.project.impl.Specific"
* // }
* proxy.resolveToSpecific("com.acme.data.message.Editor", "org.project.impl.IClass");
* // returns "org.project.impl.IClass"
*
* @param {String} targetClass
* @param {String} defaultrequiredTypeClass
*
* @returns {String}
*
* @private
*/
resolveToSpecific (targetClass, requiredType) {

const
me = this,
targets = Object.entries(me.bindings.getData());

let cfg = targets.filter(([target]) => target.toLowerCase() === targetClass.toLowerCase());

if (!cfg.length) {
cfg = targets.filter(([target]) => targetClass.toLowerCase().indexOf(target.toLowerCase()) === 0);
if (!cfg.length) {
return requiredType;
}
}

const availableClasses = Object.entries(cfg[0][1]);
let specific;

availableClasses.some(([cfgClassName, specInst]) => {

if (cfgClassName.toLowerCase() === requiredType.toLowerCase()) {
specific = specInst;
return true;
}

});

return specific || requiredType;
},


/**
* Will resolve dependencies available with "requires" for "targetClass".
* "requires" must be an object where the keys are property-names defined by "targetClass", and the values
* are type hints in form of class names.
*
* @example
* // "com.acme.BaseProxy" has a dependency to "coon.core.data.request.Configurator",
* // with its "requestConfigurator" property
* // const requires = {"requestConfigurator": "coon.core.data.request.Configurator"};
* const resolved = resolveDependencies("com.acme.BaseProxy", requires);
* // resolveDependencies will delegate to "resolveToSpecific()" to see if a more specific type was bound
* // to com.acme.BaseProxy`s "requestConfigurator" property, otherwise, it will return an instance of
* // "coon.core.data.request.Configurator"
* console.log(resolved); // {"requestConfigurator": INSTANCE_OF[coon.core.data.request.Configurator]}
*
* @param {String} targetClass The class name for which the dependencies get resolved
* @param {Object} requires Definitions of the dependencies required by forClass.
*
* @returns {{}}
*
* @see resolveToSpecific
*
* @throws if a resolved class name cannot be instantiated
*/
resolveDependencies ( targetClass, requires) {

const
me = this,
dependencies = Object.entries(requires),
deps = {},
scope = me.getScope();

dependencies.forEach(([prop, requiredType]) => {
if (!deps[prop]) {
const specific = me.resolveToSpecific(targetClass, requiredType);

if (!l8.unchain(specific, scope)) {
throw new Error(`${specific} bound to ${targetClass}.${prop}, but was not found in the scope available`);
}

deps[prop] = Ext.create(specific);
}
});

return deps;
},


/**
* Scope where the defined classes should be defined.
* Defaults to the "window"-object.
* @private
*/
getScope () {
return window;
}


});
Loading

0 comments on commit a023a87

Please sign in to comment.