Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vocabulary): vocaulary for map types #755

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
3 changes: 3 additions & 0 deletions packages/concerto-core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ class MapKeyType extends Decorated {
+ String toString()
+ boolean isKey()
+ boolean isValue()
+ string getNamespace()
}
class MapValueType extends Decorated {
+ void constructor(MapDeclaration,Object) throws IllegalModelException
Expand All @@ -190,6 +191,7 @@ class MapValueType extends Decorated {
+ String toString()
+ boolean isKey()
+ boolean isValue()
+ string getNamespace()
}
+ ModelManager newMetaModelManager()
+ object validateMetaModel()
Expand Down Expand Up @@ -272,6 +274,7 @@ class ScalarDeclaration extends Declaration {
+ boolean isTransaction()
+ boolean isEvent()
+ boolean isConcept()
+ boolean isMapDeclaration()
}
class TransactionDeclaration extends IdentifiedDeclaration {
+ void constructor(ModelFile,Object) throws IllegalModelException
Expand Down
3 changes: 3 additions & 0 deletions packages/concerto-core/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
# Note that the latest public API is documented using JSDocs and is available in api.txt.
#

Version 3.13.2 {8aa6c0e12fe380d694604e3edd965730} 2023-10-18
- Add getNamespace method to key type and value type of maps

Version 3.13.1 {f5a9a1ea6a64865843a3abb77798cbb0} 2023-10-18
- Add migrate option to DecoratorManager options

Expand Down
6 changes: 4 additions & 2 deletions packages/concerto-core/lib/decoratormanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ enum CommandType {

/**
* Which models elements to add the decorator to. Any null
* elements are 'wildcards'.
* elements are 'wildcards'.
*/
concept CommandTarget {
o String namespace optional
o String declaration optional
o String property optional
o String[] properties optional // property and properties are mutually exclusive
o String type optional
o String type optional
o MapElement mapElement optional
}

Expand Down Expand Up @@ -438,6 +438,8 @@ class DecoratorManager {
if (this.falsyOrEqual(target.type, declaration.value.$class)) {
this.applyDecorator(declaration.value, type, decorator);
}
} else {
this.applyDecorator(declaration, type, decorator);
mttrbrts marked this conversation as resolved.
Show resolved Hide resolved
}
} else if (!(target.property || target.properties || target.type)) {
this.applyDecorator(declaration, type, decorator);
Expand Down
8 changes: 8 additions & 0 deletions packages/concerto-core/lib/introspect/mapkeytype.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@ class MapKeyType extends Decorated {
isValue() {
return false;
}

/**
* Return the namespace of this map key.
* @return {string} namespace - a namespace.
mttrbrts marked this conversation as resolved.
Show resolved Hide resolved
*/
getNamespace() {
return this.modelFile.getNamespace();
}
}

module.exports = MapKeyType;
8 changes: 8 additions & 0 deletions packages/concerto-core/lib/introspect/mapvaluetype.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ class MapValueType extends Decorated {
isValue() {
return true;
}

/**
* Return the namespace of this map value.
* @return {string} namespace - a namespace.
*/
getNamespace() {
return this.modelFile.getNamespace();
}
}

module.exports = MapValueType;
10 changes: 10 additions & 0 deletions packages/concerto-core/lib/introspect/scalardeclaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,16 @@ class ScalarDeclaration extends Declaration {
return false;
}

/**
* Returns true if this class is the definition of a map-declaration.
*
* @return {boolean} true if the class is a map-declaration
* @deprecated
*/
isMapDeclaration() {
mttrbrts marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

}

module.exports = ScalarDeclaration;
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
"name" : "web",
"version": "1.0.0",
"commands" : [
{
"$class" : "org.accordproject.decoratorcommands@0.3.0.Command",
"type" : "UPSERT",
"target" : {
"$class" : "org.accordproject.decoratorcommands@0.3.0.CommandTarget",
"namespace" : "test@1.0.0",
"declaration" : "Dictionary"
},
"decorator" : {
"$class" : "concerto.metamodel@1.0.0.Decorator",
"name" : "MapDeclarationDecorator",
"arguments" : []
}
},
{
"$class" : "org.accordproject.decoratorcommands@0.3.0.Command",
"type" : "APPEND",
Expand Down
18 changes: 17 additions & 1 deletion packages/concerto-core/test/decoratormanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,21 @@ describe('DecoratorManager', () => {
decoratorCity2Property.should.not.be.null;
});

it('should decorate the specified element on the specified Map Declaration (Map Declaration)', async function() {
mttrbrts marked this conversation as resolved.
Show resolved Hide resolved
// load a model to decorate
const testModelManager = new ModelManager({strict:true, skipLocationNodes: true});
const modelText = fs.readFileSync('./test/data/decoratorcommands/test.cto', 'utf-8');
testModelManager.addCTOModel(modelText, 'test.cto');

const dcs = fs.readFileSync('./test/data/decoratorcommands/map-declaration.json', 'utf-8');
const decoratedModelManager = DecoratorManager.decorateModels( testModelManager, JSON.parse(dcs),
{validate: true, validateCommands: true});

const dictionary = decoratedModelManager.getType('test@1.0.0.Dictionary');
dictionary.should.not.be.null;
dictionary.getDecorator('MapDeclarationDecorator').should.not.be.null;
});

it('should decorate the specified element on the specified Map Declaration (Map Key)', async function() {
// load a model to decorate
const testModelManager = new ModelManager({strict:true, skipLocationNodes: true});
Expand Down Expand Up @@ -284,7 +299,7 @@ describe('DecoratorManager', () => {
dictionary.value.getDecorator('DecoratesValueByType').should.not.be.null;
});

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

dictionary.should.not.be.null;
dictionary.getDecorator('MapDeclarationDecorator').should.not.be.null;
dictionary.key.getDecorator('Baz').should.not.be.null;
dictionary.value.getDecorator('Baz').should.not.be.null;
});
Expand Down
8 changes: 8 additions & 0 deletions packages/concerto-core/test/introspect/mapdeclaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -750,4 +750,12 @@ describe('MapDeclaration', () => {
declaration.getValue().getParent().should.equal(declaration);
});
});

describe('#getNamespace', () => {
it('should return the correct namespace for a Map Declaration Key and Value', () => {
let declaration = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.primitive.string.cto', MapDeclaration);
declaration.getKey().getNamespace().should.equal('com.acme@1.0.0');
declaration.getValue().getNamespace().should.equal('com.acme@1.0.0');
});
});
});
13 changes: 13 additions & 0 deletions packages/concerto-core/test/introspect/scalardeclaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,4 +305,17 @@ describe('ScalarDeclaration', () => {
testClass.isClassDeclaration().should.be.false;
});
});

