diff --git a/packages/concerto-core/api.txt b/packages/concerto-core/api.txt index 388e7c59d..50268070a 100644 --- a/packages/concerto-core/api.txt +++ b/packages/concerto-core/api.txt @@ -109,14 +109,17 @@ class ModelFileDownloader { + Promise downloadExternalDependencies(ModelFile[],Object) + Promise runJob(Object,Object) } + + void createMetaModelManager() + object fieldToMetaModel() + object relationshipToMetaModel() + object enumPropertyToMetaModel() + object declToMetaModel() + object modelToMetaModel() + + object modelFileToMetaModel() + string fieldFromMetaModel() + string declFromMetaModel() - + string modelFromMetaModel() + + string ctoFromMetaModel() + + object ctoToMetaModel() class ModelFile { + void constructor(ModelManager,string,string) throws IllegalModelException + Boolean isSystemModelFile() @@ -142,7 +145,6 @@ class ModelFile { + string getDefinitions() + string getConcertoVersion() + void isCompatibleVersion() - + object toMetaModel() + boolean hasInstance(object) } class ParticipantDeclaration extends IdentifiedDeclaration { diff --git a/packages/concerto-core/changelog.txt b/packages/concerto-core/changelog.txt index f899e516e..416c9c9e7 100644 --- a/packages/concerto-core/changelog.txt +++ b/packages/concerto-core/changelog.txt @@ -24,7 +24,7 @@ # Note that the latest public API is documented using JSDocs and is available in api.txt. # -Version 1.0.5 {febf7c3ec5ac5ec2f7f9fa88bd823555} 2021-07-13 +Version 1.0.5 {5af6b5621ee3ca9c92d5c665b641cc62} 2021-07-13 - Add support for Concerto metamodel with import/export to CTO Version 1.0.3 {1fe469fe1a79af5d5a4f5ec7dee6b7d4} 2021-06-25 diff --git a/packages/concerto-core/lib/introspect/metamodel.js b/packages/concerto-core/lib/introspect/metamodel.js index 7beda5673..67f4a00e7 100644 --- a/packages/concerto-core/lib/introspect/metamodel.js +++ b/packages/concerto-core/lib/introspect/metamodel.js @@ -14,6 +14,232 @@ 'use strict'; +const parser = require('./parser'); +const ModelManager = require('../modelmanager'); +const Factory = require('../factory'); +const Serializer = require('../serializer'); + +const metaModelCto = `/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace concerto.metamodel + +/** + * The metadmodel for Concerto files + */ +abstract concept DecoratorLiteral { +} + +concept DecoratorString extends DecoratorLiteral { + o String value +} + +concept DecoratorNumber extends DecoratorLiteral { + o Double value +} + +concept DecoratorBoolean extends DecoratorLiteral { + o Boolean value +} + +concept TypeIdentifier { + @FormEditor("selectOptions", "types") + o String name default="Concept" +} + +concept DecoratorIdentifier extends DecoratorLiteral { + o TypeIdentifier identifier + o Boolean isArray default=false +} + +concept Decorator { + o String name + o DecoratorLiteral[] arguments optional +} + +concept Identified { +} + +concept IdentifiedBy extends Identified { + o String name +} + +@FormEditor("defaultSubclass","concerto.metamodel.ConceptDeclaration") +abstract concept ClassDeclaration { + @FormEditor("hide", true) + o Decorator[] decorators optional + o Boolean isAbstract default=false + // TODO use regex /^(?!null|true|false)(\\p{Lu}|\\p{Ll}|\\p{Lt}|\\p{Lm}|\\p{Lo}|\\p{Nl}|\\$|_|\\\\u[0-9A-Fa-f]{4})(?:\\p{Lu}|\\p{Ll}|\\p{Lt}|\\p{Lm}|\\p{Lo}|\\p{Nl}|\\$|_|\\\\u[0-9A-Fa-f]{4}|\\p{Mn}|\\p{Mc}|\\p{Nd}|\\p{Pc}|\\u200C|\\u200D)*/u + @FormEditor("title", "Name") + o String name default="ClassName" // regex=/^(?!null|true|false)(\\w|\\d|\\$|_|\\\\u[0-9A-Fa-f]{4})(?:\\w|\\d|\\$|_|\\\\u[0-9A-Fa-f]{4}|\\S|\\u200C|\\u200D)*$/ + o Identified identified optional + @FormEditor("title", "Super Type") + o TypeIdentifier superType optional + o FieldDeclaration[] fields +} + +concept AssetDeclaration extends ClassDeclaration { +} + +concept ParticipantDeclaration extends ClassDeclaration { +} + +concept TransactionDeclaration extends ClassDeclaration { +} + +concept EventDeclaration extends ClassDeclaration { +} + +concept ConceptDeclaration extends ClassDeclaration { +} + +concept EnumDeclaration extends ClassDeclaration { +} + +concept StringDefault { + o String value +} + +concept BooleanDefault { + o Boolean value +} + +concept IntegerDefault { + o Integer value +} + +concept LongDefault { + o Long value +} + +concept DoubleDefault { + o Double value +} + +@FormEditor("defaultSubclass","concerto.metamodel.StringFieldDeclaration") +abstract concept FieldDeclaration { + // TODO Allow regex modifiers e.g. //ui + // regex /^(?!null|true|false)(\\p{Lu}|\\p{Ll}|\\p{Lt}|\\pLm}|\\p{Lo}|\\p{Nl}|\\$|_|\\\\u[0-9A-Fa-f]{4})(?:\\p{Lu}|\\p{Ll}|\\p{Lt}|\\p{Lm}|\\p{Lo}|\\p{Nl}|\\$|_|\\\\u[0-9A-Fa-f]{4}|\\p{Mn}|\\p{Mc}|\\p{Nd}|\\p{Pc}|\\u200C|\\u200D)*/u + // This regex is an approximation of what the parser accepts without using unicode character classes + o String name default="fieldName" // regex=/^(?!null|true|false)(\\w|\\d|\\$|_|\\\\u[0-9A-Fa-f]{4})(?:\\w|\\d|\\$|_|\\\\u[0-9A-Fa-f]{4}|\\S|\\u200C|\\u200D)*$/ + @FormEditor("title", "Is Array?") + o Boolean isArray default=false + @FormEditor("title", "Is Optional?") + o Boolean isOptional default=false + @FormEditor("hide", true) + o Decorator[] decorators optional +} + +concept ObjectFieldDeclaration extends FieldDeclaration { + @FormEditor("hide", true) + o StringDefault defaultValue optional + @FormEditor("title", "Type Name", "selectOptions", "types") + o TypeIdentifier type +} + +concept EnumFieldDeclaration extends FieldDeclaration { +} + +concept BooleanFieldDeclaration extends FieldDeclaration { + @FormEditor("hide", true) + o BooleanDefault defaultValue optional +} + +concept DateTimeFieldDeclaration extends FieldDeclaration { +} + +concept StringFieldDeclaration extends FieldDeclaration { + @FormEditor("hide", true) + o StringDefault defaultValue optional + @FormEditor("hide", true) + o StringRegexValidator validator optional +} + +concept StringRegexValidator { + o String regex +} + +concept DoubleDomainValidator { + o Double lower optional + o Double upper optional +} + +concept IntegerDomainValidator { + o Integer lower optional + o Integer upper optional +} + +concept LongDomainValidator { + o Long lower optional + o Long upper optional +} + +concept DoubleFieldDeclaration extends FieldDeclaration { + o DoubleDefault defaultValue optional + o DoubleDomainValidator validator optional +} + +concept IntegerFieldDeclaration extends FieldDeclaration { + @FormEditor("hide", true) + o IntegerDefault defaultValue optional + @FormEditor("hide", true) + o IntegerDomainValidator validator optional +} + +concept LongFieldDeclaration extends FieldDeclaration { + @FormEditor("hide", true) + o LongDefault defaultValue optional + @FormEditor("hide", true) + o LongDomainValidator validator optional +} + +concept RelationshipDeclaration extends FieldDeclaration { + @FormEditor("title", "Type Name", "selectOptions", "types") + o TypeIdentifier type +} + +abstract concept Import { + o String namespace + o String uri optional +} + +concept ImportAll extends Import { +} + +concept ImportType extends Import { + o TypeIdentifier identifier +} + +concept ModelFile { + o String namespace default="my.namespace" + @FormEditor("hide", true) + o Import[] imports optional + @FormEditor("title", "Classes") + o ClassDeclaration[] declarations optional +} +`; + +/** + * Create a metamodel manager (for validation against the metamodel) + * @return {*} the metamodel manager + */ +function createMetaModelManager() { + const metaModelManager = new ModelManager(); + metaModelManager.addModelFile(metaModelCto, 'concerto.metamodel'); + return metaModelManager; +} + /** * Create metamodel for a field * @param {object} ast - the AST for the field @@ -200,7 +426,7 @@ function declToMetaModel(ast) { } /** - * Export metamodel + * Export metamodel from an AST * @param {object} ast - the AST for the model * @return {object} the metamodel for this model */ @@ -247,6 +473,15 @@ function modelToMetaModel(ast) { return metamodel; } +/** + * Export metamodel from a model file + * @param {object} modelFile - the AST for the model + * @return {object} the metamodel for this model + */ +function modelFileToMetaModel(modelFile) { + return modelToMetaModel(modelFile.ast); +} + /** * Create a field string from a metamodel * @param {object} mm - the metamodel @@ -348,10 +583,17 @@ function declFromMetaModel(mm) { /** * Create a model string from a metamodel - * @param {object} mm - the metamodel + * @param {object} metaModel - the metamodel * @return {string} the string for that model */ -function modelFromMetaModel(mm) { +function ctoFromMetaModel(metaModel) { + const metaModelManager = createMetaModelManager(); + const factory = new Factory(metaModelManager); + const serializer = new Serializer(factory, metaModelManager); + // First validate the metaModel + const object = serializer.fromJSON(metaModel); + const mm = serializer.toJSON(object); + let result = ''; result += `namespace ${mm.namespace}`; if (mm.imports && mm.imports.length > 0) { @@ -375,7 +617,19 @@ function modelFromMetaModel(mm) { return result; } +/** + * Export metamodel from a model string + * @param {object} model - the string for the model + * @return {object} the metamodel for this model + */ +function ctoToMetaModel(model) { + const ast = parser.parse(model); + return modelToMetaModel(ast); +} + module.exports = { - modelToMetaModel, - modelFromMetaModel, + metaModelCto, + modelFileToMetaModel, + ctoToMetaModel, + ctoFromMetaModel, }; diff --git a/packages/concerto-core/lib/introspect/modelfile.js b/packages/concerto-core/lib/introspect/modelfile.js index 0bfb6134c..127a56c43 100644 --- a/packages/concerto-core/lib/introspect/modelfile.js +++ b/packages/concerto-core/lib/introspect/modelfile.js @@ -17,7 +17,6 @@ const packageJson = require('../../package.json'); const semver = require('semver'); const parser = require('./parser'); -const MetaModel = require('./metamodel'); const AssetDeclaration = require('./assetdeclaration'); const EnumDeclaration = require('./enumdeclaration'); const ConceptDeclaration = require('./conceptdeclaration'); @@ -573,14 +572,6 @@ class ModelFile { } } - /** - * Export metamodel - * @return {object} the metamodel for this model file - */ - toMetaModel() { - return MetaModel.modelToMetaModel(this.ast); - } - /** * Populate from an AST * @param {object} ast - the AST obtained from the parser diff --git a/packages/concerto-core/test/introspect/metamodel.js b/packages/concerto-core/test/introspect/metamodel.js index a54c63ccb..0912de44a 100644 --- a/packages/concerto-core/test/introspect/metamodel.js +++ b/packages/concerto-core/test/introspect/metamodel.js @@ -29,16 +29,20 @@ describe('MetaModel', () => { const personMetaModel = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../data/model/person.json'), 'utf8')); describe('#toMetaModel', () => { + it('should convert a CTO model to its metamodel', () => { + const mm1 = MetaModel.ctoToMetaModel(personModel); + mm1.should.deep.equal(personMetaModel); + }); - it('should convert CTO file to its metamodel', () => { + it('should convert a ModelFile to its metamodel', () => { const modelManager1 = new ModelManager(); const mf1 = new ModelFile(modelManager1, personModel); - const mm1 = mf1.toMetaModel(); + const mm1 = MetaModel.modelFileToMetaModel(mf1); mm1.should.deep.equal(personMetaModel); - const model2 = MetaModel.modelFromMetaModel(mm1); + const model2 = MetaModel.ctoFromMetaModel(mm1); const modelManager2 = new ModelManager(); const mf2 = new ModelFile(modelManager2, model2); - const mm2 = mf2.toMetaModel(); + const mm2 = MetaModel.modelFileToMetaModel(mf2); mm2.should.deep.equal(personMetaModel); }); });