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=': '/exportM2Ecore'}], '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 xmi code. var file_contents = ''; //This variable represent the abstract syntax. It contains all the information. var as = _utils.jsonp(asdata['data']); //This variable will contain the root and all the other elements contained by the root. var root = {}; var listRoots = []; var graph = []; var taken = []; var listCycle = []; /** 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 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 there are classes that form a cycle, add them all to listNodes; - 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) listNodes.push(key); //removing links node for (var edge = 0; edge < as.edges.length; edge = edge + 2) remove(listNodes, as.edges[edge].dest); //removing accessible classes for (var edge = 0; edge < as.edges.length; edge = edge + 2) { if (as.edges[edge].src != as.edges[edge + 1].dest) remove(listNodes, as.edges[edge + 1].dest); } //if there are graph cycles, add the nodes in listNodes constructGraph(); for (var edge = 0; edge < as.edges.length; edge = edge + 2) { var id = as.edges[edge].src; var cycle = detectCycle(id, id); if (cycle && !inside(listNodes, id)) { listNodes.push(id); listCycle.push(id); } } return listNodes; } /** This function returns the root. If none was found, it will create one. **/ function setRoot() { listRoots = hasRoot(); if (listRoots.length == 1) root = createNode(listRoots[0]); else root = createRootClass(listRoots); } /** This function creates a root and assigns all the possible roots as references. **/ function createRootClass(list) { var node = {}; node.name = reqData['root']; var contain = []; for (var i = 0; i < list.length; i++) contain.push(createNode(list[i])); node.contain = contain; return node; } /** 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 createNode(identifier) { var node = {}; var elem = as.nodes[identifier]; var linkType = findType(identifier); if (inside(listRoots, identifier)) node.linkType = linkType + 'Link'; else node.linkType = linkType; node.id = identifier; var keys = Object.keys(elem); remove(keys, "$type"); var attr = []; for (var i = 0; i < keys.length; i++) attr.push(findAttribute(keys[i], identifier)); node.attributes = attr; node.contain = findContained(identifier); return node; } /** This function creates the appropriate indentation. **/ function space(deep) { var space = ''; for (var i = 0; i < deep; i++) space += ' '; return space; } /** This function finds the link's type of the attribute given the link's identifier. Input : identifier Output : type **/ function findType(linkIdentifier) { var midType = as.nodes[linkIdentifier]["$type"].split("/"); var attrType = midType[midType.length - 1]; return attrType; } /** This function finds all the attributes of an element. **/ function findAttribute(key, keyDest) { var attr = {}; attr.name = key; attr.value = as.nodes[keyDest][key].value; var attrType = as.nodes[keyDest][key].type; if (attr.value.length > 0) { if (attrType.startsWith("list")) attr.list = true; else attr.list = false; } return attr; } /** This function finds all the elements contained in another one, in a recursive way. **/ function findContained(nodeKey) { var listContained = []; if (inside(listCycle, nodeKey) && inside(taken, nodeKey)) return []; for (var k = 0; k < as.edges.length; k++) { if (nodeKey == as.edges[k].src) { if (inside(listCycle, nodeKey)) taken.push(nodeKey); var lien = as.edges[k].dest; var keyDest = as.edges[k + 1].dest; var linkType = findType(lien); var elem = {}; elem.linkType = linkType; elem.attributes = []; var keys = Object.keys(as.nodes[keyDest]); remove(keys, "$type"); for (var i = 0; i < keys.length; i++) { var attr = findAttribute(keys[i], keyDest); if (attr.value.length > 0) elem.attributes.push(attr); } var contain = []; if (!isInRoot(keyDest)) contain = findContained(keyDest); elem.contain = contain; listContained.push(elem); } } return listContained; } /** This function returns true if the node, specified by its identifier, is directly in root.contain, not in one of the subnode. **/ function isInRoot(key) { for (var i = 0; i < listCycle.length; i++) { if (listCycle[i] == key) return true; } return false; } /** This function will write the header of the file including the name of the root and the URI of the metamodel. **/ function writeHeader() { var head = ' \n'; head += '<' + root.name; head += ' xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns="' + reqData['uri'] + '"'; if (root.attributes != null) head += writeAttributes(root, 0); else head += '> \n'; return head; } /** This function writes the attributes of an element. **/ function writeAttributes(node, deep) { var attribut = ''; var listAttr = ''; for (var i = 0; i < node.attributes.length; i++) { var attr = node.attributes[i]; if (attr.list) listAttr += writeListAttribute(attr.value, attr.name, deep + 2); else attribut += ' ' + attr.name + '="' + attr.value + '"'; } attribut += '> \n' + listAttr; return attribut; } /** This function writes a list attribute. **/ function writeListAttribute(list, name, deep) { var liste = ''; for (var i = 0; i < list.length; i++) liste += space(deep) + '<' + name + '>' + list[i] + ' \n'; return liste; } /** This function writes all the contained nodes of the specified node **/ function writeContained(listContained, deep) { var contained = ''; for (var i = 0; i < listContained.length; i++) { contained += space(deep + 2) + '<' + listContained[i].linkType; var attributes = ''; // if (listContained[i].attributes != null) { // } contained += writeAttributes(listContained[i], deep + 2); if (listContained[i].contain.length > 0) contained += writeContained(listContained[i].contain, deep + 2); contained += space(deep + 2) + ' \n'; } return contained; } /** This function will write everything (classes, attributes, references, enumerations) in a string. **/ function writeFile() { file_contents += writeHeader(); file_contents += writeContained(root.contain, 0); file_contents += ''; } /** 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 populates graph. It takes all the classes and register them in graph. graph is a list. **/ function constructGraph() { var list = []; for (var i = 0; i < as.edges.length; i += 2) { var src = as.edges[i].src; if (!inside(list, src)) { list.push(src); var srcNode = {}; srcNode.id = src; var linked = findLinked(src); srcNode.linked = linked; graph.push(srcNode); } } } /** This function detects graph cycle. Input : - id : the identifier of the actual node; - initial : the identifier of the first node of the cycle. Output : - true : there is a graph cycle; - false : no graph cycle. **/ function detectCycle(id, initial) { for (var i = 0; i < graph.length; i++) { if (graph[i].id == id) { if (inside(graph[i].linked, initial)) return true; else { for (var j = 0; j < graph[i].linked.length; j++) return detectCycle(graph[i].linked[j], initial); } return false; } } } /** This function finds all the related class of a class. **/ function findLinked(src) { var listLinked = []; for (var i = 0; i < as.edges.length; i += 2) { if (as.edges[i].src == src && as.edges[i + 1].dest != src) { listLinked.push(as.edges[i + 1].dest); } } return listLinked; } /*************************************************************************************************************************************************************** The following is like the main class of this file. It will call the appropriate functions in order to create the export file. ****************************************************************************************************************************************************************/ setRoot(); writeFile(); _fs.writeFileSync('./exported_to_ecore/' + reqData['name'] + 'Model.xmi', 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 }); } };