Skip to content

Commit 53abb6d

Browse files
sanketshevkarSanket Shevkar
and
Sanket Shevkar
authored
feat(vocabulary): vocaulary for map types (#755)
* feat(vocabulary): vocaulary for map types Signed-off-by: Sanket Shevkar <Sanket.Shevkar@docusign.com> * feat(vocabulary): enable map types for tests Signed-off-by: Sanket Shevkar <Sanket.Shevkar@docusign.com> * feat(vocabulary): resolved review comments Signed-off-by: Sanket Shevkar <sanket.shevkar@docusign.com> * feat(vocabulary): addressed comments Signed-off-by: Sanket Shevkar <sanket.shevkar@docusign.com> * feat(vocabulary): addressed comments Signed-off-by: Sanket Shevkar <Sanket.Shevkar@docusign.com> * feat(vocabulary): addressed comments Signed-off-by: Sanket Shevkar <Sanket.Shevkar@docusign.com> --------- Signed-off-by: Sanket Shevkar <Sanket.Shevkar@docusign.com> Signed-off-by: Sanket Shevkar <sanket.shevkar@docusign.com> Co-authored-by: Sanket Shevkar <Sanket.Shevkar@docusign.com>
1 parent 4758d64 commit 53abb6d

18 files changed

+269
-23
lines changed

packages/concerto-core/api.txt

+3
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ class Declaration extends Decorated {
124124
+ boolean isEnum()
125125
+ boolean isClassDeclaration()
126126
+ boolean isScalarDeclaration()
127+
+ boolean isMapDeclaration()
127128
}
128129
class Decorator {
129130
+ void constructor(ClassDeclaration|Property,Object) throws IllegalModelException
@@ -180,6 +181,7 @@ class MapKeyType extends Decorated {
180181
+ String toString()
181182
+ boolean isKey()
182183
+ boolean isValue()
184+
+ string getNamespace()
183185
}
184186
class MapValueType extends Decorated {
185187
+ void constructor(MapDeclaration,Object) throws IllegalModelException
@@ -190,6 +192,7 @@ class MapValueType extends Decorated {
190192
+ String toString()
191193
+ boolean isKey()
192194
+ boolean isValue()
195+
+ string getNamespace()
193196
}
194197
+ ModelManager newMetaModelManager()
195198
+ object validateMetaModel()

packages/concerto-core/changelog.txt

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
# Note that the latest public API is documented using JSDocs and is available in api.txt.
2525
#
2626

27+
Version 3.13.2 {dccc690753912cf87e7ceec56d949058} 2023-10-18
28+
- Add getNamespace method to key type and value type of maps
29+
2730
Version 3.13.1 {f5a9a1ea6a64865843a3abb77798cbb0} 2023-10-18
2831
- Add migrate option to DecoratorManager options
2932

packages/concerto-core/lib/decoratormanager.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,14 @@ enum CommandType {
5454
5555
/**
5656
* Which models elements to add the decorator to. Any null
57-
* elements are 'wildcards'.
57+
* elements are 'wildcards'.
5858
*/
5959
concept CommandTarget {
6060
o String namespace optional
6161
o String declaration optional
6262
o String property optional
6363
o String[] properties optional // property and properties are mutually exclusive
64-
o String type optional
64+
o String type optional
6565
o MapElement mapElement optional
6666
}
6767
@@ -438,6 +438,8 @@ class DecoratorManager {
438438
if (this.falsyOrEqual(target.type, declaration.value.$class)) {
439439
this.applyDecorator(declaration.value, type, decorator);
440440
}
441+
} else {
442+
this.applyDecorator(declaration, type, decorator);
441443
}
442444
} else if (!(target.property || target.properties || target.type)) {
443445
this.applyDecorator(declaration, type, decorator);

packages/concerto-core/lib/introspect/declaration.js

+9
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,15 @@ class Declaration extends Decorated {
177177
isScalarDeclaration() {
178178
return false;
179179
}
180+
181+
/**
182+
* Returns true if this class is the definition of a map-declaration.
183+
*
184+
* @return {boolean} true if the class is a map-declaration
185+
*/
186+
isMapDeclaration() {
187+
return false;
188+
}
180189
}
181190

182191
module.exports = Declaration;

packages/concerto-core/lib/introspect/mapkeytype.js

+8
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,14 @@ class MapKeyType extends Decorated {
163163
isValue() {
164164
return false;
165165
}
166+
167+
/**
168+
* Return the namespace of this map key.
169+
* @return {string} namespace - a namespace.
170+
*/
171+
getNamespace() {
172+
return this.modelFile.getNamespace();
173+
}
166174
}
167175

168176
module.exports = MapKeyType;

packages/concerto-core/lib/introspect/mapvaluetype.js

+8
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@ class MapValueType extends Decorated {
185185
isValue() {
186186
return true;
187187
}
188+
189+
/**
190+
* Return the namespace of this map value.
191+
* @return {string} namespace - a namespace.
192+
*/
193+
getNamespace() {
194+
return this.modelFile.getNamespace();
195+
}
188196
}
189197

190198
module.exports = MapValueType;

packages/concerto-core/test/data/decoratorcommands/map-declaration.json

+14
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@
33
"name" : "web",
44
"version": "1.0.0",
55
"commands" : [
6+
{
7+
"$class" : "org.accordproject.decoratorcommands@0.3.0.Command",
8+
"type" : "UPSERT",
9+
"target" : {
10+
"$class" : "org.accordproject.decoratorcommands@0.3.0.CommandTarget",
11+
"namespace" : "test@1.0.0",
12+
"declaration" : "Dictionary"
13+
},
14+
"decorator" : {
15+
"$class" : "concerto.metamodel@1.0.0.Decorator",
16+
"name" : "MapDeclarationDecorator",
17+
"arguments" : []
18+
}
19+
},
620
{
721
"$class" : "org.accordproject.decoratorcommands@0.3.0.Command",
822
"type" : "APPEND",

packages/concerto-core/test/decoratormanager.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,21 @@ describe('DecoratorManager', () => {
188188
decoratorCity2Property.should.not.be.null;
189189
});
190190

191+
it('should decorate the specified MapDeclaration', async function() {
192+
// load a model to decorate
193+
const testModelManager = new ModelManager({strict:true, skipLocationNodes: true});
194+
const modelText = fs.readFileSync('./test/data/decoratorcommands/test.cto', 'utf-8');
195+
testModelManager.addCTOModel(modelText, 'test.cto');
196+
197+
const dcs = fs.readFileSync('./test/data/decoratorcommands/map-declaration.json', 'utf-8');
198+
const decoratedModelManager = DecoratorManager.decorateModels( testModelManager, JSON.parse(dcs),
199+
{validate: true, validateCommands: true});
200+
201+
const dictionary = decoratedModelManager.getType('test@1.0.0.Dictionary');
202+
dictionary.should.not.be.null;
203+
dictionary.getDecorator('MapDeclarationDecorator').should.not.be.null;
204+
});
205+
191206
it('should decorate the specified element on the specified Map Declaration (Map Key)', async function() {
192207
// load a model to decorate
193208
const testModelManager = new ModelManager({strict:true, skipLocationNodes: true});
@@ -284,7 +299,7 @@ describe('DecoratorManager', () => {
284299
dictionary.value.getDecorator('DecoratesValueByType').should.not.be.null;
285300
});
286301

287-
it('should decorate both Key and Value elements on the specified Map Declaration', async function() {
302+
it('should decorate Declaration, Key and Value elements on the specified Map Declaration', async function() {
288303
// load a model to decorate
289304
const testModelManager = new ModelManager({strict:true, skipLocationNodes: true});
290305
const modelText = fs.readFileSync('./test/data/decoratorcommands/test.cto', 'utf-8');
@@ -297,6 +312,7 @@ describe('DecoratorManager', () => {
297312
const dictionary = decoratedModelManager.getType('test@1.0.0.Dictionary');
298313

299314
dictionary.should.not.be.null;
315+
dictionary.getDecorator('MapDeclarationDecorator').should.not.be.null;
300316
dictionary.key.getDecorator('Baz').should.not.be.null;
301317
dictionary.value.getDecorator('Baz').should.not.be.null;
302318
});

packages/concerto-core/test/introspect/declaration.js

+6
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ describe('Declaration', () => {
4747
});
4848
});
4949

50+
describe('#isMapDeclaration', () => {
51+
it('should be false', () => {
52+
declaration.isMapDeclaration().should.equal(false);
53+
});
54+
});
55+
5056
describe('#isSystemIdentified', () => {
5157
it('should be false', () => {
5258
declaration.isSystemIdentified().should.equal(false);

packages/concerto-core/test/introspect/mapdeclaration.js

+8
Original file line numberDiff line numberDiff line change
@@ -757,4 +757,12 @@ describe('MapDeclaration', () => {
757757
declaration.getValue().getParent().should.equal(declaration);
758758
});
759759
});
760+
761+
describe('#getNamespace', () => {
762+
it('should return the correct namespace for a Map Declaration Key and Value', () => {
763+
let declaration = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.primitive.string.cto', MapDeclaration);
764+
declaration.getKey().getNamespace().should.equal('com.acme@1.0.0');
765+
declaration.getValue().getNamespace().should.equal('com.acme@1.0.0');
766+
});
767+
});
760768
});

packages/concerto-vocabulary/lib/vocabulary.js

+36-5
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,47 @@ class Vocabulary {
144144
* @returns {*} an object with missingTerms and additionalTerms properties
145145
*/
146146
validate(modelFile) {
147-
const getOwnProperties = (d) => {
148-
// ensures we have a valid return, even for scalars
149-
return d.getOwnProperties?.() ? d.getOwnProperties?.() : [];
147+
const getOwnProperties = (declaration) => {
148+
// ensures we have a valid return, even for scalars and map-declarations
149+
if(declaration.isMapDeclaration()) {
150+
return [declaration.getKey(), declaration.getValue()];
151+
} else {
152+
return declaration.getOwnProperties?.() ? declaration.getOwnProperties?.() : [];
153+
}
150154
};
155+
156+
const getPropertyName = (property) => {
157+
if(property.isKey?.()) {
158+
return 'KEY';
159+
} else if(property.isValue?.()) {
160+
return 'VALUE';
161+
} else {
162+
return property.getName();
163+
}
164+
};
165+
166+
const checkPropertyExists = (k, p) => {
167+
const declaration = modelFile.getLocalType(Object.keys(k)[0]);
168+
const property = Object.keys(p)[0];
169+
if(declaration.isMapDeclaration()) {
170+
if (property === 'KEY') {
171+
return true;
172+
} else if(property === 'VALUE') {
173+
return true;
174+
} else {
175+
return false;
176+
}
177+
} else {
178+
return declaration.getOwnProperty(Object.keys(p)[0]);
179+
}
180+
};
181+
151182
const result = {
152183
missingTerms: modelFile.getAllDeclarations().flatMap( d => this.getTerm(d.getName())
153-
? getOwnProperties(d).flatMap( p => this.getTerm(d.getName(), p.getName()) ? null : `${d.getName()}.${p.getName()}`)
184+
? getOwnProperties(d).flatMap( p => this.getTerm(d.getName(), getPropertyName(p)) ? null : `${d.getName()}.${getPropertyName(p)}`)
154185
: d.getName() ).filter( i => i !== null),
155186
additionalTerms: this.content.declarations.flatMap( k => modelFile.getLocalType(Object.keys(k)[0])
156-
? Array.isArray(k.properties) ? k.properties.flatMap( p => modelFile.getLocalType(Object.keys(k)[0]).getOwnProperty(Object.keys(p)[0]) ? null : `${Object.keys(k)[0]}.${Object.keys(p)[0]}`) : null
187+
? Array.isArray(k.properties) ? k.properties.flatMap( p => checkPropertyExists(k, p) ? null : `${Object.keys(k)[0]}.${Object.keys(p)[0]}`) : null
157188
: k ).filter( i => i !== null)
158189
};
159190

packages/concerto-vocabulary/lib/vocabularymanager.js

+29-6
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,18 @@ class VocabularyManager {
205205
resolveTerms(modelManager, namespace, locale, declarationName, propertyName) {
206206
const modelFile = modelManager.getModelFile(namespace);
207207
const classDecl = modelFile ? modelFile.getType(declarationName) : null;
208-
const property = propertyName ? classDecl ? classDecl.getProperty(propertyName) : null : null;
208+
let property;
209+
if(classDecl && !classDecl.isScalarDeclaration()) {
210+
if(classDecl.isMapDeclaration()) {
211+
if(propertyName === 'KEY') {
212+
property = classDecl.getKey();
213+
} else if(propertyName === 'VALUE') {
214+
property = classDecl.getValue();
215+
}
216+
} else {
217+
property = propertyName ? classDecl ? classDecl.getProperty(propertyName) : null : null;
218+
}
219+
}
209220
return this.getTerms(property ? property.getNamespace() : namespace, locale, property ? property.getParent().getName() : declarationName, propertyName);
210221
}
211222

@@ -286,6 +297,16 @@ class VocabularyManager {
286297
'commands': []
287298
};
288299

300+
const getPropertyNames = (declaration) => {
301+
if (declaration.getProperties) {
302+
return declaration.getProperties().map(property => property.getName());
303+
} else if(declaration.isMapDeclaration?.()) {
304+
return ['KEY', 'VALUE'];
305+
} else {
306+
return [];
307+
}
308+
};
309+
289310
modelManager.getModelFiles().forEach(model => {
290311
model.getAllDeclarations().forEach(decl => {
291312
const terms = this.resolveTerms(modelManager, model.getNamespace(), locale, decl.getName());
@@ -336,19 +357,21 @@ class VocabularyManager {
336357
});
337358
}
338359

339-
decl.getProperties?.().forEach(property => {
340-
const propertyTerms = this.resolveTerms(modelManager, model.getNamespace(), locale, decl.getName(), property.getName());
360+
const propertyNames = getPropertyNames(decl);
361+
propertyNames.forEach(propertyName => {
362+
const propertyTerms = this.resolveTerms(modelManager, model.getNamespace(), locale, decl.getName(), propertyName);
341363
if (propertyTerms) {
342364
Object.keys(propertyTerms).forEach( term => {
343-
if(term === property.getName()) {
365+
const propertyType = propertyName === 'KEY' || propertyName === 'VALUE' ? 'mapElement' : 'property';
366+
if(term === propertyName) {
344367
decoratorCommandSet.commands.push({
345368
'$class': `${DC_NAMESPACE}.Command`,
346369
'type': 'UPSERT',
347370
'target': {
348371
'$class': `${DC_NAMESPACE}.CommandTarget`,
349372
'namespace': model.getNamespace(),
350373
'declaration': decl.getName(),
351-
'property': property.getName()
374+
[propertyType]: propertyName
352375
},
353376
'decorator': {
354377
'$class': `${MetaModelNamespace}.Decorator`,
@@ -370,7 +393,7 @@ class VocabularyManager {
370393
'$class': `${DC_NAMESPACE}.CommandTarget`,
371394
'namespace': model.getNamespace(),
372395
'declaration': decl.getName(),
373-
'property': property.getName()
396+
[propertyType]: propertyName
374397
},
375398
'decorator': {
376399
'$class': `${MetaModelNamespace}.Decorator`,

0 commit comments

Comments
 (0)