aboutsummaryrefslogtreecommitdiffstats
path: root/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/model/Model.js
diff options
context:
space:
mode:
Diffstat (limited to 'dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/model/Model.js')
-rw-r--r--dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/model/Model.js512
1 files changed, 512 insertions, 0 deletions
diff --git a/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/model/Model.js b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/model/Model.js
new file mode 100644
index 0000000000..1e68cd6034
--- /dev/null
+++ b/dox-sequence-diagram-ui/src/main/webapp/lib/ecomp/asdc/sequencer/model/Model.js
@@ -0,0 +1,512 @@
+/*!
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ *
+ * 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.
+ */
+
+import _merge from 'lodash/merge';
+// import jsonschema from 'jsonschema';
+
+import Common from '../common/Common';
+import Metamodel from './Metamodel';
+
+/**
+ * A wrapper for a model instance.
+ */
+export default class Model {
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Construct model from model JSON. JSON is assumed to be in more or less
+ * the correct structure, but it's OK if it's missing IDs.
+ *
+ * @param json initial JSON; will be updated in situ.
+ * @param metamodel Metaobject definition.
+ */
+ constructor(json, metamodel) {
+
+ if (metamodel) {
+ Common.assertInstanceOf(metamodel, Metamodel);
+ }
+
+ this.metamodel = metamodel || Metamodel.getDefault();
+ Common.assertInstanceOf(this.metamodel, Metamodel);
+
+ this.jsonschema = require('./schema/asdc_sequencer_schema.json');
+ this.templates = {
+ defaultModel: require('./templates/default.model.json'),
+ defaultMetamodel: require('./templates/default.metamodel.json'),
+ };
+
+ this.model = this._preprocess(Common.assertType(json, 'Object'));
+ Common.assertPlainObject(this.model);
+
+ this.renumber();
+
+ this.addLifeline = this.addLifeline.bind(this);
+ this.addMessage = this.addMessage.bind(this);
+ this.renumber = this.renumber.bind(this);
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Unwrap to get model object.
+ * @returns {*}
+ */
+ unwrap() {
+ return Common.assertPlainObject(this.model);
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get the metamodel which defines valid states for this model.
+ * @returns Metamodel definition.
+ */
+ getMetamodel() {
+ return Common.assertInstanceOf(this.metamodel, Metamodel);
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Find lifeline by its ID.
+ * @param id lifeline ID.
+ * @returns lifeline object, if found.
+ */
+ getLifelineById(id) {
+ for (const lifeline of this.model.diagram.lifelines) {
+ if (lifeline.id === id) {
+ return lifeline;
+ }
+ }
+ return undefined;
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get message by ID.
+ * @param id message ID.
+ * @returns message if matched.
+ */
+ getMessageById(id) {
+ Common.assertNotNull(id);
+ const step = this.getStepByMessageId(id);
+ if (step) {
+ return step.message;
+ }
+ return undefined;
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get step by message ID.
+ * @param id step ID.
+ * @returns step if matched.
+ */
+ getStepByMessageId(id) {
+ Common.assertNotNull(id);
+ for (const step of this.model.diagram.steps) {
+ if (step.message && step.message.id === id) {
+ return step;
+ }
+ }
+ return undefined;
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Add message to steps.
+ * @returns {{}}
+ */
+ addMessage(index) {
+ const d = this.model.diagram;
+ const step = {};
+ step.message = {};
+ step.message.id = Model._guid();
+ step.message.name = '[Unnamed Message]';
+ step.message.type = 'request';
+ step.message.from = d.lifelines.length > 0 ? d.lifelines[0].id : -1;
+ step.message.to = d.lifelines.length > 1 ? d.lifelines[1].id : -1;
+ if (index >= 0) {
+ d.steps.splice(index, 0, step);
+ } else {
+ d.steps.push(step);
+ }
+ this.renumber();
+ return step;
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Delete message with ID.
+ * @param id to be deleted.
+ */
+ deleteMessageById(id) {
+ Common.assertNotNull(id);
+ const step = this.getStepByMessageId(id);
+ if (step) {
+ const index = this.model.diagram.steps.indexOf(step);
+ if (index !== -1) {
+ this.model.diagram.steps.splice(index, 1);
+ }
+ }
+ this.renumber();
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Add lifeline to lifelines.
+ * @param index optional index.
+ * @returns {{}}
+ */
+ addLifeline(index) {
+ const lifeline = {};
+ lifeline.id = Model._guid();
+ lifeline.name = '[Unnamed Lifeline]';
+ if (index >= 0) {
+ this.model.diagram.lifelines.splice(index, 0, lifeline);
+ } else {
+ this.model.diagram.lifelines.push(lifeline);
+ }
+ this.renumber();
+ return lifeline;
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Delete lifeline with ID.
+ * @param id to be deleted.
+ */
+ deleteLifelineById(id) {
+ Common.assertNotNull(id);
+ this.deleteStepsByLifelineId(id);
+ const lifeline = this.getLifelineById(id);
+ if (lifeline) {
+ const index = this.model.diagram.lifelines.indexOf(lifeline);
+ if (index !== -1) {
+ this.model.diagram.lifelines.splice(index, 1);
+ }
+ }
+ this.renumber();
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Delete all steps corresponding to lifeline.
+ * @param id lifeline ID.
+ */
+ deleteStepsByLifelineId(id) {
+ Common.assertNotNull(id);
+ const steps = this.getStepsByLifelineId(id);
+ for (const step of steps) {
+ this.deleteMessageById(step.message.id);
+ }
+ this.renumber();
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Get all steps corresponding to lifeline.
+ * @param id lifeline ID.
+ * @return steps from/to lifeline.
+ */
+ getStepsByLifelineId(id) {
+ Common.assertNotNull(id);
+ const steps = [];
+ for (const step of this.model.diagram.steps) {
+ if (step.message) {
+ if (step.message.from === id || step.message.to === id) {
+ steps.push(step);
+ }
+ }
+ }
+ return steps;
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Validate model. Disabled, because we removed the jsonschema dependency.
+ * @returns {Array} of validation errors, if any.
+ */
+ validate() {
+ const errors = [];
+ return errors;
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Reorder messages.
+ * @param index message index.
+ * @param afterIndex new (after) index.
+ */
+ reorderMessages(index, afterIndex) {
+ Common.assertType(index, 'Number');
+ Common.assertType(afterIndex, 'Number');
+ const steps = this.model.diagram.steps;
+ const element = steps[index];
+ steps.splice(index, 1);
+ steps.splice(afterIndex, 0, element);
+ this.renumber();
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Reorder lifelines.
+ * @param index lifeline index.
+ * @param afterIndex new (after) index.
+ */
+ reorderLifelines(index, afterIndex) {
+ Common.assertType(index, 'Number');
+ Common.assertType(afterIndex, 'Number');
+ const lifelines = this.model.diagram.lifelines;
+ const element = lifelines[index];
+ lifelines.splice(index, 1);
+ lifelines.splice(afterIndex, 0, element);
+ this.renumber();
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Renumber lifelines and messages.
+ */
+ renumber() {
+ const modelJSON = this.unwrap();
+ let stepIndex = 1;
+ let lifelineIndex = 1;
+ for (const step of modelJSON.diagram.steps) {
+ if (step.message) {
+ step.message.index = stepIndex++;
+ }
+ }
+ for (const lifeline of modelJSON.diagram.lifelines) {
+ lifeline.index = lifelineIndex++;
+ }
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Build a simple, navigable dataset describing fragments.
+ * @returns {{}}, indexed by (stop) message ID, describing fragments.
+ */
+ analyzeFragments() {
+
+ const fData = {};
+
+ let depth = 0;
+ const modelJSON = this.unwrap();
+ const open = [];
+
+ const getData = function g(stop, fragment) {
+ let data = fData[stop];
+ if (!data) {
+ data = { stop, start: [], fragment };
+ fData[stop] = data;
+ }
+ return data;
+ };
+
+ const fragmentsByStart = {};
+ for (const step of modelJSON.diagram.steps) {
+ if (step.message && step.message.fragment) {
+ const message = step.message;
+ const fragment = message.fragment;
+ if (fragment.start) {
+ fragmentsByStart[fragment.start] = fragment;
+ open.push(message.id);
+ depth++;
+ }
+ if (fragment.stop) {
+ if (open.length > 0) {
+ getData(message.id).start.push(open.pop());
+ }
+ depth = Math.max(depth - 1, 0);
+ }
+ }
+ }
+
+ if (open.length > 0) {
+ for (const o of open) {
+ getData(o, fragmentsByStart[o]).start.push(o);
+ }
+ }
+
+ return fData;
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Build a simple, navigable dataset describing occurrences.
+ * @returns a map, indexed by lifeline ID, of objects containing {start:[],stop:[],active[]}.
+ * @private
+ */
+ analyzeOccurrences() {
+
+ const oData = {};
+
+ // A few inline functions. They make this method kinda lengthy, but they
+ // reduce clutter in the class and keep it coherent, so it's OK.
+
+ const getDataByLifelineId = function get(lifelineId) {
+ if (!oData[lifelineId]) {
+ oData[lifelineId] = { active: [], start: {}, stop: {} };
+ }
+ return oData[lifelineId];
+ };
+
+ const contains = function contains(array, value) {
+ return (array && (array.indexOf(value) !== -1));
+ };
+
+ const process = function process(message, lifelineId) {
+ const oRule = message.occurrences;
+ if (oRule) {
+
+ const oDataLifeline = getDataByLifelineId(lifelineId);
+ if (oDataLifeline) {
+
+ // Record all starts.
+
+ if (contains(oRule.start, lifelineId)) {
+ oDataLifeline.active.push(message.id);
+ oDataLifeline.start[message.id] = undefined;
+ }
+
+ // Reconcile with stops.
+
+ if (contains(oRule.stop, lifelineId)) {
+ const startMessageId = oDataLifeline.active.pop();
+ oDataLifeline.stop[message.id] = startMessageId;
+ if (startMessageId) {
+ oDataLifeline.start[startMessageId] = message.id;
+ }
+ }
+ }
+ }
+ };
+
+ // Analyze start and end.
+
+ const modelJSON = this.unwrap();
+ for (const step of modelJSON.diagram.steps) {
+ if (step.message) {
+ const message = step.message;
+ if (message.occurrences) {
+ process(message, message.from);
+ process(message, message.to);
+ }
+ }
+ }
+
+ // Reset active. (We used it, but it's not actually for us; it's for keeping
+ // track of active occurrences when rendering the diagram.)
+
+ for (const lifelineId of Object.keys(oData)) {
+ oData[lifelineId].active = [];
+ }
+
+ // Reconcile the start and end (message ID) maps for each lifeline,
+ // finding a "stop" for every start. Default to starting and stopping
+ // on the same message, which is the same as no occurrence.
+
+ for (const lifelineId of Object.keys(oData)) {
+ const lifelineData = oData[lifelineId];
+ for (const startId of Object.keys(lifelineData.start)) {
+ const stopId = lifelineData.start[startId];
+ if (!stopId) {
+ lifelineData.start[startId] = startId;
+ lifelineData.stop[startId] = startId;
+ }
+ }
+ }
+
+ return oData;
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Preprocess model, adding IDs and whatnot.
+ * @param original to be preprocessed.
+ * @returns preprocessed JSON.
+ * @private
+ */
+ _preprocess(original) {
+
+ const json = _merge({}, this.templates.defaultModel, original);
+ const metamodel = this.metamodel.unwrap();
+ if (!json.diagram.metadata.ref) {
+ if (metamodel.diagram.metadata.id) {
+ json.diagram.metadata.ref = metamodel.diagram.metadata.id;
+ } else {
+ json.diagram.metadata.ref = '$';
+ }
+ }
+
+ for (const lifeline of json.diagram.lifelines) {
+ lifeline.id = lifeline.id || lifeline.name;
+ }
+
+ for (const step of json.diagram.steps) {
+ if (step.message) {
+ step.message.id = step.message.id || Model._guid();
+ const occurrences = step.message.occurrences;
+ if (occurrences) {
+ occurrences.start = occurrences.start || [];
+ occurrences.stop = occurrences.stop || [];
+ }
+ }
+ }
+
+ if (!json.diagram.metadata.id || json.diagram.metadata.id === '$') {
+ json.diagram.metadata.id = Model._guid();
+ }
+
+ return json;
+ }
+
+ // ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Create pseudo-UUID.
+ * @returns {string}
+ * @private
+ */
+ static _guid() {
+ function s4() {
+ return Math.floor((1 + Math.random()) * 0x10000)
+ .toString(16)
+ .substring(1);
+ }
+ return `${s4()}-${s4()}-${s4()}-${s4()}-${s4()}-${s4()}-${s4()}-${s4()}`;
+ }
+
+}