Skip to content
This repository was archived by the owner on Mar 8, 2020. It is now read-only.

Commit 8db94bf

Browse files
author
Mark S. Lewis
authored
Fixes for issue #949 (#1047)
* Add ClassDeclaration.getSuperTypeDeclaration() function Add additional ClassDeclaration unit test to ensure superclasses are located in both local and remote model files. Refactor (simplify) ModelUtil.isAssignableTo(). * Add ClassDeclaration.getAllSuperTypeDeclarations() Refactor ModelUtil.isAssignableTo() to use this function. Several other places in the code do awkward walking of the superclass hierarchy and these can now be refactored to use this function.
1 parent 5c71092 commit 8db94bf

File tree

7 files changed

+120
-90
lines changed

7 files changed

+120
-90
lines changed

packages/composer-common/lib/introspect/classdeclaration.js

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -356,17 +356,29 @@ class ClassDeclaration {
356356
* @return {string} the FQN name of the super type or null
357357
*/
358358
getSuperType() {
359-
if(this.superType) {
360-
const type = this.getModelFile().getType(this.superType);
361-
if(type === null) {
362-
throw new Error('Could not find super type:' + this.superType );
363-
}
364-
else {
365-
return type.getFullyQualifiedName();
366-
}
359+
const superTypeDeclaration = this.getSuperTypeDeclaration();
360+
if (superTypeDeclaration) {
361+
return superTypeDeclaration.getFullyQualifiedName();
362+
} else {
363+
return null;
367364
}
365+
}
368366

369-
return null;
367+
/**
368+
* Get the super type class declaration for this class.
369+
* @return {ClassDeclaration} the super type declaration, or null if there is no super type.
370+
*/
371+
getSuperTypeDeclaration() {
372+
if (!this.superType) {
373+
return null;
374+
}
375+
376+
const supertypeDeclaration = this.getModelFile().getType(this.superType);
377+
if (!supertypeDeclaration) {
378+
throw new Error('Could not find super type: ' + this.superType);
379+
}
380+
381+
return supertypeDeclaration;
370382
}
371383

372384
/**
@@ -407,6 +419,19 @@ class ClassDeclaration {
407419
return Array.from(results);
408420
}
409421

422+
/**
423+
* Get all the super-type declarations for this type.
424+
* @return {ClassDeclaration[]} super-type declarations.
425+
*/
426+
getAllSuperTypeDeclarations() {
427+
const results = [];
428+
for (let type = this; (type = type.getSuperTypeDeclaration()); ) {
429+
results.push(type);
430+
}
431+
432+
return results;
433+
}
434+
410435
/**
411436
* Returns the property with a given name or null if it does not exist.
412437
* Fields defined in super-types are also introspected.

packages/composer-common/lib/modelutil.js

Lines changed: 13 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -89,64 +89,28 @@ class ModelUtil {
8989
* Returns true if the type is assignable to the propertyType.
9090
*
9191
* @param {ModelFile} modelFile - the ModelFile that owns the Property
92-
* @param {string} type - the FQN of the type we are trying to assign
92+
* @param {string} typeName - the FQN of the type we are trying to assign
9393
* @param {Property} property - the property that we'd like to store the
9494
* type in.
9595
* @return {boolean} - true if the type can be assigned to the property
9696
* @private
9797
*/
98-
static isAssignableTo(modelFile, type, property) {
99-
const propertyType = property.getFullyQualifiedTypeName();
98+
static isAssignableTo(modelFile, typeName, property) {
99+
const propertyTypeName = property.getFullyQualifiedTypeName();
100100

101-
if (ModelUtil.isPrimitiveType(type) || ModelUtil.isPrimitiveType(propertyType)) {
102-
return type === propertyType;
101+
const isDirectMatch = (typeName === propertyTypeName);
102+
if (isDirectMatch || ModelUtil.isPrimitiveType(typeName) || ModelUtil.isPrimitiveType(propertyTypeName)) {
103+
return isDirectMatch;
103104
}
104105

105-
// simple case
106-
if (type === propertyType) {
107-
return true;
106+
const typeDeclaration = modelFile.getType(typeName);
107+
if (!typeDeclaration) {
108+
throw new Error('Cannot find type ' + typeName);
108109
}
109110

110-
// type = SuperType
111-
// property = BaseType
112-
// return = true
113-
const typeDeclaration = modelFile.getType(type);
114-
if(!typeDeclaration) {
115-
throw new Error('Cannot find type ' + type );
116-
}
117-
118-
let superTypeName = typeDeclaration.getSuperType();
119-
let superType = null;
120-
121-
if(superTypeName) {
122-
// we cannot assume that the super type is in the same namespace as the derived type!
123-
superType = modelFile.getModelManager().getType(superTypeName);
124-
125-
if(!superType) {
126-
throw new Error('Cannot find type ' + superTypeName );
127-
}
128-
129-
// console.log( 'superTypeName ' + superTypeName );
130-
// console.log( 'superType ' + superType.getFullyQualifiedName() );
131-
}
132-
133-
while(superType) {
134-
if(superType.getFullyQualifiedName() === property.getFullyQualifiedTypeName()) {
135-
// console.log('Found superType ' + superType.getFullyQualifiedName() );
136-
return true;
137-
}
138-
superTypeName = superType.getSuperType();
139-
140-
if(superTypeName) {
141-
superType = modelFile.getModelManager().getType(superTypeName);
142-
// console.log('superType ' + superType.getFullyQualifiedName() );
143-
}
144-
else {
145-
superType = null;
146-
}
147-
}
148-
149-
return false;
111+
return typeDeclaration.getAllSuperTypeDeclarations().
112+
map(type => type.getFullyQualifiedName()).
113+
includes(propertyTypeName);
150114
}
151115

152116
/**
@@ -170,6 +134,7 @@ class ModelUtil {
170134
const typeDeclaration = modelFile.getType(field.getType());
171135
return (typeDeclaration !== null && typeDeclaration.isEnum());
172136
}
137+
173138
}
174139

175140
module.exports = ModelUtil;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace com.testing.child
2+
3+
import com.testing.parent.Super
4+
5+
participant Sub extends Super { }
6+
7+
participant Sub2 extends Super { }

packages/composer-common/test/data/parser/classdeclaration.participantwithparents.cto

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace com.testing.parent
2+
3+
abstract participant Base identified by id {
4+
o String id
5+
}
6+
7+
abstract participant Super extends Base { }

packages/composer-common/test/introspect/classdeclaration.js

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,27 @@ describe('ClassDeclaration', () => {
3535
beforeEach(() => {
3636
mockModelManager = sinon.createStubInstance(ModelManager);
3737
mockModelFile = sinon.createStubInstance(ModelFile);
38-
mockModelFile.getModelManager.returns(mockModelManager);
3938
});
4039

40+
/**
41+
* Load an arbitrary number of model files.
42+
* @param {String[]} modelFileNames array of model file names.
43+
* @param {ModelManager} modelManager the model manager to which the created model files will be registered.
44+
* @return {ModelFile[]} array of loaded model files, matching the supplied arguments.
45+
*/
46+
const loadModelFiles = (modelFileNames, modelManager) => {
47+
const modelFiles = [];
48+
for (let modelFileName of modelFileNames) {
49+
const modelDefinitions = fs.readFileSync(modelFileName, 'utf8');
50+
const modelFile = new ModelFile(modelManager, modelDefinitions);
51+
modelFiles.push(modelFile);
52+
}
53+
modelManager.addModelFiles(modelFiles, modelFileNames);
54+
return modelFiles;
55+
};
56+
4157
const loadModelFile = (modelFileName) => {
42-
const modelDefinitions = fs.readFileSync(modelFileName, 'utf8');
43-
const modelFile = new ModelFile(mockModelManager, modelDefinitions);
44-
mockModelManager.getModelFiles.returns([modelFile]);
45-
return modelFile;
58+
return loadModelFiles([modelFileName], mockModelManager)[0];
4659
};
4760

4861
const loadLastDeclaration = (modelFileName, type) => {
@@ -216,35 +229,62 @@ describe('ClassDeclaration', () => {
216229
});
217230

218231
describe('#getSuperType', function() {
219-
it('should return superclass when one exists', function() {
220-
const modelFile = loadModelFile('test/data/parser/classdeclaration.participantwithparents.cto');
221-
const subclass = modelFile.getLocalType('Sub');
232+
const modelFileNames = [
233+
'test/data/parser/classdeclaration.participantwithparents.parent.cto',
234+
'test/data/parser/classdeclaration.participantwithparents.child.cto'
235+
];
236+
let modelManager;
237+
238+
beforeEach(() => {
239+
modelManager = new ModelManager();
240+
const modelFiles = loadModelFiles(modelFileNames, modelManager);
241+
modelManager.addModelFiles(modelFiles);
242+
});
243+
244+
it('should return superclass when one exists in the same model file', function() {
245+
const subclass = modelManager.getType('com.testing.parent.Super');
246+
should.exist(subclass);
247+
const superclassName = subclass.getSuperType();
248+
superclassName.should.equal('com.testing.parent.Base');
249+
});
250+
251+
it('should return superclass when one exists in a different model file', function() {
252+
const subclass = modelManager.getType('com.testing.child.Sub');
222253
should.exist(subclass);
223254
const superclassName = subclass.getSuperType();
224-
superclassName.should.equal('com.testing.Super');
255+
superclassName.should.equal('com.testing.parent.Super');
225256
});
226257

227258
it('should return null when none exists', function() {
228-
const modelFile = loadModelFile('test/data/parser/classdeclaration.participantwithparents.cto');
229-
const baseclass = modelFile.getLocalType('Base');
259+
const baseclass = modelManager.getType('com.testing.parent.Base');
230260
should.exist(baseclass);
231261
const superclassName = baseclass.getSuperType();
232262
should.equal(superclassName, null);
233263
});
234264
});
235265

236266
describe('#getAssignableClassDeclarations', function() {
267+
const modelFileNames = [
268+
'test/data/parser/classdeclaration.participantwithparents.parent.cto',
269+
'test/data/parser/classdeclaration.participantwithparents.child.cto'
270+
];
271+
let modelManager;
272+
273+
beforeEach(() => {
274+
modelManager = new ModelManager();
275+
const modelFiles = loadModelFiles(modelFileNames, modelManager);
276+
modelManager.addModelFiles(modelFiles);
277+
});
278+
237279
it('should return itself only if there are no subclasses', function() {
238-
const modelFile = loadModelFile('test/data/parser/classdeclaration.participantwithparents.cto');
239-
const baseclass = modelFile.getLocalType('Sub');
280+
const baseclass = modelManager.getType('com.testing.child.Sub');
240281
should.exist(baseclass);
241282
const subclasses = baseclass.getAssignableClassDeclarations();
242283
subclasses.should.have.same.members([baseclass]);
243284
});
244285

245286
it('should return all subclass definitions', function() {
246-
const modelFile = loadModelFile('test/data/parser/classdeclaration.participantwithparents.cto');
247-
const baseclass = modelFile.getLocalType('Base');
287+
const baseclass = modelManager.getType('com.testing.parent.Base');
248288
should.exist(baseclass);
249289
const subclasses = baseclass.getAssignableClassDeclarations();
250290
const subclassNames = subclasses.map(classDef => classDef.getName());

packages/composer-common/test/modelutil.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
const ModelFile = require('../lib/introspect/modelfile');
1818
const Property = require('../lib/introspect/property');
19+
const ModelManager = require('../lib/modelmanager');
1920
const ModelUtil = require('../lib/modelutil');
2021

2122
require('chai').should();
@@ -145,7 +146,9 @@ describe('ModelUtil', function () {
145146
it('throws error when type cannot be found', function() {
146147
mockProperty.getName.returns('theDoge');
147148
mockProperty.getFullyQualifiedTypeName.returns('org.doge.BaseDoge');
148-
mockModelFile.getType.returns(null);
149+
const mockModelManager = sinon.createStubInstance(ModelManager);
150+
mockModelManager.getType.returns(null);
151+
mockModelFile.getModelManager.returns(mockModelManager);
149152
(() => {
150153
ModelUtil.isAssignableTo(mockModelFile, 'org.doge.Doge', mockProperty);
151154
}).should.throw(/Cannot find type/);

0 commit comments

Comments
 (0)