/**
* @author Wolfgang Kowarschick <kowa@hs-augsburg.de>
* @copyright 2017-2018
* @license CC-BY-NC-SA-4.0
*/
import {concretize} from './concretize';
/** @private */
const
C_EMPTY = Object.freeze({}),
C_EMPTY_CONFIG_NAME = Symbol(),
c_cache = new Map(),
// private members
v_config = Symbol('v_config'),
v_environment = Symbol('v_environment'),
v_default = Symbol('v_default');
/**
* A class for storing configuration data. It does not define
* any attribute. A <code>Config</code> object, however,
* yields a (readonly) result for every attribute name
* you pass to it. In all cases the result is a <code>Config</code> object,
* which is initialized by the appropriate subobject of <code>p_config</code>.
* By default <code>{@link Config.EMPTY}</code> is returned.
*
* @class
*/
class Config
{ /**
* @param {object} p_config
* A configuration template object (a JSON object!).
* As a template object it may contain @-values that
* are replaced by concrete values by means of the
* function <code>{@link module:wk/config/concretize}</code>.
* @param {object} p_environment
* An environment object that is used for computing
* a concrete value. It should contain concrete values
* for @-values that are referenced by <code>p_config</code>.
* For example:
*<pre>
*{ '@width': 500,
* '@height': 400
*}
*</pre>
*/
constructor(p_config, p_environment = {})
{ const
c_config_string = JSON.stringify(p_config),
c_config = c_cache.get(c_config_string);
if (c_config != null)
return c_config;
c_cache.set(c_config_string, this);
this[v_config] = p_config===C_EMPTY ? p_config : JSON.parse(c_config_string);
this[v_environment] = p_environment;
if (p_config !== C_EMPTY && p_environment[C_EMPTY_CONFIG_NAME] == null)
p_environment[C_EMPTY_CONFIG_NAME] = new Config(C_EMPTY, p_environment);
this[v_default] = false;
return new Proxy
( this,
{ get(p_this, p_name)
{ let l_member = p_this[p_name];
if (typeof l_member === 'function')
return l_member;
if (l_member == null)
{ const c_config = p_this[v_config][p_name];
if (c_config == null)
{ return p_environment[C_EMPTY_CONFIG_NAME]; }
l_member = p_this[p_name]
= new Config(c_config, p_this[v_environment]);
}
return l_member;
}
}
);
}
/**
* The environment to be used for <code>this.config</code>.
* As all sub objects of a configuration object share the same
* environment, it suffices to change the content of the environment
* one of those objects to change the environment of all related
* config objects.
*
* @return {Object}
*/
environment()
{ return this[v_environment]; }
/**
* The original configuration object
* @return {Object}
*/
config()
{ return this[v_config]; }
/**
* @param {Object} p_config
* @param {Object} [p_config.environment = this.environment()]
* @param {?Array.<String>} p_config.specials
* @param {?number} p_config.level
*
* @return {Object} The concretized value of <code>this.config()</code>
*/
value({environment: p_environment = this.environment(),
specials: p_specials = null,
level: p_level = null
} = {}
)
{ return this.isUndefined()
? null
: concretize({config: this[v_config],
environment: p_environment,
specials: p_specials,
level: p_level
});
}
/**
* Returns <code>true</code>, if <code>this.config</code>
* has been defined.
* @return {Boolean}
*/
isDefined()
{ return this[v_config] !== C_EMPTY; }
/**
* Returns <code>true</code>, if <code>this.config</code>
* has not been defined.
* @return {Boolean}
*/
isUndefined()
{ return this[v_config] === C_EMPTY; }
/**
* Sets default values for JSON attributes which are not found in
* the configuration object itself.
*
* @param {object} p_default
*/
default(p_default) // deep prototype mixin should be used
{ Object.setPrototypeOf
( this[v_config],
{...Object.getPrototypeOf(this[v_config]), ...p_default}
);
}
/**
* An empty config Object which can be used as a default value.
* @type {Config}
*/
static get EMPTY()
{ return C_EMPTY_CONFIG; }
}
const C_EMPTY_CONFIG = new Config(C_EMPTY);
export {Config};
export default Config;