/**
* The `proact-dsl` module provides DSL for creating and managing different ProAct objects.
*
*
* @module proact-dsl
* @main proact-dsl
*/
/**
* <p>
* Constructs a `ProAct.Registry`.
* It is used to store/create objects that can be referenced or configured using the {{#crossLink "ProAct.DSL"}}{{/crossLink}}.
* </p>
* <p>
* `ProAct.Registry` is part of the `proact-dsl` module of ProAct.js.
* </p>
*
* @class ProAct.Registry
* @constructor
*/
function Registry () {
this.providers = {};
}
ProAct.Registry = P.R = Registry;
ProAct.Registry.prototype = rProto = {
/**
* Reference to the constructor of this object.
*
* @property constructor
* @type ProAct.Registry
* @final
* @for ProAct.Registry
*/
constructor: ProAct.Registry,
/**
* Registers a {{#crossLink "ProAct.Registry.Provider"}}{{/crossLink}} for the passed <i>namespace</i> in the registry.
*
* @for ProAct.Registry
* @instance
* @method register
* @param {String} namespace
* The namespace to register the <i>provider</i> in.
* @param {ProAct.Registry.Provider} provider
* The {{#crossLink "ProAct.Registry.Provider"}}{{/crossLink}} to register.
* @return {ProAct.Registers}
* <i>this</i>
* @throws {Error}
* If a {{#crossLink "ProAct.Registry.Provider"}}{{/crossLink}} is already registered for the passed <i>namespace</i>.
*/
register: function (namespace, provider) {
if (this.providers[namespace]) {
throw new Error(namespace + 'is already registered in this registry.');
}
this.providers[namespace] = provider;
if (provider.registered) {
provider.registered(this);
}
return this;
},
/**
* Retrieves the right {{#crossLink "ProAct.Registry.Provider"}}{{/crossLink}} using the <i>name</i> of stored
* in <i>this</i> ProAct.Registry object, or the <i>name</i> of an object to be stored
*
* @for ProAct.Registry
* @instance
* @method getProviderByName
* @param {String} name
* The name of storable object.
* <p>
* It must be in the format '{namespace}:{key}'.
* </p>
* <p>
* Here the namespace is the namespace the {{#crossLink "ProAct.Registry.Provider"}}{{/crossLink}} manages.
* </p>
* @return {Array}
* The first element in the result is the {{#crossLink "ProAct.Registry.Provider"}}{{/crossLink}} or undefined if not found.
* <p>
* The second one is the <b>key</b> at which an object is stored or will be stored in the provider.
* </p>
* <p>
* The third element is an array with options for storing/creating an object passed to the provider using
* the <i>name</i> string.
* </p>
*/
getProviderByName: function (name) {
var parts = name.split(':');
return [this.providers[parts[0]], parts[1], parts.slice(2)];
},
/**
* Configures an object to be stored using {{#crossLink "ProAct.DSL"}}{{/crossLink}} passed through <i>options</i> and DSL arguments.
* <p>
* Example usage:
* </p>
* <p>
* A {{#crossLink "ProAct.Stream"}}{{/crossLink}} is passed to the registry for setup with DSL data.
* </p>
* <p>
* The data passed through the <i>options</i> parameter is
* <pre>
* '<<(s:foo)|map(-)|filter($1)'
* </pre>
* </p>
* <p>
* And the arguments for the DSL machine passed through the <i>args</i> parameter are
* <pre>
* [function (v) {
* return v % 2 === 0;
* }]
* </pre>
* </p>
* <p>
* This means that a {{#crossLink "ProAct.Stream"}}{{/crossLink}} stored in <i>this</i> registry by the key 'foo' should be set
* as a source to the passed as the <i>object</i> parameter simple {{#crossLink "ProAct.Stream"}}{{/crossLink}}.
* </p>
* <p>
* It also means that for every value comming in the <i>object</i> parameter's stream there should be mapping of negativity and
* only even values should be passed to it.
* </p>
* <p>
* So if we trigger in the 'foo' stream the value of <b>4</b> in our stream we will get <b>-4</b>, and if we trigger 5, we won't get anything.
* </p>
*
* @for ProAct.Registry
* @instance
* @method setup
* @param {Object} object
* The object to setup.
* @param {String|Object} options
* A {{#crossLink "ProAct.DSL"}}{{/crossLink}} data object or string used to setup the object.
* @param {Array} args
* Arguments to be used by the {{#crossLink "ProAct.DSL/run:method"}}{{/crossLink}} method while configuring the passed <i>object</i>.
* @return {Object}
* Ready to strore object.
*/
setup: function (object, options, args) {
return dsl.run.apply(null, [object, options, this].concat(args));
},
/**
* Creates a new object and stores it in <i>this</i> registry, using the right provider for the creation
* and configuring it using the DSL passed through the <i>options</i> parameter.
* <p>
* {{#crossLink "ProAct.Registry/getProviderByName:method"}}{{/crossLink}} is used to locate the right provider to create the object with.
* </p>
* <p>
* {{#crossLink "ProAct.Registry/setup:method"}}{{/crossLink}} is used to setup the newly created object using the {{#crossLink "ProAct.DSL"}}{{/crossLink}}
* </p>
* <p>
* The idea of this method is to create and configure {{#crossLink "ProAct.Actor"}}{{/crossLink}} objects.
* </p>
*
* @for ProAct.Registry
* @instance
* @method make
* @param {String} name
* Name of the object to create and store.
* <p>
* It must be in the format '{namespace}:{key}'
* </p>
* @param {String|Object} options
* A {{#crossLink "ProAct.DSL"}}{{/crossLink}} data object or string used to setup the object to be created.
* @param [...]
* <b>Arguments</b> to be used by the {{#crossLink "ProAct.DSL/run:method"}}{{/crossLink}} method while configuring the newly created <i>object</i>.
* @return {Object}
* The newly created, stored and configured object, or null if there was no {{#crossLink "ProAct.Registry.Provider"}}{{/crossLink}} register for its type.
*/
make: function (name, options) {
var args = slice.call(arguments, 2),
p = this.getProviderByName(name),
actor;
if (p[0]) {
actor = p[0].make.apply(p[0], [p[1], p[2]].concat(args));
return this.setup(actor, options, args);
}
return null;
},
/**
* Stores an object in <i>this</i> registry, using the right provider to configure it using the DSL passed through the <i>options</i> parameter.
* <p>
* {{#crossLink "ProAct.Registry/getProviderByName:method"}}{{/crossLink}} is used to locate the right provider to store the object to.
* </p>
*
* @for ProAct.Registry
* @instance
* @method store
* @param {String} name
* Name of the object to store.
* <p>
* It must be in the format '{namespace}:{key}'
* </p>
* @param {Object} object
* The object to store.
* @param {String|Object} options
* A {{#crossLink "ProAct.DSL"}}{{/crossLink}} data object or string used to setup the object to be stored (optional).
* @param [...]
* <b>Arguments</b> to be used by the {{#crossLink "ProAct.DSL/run:method"}}{{/crossLink}} method while configuring the <i>object</i>.
* @return {Object}
* The stored and configured object, or null if there was no {{#crossLink "ProAct.Registry.Provider"}}{{/crossLink}} register for its type.
*/
store: function (name, object, options) {
var args = slice.call(arguments, 2),
p = this.getProviderByName(name);
if (p[0]) {
return p[0].store.apply(p[0], [p[1], object, p[2]].concat(args));
}
return null;
},
/**
* Retrieves an object, stored <i>this</i> registry.
* <p>
* {{#crossLink "ProAct.Registry/getProviderByName:method"}}{{/crossLink}} is used to locate the right provider to retrieve the object from.
* </p>
*
* @for ProAct.Registry
* @instance
* @method get
* @param {String} name
* Name of the object to find.
* <p>
* It must be in the format '{namespace}:{key}'
* </p>
* @return {Object}
* The stored object, or null if there was no {{#crossLink "ProAct.Registry.Provider"}}{{/crossLink}} register for its type or no object registered for the passed <i>name</i>.
*/
get: function (name) {
var p = this.getProviderByName(name);
if (p[0]) {
return p[0].get(p[1]);
}
return null;
},
/**
* Helper method for transforming an array of keys of stored items in <i>this</i> `ProAct.Registry` to an array of the actual items.
* <p>
* Mainly used by the {{#crossLink "ProAct.DSL"}}{{/crossLink}} logic.
* </p>
*
* @for ProAct.Registry
* @protected
* @instance
* @method toObjectArray
* @param {Array} array
* Array of string keys to objects stored in <i>this</i> registry to be retrieved using {{#crossLink "ProAct.Registry/toObject:method"}}{{/crossLink}}.
* <p>
* If object is not stored on some key, the key itself is returned in the same possition in the result array.
* </p>
* @return {Array}
* Of the retrieved objects, in the same order as the keys.
*/
toObjectArray: function (array) {
var self = this;
if (!P.U.isArray(array)) {
return this.toObject(array);
}
return map.call(array, function (el) {
return self.toObject(el);
});
},
/**
* Helper method for transforming a key of stored item in <i>this</i> `ProAct.Registry` to the actual item or returning the key, if
* the item is not found in the `ProAct.Registry`.
* <p>
* Mainly used by the {{#crossLink "ProAct.DSL"}}{{/crossLink}} logic.
* </p>
*
* @for ProAct.Registry
* @protected
* @instance
* @method toObject
* @param {String|Object} data
* Key of strored object or something else. If the key is valid and there is something stored on it, the stored object is retrieved.
* <p>
* If there is nothing stored for this <i>data</i>, the <i>data</i> itself is returned.
* </p>
* @return {Object}
* Stored object, if found using the passed <i>data</i> or the <i>data</i> itself.
*/
toObject: function (data) {
if (P.U.isString(data)) {
var result = this.get(data);
return result ? result : data;
}
return data;
}
};
P.U.ex(P.Actor.prototype, {
/**
* Adds a new <i>transformation</i> to the list of transformations
* of <i>this actor</i>.
*
* <p>
* A transformation is a function or an object that has a <i>call</i> method defined.
* This function or call method should have one argument and to return a transformed version of it.
* If the returned value is {{#crossLink "ProAct.Actor/BadValue:property"}}{{/crossLink}}, the next transformations are skipped and the updating
* value/event becomes - bad value.
* </p>
*
* <p>
* Every value/event that updates <i>this actor</i> will be transformed using the new transformation.
* </p>
*
* This method uses {{#crossLink "ProAct.Actor/transform:method"}}{{/crossLink}}, but can read transformation
* funtion/object stored in the registry (if the proact-dsl module is present) by it's string name.
*
* @for ProAct.Actor
* @instance
* @method transformStored
* @protected
* @param {Object|String} transformation The transformation to add. Can be string - to be retrieved by name.
* @param {String} type The type of the transformation, for example `mapping`.
* @return {ProAct.Actor}
* <b>this</b>
*/
transformStored: function (transformation, type) {
if (P.U.isString(transformation)) {
P.DSL.run(this, type + '(' + transformation + ')', P.registry);
return this;
}
return this.transform(transformation);
}
});
P.U.ex(P.S, {
fromString: function (str, args) {
return P.registry.setup(
new ProAct.Stream(), str, args
);
}
});
P.U.ex(P.ObjectCore.prototype, {
/**
* Applies meta information and actions on already created property.
*
* This method is called by the {{#crossLink "ProAct.ObjectCore/makeProp:method"}}{{/crossLink}} one,
* other modules can inject logic by overriding it.
*
* The meta is in format of the {{#crossLink "ProAct.DSL"}}{{/crossLink}}.
*
* @for ProAct.ObjectCore
* @protected
* @instance
* @method applyMeta
* @param {String|Array} meta
* Meta information for the property to modify with.
* @param {ProAct.Property} property
* The property to update.
*/
applyMeta: function (meta, property) {
if (meta && P.registry) {
if (!P.U.isArray(meta)) {
meta = [meta];
}
if (!(meta[0] instanceof ProAct.Property)) {
P.registry.setup.apply(P.registry, [property].concat(meta));
}
}
}
});