Add new code new version
[sdc.git] / dox-sequence-diagram-ui / src / main / webapp / lib / ecomp / asdc / sequencer / common / Common.js
1 /*!
2  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13  * or implied. See the License for the specific language governing
14  * permissions and limitations under the License.
15  */
16
17 /**
18  * Common operations.
19  */
20 export default class Common {
21
22   // ///////////////////////////////////////////////////////////////////////////////////////////////
23
24   /**
25    * Retrieve and start a simple timer. Retrieve elapsed time by calling #ms().
26    * @returns {*}
27    */
28   static timer() {
29     const start = new Date().getTime();
30     return {
31       ms() {
32         return (new Date().getTime() - start);
33       },
34     };
35   }
36
37   // ///////////////////////////////////////////////////////////////////////////////////////////////
38
39   /**
40    * Get datatype, stripping '[object Boolean]' to just 'Boolean'.
41    * @param o JS object.
42    * @return String like String, Number, Date, Null, Undefined, stuff like that.
43    */
44   static getType(o) {
45     const str = Object.prototype.toString.call(o);
46     const prefix = '[object ';
47     if (str.substr(str, prefix.length) === prefix) {
48       return str.substr(prefix.length, str.length - (prefix.length + 1));
49     }
50     return str;
51   }
52
53   // ///////////////////////////////////////////////////////////////////////////////////////////////
54
55   /**
56    * Assert that an argument was provided.
57    * @param value to be checked.
58    * @param message message on assertion failure.
59    * @return value.
60    */
61   static assertNotNull(value, message = 'Unexpected null value') {
62     if (!value) {
63       throw new Error(message);
64     }
65     return value;
66   }
67
68   // ///////////////////////////////////////////////////////////////////////////////////////////////
69
70   /**
71    * Assert argument type.
72    * @param value to be checked.
73    * @param expected expected type string, e,g. Number from [object Number].
74    * @return value.
75    */
76   static assertType(value, expected) {
77     const type = this.getType(value);
78     if (type !== expected) {
79       throw new Error(`Expected type ${expected}, got ${type}`);
80     }
81     return value;
82   }
83
84   // ///////////////////////////////////////////////////////////////////////////////////////////////
85
86   /**
87    * Assert argument type.
88    * @param value to be checked.
89    * @param unexpected unexpected type string, e,g. Number from [object Number].
90    * @return value.
91    */
92   static assertNotType(value, unexpected) {
93     const type = this.getType(value);
94     if (type === unexpected) {
95       throw new Error(`Forbidden type "${unexpected}"`);
96     }
97     return value;
98   }
99
100   // ///////////////////////////////////////////////////////////////////////////////////////////////
101
102   /**
103    * Assert argument is a simple JSON object, and specifically not (something like an) ES6 class.
104    * @param value to be checked.
105    * @return value.
106    */
107   static assertPlainObject(value) {
108     Common.assertType(value, 'Object');
109     // TODO
110     /*
111     if (!($.isPlainObject(value))) {
112       throw new Error(`Expected plain object: ${value}`);
113     }
114     */
115     return value;
116   }
117
118   // ///////////////////////////////////////////////////////////////////////////////////////////////
119
120   /**
121    * Assert argument type.
122    * @param value to be checked.
123    * @param c expected class.
124    * @return value.
125    */
126   static assertInstanceOf(value, c) {
127     Common.assertNotNull(value);
128     if (!(value instanceof c)) {
129       throw new Error(`Expected instanceof ${c}: ${value}`);
130     }
131     return value;
132   }
133
134   // ///////////////////////////////////////////////////////////////////////////////////////////////
135
136   /**
137    * Assert that a string matches a regex.
138    * @param value value to be tested.
139    * @param re pattern to be applied.
140    * @return value.
141    */
142   static assertMatches(value, re) {
143     this.assertType(value, 'String');
144     this.assertType(re, 'RegExp');
145     if (!re.test(value)) {
146       throw new Error(`Value ${value} doesn't match pattern ${re}`);
147     }
148     return value;
149   }
150
151   // ///////////////////////////////////////////////////////////////////////////////////////////////
152
153   /**
154    * Assert the value of a boolean.
155    *
156    * @param bool to be checked.
157    * @param message optional message on assertion failure.
158    * @return value.
159    */
160   static assertThat(bool, message) {
161     if (!bool) {
162       throw new Error(message || `Unexpected: ${bool}`);
163     }
164     return bool;
165   }
166
167   // ///////////////////////////////////////////////////////////////////////////////////////////////
168
169   /**
170    * Verify that a value, generally a function arg, is a DOM element.
171    * @param value to be checked.
172    * @return value.
173    */
174   static assertHTMLElement(value) {
175     if (!Common.isHTMLElement(value)) {
176       throw new Error(`Expected HTMLElement: ${value}`);
177     }
178     return value;
179   }
180
181   // ///////////////////////////////////////////////////////////////////////////////////////////////
182
183   /**
184    * Check whether a value, generally a function arg, is an HTML DOM element.
185    * @param o to be checked.
186    * @return true if DOM element.
187    */
188   static isHTMLElement(o) {
189     if (typeof HTMLElement === 'object') {
190       return o instanceof HTMLElement;
191     }
192     return o && typeof o === 'object' && o !== null
193       && o.nodeType === 1 && typeof o.nodeName === 'string';
194   }
195
196   // ///////////////////////////////////////////////////////////////////////////////////////////////
197
198   /**
199    * Check if a string is non-empty.
200    * @param s string to be checked.
201    * @returns false if non-blank string, true otherwise.
202    */
203   static isBlank(s) {
204     if (Common.getType(s) === 'String') {
205       return (s.trim().length === 0);
206     }
207     return true;
208   }
209
210   // ///////////////////////////////////////////////////////////////////////////////////////////////
211
212   /**
213    * Detect dates that are numbers, milli/seconds since epoch..
214    *
215    * @param n candidate number.
216    * @returns {boolean}
217    */
218   static isNumber(n) {
219     return !isNaN(parseFloat(n)) && isFinite(n);
220   }
221
222   // ///////////////////////////////////////////////////////////////////////////////////////////////
223
224   /**
225    * Parse the text output from a template to a DOM element.
226    * @param txt input text.
227    * @returns {Element}
228    */
229   static txt2dom(txt) {
230     return new DOMParser().parseFromString(txt, 'image/svg+xml').documentElement;
231   }
232
233   // ///////////////////////////////////////////////////////////////////////////////////////////////
234
235   /**
236    * Recursively convert a DOM element to an SVG (namespaced) element. Otherwise
237    * you get HTML elements that *happen* to have SVG names, but which aren't actually SVG.
238    *
239    * @param node DOM node to be converted.
240    * @param svg to be updated.
241    * @returns {*} for chaining.
242    */
243   static dom2svg(node, svg) {
244
245     Common.assertNotType(node, 'String');
246
247     if (node.childNodes && node.childNodes.length > 0) {
248
249       for (const c of node.childNodes) {
250         switch (c.nodeType) {
251           case document.TEXT_NODE:
252             svg.text(c.nodeValue);
253             break;
254           default:
255             break;
256         }
257       }
258
259       for (const c of node.childNodes) {
260         switch (c.nodeType) {
261           case document.ELEMENT_NODE:
262             Common.dom2svg(c, svg.append(`svg:${c.nodeName.toLowerCase()}`));
263             break;
264           default:
265             break;
266         }
267       }
268     }
269
270     if (node.hasAttributes()) {
271       for (let i = 0; i < node.attributes.length; i++) {
272         const a = node.attributes.item(i);
273         svg.attr(a.name, a.value);
274       }
275     }
276
277     return svg;
278   }
279
280   // ///////////////////////////////////////////////////////////////////////////////////////////////
281
282   /**
283    * Get the lines to be shown in the label.
284    *
285    * @param labelText original label text.
286    * @param wordWrapAt chars at which to break words.
287    * @param lineWrapAt chars at which to wrap.
288    * @param maximumLines lines at which to truncate.
289    * @returns {Array}
290    */
291   static tokenize(labelText = '', wordWrapAt, lineWrapAt, maximumLines) {
292
293     let l = labelText;
294
295     // Hyphenate and break long words.
296
297     const regex = new RegExp(`(\\w{${wordWrapAt - 1}})(?=\\w)`, 'g');
298     l = l.replace(regex, '$1- ');
299
300     const labelTokens = l.split(/\s+/);
301     const lines = [];
302     let label = '';
303     for (const labelToken of labelTokens) {
304       if (label.length > 0) {
305         const length = label.length + labelToken.length + 1;
306         if (length > lineWrapAt) {
307           lines.push(label.trim());
308           label = labelToken;
309           continue;
310         }
311       }
312       label = `${label} ${labelToken}`;
313     }
314
315     if (label) {
316       lines.push(label.trim());
317     }
318
319     const truncated = lines.slice(0, maximumLines);
320     if (truncated.length < lines.length) {
321       let finalLine = truncated[maximumLines - 1];
322       if (finalLine.length > (lineWrapAt - 4)) {
323         finalLine = finalLine.substring(0, lineWrapAt - 4);
324       }
325       finalLine = `${finalLine} ...`;
326       truncated[maximumLines - 1] = finalLine;
327     }
328
329     return truncated;
330   }
331
332   // ///////////////////////////////////////////////////////////////////////////////////////////////
333
334   /**
335    * Brutally sanitize an input string. We have no syntax rules, and hence no specific
336    * rules to apply, but we have very few unconstrained fields, so we can implement a
337    * crude default and devolve the rest to options.
338    * @param value value to be sanitized.
339    * @param options control options including validation rules.
340    * @param type validation type.
341    * @returns {*} sanitized string.
342    * @private
343    */
344   static sanitizeText(value, options, type) {
345     const rules = Common.assertNotNull(options.validation[type]);
346     let v = value || rules.defaultValue || '';
347     if (rules.replace) {
348       v = v.replace(rules.replace, '');
349     }
350     if (v.length > rules.maxLength) {
351       v = `${v.substring(0, rules.maxLength)}...`;
352     }
353     return v;
354   }
355
356 }