const { __errorContinuable, __httpReq, __wHttpReq, __postInternalErrorMsg, __postMessage, __sequenceNumber, __successContinuable, __uri_to_id } = require("../__worker"); const _do = require("../___do"); const _utils = require('../utils'); const _mmmk = require("../mmmk"); const _fs = _do.convert(require('fs'), ['readFile', 'writeFile', 'readdir']); const _fspp = _do.convert(require('../___fs++'), ['mkdirs']); module.exports = { 'interfaces' : [{'method': 'POST', 'url=': '/exportMM2Ecore'}], 'csworker' : function (resp, method, uri, reqData, wcontext) { var actions = [__wHttpReq('GET', '/current.model?wid=' + wcontext.__aswid)]; _do.chain(actions)( function (asdata) { //The generated file will be in the "exported_to_ecore" folder var writeActions = [_fspp.mkdirs('./exported_to_ecore/')]; _do.chain(writeActions)( function () { //This variable will contain the lines of the generated files, i.e. the xml code. var file_contents = ''; //This variable will contain the identifier of the root. var rootNode = null; //This variable represent the abstract syntax. It contains all the information. var as = _utils.jsonp(asdata['data']); //This variable will contain a list of possible roots var listRoots = []; //This variable will contain all the classes to be created var listClasses = []; //This variable will contain all the enumerations to be created var listEnums = []; /** We create the package that will contain all the classes. The name of the package is the name of the metamodel, aside of the MM, if apply. The URI of the metamodel is asked to the user. **/ var packageName = reqData['name']; var nsURI = reqData['uri']; if (reqData['name'].endsWith("MM")) packageName = packageName.substr(0, packageName.length - 2); /** This object maps how the type is written. For example, in AToMPM it's "string", while in Ecore it's "EString" **/ var dataType = { 'string': 'EString', 'int': 'EInt', 'float': 'EFloat', 'boolean': 'EBoolean', 'code': 'EString' }; /** This function removes an element of an array. **/ function remove(array, elem) { var index = array.indexOf(elem); if (index !== -1) { array.splice(index, 1); } } /** This function returns true if the element is in the array. **/ function inside(array, elem) { for (var i = 0; i < array.length; i++) { if (array[i] == elem) return true; } return false; } /** This function will search if the class, specified by its identifier (nodeIdentifier), is a subclass of another one, i.e. if it inherits from another class. It will return the name of the superclass, if it exists. If the class has multiple inheritance, it will return the last one that was found since ecore doesn't allow multiple inheritance. **/ function findSuperClass(nodeIdentifier) { var superclassName = null; var superclassKey = null; for (var edge in as.edges) { if (as.edges[edge].src == nodeIdentifier) { var linkKey = as.edges[edge].dest; if (isInheritanceLink(linkKey)) superclassKey = linkDestination(linkKey); } } if (superclassKey != null) superclassName = as.nodes[superclassKey].name.value; return superclassName; } /** This function will return the identifier of the destination of a link, specified by its identifier. **/ function linkDestination(linkIdentifier) { var cleDest = null; for (var dest in as.edges) { if (as.edges[dest].src == linkIdentifier) cleDest = as.edges[dest].dest; } return cleDest; } /** This function will return true if the class is an inheritance link. It will return false otherwise. **/ function isInheritanceLink(nodeIdentifier) { return as.nodes[nodeIdentifier]["$type"].endsWith("Inheritance"); } /** This function will return true if the class is an association link. It will return false otherwise. **/ function isAssociationLink(nodeIdentifier) { return as.nodes[nodeIdentifier]["$type"].endsWith("Association"); } /** This function will return true if the class is an actual class. It will return false otherwise. **/ function isClass(nodeIdentifier) { return as.nodes[nodeIdentifier]["$type"].endsWith("Class"); } /** This function searches if a root exists. A root is a class from which all the other classes are accessible. The process : - find all the classes; - eliminate all the classes that are the end of a link, that means they are accessible; - if listNodes contains only an element, that is the root. Else, there is no root. **/ function hasRoot() { var listNodes = []; for (var key in as.nodes) { if (isClass(key)) listNodes.push(key); } for (var i = 0; i < as.edges.length; i += 2) { if (as.edges[i].src != as.edges[i + 1].dest) remove(listNodes, as.edges[i + 1].dest); } return listNodes; } /** This function returns the root. If none was found, it will create one. **/ function setRoot() { listRoots = hasRoot(); if (listRoots.length == 1) { rootNode = listRoots[0]; reformRootClass(rootNode); } else createRootClass(); } /** This function creates a root and assigns all the possible roots as references. **/ function createRootClass() { var root = {}; root.name = packageName + 'Root'; var refers = []; for (var i = 0; i < listRoots.length; i++) { reformRootClass(listRoots[i]); var refType = as.nodes[listRoots[i]].name.value; var refName = refType + 'Link'; var ref = createEReference(refName, "containment", refType, [1, -1]); refers.push(ref); } root.references = refers; root.attributes = []; listClasses.push(root); } /** This function change all the linkType property of each reference of the node to containment. The node is the root or a possible one. **/ function reformRootClass(rootNode) { var rootClass = createEClass(rootNode); for (var i = 0; i < rootClass.references.length; i++) rootClass.references[i].linkType = "containment"; listClasses.push(rootClass); } /** This function creates an EClassifier. Input: the identifier of the node Output: an object with all the needed properties **/ function createEClass(nodeIdentifier) { var eClass = {}; var node = as.nodes[nodeIdentifier]; eClass.name = node.name.value; eClass.abstract = node.abstract.value; eClass.superclass = findSuperClass(nodeIdentifier); eClass.references = findReferences(nodeIdentifier); eClass.attributes = findAttributes(eClass, nodeIdentifier); return eClass; } /** This function creates an EReference. Input: the name, linktype, type and bounds of the reference Output: an object with all the needed properties **/ function createEReference(name, linktype, type, bounds) { var eReference = {}; eReference.name = name; eReference.linkType = linktype; //containment or visual if (bounds != null) { if (bounds[0] != 0) eReference.lowerBound = bounds[0]; if (bounds[1] == 'inf') eReference.upperBound = -1; else if (bounds[1] != 1) //else we have the value 1 by default in ecore eReference.upperBound = bounds[1]; } eReference.type = type; //eType, type of the destination class return eReference; } /** This function create an EAttribute. Input: the name, datatype, default value of the attribute and the booleans eenum, map and list Output: an object with all the needed properties **/ function createEAttribute(name, datatype, defaultValue, eenum, list, map) { var eAttribute = {}; eAttribute.name = name; eAttribute.complexType = (eenum || list || map); eAttribute.list = list; eAttribute.eenum = eenum; eAttribute.map = map; if (!eAttribute.complexType) { eAttribute.dataType = dataType[datatype]; if (defaultValue != '') eAttribute.defaultValue = defaultValue; } else setDataType(eAttribute, datatype, eenum, list, map, defaultValue); return eAttribute; } /** This function is to assign the datatype and related variables to an attribute, but just for complex types, i.e. list or ENUM. **/ function setDataType(eAttribute, datatype, eenum, list, map, defaultValue) { if (list) setListType(eAttribute, datatype); else if (eenum) setEnumType(eAttribute, datatype, defaultValue); else if (map) setMapType(eAttribute, datatype, defaultValue); } /** This function sets the default value and matches the datatype of a list attribute. The matching types are contained in the object dataType. **/ function setListType(eAttribute, datatype) { var listType = datatype.split('<')[1].split('>')[0]; listType = dataType[listType]; eAttribute.dataType = listType; } /** This function sets the datatype (and the default value?) of an enumeration attribute. **/ function setEnumType(eAttribute, datatype, defaultValue) { var name = eAttribute.name; var nameEnum = name.charAt(0).toUpperCase() + name.slice(1); eAttribute.dataType = nameEnum; eAttribute.defaultValue = defaultValue; createEnumClass(nameEnum, datatype); } /** This function creates an Enum class related to an enumeration attribute. **/ function createEnumClass(nameEnum, datatype) { var eEnum = {}; eEnum.name = nameEnum; var options = datatype.split('(')[1].split(')')[0]; options = options.split(', '); eEnum.options = options; listEnums.push(eEnum); } /** This function sets the datatype (and the default value?) of a map attribute. **/ function setMapType(eAttribute, datatype, defaultValue) { //var keys = datatype.split('[')[1].split(']')[0]; //keys = keys.split(','); var values = datatype.split(']')[1].split('[')[1].split(']')[0]; values = values.split(','); //for(var i = 0; i < keys.length; i++) // createMapClass(keys[i], values[i]); } /** This function finds all the attributes of a class. Input: the class identifier Output: a list of all the attributes of the class **/ function findAttributes(classe, nodeIdentifier) { var attributesList = []; var node = as.nodes[nodeIdentifier].attributes['value']; for (var attr in node) { var name = node[attr].name; var defaultValue = node[attr]['default']; var datatype = node[attr].type; var eenum = false; var list = false; var map = false; if (datatype.startsWith("list")) list = true; else if (datatype.startsWith("ENUM")) eenum = true; else if (datatype.startsWith("map")) map = true; var attribute = createEAttribute(name, datatype, defaultValue, eenum, list, map); attributesList.push(attribute); } return attributesList; } /** This function finds the cardinalities of a reference. Input: the class identifier (nodeIdentifier) and the reference name Output: a list of bounds (min and max) **/ function findCardinalities(nodeIdentifier, referenceName) { var bounds = []; var cardinal = as.nodes[nodeIdentifier].cardinalities['value']; for (var car in cardinal) { if (cardinal[car].type == referenceName) { bounds.push(cardinal[car].min); bounds.push(cardinal[car].max); } } return bounds; } /** This function finds all the references of a class. Input: the class identifier Output: a list of all the references of the class **/ function findReferences(nodeIdentifier) { var referencesList = []; for (var edge in as.edges) { if (as.edges[edge].src == nodeIdentifier) { var linkKey = as.edges[edge].dest; if (isAssociationLink(linkKey)) { var name = as.nodes[linkKey].name.value; var linktype = as.nodes[linkKey].linktype['value']; if (nodeIdentifier == rootNode || inside(listRoots, nodeIdentifier)) linktype = "containment"; var bounds = findCardinalities(nodeIdentifier, name); var dest = linkDestination(linkKey); var type = as.nodes[dest].name.value; var reference = createEReference(name, linktype, type, bounds); referencesList.push(reference); } } } return referencesList; } /** This function returns what's to be written in the file in the beginning, including the package's name and the URI. **/ function writeHeader() { var header = ' \n'; header += ' \n'; return header; } /** This function returns what's to be written in the file in the case of an EClass. **/ function writeEClassifier(classe) { var classLine = ' \n'; for (var i = 0; i < classe.attributes.length; i++) classLine += writeEAttribute(classe.attributes[i]); for (var i = 0; i < classe.references.length; i++) classLine += writeEReference(classe.references[i]); classLine += ' \n'; } else classLine += '"/> \n'; return classLine; } /** This function returns what's to be written in the file in the case of an EAttribute. **/ function writeEAttribute(attr) { var attrLine = ' \n'; } return attrLine; } /** This function returns what's to be written in a Map attribute. **/ function writeMapAttribute(attr) { var mapAttr = ''; mapAttr += '" upperBound="-1" transient="true"> \n'; mapAttr += writeEGenericType("EMap"); mapAttr += ' \n'; return mapAttr; } /** This function returns what's to be written in a GenericType. **/ function writeEGenericType(datatype) { var gentype = ' \n'; gentype += writeETypeArguments(["EEList", "EString"]); gentype += writeETypeArguments(["EEList", "EDataType"]); gentype += ' \n'; return gentype; } /** This function returns what's to be written in a ETypeArguments. **/ function writeETypeArguments(types) { var typeArg = ' \n'; typeArg += writeSingleETypeArguments(types[1]); typeArg += ' \n'; return typeArg; } /** This function returns what's to be written in a ETypeArguments. **/ function writeSingleETypeArguments(type) { var singleTypeArg = ' \n'; return singleTypeArg; } /** This function returns what's to be written in the file in the case of an EReference. **/ function writeEReference(ref) { var refLine = ' \n'; for (var i = 0; i < enume.options.length; i++) enumLine += ' \n'; enumLine += ' \n'; return enumLine; } /** This function creates the classes then put them in the variable listClasses. **/ function populateListClasses() { for (var node in as.nodes) { if (!(inside(listRoots, node)) && isClass(node)) { var eclass = createEClass(node); listClasses.push(eclass); } } } /** This function will write everything (classes, attributes, references, enumerations) in a string. **/ function writeFile() { file_contents += writeHeader(); for (var i = 0; i < listClasses.length; i++) file_contents += writeEClassifier(listClasses[i]); for (var i = 0; i < listEnums.length; i++) file_contents += writeEEnum(listEnums[i]); file_contents += ''; } /**************************************************************************************************************************************************************** The following is like the main class of this file. It will call the appropriate functions in order to create the export file. ****************************************************************************************************************************************************************/ //creation of root and, if apply, possible roots setRoot(); //creation of classes, including their attributes and references populateListClasses(); //write the file writeFile(); _fs.writeFileSync('./exported_to_ecore/' + packageName + 'Metamodel.ecore', file_contents); __postMessage({ 'statusCode': 200, 'respIndex': resp }); }, function (writeErr) { __postInternalErrorMsg(resp, writeErr); } ); }, function (err) { __postInternalErrorMsg(resp, err); } ); } , 'asworker' : function (resp, method, uri, reqData, wcontext) { __postMessage( { 'statusCode': 200, 'respIndex': resp }); } };