describe('#isMapDeclaration', () => {
const modelFileName = 'test/data/parser/scalardeclaration.ssn.cto';

beforeEach(() => {
const modelFiles = introspectUtils.loadModelFiles([modelFileName], modelManager);
modelManager.addModelFiles(modelFiles);
});
it('should return false', () => {
const testClass = modelManager.getType('com.testing.SSN');
testClass.isMapDeclaration().should.be.false;
});
});
});
41 changes: 36 additions & 5 deletions packages/concerto-vocabulary/lib/vocabulary.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,47 @@ class Vocabulary {
* @returns {*} an object with missingTerms and additionalTerms properties
*/
validate(modelFile) {
const getOwnProperties = (d) => {
// ensures we have a valid return, even for scalars
return d.getOwnProperties?.() ? d.getOwnProperties?.() : [];
const getOwnProperties = (declaration) => {
// ensures we have a valid return, even for scalars and map-declarations
mttrbrts marked this conversation as resolved.
Show resolved Hide resolved
if(declaration.isMapDeclaration()) {
return [declaration.getKey(), declaration.getValue()];
} else {
return declaration.getOwnProperties?.() ? declaration.getOwnProperties?.() : [];
}
};

const getPropertyName = (property) => {
if(property.isKey?.()) {
return 'KEY';
} else if(property.isValue?.()) {
return 'VALUE';
} else {
return property.getName();
}
};

const checkPropertyExists = (k, p) => {
const declaration = modelFile.getLocalType(Object.keys(k)[0]);
const property = Object.keys(p)[0];
if(declaration.isMapDeclaration()) {
if (property === 'KEY') {
return true;
} else if(property === 'VALUE') {
return true;
} else {
return false;
}
} else {
return declaration.getOwnProperty(Object.keys(p)[0]);
}
};

const result = {
missingTerms: modelFile.getAllDeclarations().flatMap( d => this.getTerm(d.getName())
? getOwnProperties(d).flatMap( p => this.getTerm(d.getName(), p.getName()) ? null : `${d.getName()}.${p.getName()}`)
? getOwnProperties(d).flatMap( p => this.getTerm(d.getName(), getPropertyName(p)) ? null : `${d.getName()}.${getPropertyName(p)}`)
: d.getName() ).filter( i => i !== null),
additionalTerms: this.content.declarations.flatMap( k => modelFile.getLocalType(Object.keys(k)[0])
? 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
? Array.isArray(k.properties) ? k.properties.flatMap( p => checkPropertyExists(k, p) ? null : `${Object.keys(k)[0]}.${Object.keys(p)[0]}`) : null
: k ).filter( i => i !== null)
};

Expand Down
35 changes: 29 additions & 6 deletions packages/concerto-vocabulary/lib/vocabularymanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,18 @@ class VocabularyManager {
resolveTerms(modelManager, namespace, locale, declarationName, propertyName) {
const modelFile = modelManager.getModelFile(namespace);
const classDecl = modelFile ? modelFile.getType(declarationName) : null;
const property = propertyName ? classDecl ? classDecl.getProperty(propertyName) : null : null;
let property;
if(classDecl && !classDecl.isScalarDeclaration()) {
if(classDecl.isMapDeclaration()) {
mttrbrts marked this conversation as resolved.
Show resolved Hide resolved
if(propertyName === 'KEY') {
property = classDecl.getKey();
} else if(propertyName === 'VALUE') {
property = classDecl.getValue();
}
} else {
property = propertyName ? classDecl ? classDecl.getProperty(propertyName) : null : null;
}
}
return this.getTerms(property ? property.getNamespace() : namespace, locale, property ? property.getParent().getName() : declarationName, propertyName);
}

Expand Down Expand Up @@ -286,6 +297,16 @@ class VocabularyManager {
'commands': []
};

const getPropertyNames = (declaration) => {
if (declaration.getProperties) {
return declaration.getProperties().map(property => property.getName());
} else if(declaration.isMapDeclaration?.()) {
return ['KEY', 'VALUE'];
} else {
return [];
}
};

modelManager.getModelFiles().forEach(model => {
model.getAllDeclarations().forEach(decl => {
const terms = this.resolveTerms(modelManager, model.getNamespace(), locale, decl.getName());
Expand Down Expand Up @@ -336,19 +357,21 @@ class VocabularyManager {
});
}

decl.getProperties?.().forEach(property => {
const propertyTerms = this.resolveTerms(modelManager, model.getNamespace(), locale, decl.getName(), property.getName());
const propertyNames = getPropertyNames(decl);
propertyNames.forEach(propertyName => {
const propertyTerms = this.resolveTerms(modelManager, model.getNamespace(), locale, decl.getName(), propertyName);
if (propertyTerms) {
Object.keys(propertyTerms).forEach( term => {
if(term === property.getName()) {
const propertyType = propertyName === 'KEY' || propertyName === 'VALUE' ? 'mapElement' : 'property';
if(term === propertyName) {
decoratorCommandSet.commands.push({
'$class': `${DC_NAMESPACE}.Command`,
'type': 'UPSERT',
'target': {
'$class': `${DC_NAMESPACE}.CommandTarget`,
'namespace': model.getNamespace(),
'declaration': decl.getName(),
'property': property.getName()
[propertyType]: propertyName
},
'decorator': {
'$class': `${MetaModelNamespace}.Decorator`,
Expand All @@ -370,7 +393,7 @@ class VocabularyManager {
'$class': `${DC_NAMESPACE}.CommandTarget`,
'namespace': model.getNamespace(),
'declaration': decl.getName(),
'property': property.getName()
[propertyType]: propertyName
},
'decorator': {
'$class': `${MetaModelNamespace}.Decorator`,
Expand Down
Loading
Loading