UNPKG

webgme-bip

Version:

Design studio for component-based modeling with BIP.

505 lines (477 loc) 24.1 kB
/*globals define*/ /*jshint node:true, browser:true*/ /** * Generated by PluginGenerator 1.7.0 from webgme on Mon Mar 20 2017 21:44:14 GMT-0500 (CDT). * A plugin that inherits from the PluginBase. To see source code documentation about available * properties and methods visit %host%/docs/source/PluginBase.html. */ define([ 'plugin/PluginConfig', 'text!./metadata.json', 'plugin/PluginBase', 'q', 'plugin/JavaBIPEngine/JavaBIPEngine/ArithmeticExpressionParser', 'plugin/ArchitectureSpecGenerator/ArchitectureSpecGenerator/ArchitectureSpecGenerator', 'plugin/BehaviorSpecGenerator/BehaviorSpecGenerator/BehaviorSpecGenerator', 'common/util/ejs', 'text!./Templates/caseStudy.ejs', 'common/util/guid' ], function ( PluginConfig, pluginMetadata, PluginBase, Q, ArithmeticExpressionParser, ArchitectureSpecGenerator, BehaviorSpecGenerator, ejs, caseStudyTemplate, guid) { 'use strict'; pluginMetadata = JSON.parse(pluginMetadata); /** * Initializes a new instance of JavaBIPEngine. * @class * @augments {PluginBase} * @classdesc This class represents the plugin JavaBIPEngine. * @constructor */ var JavaBIPEngine = function () { // Call base class' constructor. PluginBase.call(this); this.pluginMetadata = pluginMetadata; }; /** * Metadata associated with the plugin. Contains id, name, version, description, icon, configStructue etc. * This is also available at the instance at this.pluginMetadata. * @type {object} */ JavaBIPEngine.metadata = pluginMetadata; // Prototypical inheritance from PluginBase. JavaBIPEngine.prototype = Object.create(PluginBase.prototype); JavaBIPEngine.prototype.constructor = JavaBIPEngine; /** * Main function for the plugin to execute. This will perform the execution. * Notes: * - Always log with the provided logger.[error,warning,info,debug]. * - Do NOT put any user interaction logic UI, etc. inside this method. * - callback always has to be called even if error happened. * * @param {function(string, plugin.PluginResult)} callback - the result callback */ JavaBIPEngine.prototype.main = function (callback) { // Use self to access core, project, result, logger etc from PluginBase. // These are all instantiated at this point. var self = this, path, fs, artifact, nodes; if (typeof window === 'undefined') { path = process.cwd(); fs = require('fs'); if (!fs.existsSync('projectOutputs')) { fs.mkdirSync('projectOutputs'); } path += '/projectOutputs/' + self.core.getAttribute(self.activeNode, 'name') + guid(); path = path.replace(/\s+/g, ''); } self.loadNodeMap(self.activeNode) .then(function (nodes_) { var glueObject; nodes = nodes_; glueObject = ArchitectureSpecGenerator.getGeneratedFile(self, nodes, self.activeNode); if (fs && path && glueObject.violations.length === 0) { try { fs.statSync(path); } catch (err) { if (err.code === 'ENOENT') { fs.mkdirSync(path); } } fs.writeFileSync(path + '/' + 'Glue.xml', glueObject.fileContent, 'utf8'); } else if (glueObject.violations.length > 0) { glueObject.violations.forEach(function (violation) { self.createMessage(violation.node, violation.message, 'error'); }); throw new Error('Architecture model has ' + glueObject.violations.length + ' violation(s), see messages for details.'); } return BehaviorSpecGenerator.getGeneratedFiles(self, nodes, self.activeNode); }) .then(function (behaviorObject) { var behaviorFiles, file, violations, inconsistencies, fileName, testInfo, pathArrayForFile, compilationOutput, compilationjson, currentConfig = self.getCurrentConfig(), filesToAdd = {}, architectureModel = {}; if (fs && path && behaviorObject.violations.length === 0) { behaviorFiles = behaviorObject.files; self.logger.debug(behaviorFiles.length); for (file in behaviorFiles) { fs.writeFileSync(path + '/' + file, behaviorFiles[file], 'utf8'); } } else if (behaviorObject.violations.length > 0) { behaviorObject.violations.forEach(function (violation) { self.createMessage(violation.node, violation.message, 'error'); }); throw new Error('Behavior model has ' + behaviorObject.violations.length + ' violation(s), see messages for details.'); } violations = self.hasViolations(nodes); if (violations.length > 0) { violations.forEach(function (violation) { self.createMessage(violation.node, violation.message, 'error'); }); throw new Error('Parameterized model has ' + violations.length + ' violation(s), see messages for details.'); } architectureModel = self.getArchitectureModel(nodes); inconsistencies = self.checkConsistency(architectureModel, nodes); self.logger.debug('number of inconsistencies: '+ inconsistencies.length); if (inconsistencies.length === 0) { testInfo = self.generateTestInfo(architectureModel, path); fileName = testInfo.className + '.java'; pathArrayForFile = fileName.split('/'); filesToAdd[fileName] = ejs.render(caseStudyTemplate, testInfo); if (path && fs) { if (pathArrayForFile.length >= 1) { self.compileAndSimulate(behaviorFiles, filesToAdd[fileName], fileName, path, fs); compilationOutput = fs.readFileSync(path + '/engineOutput.json', 'utf8'); compilationjson = JSON.parse(compilationOutput); if(compilationjson.output.length > currentConfig['transitions']) { compilationjson.output.splice(currentConfig['transitions'], compilationjson.output.length-currentConfig['transitions']); } filesToAdd['engineOutput.json'] = JSON.stringify(compilationjson); } } artifact = self.blobClient.createArtifact('EngineInputAndOutput'); if (path && fs) { return Q.all([ artifact.addFile(fileName, filesToAdd[fileName]), artifact.addFile('engineOutput.json', filesToAdd['engineOutput.json']) ]); } else { return artifact.addFiles(filesToAdd); } } else { inconsistencies.forEach(function (inconsistency) { self.createMessage(inconsistency.node, inconsistency.message, 'error'); }); throw new Error('Model has ' + inconsistencies.length + ' inconsistencies, see messages for details.'); } }) .then(function (fileHashes) { self.logger.debug(fileHashes); fileHashes.forEach(function (fileHash) { self.result.addArtifact(fileHash); }); if (path && fs) { self.core.setAttribute(self.activeNode, 'engineOutput', fileHashes[1]); self.logger.debug('before save', self.currentHash); return self.save('Engine output added to results'); } }) .then(function () { self.logger.debug('after save', self.currentHash); //TODO: Better name of tag.. return self.project.createTag('Engine' + Date.now(), self.currentHash); }) .then(function () { return artifact.save(); }) .then(function (artifactHash) { self.result.addArtifact(artifactHash); self.result.setSuccess(true); callback(null, self.result); }) .catch(function (err) { self.logger.error(err.stack); // Result success is false at invocation. callback(err, self.result); }); }; JavaBIPEngine.prototype.compileAndSimulate = function (behaviorFiles, fileValue, fileName, path, fs) { var file, child, execSync, self = this, compileCode = '', simulateCode = ''; execSync = require('child_process').execSync; try { fs.statSync(path); } catch (err) { if (err.code === 'ENOENT') { fs.mkdirSync(path); } } fs.writeFileSync(path + '/' + fileName, fileValue, 'utf8'); compileCode += '#!/bin/bash '+ '\n\n'; for (file in behaviorFiles) { compileCode += 'javac -cp "' + process.cwd() + '/engineLibraries/*" ' + path + '/' + file + '\n\n'; } compileCode += 'javac -cp "' + path + '/:' + process.cwd() + '/engineLibraries/*" ' + path + '/' + fileName; self.logger.debug(compileCode); fs.writeFileSync(path + '/compile.sh', compileCode, 'utf8'); //self.sendNotification('Compilation script has been successfully created.'); //child = execSync('chmod 775 ' + path + '/compile.sh'); self.sendNotification('Compiling Java code..'); try { child = execSync('/bin/bash ' + path + '/compile.sh'); } catch (e) { self.logger.error(e.stderr); throw e; } simulateCode = 'java -cp "' + path + '/:' + process.cwd() + '/engineLibraries/*" org.junit.runner.JUnitCore ' + fileName.slice(0, -5); self.logger.debug(simulateCode); fs.writeFileSync(path + '/simulate.sh', simulateCode, 'utf8'); self.sendNotification('Compilation successful.'); //child = execSync('chmod 775 ' + path + '/simulate.sh'); self.sendNotification('Calling simulate..'); try { child = execSync('/bin/bash ' + path + '/simulate.sh'); } catch (e) { self.logger.error('stderr ' + e.stderr); throw e; } self.sendNotification('Simulation successful.'); }; JavaBIPEngine.prototype.getArchitectureModel = function (nodes) { var self = this, path, node, cardinality, child, srcConnectorEnd, dstConnectorEnd, end, currentConfig = self.getCurrentConfig(), architectureModel = { componentTypes: [], ports: [], subConnectors: [], connectorEnds: [], connections: [], connectors: [] }; for (path in nodes) { node = nodes[path]; //TODO: Update for hierarchical models if (!self.isMetaTypeOf(self.core.getParent(node), self.META.ArchitectureStyle) && !self.isMetaTypeOf(self.core.getParent(node), self.META.ComponentTypesLibrary)) { if (self.isMetaTypeOf(node, self.META.ComponentType)) { cardinality = self.core.getAttribute(node, 'cardinality'); architectureModel.componentTypes.push(node); node.name = self.core.getAttribute(node, 'name'); node.path = path; for (child of self.core.getChildrenPaths(node)) { if (self.isMetaTypeOf(nodes[child], self.META.EnforceableTransition)) { architectureModel.ports.push(nodes[child]); if (/^[a-z]$/.test(cardinality)) { node.cardinalityParameter = cardinality; self.logger.debug('cardinalityParameter ' + node.cardinalityParameter); cardinality = currentConfig[cardinality]; } self.logger.debug('cardinality: ' + cardinality); nodes[child].cardinality = cardinality; } } node.cardinalityValue = cardinality; self.logger.debug('cardinalityValue ' + node.cardinalityValue); } else if (self.isMetaTypeOf(node, self.META.Connector)) { /* If the connector is binary */ if (self.getMetaType(nodes[self.core.getPointerPath(node, 'dst')]) !== self.META.Connector) { architectureModel.connectors.push(node); srcConnectorEnd = nodes[self.core.getPointerPath(node, 'src')]; dstConnectorEnd = nodes[self.core.getPointerPath(node, 'dst')]; srcConnectorEnd.connector = node; dstConnectorEnd.connector = node; node.ends = [srcConnectorEnd, dstConnectorEnd]; /* If it is part of an n-ary connector */ } else { architectureModel.subConnectors.push(node); } } else if (self.isMetaTypeOf(node, self.META.Connection) && self.getMetaType(node) !== node) { architectureModel.connections.push(node); end = nodes[self.core.getPointerPath(node, 'src')]; if (self.getMetaType(end) !== self.META.Connector) { architectureModel.connectorEnds.push(end); end.degree = self.core.getAttribute(end, 'degree'); end.multiplicity = self.core.getAttribute(end, 'multiplicity'); } //TODO: add export ports for hierarchical connector motifs } } } return architectureModel; }; JavaBIPEngine.prototype.checkConnectorConsistency = function (architectureModel) { var subPart, end, matchingFactor, type, newMatchingFactor, inconsistencies = []; for (subPart of architectureModel.connectors) { matchingFactor = -1; for (end of subPart.ends) { if (!/^[0-9]+$/.test(end.degree)) { for (type of architectureModel.componentTypes) { if (type.cardinalityParameter !== undefined && end.degree.includes(type.cardinalityParameter)) { end.degree = end.degree.replace(type.cardinalityParameter, type.cardinalityValue); } } //TODO: Change the eval end.degree = eval(end.degree); } if (!/^[0-9]+$/.test(end.multiplicity)) { for (type of architectureModel.componentTypes) { if (type.cardinalityParameter !== undefined && end.multiplicity.includes(type.cardinalityParameter)) { end.multiplicity = end.multiplicity.replace(type.cardinalityParameter, type.cardinalityValue); } } //TODO: Change the eval end.multiplicity = eval(end.multiplicity); if (end.multiplicity > end.cardinality) { inconsistencies.push({ node: end, message: 'Multiplicity of connector end [' + this.core.getPath(end) + '] is greater than the cardinality of the corresponding component type.' }); } } newMatchingFactor = (end.degree * end.cardinality) / end.multiplicity; if (/^[0-9]+$/.test(newMatchingFactor)) { if (matchingFactor === -1) { matchingFactor = newMatchingFactor; } else if (matchingFactor !== newMatchingFactor) { inconsistencies.push({ node: subPart, message: 'Matching factors (cardinality * degree / multiplicity) of ends in connector motif [' + this.core.getPath(subPart) + '] are not equal.' }); break; } } else { inconsistencies.push({ node: end, message: 'Matching factor (cardinality * degree / multiplicity) [' + newMatchingFactor +'] of connector end [' + this.core.getPath(end) + '] is not a non-negative integer.' }); } } } return inconsistencies; }; JavaBIPEngine.prototype.checkConsistency = function (architectureModel, nodes) { var self = this, subPart, node, srcNode, end, connector; /*1. Checks whether multiplicities are less or equal to corresponding cardinalities 2. Checks equality of matching factors of the same connector */ for (subPart of architectureModel.connections) { node = nodes[self.core.getPointerPath(subPart, 'src')]; if (self.getMetaType(node) !== self.META.Connector) { node.cardinality = nodes[self.core.getPointerPath(subPart, 'dst')].cardinality; } } for (subPart of architectureModel.subConnectors) { node = nodes[self.core.getPointerPath(subPart, 'dst')]; srcNode = nodes[self.core.getPointerPath(node, 'src')]; end = nodes[self.core.getPointerPath(subPart, 'src')]; if (architectureModel.connectors.includes(node)) { node.ends.push(end); end.connector = node; } else if (architectureModel.connectorEnds.includes(srcNode)) { for (connector in architectureModel.connectors) { if (connector.ends.includes(srcNode)) { connector.ends.push(end); end.connector = connector; } } } } return self.checkConnectorConsistency(architectureModel); }; JavaBIPEngine.prototype.generateTestInfo = function (architectureModel, path) { var self = this, currentConfig = this.getCurrentConfig(), info = { className: self.core.getAttribute(self.activeNode, 'name'), path: path, componentType: architectureModel.componentTypes, noOfRequiredTransitions: currentConfig['transitions'] }; info.className = info.className.replace(/\s+/g, ''); self.logger.debug('Engine test name: '+ info.className); return info; }; JavaBIPEngine.prototype.checkMultiplicityAndDegree = function (connectorEnds, violations, cardinalityRegEx) { var end, multiplicity, degree, self = this; for (end of connectorEnds) { // Checks multiplicities and degrees multiplicity = self.core.getAttribute(end, 'multiplicity'); degree = self.core.getAttribute(end, 'degree'); try { ArithmeticExpressionParser.parse(multiplicity); } catch (e) { violations.push({ node: end, message: 'Multiplicity [' + multiplicity + '] of component end [' + this.core.getPath(end) + '] is not a valid arithmetic expression with integers and lower-case parameters defined by the cardinalities of the model. Please change it.' }); } try { ArithmeticExpressionParser.parse(degree); } catch (e) { violations.push({ node: end, message: 'Degree [' + degree + '] of component end [' + this.core.getPath(end) + '] is not a valid arithmetic expression with integers and lower-case variables: ' }); } cardinalityRegEx.lastIndex = 0; if (!(cardinalityRegEx.test(multiplicity))) { violations.push({ node: end, message: 'Multiplicity [' + multiplicity + '] of component end [' + this.core.getPath(end) + '] is not a natural number or an arithmetic expression of cardinality parameters.' }); } cardinalityRegEx.lastIndex = 0; if (!cardinalityRegEx.test(degree)) { violations.push({ node: end, message: 'Degree [' + degree + '] of component end [' + this.core.getPath(end) + '] is not a natural number or an arithmetic expression of cardinality parameters.' }); } } return violations; }; JavaBIPEngine.prototype.hasViolations = function (nodes) { var violations = [], cardinalities = [], connectorEnds = [], self = this, nodePath, node, cardinality, violations_, regExpArray, cardinalityRegEx; /* Check that multiplicities, degrees are valid arithmetic expressions of cardinalities */ for (nodePath in nodes) { node = nodes[nodePath]; if (self.isMetaTypeOf(node, this.META.ComponentType)) { // Checks cardinality whether it is non zero positive integer or a lower-case character if (/^([a-z]|[1-9][0-9]*){1}$/.test(self.core.getAttribute(node, 'cardinality'))) { cardinalities.push(self.core.getAttribute(node, 'cardinality')); } else { violations.push({ node: node, message: 'Cardinality [' + this.core.getAttribute(node, 'cardinality') + '] of component type [' + this.core.getAttribute(node, 'name') + '] is not a natural non-zero number or a character.' }); } } else if (self.isMetaTypeOf(node, this.META.Synchron) || self.isMetaTypeOf(node, this.META.Trigger)) { connectorEnds.push(node); } } regExpArray = ['^[', '+*\\-\\\/', '\(\)', '0-9']; for (cardinality of cardinalities) { if (/^[a-z]$/.test(cardinality)) { regExpArray.push(cardinality); } } regExpArray.push.apply(regExpArray, [']', '+$']); cardinalityRegEx = new RegExp(regExpArray.join(''), 'g'); self.logger.debug(cardinalityRegEx); violations_ = self.checkMultiplicityAndDegree(connectorEnds, violations, cardinalityRegEx); if (violations_ > 0 ) { violations = violations.concat(violations_); } return violations; }; return JavaBIPEngine; });