Skip to content
Snippets Groups Projects
discoverer.js 22.6 KiB
Newer Older
define(['attributeList', 'attribute', 'translation', 'parameter', 'parameterList', 'widget', 'interpreter', 'aggregator',  'interpretation' ],
	function(AttributeList, Attribute, Translation, Parameter, ParameterList, Widget, Interpreter, Aggregator, Interpretation) {
		return (function() {
			/**
			 * Constructor: All known components given in the associated functions will be registered as startup.
			 *
			 * @classdesc The Discoverer handles requests for components and attributes.
			function Discoverer(widgets, interpreters, translations) {
				/**
				 * List of available Widgets.
				 *
				 * @type {Array}
				 * @private
				 */
				this._widgets = [];

				/**
				 * List of unregistered Widgets
				 *
				 * @type (Array)
				 * @private
				 */
				this._unregisteredWidgets = widgets;


				/**
				 * List of available Aggregators.
				 *
				 * @type {Array}
				 * @private
				 */
				this._aggregators = [];

				/**
				 * List of available Interpreter.
				 *
				 * @type {Object}
				 * @private
				 */
				this._interpreters = [];

				/**
				 * List of unregistered Interpreters
				 *
				 * @type (Array)
				 * @private
				 */
				this._unregisteredInterpreters = interpreters;

				/**
				 * List of translations or synonymous attributes, respectively.
				 *
				 * @type {Array}
				 * @private
				 */
Helena Jank's avatar
Helena Jank committed
				// build translations from input array
                for (var i in translations) {
Helena Jank's avatar
Helena Jank committed
					// get translation (an array) from array of translations
                    var translationArray = translations[i];
Helena Jank's avatar
Helena Jank committed
					// check for correct cardinality
                    if (translationArray.length != 2)
                        throw new Error("Translations must consist of exactly 2 attributes!");
Helena Jank's avatar
Helena Jank committed
					// check for correct number of attribute building blocks
					for (var j in translationArray) {
                        if (translationArray[j].length > 3 || translationArray[j].length < 2)
                            throw new Error("Please provide a name, type and (optional) list of parameters!");
Helena Jank's avatar
Helena Jank committed
					// build attributes from arrays containing name, type (and parameters)
                    var firstAttribute = this.buildAttribute(
                        translationArray[0][0],
                        translationArray[0][1],
                        translationArray[0][2],
                        false
                    );
                    var secondAttribute = this.buildAttribute(
                        translationArray[1][0],
                        translationArray[1][1],
                        translationArray[1][2],
                        false
                    );
Helena Jank's avatar
Helena Jank committed
					// add built attributes to translations
                    this._translations.push(new Translation(firstAttribute,secondAttribute));
                }
				return this;
Stefanie Lemcke's avatar
Stefanie Lemcke committed
			}

			/**
			 * Registers the specified component.
			 *
			 * @param {Widget|Aggregator|Interpreter} component the component that should be registered
			 */
			Discoverer.prototype.registerNewComponent = function(component) {
				if (component instanceof Aggregator && this.getAggregator(component.getId()) == null) this._aggregators.push(component);
				if (component instanceof Widget && !(component instanceof Aggregator) && this.getWidget(component.getId()) == null) this._widgets.push(component);
				if (component instanceof Interpreter && this.getInterpreter(component.getId()) == null) this._interpreters.push(component);
			};

			/**
			 * Deletes a component from the Discoverer.
			 *
			 * @param {string} componentId id of the component that should be registered
			 */
			Discoverer.prototype.unregisterComponent = function(componentId) {
				for (var wi in this._widgets) {
					var theWidget = this._widgets[wi];
					if (componentId == theWidget.getId()) this._widgets.splice(wi, 1);
				}
				for (var ii in this._interpreters) {
					var theInterpreter = this._interpreters[ii];
					if (componentId == theInterpreter.getId()) this._interpreters.splice(ii, 1);
				}
				for (var ai in this._aggregators) {
					var theAggregator= this._aggregators[ai];
					if (componentId == theAggregator.getId()) this._aggregators.splice(ai, 1);
				}
			};

			/**
			 * Returns the widget for the specified id.
			 *
			 * @param {string} widgetId id of the component that should be returned
			 * @returns {?Widget}
			 */
			Discoverer.prototype.getWidget = function(widgetId) {
				for (var index in this._widgets) {
					var theWidget = this._widgets[index];
					if (theWidget.getId() == widgetId) return theWidget;
				}
				return null;
			};

			/**
			 * Returns the aggregator for the specified id.
			 *
			 * @param {string} aggregatorId id of the component that should be returned
			 * @returns {?Aggregator}
			 */
			Discoverer.prototype.getAggregator = function(aggregatorId) {
				for (var index in this._aggregators) {
					var theAggregator = this._aggregators[index];
					if (theAggregator.getId() == aggregatorId) return theAggregator;
				}
				return null;
			};

			/**
			 * Returns the interpreter for the specified id.
			 *
			 * @param {string} interpreterId id of the component that should be returned
			 * @returns {Interpreter}
			 */
			Discoverer.prototype.getInterpreter = function(interpreterId) {
				for (var index in this._interpreters) {
					var theInterpreter = this._interpreters[index];
					if (theInterpreter.getId() == interpreterId) return theInterpreter;
				}
				return null;
			};

			/**
			 * Returns all registered components (widget, aggregator and interpreter).
			 *
			 * @param {Array} componentTypes Component types to get descriptions for. Defaults to Widget, Interpreter and Aggregator.
			 * @returns {Array}
			 */
			Discoverer.prototype.getComponents = function(componentTypes) {
				if (typeof componentTypes == "undefined") componentTypes = [Widget, Interpreter, Aggregator];
				var response = [];
				if (jQuery.inArray(Widget, componentTypes) != -1) response = response.concat(this._widgets);
				if (jQuery.inArray(Aggregator, componentTypes) != -1) response = response.concat(this._aggregators);
				if (jQuery.inArray(Interpreter, componentTypes) != -1) response = response.concat(this._interpreters);
				return response;
			};

			/**
			 * Returns the instance (widget, aggregator or interpreter) for the specified id.
			 *
			 * @param {string} componentId id of the component that should be returned
			 * @returns {?(Widget|Aggregator|Interpreter)}
			 */
			Discoverer.prototype.getComponent = function(componentId) {
				var theWidget = this.getWidget(componentId);
				if (theWidget) {
					return theWidget;
				}
				var theAggregator = this.getAggregator(componentId);
				if (theAggregator) {
					return theAggregator;
				}
				var theInterpreter = this.getInterpreter(componentId);
				if (theInterpreter) {
					return theInterpreter;
				}
				return null;
			};

			/**
			 * Returns all components that have the specified attribute as
			 * outAttribute. It can be chosen between the verification of
			 * all attributes or at least one attribute.
			 *
			 * @param {AttributeList|Array} attributeListOrArray list of searched attributes
			 * @param {Boolean} all choise of the verification mode
			 * @param {Array} componentTypes Components types to search for
			 * @returns {Array}
			 */
			Discoverer.prototype.getComponentsByAttributes = function(attributeListOrArray, all, componentTypes) {
				var componentList = [];
				var list = [];
				if (typeof componentTypes == "undefined") componentTypes = [Widget, Interpreter, Aggregator];
				if (attributeListOrArray instanceof Array) {
					list = attributeListOrArray;
				} else if (attributeListOrArray.constructor === AttributeList) {
					list = attributeListOrArray.getItems();
				}
				if (typeof list != "undefined") {
					var components = this.getComponents(componentTypes);
					for (var i in components) {
						var theComponent = components[i];
						if(all && this._containsAllAttributes(theComponent, list)) {
							componentList.push(theComponent);
						} else if(!all && this._containsAtLeastOneAttribute(theComponent, list)) {
							componentList.push(theComponent);
Stefanie Lemcke's avatar
Stefanie Lemcke committed
					}
				}
				return componentList;
			};

			/**
			 * Returns an array of all translations known to the discoverer.
			 *
			 * @returns {Array}
			 */
			Discoverer.prototype.getTranslations = function() {
				return this._translations;
			};

			/**
			 * Builds a new attribute from given name, type and parameters,
			 * adding known translations to its synonyms.
			 *
			 * @param name
			 * @param type
             * @param {Boolean} [withSynonyms=true]
			Discoverer.prototype.buildAttribute = function(name, type, parameterList, withSynonyms) {
				if (typeof withSynonyms == 'undefined') withSynonyms = true;
				if (typeof parameterList == 'undefined') parameterList = [];

                if (typeof name != 'string' || typeof type != 'string')
                    throw new Error("Parameters name and type must be of type String!");

                var newAttribute = new Attribute(true).withName(name).withType(type);
				for (var i = 0; i < parameterList.length; i++) {
					var param = parameterList[i];
					var value = param[1];
					var key = param[0];
					if (typeof key != 'undefined' && typeof value != 'undefined') newAttribute = newAttribute.withParameter(new Parameter().withKey(key).withValue(value));
                    for (var index in this._translations) {
                        var translation = this._translations[index];
						newAttribute = translation.translate(newAttribute);
			/***********************************************************************
			 * Helper *
			 **********************************************************************/
			/**
			 * Helper: Verifies whether a component description contains all searched attributes.
			 *
			 * @private
			 * @param {Widget|Interpreter|Aggregator} component description of a component
			 * @param {Array} list searched attributes
			 * @returns {boolean}
			 */
			Discoverer.prototype._containsAllAttributes = function(component, list) {
				for (var j in list) {
					var attribute = list[j];
					if (!component.doesSatisfyTypeOf(attribute)) {
						return false;
					}
Stefanie Lemcke's avatar
Stefanie Lemcke committed
				}
				return true;
			};

			/**
			 * Helper: Verifies whether a component description contains at least on searched attributes.
			 *
			 * @private
			 * @param {Widget|Interpreter|Aggregator} component description of a component
			 * @param {Array} list searched attributes
			 * @returns {boolean}
			 */
			Discoverer.prototype._containsAtLeastOneAttribute = function(component, list) {
				for (var j in list) {
					var attribute = list[j];
					if (component.doesSatisfyTypeOf(attribute)) {
						return true;
					}
Stefanie Lemcke's avatar
Stefanie Lemcke committed
				}
				return false;
			};
Stefanie Lemcke's avatar
Stefanie Lemcke committed

			/**
			 * Searches for components that can satisfy the requested attributes. Searches recursively through all
			 * registered and unregistered components and initiates them.
			 *
			 * @private
			 * @param {String} aggregatorId the aggregator's ID
			 * @param {AttributeList} unsatisfiedAttributes A list of attributes that components should be searched for.
			 * @param {boolean} all If true all attributes must be satisfied by a single component.
			 * @param {Array} componentTypes An array of components classes that should be searched for (e.g. Widget, Interpreter and Aggregator).
			 */
mirueger's avatar
mirueger committed
			Discoverer.prototype.getComponentsForUnsatisfiedAttributes = function(aggregatorId, unsatisfiedAttributes, all, componentTypes){
				//Discoverer gets a list of attributes to satisfy
				console.log('Discoverer: I need to satisfy the following attributes: '+unsatisfiedAttributes.getItems()+' .' );

				//look at all the already registered components
				console.log("Discoverer: Let's look at my registered components.");

				var relevantComponents = this.getComponentsByAttributes(unsatisfiedAttributes, all, componentTypes);
				console.log("Discoverer: I found " + relevantComponents.length + " registered component(s) that might satisfy the requested attributes.");

				//iterate over the found components
				for(var index in relevantComponents) {
					var theComponent = relevantComponents[index];
					console.log("Discoverer: Let's look at component " + theComponent.getName() + ".");

					// if the component was added before, ignore it
					if (!this.getAggregator(aggregatorId)._hasComponent(theComponent.getId())) {
						var outAttributes = theComponent.getOutAttributes().getItems();

						// if component is a widget and it wasn't added before, subscribe to its callbacks
						if (theComponent instanceof Widget) {
							console.log("Discoverer: It's a Widget.");

							console.log("Discoverer: You should subscribe to the Widget.");
							this.getAggregator(aggregatorId).addWidgetSubscription(theComponent);
							// remove satisfied attributes
							for (var widgetOutAttributeIndex in outAttributes) {
								var widgetOutAttribute = outAttributes[widgetOutAttributeIndex];
								// add the attribute type to the aggregators list of handled attribute types
								if (!this.getAggregator(aggregatorId).getOutAttributes().containsTypeOf(widgetOutAttribute)) this.getAggregator(aggregatorId).addOutAttribute(widgetOutAttribute);
								console.log("Aggregator: I can now satisfy attribute " + widgetOutAttribute + " with the help of " + theComponent.getName() + "! That was easy :)");
								unsatisfiedAttributes.removeAttributeWithTypeOf(widgetOutAttribute);
							}
						} else if (theComponent instanceof Interpreter) { // if the component is an interpreter and all its in attributes can be satisfied, add the interpreter
							console.log("Discoverer: It's an Interpreter.");
							var inAttributes = theComponent.getInAttributes().getItems();
							var canSatisfyInAttributes = true;

							// iterate over the attributes needed to satisfy the interpreter
							for (var inAttributeIdentifier in inAttributes) {
								// get the attribute
								var theInAttribute = inAttributes[inAttributeIdentifier];
								console.log("Discoverer: The Interpreter needs the attribute: " + theInAttribute + ".");

								// if required attribute is not already satisfied by the aggregator search for components that do
								if (!this.getAggregator(aggregatorId).doesSatisfyTypeOf(theInAttribute)) {
									console.log("Discoverer: I can't satisfy " + theInAttribute + ", but i will search for components that can");
									var newAttributeList = new AttributeList();
									newAttributeList.put(theInAttribute);
mirueger's avatar
mirueger committed
									this.getComponentsForUnsatisfiedAttributes(aggregatorId, newAttributeList, false, [Widget, Interpreter]);
									// if the attribute still can't be satisfied drop the interpreter
									if (!this.getAggregator(aggregatorId).doesSatisfyTypeOf(theInAttribute)) {
										console.log("Discoverer: I couldn't find a component to satisfy " + theInAttribute + ". Dropping interpreter " + theComponent.getName() + ". Bye bye.");
										canSatisfyInAttributes = false;
										break;
									}
								} else {
									console.log("Discoverer: It seems that I already satisfy the attribute " + theInAttribute + ". Let's move on.");
								}
							}
							if (canSatisfyInAttributes) {
								// remove satisfied attribute
								for (var interpreterOutAttributeIndex in outAttributes) {
									var interpreterOutAttribute = outAttributes[interpreterOutAttributeIndex];
									// add the attribute type to the aggregators list of handled attribute types
									for (var unsatisfiedAttributeIndex in unsatisfiedAttributes.getItems()) {
										var theUnsatisfiedAttribute = unsatisfiedAttributes.getItems()[unsatisfiedAttributeIndex];
										if (theUnsatisfiedAttribute.equalsTypeOf(interpreterOutAttribute)) {
											this.getAggregator(aggregatorId).addOutAttribute(theUnsatisfiedAttribute);
											console.log("Discoverer: I can now satisfy attribute " + theUnsatisfiedAttribute + " with the help of " + theComponent.getName() + "! Great!");
											this.getAggregator(aggregatorId)._interpretations.push(new Interpretation(theComponent.getId(), theComponent.getInAttributes(), new AttributeList().withItems([theUnsatisfiedAttribute])));
										}
									}
									unsatisfiedAttributes.removeAttributeWithTypeOf(interpreterOutAttribute, true);
								}
							} else {
								console.log("Discoverer: Found interpreter but can't satisfy required attributes.");
								for (var j in theComponent.getInAttributes().getItems()) {
									console.log("Discoverer: Missing " + theComponent.getInAttributes().getItems()[j] + ".");
								}
							}
						}
					}
				}

				//look at all unregistered components
				console.log("Discoverer: Let's look at the unregistered Components.");

				//check all Widget's outAttributes
				console.log("Discoverer: Let's look at the unregistered Widgets.")
				var foundWidget = false;
				for(var widgetIndex in this._unregisteredWidgets){
					var theWidget = this._unregisteredWidgets[widgetIndex];
					for(var unsatisfiedAttributeIndex in unsatisfiedAttributes.getItems()){
						var theUnsatisfiedAttribute = unsatisfiedAttributes.getItems()[unsatisfiedAttributeIndex];
						//if a Widget can satisfy the Attribute, register it and subscribe the Aggregator

						//create temporary OutAttributeList
						var tempWidgetOutList = AttributeList.fromAttributeDescription(this, theWidget.inOut.out);
						
						for(var tempOutAttribute = 0; tempOutAttribute < tempWidgetOutList.size(); tempOutAttribute++) {
							if (theUnsatisfiedAttribute.equalsTypeOf(tempWidgetOutList.getItems()[tempOutAttribute])) {
								console.log("Discoverer: I have found an unregistered Widget \"" + theWidget.name + "\".");
								var newWidget = new theWidget(this, tempWidgetOutList);
								this.getAggregator(aggregatorId).addWidgetSubscription(newWidget);

								console.log("Discoverer: I registered the Widget \"" + theWidget.name + "\" and subscribed to it.");
								// remove satisfied attributes
								var widgetOutAttributes = newWidget.getOutAttributes();
								for (var toRemoveIndex = 0; toRemoveIndex < widgetOutAttributes.size(); toRemoveIndex++) {
									var toRemoveOutAttribute = newWidget.getOutAttributes().getItems()[toRemoveIndex];
									// add the attribute type to the aggregators list of handled attribute types
									if (!this.getAggregator(aggregatorId).getOutAttributes().containsTypeOf(toRemoveOutAttribute)) this.getAggregator(aggregatorId).addOutAttribute(toRemoveOutAttribute);
									console.log("Aggregator "+this.getAggregator(aggregatorId).id+": I can now satisfy attribute " + toRemoveOutAttribute + " with the help of " + newWidget.getName() + "! That was easy :)");
									unsatisfiedAttributes.removeAttributeWithTypeOf(toRemoveOutAttribute);
								}
								foundWidget = true;

							}
						}


					}

				}
				if (!foundWidget) {
					//check all interpreters' outAttributes
					console.log("Discoverer: Let's look at the unregistered interpreters.")
					for (var index in this._unregisteredInterpreters) {
						var theInterpreter = this._unregisteredInterpreters[index];
						for (var unsatisfiedAttributeIndex in unsatisfiedAttributes.getItems()) {
							var theUnsatisfiedAttribute = unsatisfiedAttributes.getItems()[unsatisfiedAttributeIndex];
							//if an interpreter can satisfy the Attribute, check if the inAttributes are satisfied

							//create temporary outAttributeList
							var tempOutList = AttributeList.fromAttributeDescription(this, theInterpreter.inOut.out);
							//create temporary inAttributeList
							var tempInList = AttributeList.fromAttributeDescription(this, theInterpreter.inOut.in);

							for (var tempOutAttribute = 0; tempOutAttribute < tempOutList.size(); tempOutAttribute++) {
								if (theUnsatisfiedAttribute.equalsTypeOf(tempOutList.getItems()[tempOutAttribute])) {
									console.log("Discoverer: I have found an unregistered Interpreter \"" + theInterpreter.name + "\".");
									for (var inAttributeIndex = 0; inAttributeIndex < tempInList.size(); inAttributeIndex++) {
										var theInAttribute = tempInList.getItems()[inAttributeIndex];
										console.log("Discoverer: The Interpreter needs the attribute: " + theInAttribute + ".");

										// if required attribute is not already satisfied by the aggregator search for components that do
										if (!this.getAggregator(aggregatorId).doesSatisfyTypeOf(theInAttribute)) {
											console.log("Discoverer: I can't satisfy " + theInAttribute + ", but i will search for components that can");
											var newAttributeList = new AttributeList();
											newAttributeList.put(theInAttribute);
mirueger's avatar
mirueger committed
											this.getComponentsForUnsatisfiedAttributes(aggregatorId, newAttributeList, false, [Widget, Interpreter]);
											// if the attribute still can't be satisfied drop the interpreter
											if (!this.getAggregator(aggregatorId).doesSatisfyTypeOf(theInAttribute)) {
												console.log("Discoverer: I couldn't find a component to satisfy " + theInAttribute + ". Dropping interpreter " + theInterpreter.name + ". Bye bye.");
												canSatisfyInAttributes = false;
												break;
											}
											var newInterpreter = new theInterpreter(this, tempInList, tempOutList);
											this.getAggregator(aggregatorId).addWidgetSubscription(newInterpreter);
mirueger's avatar
mirueger committed
											console.log("Discoverer: I registered the Interpreter \"" + theInterpreter.name + "\" .");
											// remove satisfied attributes
											var interpreterOutAttributes = newInterpreter.getOutAttributes();
											for (var toRemoveIndex = 0; toRemoveIndex < interpreterOutAttributes.size(); toRemoveIndex++) {
												var toRemoveOutAttribute = newInterpreter.getOutAttributes().getItems()[toRemoveIndex];
												// add the attribute type to the aggregators list of handled attribute types
												if (!this.getAggregator(aggregatorId).getOutAttributes().containsTypeOf(toRemoveOutAttribute)) this.getAggregator(aggregatorId).addOutAttribute(toRemoveOutAttribute);
												console.log("Aggregator "+this.getAggregator(aggregatorId).id+": I can now satisfy attribute " + toRemoveOutAttribute + " with the help of " + newInterpreter.getName() + "! That was easy :)");
												this.getAggregator(aggregatorId)._interpretations.push(new Interpretation(newInterpreter.getId(), newInterpreter.getInAttributes(), new AttributeList().withItems([theUnsatisfiedAttribute])));
												unsatisfiedAttributes.removeAttributeWithTypeOf(toRemoveOutAttribute);
											}
										} else {
											console.log("Discoverer: It seems that I already satisfy the attribute " + theInAttribute + ". Let's move on.");
										}
									}
								}
							}

						}
					}
				}
			};
Stefanie Lemcke's avatar
Stefanie Lemcke committed

			return Discoverer;
		})();
	}
);