999 lines
22 KiB
JavaScript
999 lines
22 KiB
JavaScript
|
|
import { forEach, bind, pick, assign, isString, isObject, set } from 'min-dash';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Moddle base element.
|
||
|
|
*/
|
||
|
|
function Base() { }
|
||
|
|
|
||
|
|
Base.prototype.get = function(name) {
|
||
|
|
return this.$model.properties.get(this, name);
|
||
|
|
};
|
||
|
|
|
||
|
|
Base.prototype.set = function(name, value) {
|
||
|
|
this.$model.properties.set(this, name, value);
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* A model element factory.
|
||
|
|
*
|
||
|
|
* @param {Moddle} model
|
||
|
|
* @param {Properties} properties
|
||
|
|
*/
|
||
|
|
function Factory(model, properties) {
|
||
|
|
this.model = model;
|
||
|
|
this.properties = properties;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
Factory.prototype.createType = function(descriptor) {
|
||
|
|
|
||
|
|
var model = this.model;
|
||
|
|
|
||
|
|
var props = this.properties,
|
||
|
|
prototype = Object.create(Base.prototype);
|
||
|
|
|
||
|
|
// initialize default values
|
||
|
|
forEach(descriptor.properties, function(p) {
|
||
|
|
if (!p.isMany && p.default !== undefined) {
|
||
|
|
prototype[p.name] = p.default;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
props.defineModel(prototype, model);
|
||
|
|
props.defineDescriptor(prototype, descriptor);
|
||
|
|
|
||
|
|
var name = descriptor.ns.name;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The new type constructor
|
||
|
|
*/
|
||
|
|
function ModdleElement(attrs) {
|
||
|
|
props.define(this, '$type', { value: name, enumerable: true });
|
||
|
|
props.define(this, '$attrs', { value: {} });
|
||
|
|
props.define(this, '$parent', { writable: true });
|
||
|
|
|
||
|
|
forEach(attrs, bind(function(val, key) {
|
||
|
|
this.set(key, val);
|
||
|
|
}, this));
|
||
|
|
}
|
||
|
|
|
||
|
|
ModdleElement.prototype = prototype;
|
||
|
|
|
||
|
|
ModdleElement.hasType = prototype.$instanceOf = this.model.hasType;
|
||
|
|
|
||
|
|
// static links
|
||
|
|
props.defineModel(ModdleElement, model);
|
||
|
|
props.defineDescriptor(ModdleElement, descriptor);
|
||
|
|
|
||
|
|
return ModdleElement;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Built-in moddle types
|
||
|
|
*/
|
||
|
|
var BUILTINS = {
|
||
|
|
String: true,
|
||
|
|
Boolean: true,
|
||
|
|
Integer: true,
|
||
|
|
Real: true,
|
||
|
|
Element: true
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Converters for built in types from string representations
|
||
|
|
*/
|
||
|
|
var TYPE_CONVERTERS = {
|
||
|
|
String: function(s) { return s; },
|
||
|
|
Boolean: function(s) { return s === 'true'; },
|
||
|
|
Integer: function(s) { return parseInt(s, 10); },
|
||
|
|
Real: function(s) { return parseFloat(s); }
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Convert a type to its real representation
|
||
|
|
*/
|
||
|
|
function coerceType(type, value) {
|
||
|
|
|
||
|
|
var converter = TYPE_CONVERTERS[type];
|
||
|
|
|
||
|
|
if (converter) {
|
||
|
|
return converter(value);
|
||
|
|
} else {
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Return whether the given type is built-in
|
||
|
|
*/
|
||
|
|
function isBuiltIn(type) {
|
||
|
|
return !!BUILTINS[type];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Return whether the given type is simple
|
||
|
|
*/
|
||
|
|
function isSimple(type) {
|
||
|
|
return !!TYPE_CONVERTERS[type];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Parses a namespaced attribute name of the form (ns:)localName to an object,
|
||
|
|
* given a default prefix to assume in case no explicit namespace is given.
|
||
|
|
*
|
||
|
|
* @param {String} name
|
||
|
|
* @param {String} [defaultPrefix] the default prefix to take, if none is present.
|
||
|
|
*
|
||
|
|
* @return {Object} the parsed name
|
||
|
|
*/
|
||
|
|
function parseName(name, defaultPrefix) {
|
||
|
|
var parts = name.split(/:/),
|
||
|
|
localName, prefix;
|
||
|
|
|
||
|
|
// no prefix (i.e. only local name)
|
||
|
|
if (parts.length === 1) {
|
||
|
|
localName = name;
|
||
|
|
prefix = defaultPrefix;
|
||
|
|
} else
|
||
|
|
|
||
|
|
// prefix + local name
|
||
|
|
if (parts.length === 2) {
|
||
|
|
localName = parts[1];
|
||
|
|
prefix = parts[0];
|
||
|
|
} else {
|
||
|
|
throw new Error('expected <prefix:localName> or <localName>, got ' + name);
|
||
|
|
}
|
||
|
|
|
||
|
|
name = (prefix ? prefix + ':' : '') + localName;
|
||
|
|
|
||
|
|
return {
|
||
|
|
name: name,
|
||
|
|
prefix: prefix,
|
||
|
|
localName: localName
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* A utility to build element descriptors.
|
||
|
|
*/
|
||
|
|
function DescriptorBuilder(nameNs) {
|
||
|
|
this.ns = nameNs;
|
||
|
|
this.name = nameNs.name;
|
||
|
|
this.allTypes = [];
|
||
|
|
this.allTypesByName = {};
|
||
|
|
this.properties = [];
|
||
|
|
this.propertiesByName = {};
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
DescriptorBuilder.prototype.build = function() {
|
||
|
|
return pick(this, [
|
||
|
|
'ns',
|
||
|
|
'name',
|
||
|
|
'allTypes',
|
||
|
|
'allTypesByName',
|
||
|
|
'properties',
|
||
|
|
'propertiesByName',
|
||
|
|
'bodyProperty',
|
||
|
|
'idProperty'
|
||
|
|
]);
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add property at given index.
|
||
|
|
*
|
||
|
|
* @param {Object} p
|
||
|
|
* @param {Number} [idx]
|
||
|
|
* @param {Boolean} [validate=true]
|
||
|
|
*/
|
||
|
|
DescriptorBuilder.prototype.addProperty = function(p, idx, validate) {
|
||
|
|
|
||
|
|
if (typeof idx === 'boolean') {
|
||
|
|
validate = idx;
|
||
|
|
idx = undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.addNamedProperty(p, validate !== false);
|
||
|
|
|
||
|
|
var properties = this.properties;
|
||
|
|
|
||
|
|
if (idx !== undefined) {
|
||
|
|
properties.splice(idx, 0, p);
|
||
|
|
} else {
|
||
|
|
properties.push(p);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
DescriptorBuilder.prototype.replaceProperty = function(oldProperty, newProperty, replace) {
|
||
|
|
var oldNameNs = oldProperty.ns;
|
||
|
|
|
||
|
|
var props = this.properties,
|
||
|
|
propertiesByName = this.propertiesByName,
|
||
|
|
rename = oldProperty.name !== newProperty.name;
|
||
|
|
|
||
|
|
if (oldProperty.isId) {
|
||
|
|
if (!newProperty.isId) {
|
||
|
|
throw new Error(
|
||
|
|
'property <' + newProperty.ns.name + '> must be id property ' +
|
||
|
|
'to refine <' + oldProperty.ns.name + '>');
|
||
|
|
}
|
||
|
|
|
||
|
|
this.setIdProperty(newProperty, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (oldProperty.isBody) {
|
||
|
|
|
||
|
|
if (!newProperty.isBody) {
|
||
|
|
throw new Error(
|
||
|
|
'property <' + newProperty.ns.name + '> must be body property ' +
|
||
|
|
'to refine <' + oldProperty.ns.name + '>');
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO: Check compatibility
|
||
|
|
this.setBodyProperty(newProperty, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
// validate existence and get location of old property
|
||
|
|
var idx = props.indexOf(oldProperty);
|
||
|
|
if (idx === -1) {
|
||
|
|
throw new Error('property <' + oldNameNs.name + '> not found in property list');
|
||
|
|
}
|
||
|
|
|
||
|
|
// remove old property
|
||
|
|
props.splice(idx, 1);
|
||
|
|
|
||
|
|
// replacing the named property is intentional
|
||
|
|
//
|
||
|
|
// * validate only if this is a "rename" operation
|
||
|
|
// * add at specific index unless we "replace"
|
||
|
|
//
|
||
|
|
this.addProperty(newProperty, replace ? undefined : idx, rename);
|
||
|
|
|
||
|
|
// make new property available under old name
|
||
|
|
propertiesByName[oldNameNs.name] = propertiesByName[oldNameNs.localName] = newProperty;
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
DescriptorBuilder.prototype.redefineProperty = function(p, targetPropertyName, replace) {
|
||
|
|
|
||
|
|
var nsPrefix = p.ns.prefix;
|
||
|
|
var parts = targetPropertyName.split('#');
|
||
|
|
|
||
|
|
var name = parseName(parts[0], nsPrefix);
|
||
|
|
var attrName = parseName(parts[1], name.prefix).name;
|
||
|
|
|
||
|
|
var redefinedProperty = this.propertiesByName[attrName];
|
||
|
|
if (!redefinedProperty) {
|
||
|
|
throw new Error('refined property <' + attrName + '> not found');
|
||
|
|
} else {
|
||
|
|
this.replaceProperty(redefinedProperty, p, replace);
|
||
|
|
}
|
||
|
|
|
||
|
|
delete p.redefines;
|
||
|
|
};
|
||
|
|
|
||
|
|
DescriptorBuilder.prototype.addNamedProperty = function(p, validate) {
|
||
|
|
var ns = p.ns,
|
||
|
|
propsByName = this.propertiesByName;
|
||
|
|
|
||
|
|
if (validate) {
|
||
|
|
this.assertNotDefined(p, ns.name);
|
||
|
|
this.assertNotDefined(p, ns.localName);
|
||
|
|
}
|
||
|
|
|
||
|
|
propsByName[ns.name] = propsByName[ns.localName] = p;
|
||
|
|
};
|
||
|
|
|
||
|
|
DescriptorBuilder.prototype.removeNamedProperty = function(p) {
|
||
|
|
var ns = p.ns,
|
||
|
|
propsByName = this.propertiesByName;
|
||
|
|
|
||
|
|
delete propsByName[ns.name];
|
||
|
|
delete propsByName[ns.localName];
|
||
|
|
};
|
||
|
|
|
||
|
|
DescriptorBuilder.prototype.setBodyProperty = function(p, validate) {
|
||
|
|
|
||
|
|
if (validate && this.bodyProperty) {
|
||
|
|
throw new Error(
|
||
|
|
'body property defined multiple times ' +
|
||
|
|
'(<' + this.bodyProperty.ns.name + '>, <' + p.ns.name + '>)');
|
||
|
|
}
|
||
|
|
|
||
|
|
this.bodyProperty = p;
|
||
|
|
};
|
||
|
|
|
||
|
|
DescriptorBuilder.prototype.setIdProperty = function(p, validate) {
|
||
|
|
|
||
|
|
if (validate && this.idProperty) {
|
||
|
|
throw new Error(
|
||
|
|
'id property defined multiple times ' +
|
||
|
|
'(<' + this.idProperty.ns.name + '>, <' + p.ns.name + '>)');
|
||
|
|
}
|
||
|
|
|
||
|
|
this.idProperty = p;
|
||
|
|
};
|
||
|
|
|
||
|
|
DescriptorBuilder.prototype.assertNotTrait = function(typeDescriptor) {
|
||
|
|
|
||
|
|
const _extends = typeDescriptor.extends || [];
|
||
|
|
|
||
|
|
if (_extends.length) {
|
||
|
|
throw new Error(
|
||
|
|
`cannot create <${ typeDescriptor.name }> extending <${ typeDescriptor.extends }>`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
DescriptorBuilder.prototype.assertNotDefined = function(p, name) {
|
||
|
|
var propertyName = p.name,
|
||
|
|
definedProperty = this.propertiesByName[propertyName];
|
||
|
|
|
||
|
|
if (definedProperty) {
|
||
|
|
throw new Error(
|
||
|
|
'property <' + propertyName + '> already defined; ' +
|
||
|
|
'override of <' + definedProperty.definedBy.ns.name + '#' + definedProperty.ns.name + '> by ' +
|
||
|
|
'<' + p.definedBy.ns.name + '#' + p.ns.name + '> not allowed without redefines');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
DescriptorBuilder.prototype.hasProperty = function(name) {
|
||
|
|
return this.propertiesByName[name];
|
||
|
|
};
|
||
|
|
|
||
|
|
DescriptorBuilder.prototype.addTrait = function(t, inherited) {
|
||
|
|
|
||
|
|
if (inherited) {
|
||
|
|
this.assertNotTrait(t);
|
||
|
|
}
|
||
|
|
|
||
|
|
var typesByName = this.allTypesByName,
|
||
|
|
types = this.allTypes;
|
||
|
|
|
||
|
|
var typeName = t.name;
|
||
|
|
|
||
|
|
if (typeName in typesByName) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
forEach(t.properties, bind(function(p) {
|
||
|
|
|
||
|
|
// clone property to allow extensions
|
||
|
|
p = assign({}, p, {
|
||
|
|
name: p.ns.localName,
|
||
|
|
inherited: inherited
|
||
|
|
});
|
||
|
|
|
||
|
|
Object.defineProperty(p, 'definedBy', {
|
||
|
|
value: t
|
||
|
|
});
|
||
|
|
|
||
|
|
var replaces = p.replaces,
|
||
|
|
redefines = p.redefines;
|
||
|
|
|
||
|
|
// add replace/redefine support
|
||
|
|
if (replaces || redefines) {
|
||
|
|
this.redefineProperty(p, replaces || redefines, replaces);
|
||
|
|
} else {
|
||
|
|
if (p.isBody) {
|
||
|
|
this.setBodyProperty(p);
|
||
|
|
}
|
||
|
|
if (p.isId) {
|
||
|
|
this.setIdProperty(p);
|
||
|
|
}
|
||
|
|
this.addProperty(p);
|
||
|
|
}
|
||
|
|
}, this));
|
||
|
|
|
||
|
|
types.push(t);
|
||
|
|
typesByName[typeName] = t;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* A registry of Moddle packages.
|
||
|
|
*
|
||
|
|
* @param {Array<Package>} packages
|
||
|
|
* @param {Properties} properties
|
||
|
|
*/
|
||
|
|
function Registry(packages, properties) {
|
||
|
|
this.packageMap = {};
|
||
|
|
this.typeMap = {};
|
||
|
|
|
||
|
|
this.packages = [];
|
||
|
|
|
||
|
|
this.properties = properties;
|
||
|
|
|
||
|
|
forEach(packages, bind(this.registerPackage, this));
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
Registry.prototype.getPackage = function(uriOrPrefix) {
|
||
|
|
return this.packageMap[uriOrPrefix];
|
||
|
|
};
|
||
|
|
|
||
|
|
Registry.prototype.getPackages = function() {
|
||
|
|
return this.packages;
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
Registry.prototype.registerPackage = function(pkg) {
|
||
|
|
|
||
|
|
// copy package
|
||
|
|
pkg = assign({}, pkg);
|
||
|
|
|
||
|
|
var pkgMap = this.packageMap;
|
||
|
|
|
||
|
|
ensureAvailable(pkgMap, pkg, 'prefix');
|
||
|
|
ensureAvailable(pkgMap, pkg, 'uri');
|
||
|
|
|
||
|
|
// register types
|
||
|
|
forEach(pkg.types, bind(function(descriptor) {
|
||
|
|
this.registerType(descriptor, pkg);
|
||
|
|
}, this));
|
||
|
|
|
||
|
|
pkgMap[pkg.uri] = pkgMap[pkg.prefix] = pkg;
|
||
|
|
this.packages.push(pkg);
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Register a type from a specific package with us
|
||
|
|
*/
|
||
|
|
Registry.prototype.registerType = function(type, pkg) {
|
||
|
|
|
||
|
|
type = assign({}, type, {
|
||
|
|
superClass: (type.superClass || []).slice(),
|
||
|
|
extends: (type.extends || []).slice(),
|
||
|
|
properties: (type.properties || []).slice(),
|
||
|
|
meta: assign((type.meta || {}))
|
||
|
|
});
|
||
|
|
|
||
|
|
var ns = parseName(type.name, pkg.prefix),
|
||
|
|
name = ns.name,
|
||
|
|
propertiesByName = {};
|
||
|
|
|
||
|
|
// parse properties
|
||
|
|
forEach(type.properties, bind(function(p) {
|
||
|
|
|
||
|
|
// namespace property names
|
||
|
|
var propertyNs = parseName(p.name, ns.prefix),
|
||
|
|
propertyName = propertyNs.name;
|
||
|
|
|
||
|
|
// namespace property types
|
||
|
|
if (!isBuiltIn(p.type)) {
|
||
|
|
p.type = parseName(p.type, propertyNs.prefix).name;
|
||
|
|
}
|
||
|
|
|
||
|
|
assign(p, {
|
||
|
|
ns: propertyNs,
|
||
|
|
name: propertyName
|
||
|
|
});
|
||
|
|
|
||
|
|
propertiesByName[propertyName] = p;
|
||
|
|
}, this));
|
||
|
|
|
||
|
|
// update ns + name
|
||
|
|
assign(type, {
|
||
|
|
ns: ns,
|
||
|
|
name: name,
|
||
|
|
propertiesByName: propertiesByName
|
||
|
|
});
|
||
|
|
|
||
|
|
forEach(type.extends, bind(function(extendsName) {
|
||
|
|
var extendsNameNs = parseName(extendsName, ns.prefix);
|
||
|
|
|
||
|
|
var extended = this.typeMap[extendsNameNs.name];
|
||
|
|
|
||
|
|
extended.traits = extended.traits || [];
|
||
|
|
extended.traits.push(name);
|
||
|
|
}, this));
|
||
|
|
|
||
|
|
// link to package
|
||
|
|
this.definePackage(type, pkg);
|
||
|
|
|
||
|
|
// register
|
||
|
|
this.typeMap[name] = type;
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Traverse the type hierarchy from bottom to top,
|
||
|
|
* calling iterator with (type, inherited) for all elements in
|
||
|
|
* the inheritance chain.
|
||
|
|
*
|
||
|
|
* @param {Object} nsName
|
||
|
|
* @param {Function} iterator
|
||
|
|
* @param {Boolean} [trait=false]
|
||
|
|
*/
|
||
|
|
Registry.prototype.mapTypes = function(nsName, iterator, trait) {
|
||
|
|
|
||
|
|
var type = isBuiltIn(nsName.name) ? { name: nsName.name } : this.typeMap[nsName.name];
|
||
|
|
|
||
|
|
var self = this;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Traverse the selected super type or trait
|
||
|
|
*
|
||
|
|
* @param {String} cls
|
||
|
|
* @param {Boolean} [trait=false]
|
||
|
|
*/
|
||
|
|
function traverse(cls, trait) {
|
||
|
|
var parentNs = parseName(cls, isBuiltIn(cls) ? '' : nsName.prefix);
|
||
|
|
self.mapTypes(parentNs, iterator, trait);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Traverse the selected trait.
|
||
|
|
*
|
||
|
|
* @param {String} cls
|
||
|
|
*/
|
||
|
|
function traverseTrait(cls) {
|
||
|
|
return traverse(cls, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Traverse the selected super type
|
||
|
|
*
|
||
|
|
* @param {String} cls
|
||
|
|
*/
|
||
|
|
function traverseSuper(cls) {
|
||
|
|
return traverse(cls, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!type) {
|
||
|
|
throw new Error('unknown type <' + nsName.name + '>');
|
||
|
|
}
|
||
|
|
|
||
|
|
forEach(type.superClass, trait ? traverseTrait : traverseSuper);
|
||
|
|
|
||
|
|
// call iterator with (type, inherited=!trait)
|
||
|
|
iterator(type, !trait);
|
||
|
|
|
||
|
|
forEach(type.traits, traverseTrait);
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns the effective descriptor for a type.
|
||
|
|
*
|
||
|
|
* @param {String} type the namespaced name (ns:localName) of the type
|
||
|
|
*
|
||
|
|
* @return {Descriptor} the resulting effective descriptor
|
||
|
|
*/
|
||
|
|
Registry.prototype.getEffectiveDescriptor = function(name) {
|
||
|
|
|
||
|
|
var nsName = parseName(name);
|
||
|
|
|
||
|
|
var builder = new DescriptorBuilder(nsName);
|
||
|
|
|
||
|
|
this.mapTypes(nsName, function(type, inherited) {
|
||
|
|
builder.addTrait(type, inherited);
|
||
|
|
});
|
||
|
|
|
||
|
|
var descriptor = builder.build();
|
||
|
|
|
||
|
|
// define package link
|
||
|
|
this.definePackage(descriptor, descriptor.allTypes[descriptor.allTypes.length - 1].$pkg);
|
||
|
|
|
||
|
|
return descriptor;
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
Registry.prototype.definePackage = function(target, pkg) {
|
||
|
|
this.properties.define(target, '$pkg', { value: pkg });
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// helpers ////////////////////////////
|
||
|
|
|
||
|
|
function ensureAvailable(packageMap, pkg, identifierKey) {
|
||
|
|
|
||
|
|
var value = pkg[identifierKey];
|
||
|
|
|
||
|
|
if (value in packageMap) {
|
||
|
|
throw new Error('package with ' + identifierKey + ' <' + value + '> already defined');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* A utility that gets and sets properties of model elements.
|
||
|
|
*
|
||
|
|
* @param {Model} model
|
||
|
|
*/
|
||
|
|
function Properties(model) {
|
||
|
|
this.model = model;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Sets a named property on the target element.
|
||
|
|
* If the value is undefined, the property gets deleted.
|
||
|
|
*
|
||
|
|
* @param {Object} target
|
||
|
|
* @param {String} name
|
||
|
|
* @param {Object} value
|
||
|
|
*/
|
||
|
|
Properties.prototype.set = function(target, name, value) {
|
||
|
|
|
||
|
|
if (!isString(name) || !name.length) {
|
||
|
|
throw new TypeError('property name must be a non-empty string');
|
||
|
|
}
|
||
|
|
|
||
|
|
var property = this.getProperty(target, name);
|
||
|
|
|
||
|
|
var propertyName = property && property.name;
|
||
|
|
|
||
|
|
if (isUndefined(value)) {
|
||
|
|
|
||
|
|
// unset the property, if the specified value is undefined;
|
||
|
|
// delete from $attrs (for extensions) or the target itself
|
||
|
|
if (property) {
|
||
|
|
delete target[propertyName];
|
||
|
|
} else {
|
||
|
|
delete target.$attrs[stripGlobal(name)];
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
|
||
|
|
// set the property, defining well defined properties on the fly
|
||
|
|
// or simply updating them in target.$attrs (for extensions)
|
||
|
|
if (property) {
|
||
|
|
if (propertyName in target) {
|
||
|
|
target[propertyName] = value;
|
||
|
|
} else {
|
||
|
|
defineProperty(target, property, value);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
target.$attrs[stripGlobal(name)] = value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns the named property of the given element
|
||
|
|
*
|
||
|
|
* @param {Object} target
|
||
|
|
* @param {String} name
|
||
|
|
*
|
||
|
|
* @return {Object}
|
||
|
|
*/
|
||
|
|
Properties.prototype.get = function(target, name) {
|
||
|
|
|
||
|
|
var property = this.getProperty(target, name);
|
||
|
|
|
||
|
|
if (!property) {
|
||
|
|
return target.$attrs[stripGlobal(name)];
|
||
|
|
}
|
||
|
|
|
||
|
|
var propertyName = property.name;
|
||
|
|
|
||
|
|
// check if access to collection property and lazily initialize it
|
||
|
|
if (!target[propertyName] && property.isMany) {
|
||
|
|
defineProperty(target, property, []);
|
||
|
|
}
|
||
|
|
|
||
|
|
return target[propertyName];
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Define a property on the target element
|
||
|
|
*
|
||
|
|
* @param {Object} target
|
||
|
|
* @param {String} name
|
||
|
|
* @param {Object} options
|
||
|
|
*/
|
||
|
|
Properties.prototype.define = function(target, name, options) {
|
||
|
|
|
||
|
|
if (!options.writable) {
|
||
|
|
|
||
|
|
var value = options.value;
|
||
|
|
|
||
|
|
// use getters for read-only variables to support ES6 proxies
|
||
|
|
// cf. https://github.com/bpmn-io/internal-docs/issues/386
|
||
|
|
options = assign({}, options, {
|
||
|
|
get: function() { return value; }
|
||
|
|
});
|
||
|
|
|
||
|
|
delete options.value;
|
||
|
|
}
|
||
|
|
|
||
|
|
Object.defineProperty(target, name, options);
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Define the descriptor for an element
|
||
|
|
*/
|
||
|
|
Properties.prototype.defineDescriptor = function(target, descriptor) {
|
||
|
|
this.define(target, '$descriptor', { value: descriptor });
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Define the model for an element
|
||
|
|
*/
|
||
|
|
Properties.prototype.defineModel = function(target, model) {
|
||
|
|
this.define(target, '$model', { value: model });
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Return property with the given name on the element.
|
||
|
|
*
|
||
|
|
* @param {any} target
|
||
|
|
* @param {string} name
|
||
|
|
*
|
||
|
|
* @return {object | null} property
|
||
|
|
*/
|
||
|
|
Properties.prototype.getProperty = function(target, name) {
|
||
|
|
|
||
|
|
var model = this.model;
|
||
|
|
|
||
|
|
var property = model.getPropertyDescriptor(target, name);
|
||
|
|
|
||
|
|
if (property) {
|
||
|
|
return property;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (name.includes(':')) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const strict = model.config.strict;
|
||
|
|
|
||
|
|
if (typeof strict !== 'undefined') {
|
||
|
|
const error = new TypeError(`unknown property <${ name }> on <${ target.$type }>`);
|
||
|
|
|
||
|
|
if (strict) {
|
||
|
|
throw error;
|
||
|
|
} else {
|
||
|
|
|
||
|
|
// eslint-disable-next-line no-undef
|
||
|
|
typeof console !== 'undefined' && console.warn(error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
};
|
||
|
|
|
||
|
|
function isUndefined(val) {
|
||
|
|
return typeof val === 'undefined';
|
||
|
|
}
|
||
|
|
|
||
|
|
function defineProperty(target, property, value) {
|
||
|
|
Object.defineProperty(target, property.name, {
|
||
|
|
enumerable: !property.isReference,
|
||
|
|
writable: true,
|
||
|
|
value: value,
|
||
|
|
configurable: true
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function stripGlobal(name) {
|
||
|
|
return name.replace(/^:/, '');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Moddle implementation /////////////////////////////////////////////////
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @class Moddle
|
||
|
|
*
|
||
|
|
* A model that can be used to create elements of a specific type.
|
||
|
|
*
|
||
|
|
* @example
|
||
|
|
*
|
||
|
|
* var Moddle = require('moddle');
|
||
|
|
*
|
||
|
|
* var pkg = {
|
||
|
|
* name: 'mypackage',
|
||
|
|
* prefix: 'my',
|
||
|
|
* types: [
|
||
|
|
* { name: 'Root' }
|
||
|
|
* ]
|
||
|
|
* };
|
||
|
|
*
|
||
|
|
* var moddle = new Moddle([pkg]);
|
||
|
|
*
|
||
|
|
* @param {Array<Package>} packages the packages to contain
|
||
|
|
*
|
||
|
|
* @param { { strict?: boolean } } [config] moddle configuration
|
||
|
|
*/
|
||
|
|
function Moddle(packages, config = {}) {
|
||
|
|
|
||
|
|
this.properties = new Properties(this);
|
||
|
|
|
||
|
|
this.factory = new Factory(this, this.properties);
|
||
|
|
this.registry = new Registry(packages, this.properties);
|
||
|
|
|
||
|
|
this.typeCache = {};
|
||
|
|
|
||
|
|
this.config = config;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create an instance of the specified type.
|
||
|
|
*
|
||
|
|
* @method Moddle#create
|
||
|
|
*
|
||
|
|
* @example
|
||
|
|
*
|
||
|
|
* var foo = moddle.create('my:Foo');
|
||
|
|
* var bar = moddle.create('my:Bar', { id: 'BAR_1' });
|
||
|
|
*
|
||
|
|
* @param {String|Object} descriptor the type descriptor or name know to the model
|
||
|
|
* @param {Object} attrs a number of attributes to initialize the model instance with
|
||
|
|
* @return {Object} model instance
|
||
|
|
*/
|
||
|
|
Moddle.prototype.create = function(descriptor, attrs) {
|
||
|
|
var Type = this.getType(descriptor);
|
||
|
|
|
||
|
|
if (!Type) {
|
||
|
|
throw new Error('unknown type <' + descriptor + '>');
|
||
|
|
}
|
||
|
|
|
||
|
|
return new Type(attrs);
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns the type representing a given descriptor
|
||
|
|
*
|
||
|
|
* @method Moddle#getType
|
||
|
|
*
|
||
|
|
* @example
|
||
|
|
*
|
||
|
|
* var Foo = moddle.getType('my:Foo');
|
||
|
|
* var foo = new Foo({ 'id' : 'FOO_1' });
|
||
|
|
*
|
||
|
|
* @param {String|Object} descriptor the type descriptor or name know to the model
|
||
|
|
* @return {Object} the type representing the descriptor
|
||
|
|
*/
|
||
|
|
Moddle.prototype.getType = function(descriptor) {
|
||
|
|
|
||
|
|
var cache = this.typeCache;
|
||
|
|
|
||
|
|
var name = isString(descriptor) ? descriptor : descriptor.ns.name;
|
||
|
|
|
||
|
|
var type = cache[name];
|
||
|
|
|
||
|
|
if (!type) {
|
||
|
|
descriptor = this.registry.getEffectiveDescriptor(name);
|
||
|
|
type = cache[name] = this.factory.createType(descriptor);
|
||
|
|
}
|
||
|
|
|
||
|
|
return type;
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Creates an any-element type to be used within model instances.
|
||
|
|
*
|
||
|
|
* This can be used to create custom elements that lie outside the meta-model.
|
||
|
|
* The created element contains all the meta-data required to serialize it
|
||
|
|
* as part of meta-model elements.
|
||
|
|
*
|
||
|
|
* @method Moddle#createAny
|
||
|
|
*
|
||
|
|
* @example
|
||
|
|
*
|
||
|
|
* var foo = moddle.createAny('vendor:Foo', 'http://vendor', {
|
||
|
|
* value: 'bar'
|
||
|
|
* });
|
||
|
|
*
|
||
|
|
* var container = moddle.create('my:Container', 'http://my', {
|
||
|
|
* any: [ foo ]
|
||
|
|
* });
|
||
|
|
*
|
||
|
|
* // go ahead and serialize the stuff
|
||
|
|
*
|
||
|
|
*
|
||
|
|
* @param {String} name the name of the element
|
||
|
|
* @param {String} nsUri the namespace uri of the element
|
||
|
|
* @param {Object} [properties] a map of properties to initialize the instance with
|
||
|
|
* @return {Object} the any type instance
|
||
|
|
*/
|
||
|
|
Moddle.prototype.createAny = function(name, nsUri, properties) {
|
||
|
|
|
||
|
|
var nameNs = parseName(name);
|
||
|
|
|
||
|
|
var element = {
|
||
|
|
$type: name,
|
||
|
|
$instanceOf: function(type) {
|
||
|
|
return type === this.$type;
|
||
|
|
},
|
||
|
|
get: function(key) {
|
||
|
|
return this[key];
|
||
|
|
},
|
||
|
|
set: function(key, value) {
|
||
|
|
set(this, [ key ], value);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
var descriptor = {
|
||
|
|
name: name,
|
||
|
|
isGeneric: true,
|
||
|
|
ns: {
|
||
|
|
prefix: nameNs.prefix,
|
||
|
|
localName: nameNs.localName,
|
||
|
|
uri: nsUri
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
this.properties.defineDescriptor(element, descriptor);
|
||
|
|
this.properties.defineModel(element, this);
|
||
|
|
this.properties.define(element, 'get', { enumerable: false, writable: true });
|
||
|
|
this.properties.define(element, 'set', { enumerable: false, writable: true });
|
||
|
|
this.properties.define(element, '$parent', { enumerable: false, writable: true });
|
||
|
|
this.properties.define(element, '$instanceOf', { enumerable: false, writable: true });
|
||
|
|
|
||
|
|
forEach(properties, function(a, key) {
|
||
|
|
if (isObject(a) && a.value !== undefined) {
|
||
|
|
element[a.name] = a.value;
|
||
|
|
} else {
|
||
|
|
element[key] = a;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return element;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a registered package by uri or prefix
|
||
|
|
*
|
||
|
|
* @return {Object} the package
|
||
|
|
*/
|
||
|
|
Moddle.prototype.getPackage = function(uriOrPrefix) {
|
||
|
|
return this.registry.getPackage(uriOrPrefix);
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a snapshot of all known packages
|
||
|
|
*
|
||
|
|
* @return {Object} the package
|
||
|
|
*/
|
||
|
|
Moddle.prototype.getPackages = function() {
|
||
|
|
return this.registry.getPackages();
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns the descriptor for an element
|
||
|
|
*/
|
||
|
|
Moddle.prototype.getElementDescriptor = function(element) {
|
||
|
|
return element.$descriptor;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns true if the given descriptor or instance
|
||
|
|
* represents the given type.
|
||
|
|
*
|
||
|
|
* May be applied to this, if element is omitted.
|
||
|
|
*/
|
||
|
|
Moddle.prototype.hasType = function(element, type) {
|
||
|
|
if (type === undefined) {
|
||
|
|
type = element;
|
||
|
|
element = this;
|
||
|
|
}
|
||
|
|
|
||
|
|
var descriptor = element.$model.getElementDescriptor(element);
|
||
|
|
|
||
|
|
return (type in descriptor.allTypesByName);
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns the descriptor of an elements named property
|
||
|
|
*/
|
||
|
|
Moddle.prototype.getPropertyDescriptor = function(element, property) {
|
||
|
|
return this.getElementDescriptor(element).propertiesByName[property];
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a mapped type's descriptor
|
||
|
|
*/
|
||
|
|
Moddle.prototype.getTypeDescriptor = function(type) {
|
||
|
|
return this.registry.typeMap[type];
|
||
|
|
};
|
||
|
|
|
||
|
|
export { Moddle, coerceType, isBuiltIn as isBuiltInType, isSimple as isSimpleType, parseName as parseNameNS };
|
||
|
|
//# sourceMappingURL=index.esm.js.map
|