Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions lib/descriptor-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,20 @@ DescriptorBuilder.prototype.addNamedProperty = function(p, validate) {
propsByName = this.propertiesByName;

if (validate) {
this.assertNotDefined(p, ns.name);
this.assertNotDefined(p, ns.localName);
this.assertNotDefined(p);
}

propsByName[ns.name] = propsByName[ns.localName] = p;
if (!propsByName[ns.name]) {
propsByName[ns.name] = p;

if (ns.prefix === this.ns.prefix || !propsByName[ns.localName]) {

// property defined in local ns, also add with localName
propsByName[ns.localName] = p;
}
}
else if (!propsByName[ns.localName])
propsByName[ns.localName] = p;
};

DescriptorBuilder.prototype.removeNamedProperty = function(p) {
Expand Down Expand Up @@ -172,15 +181,23 @@ DescriptorBuilder.prototype.setIdProperty = function(p, validate) {
this.idProperty = p;
};

DescriptorBuilder.prototype.assertNotDefined = function(p, name) {
var propertyName = p.name,
definedProperty = this.propertiesByName[propertyName];
DescriptorBuilder.prototype.assertNotDefined = function(p) {
var ns = p.ns,
localName = ns.localName,
nsName = ns.name,
propByName = this.propertiesByName;

// redefining properties inside the same namespace without 'redefines' or
// 'replaces' is not allowed.
if (propByName[nsName] && propByName[localName]) {
var definedProperty = propByName[localName],
prevDefinedName = definedProperty.definedBy.ns.name + '#' + localName,
curDefinedName = p.definedBy.ns.name + '#' + localName;

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');
'property <' + localName + '> already defined in namespace <' +
ns.prefix + '>; override of <' + prevDefinedName + '> by ' +
'<' + curDefinedName + '> not allowed without redefines or replaces');
}
};

Expand Down Expand Up @@ -230,4 +247,4 @@ DescriptorBuilder.prototype.addTrait = function(t, inherited) {

types.push(t);
typesByName[typeName] = t;
};
};
14 changes: 12 additions & 2 deletions lib/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,17 @@ Factory.prototype.createType = function(descriptor) {
// initialize default values
forEach(descriptor.properties, function(p) {
if (!p.isMany && p.default !== undefined) {
prototype[p.name] = p.default;
var name;

if (p.ns.prefix === descriptor.ns.prefix) {

// use local name if property already in local ns
name = p.name;
Copy link
Member

@nikku nikku May 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting question here: What I'd expect is to have local name and ns name attribute present.

I.e. I can always access the value via foo["a:id"] regardless if it has a local name set or not. On the other hand I'd like to have a#id always available as the shorthand, if it is the only id property defined for an element.

Likely this is only possible to accomplish via proxies, am I right about that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting question here: What I'd expect is to have local name and ns name attribute present.

They are present, but ...

I.e. I can always access the value via foo["a:id"] regardless if it has a local name set or not. On the other hand I'd like to have a#id always available as the shorthand, if it is the only id property defined for an element.

Likely this is only possible to accomplish via proxies, am I right about that?

For isMany-properties this is possible without a proxy, as we reference an object. But fundamental types (i.e. BUILTINS) are only accessible through get() when referenced with their localName. a#id on the other hand is always available through a.id (and a.get('id')of course), as long it was previously initialized (see my remarks below) - so yes, you're right.

IMO this is a compromise that we should accept. As you say, the alternative would be the introduction of proxy objects.

I hope we understand each other (please don't hesitate to keep raising questions). The test cases should hold the truth to specify desired behaviour. So we need to make sure to get them right and fully cover this topic as well as update the documentation before merging.

Btw, the current implementation also fails to access foo['a:id'] if not initialized during creation or has a default value or was previously accessed with get(). I've not tested this out yet, but this seems to be the case for all properties except foo.id. I.e. you need to use get() first before foo['a:id'] is mapped into the object.

This is somehow inconsistent and it might be a nice improvement to map property instances early (i.e. during type creation) even if they are not explicitly initialized or have a default value. I'd more like to get undefined instead of an unexpected exception. If you like, I'll open another issue for this.

One question in this context: should it be possible to define a default value for isMany-properties (e.g. "default": [ 1, 2 ])? I tried this, received no exception but the value was not applied. Maybe I miss it, but there seems to be no documentation or spec for this. Should I address this in a seperate issue or am I just wrong here?

}
else {
name = p.ns.name;
}
prototype[name] = p.default;
}
});

Expand Down Expand Up @@ -58,4 +68,4 @@ Factory.prototype.createType = function(descriptor) {
props.defineDescriptor(ModdleElement, descriptor);

return ModdleElement;
};
};
40 changes: 29 additions & 11 deletions lib/properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export default function Properties(model) {
Properties.prototype.set = function(target, name, value) {

var property = this.model.getPropertyDescriptor(target, name);

var propertyName = property && property.name;
var propertyName = getPropertyName(target, name, property);

if (isUndefined(value)) {

// unset the property, if the specified value is undefined;
// delete from $attrs (for extensions) or the target itself
if (property) {
Expand All @@ -31,20 +31,22 @@ Properties.prototype.set = function(target, name, value) {
delete target.$attrs[name];
}
} else {
// set the property, defining well defined properties on the fly
// or simply updating them in target.$attrs (for extensions)

// 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);
defineProperty(target, propertyName, property, value);
}
} else {
target.$attrs[name] = value;
}
}
};


/**
* Returns the named property of the given element
*
Expand All @@ -56,16 +58,15 @@ Properties.prototype.set = function(target, name, value) {
Properties.prototype.get = function(target, name) {

var property = this.model.getPropertyDescriptor(target, name);
var propertyName = getPropertyName(target, name, property);

if (!property) {
return target.$attrs[name];
}

var propertyName = property.name;

// check if access to collection property and lazily initialize it
if (!target[propertyName] && property.isMany) {
defineProperty(target, property, []);
defineProperty(target, propertyName, property, []);
}

return target[propertyName];
Expand Down Expand Up @@ -103,11 +104,28 @@ function isUndefined(val) {
return typeof val === 'undefined';
}

function defineProperty(target, property, value) {
Object.defineProperty(target, property.name, {

function getPropertyName(target, name, descriptor) {
if (!target || !name || !descriptor)
return undefined;

var propertyName = descriptor.name;

if (name.includes(':')) {
var parts = name.split(':');

if (parts[0] !== target.$descriptor.ns.prefix)
propertyName = descriptor.ns.name;
}

return propertyName;
}

function defineProperty(target, name, property, value) {
Object.defineProperty(target, name, {
enumerable: !property.isReference,
writable: true,
value: value,
configurable: true
});
}
}
7 changes: 5 additions & 2 deletions test/fixtures/model/datatype.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
"name": "Root",
"properties": [
{ "name": "bounds", "serialize": "xsi:type", "type": "Rect" },
{ "name": "otherBounds", "type": "do:Rect", "serialize": "xsi:type", "isMany": true }
{ "name": "otherBounds", "type": "do:Rect", "serialize": "xsi:type", "isMany": true },
{
"name": "defaultSingle", "type": "Boolean", "default": true
}
]
},
{
Expand All @@ -17,4 +20,4 @@
]
}
]
}
}
53 changes: 53 additions & 0 deletions test/fixtures/model/multiple-inherited-properties.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "MultipleInheritance",
"uri": "http://multipleinheritance",
"prefix": "mh",
"types": [
{
"name": "AnotherRoot",
"properties": [
{
"name": "any",
"type": "AnotherRoot",
"isMany": true
},
{
"name": "many",
"type": "String",
"isMany": true
},
{
"name": "single",
"type": "String"
},
{
"name": "defaultSingle",
"type": "String",
"default": "mh-default-string"
}
]
},
{
"name": "MultipleInherited",
"superClass": [ "AnotherRoot", "props:Root", "dt:Root" ],
"properties": [
{
"name": "anotherDefaultSingle",
"type": "Boolean",
"default": false
}
]
},
{
"name": "InvalidMultipleInherited",
"superClass": [ "AnotherRoot", "props:Root" ],
"properties": [
{
"name": "defaultSingle",
"type": "Boolean",
"default": false
}
]
}
]
}
28 changes: 27 additions & 1 deletion test/fixtures/model/properties.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@
{ "name": "idNumeric", "type": "Integer", "isAttr": true, "redefines": "BaseWithId#id", "isId": true }
]
},
{
"name": "BaseWithAlreadyDefinedId",
"superClass": [ "BaseWithId" ],
"properties": [
{ "name": "id", "type": "Integer", "isAttr": true, "isId": true }
]
},
{
"name": "Attributes",
"superClass": [ "BaseWithId" ],
Expand Down Expand Up @@ -122,6 +129,25 @@
"name": "any",
"type": "Base",
"isMany": true
},
{
"name": "many",
"type": "Integer",
"isMany": true
},
{
"name": "single",
"type": "Integer"
},
{
"name": "defaultSingle",
"type": "Integer",
"default": 42
},
{
"name": "anotherDefaultSingle",
"type": "Integer",
"default": 23
}
]
},
Expand Down Expand Up @@ -171,4 +197,4 @@
]
}
]
}
}
5 changes: 4 additions & 1 deletion test/spec/moddle.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ describe('moddle', function() {

expect(descriptor.ns).to.jsonEqual(expectedDescriptorNs);
expect(descriptor.properties).to.jsonEqual(expectedDescriptorProperties);
expect(descriptor.propertiesByName).to.jsonEqual(expectedDescriptorPropertiesByName);
expect(descriptor.propertiesByName.id).to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove the original check here?

I'd say we could add an additional test case, if we want:

Suggested change
expect(descriptor.propertiesByName.id).to
expect(descriptor.propertiesByName.id).to.equal(descriptor.propertiesByName['props:id']);

Copy link
Contributor Author

@fra-pa fra-pa Jun 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove the original check here?

Because I added a property and comparing the whole structure depends on the order. That's why I created two expects for this.

I'd say we could add an additional test case, if we want:

👍 added

I applied the suggestion and CI action failed (with an empty error message, why I missed to fix it immediately - and forgot to run tests again). Fixed it here.

.equal(descriptor.propertiesByName['props:id']);
expect(descriptor.propertiesByName['props:id']).to
.jsonEqual(expectedDescriptorPropertiesByName['props:id']);
});


Expand Down
Loading