2 * Copyright 2016-2017 ZTE Corporation.
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
17 topoUtil.topoDatas=[];
\r
18 topoUtil.svgOffsetWidth = 0;
\r
21 * recursive generate tree structure of the topology graph data
\r
22 * @param {[type]} rootName [description]
\r
23 * @param {[type]} rootNode [description]
\r
24 * @return {[type]} [description]
\r
26 topoUtil.generateSortData = function(rootName,rootNode) {
\r
27 for(var i=0;i<topoUtil.topoDatas.length;i++) {
\r
28 if(topoUtil.topoDatas[i].containIn == rootName){
\r
29 rootNode["children"].push(topoUtil.topoDatas[i]);
\r
30 var currentNum = rootNode["children"].length-1;
\r
31 topoUtil.generateSortData(topoUtil.topoDatas[i].id, rootNode["children"][currentNum])
\r
37 * generate CP data, CP is inserted into the VDU or VNF child nodes
\r
38 * @param {[type]} cpNode [description]
\r
39 * @param {[type]} rootNode [description]
\r
40 * @return {[type]} [description]
\r
42 topoUtil.generateCpData = function(cpNode, rootNode) {
\r
43 for(var i=0;i<cpNode.length;i++){
\r
44 for(var j=0;j<rootNode.length;j++) {
\r
45 var node = rootNode[j];
\r
46 if(cpNode[i].virtualbindsto == node.id) {
\r
47 rootNode[j].cp.push(cpNode[i]);
\r
55 * generate NETWORK and VL data, VL is inserted into the NETWORK child nodes
\r
56 * @param {[type]} rootNode [description]
\r
57 * @return {[type]} [description]
\r
59 topoUtil.generateNetworkData = function(vlanNode, networkNode) {
\r
60 if(networkNode.length == 0) {
\r
61 //no NETWORK, just VL
\r
65 networkNode.push(network);
\r
68 for(var i=0;i<networkNode.length;i++) {
\r
69 networkNode[i].subnets = [];
\r
70 for(var j=0;j<vlanNode.length;j++){
\r
71 var network = vlanNode[j].virtuallinksto;
\r
72 if(network == networkNode[i].id) {
\r
73 networkNode[i].subnets.push(vlanNode[j]);
\r
81 * initialize topology graph data
\r
82 * @param {[type]} resp [description]
\r
83 * @param {[type]} nodeInstanceData [description]
\r
84 * @return {[type]} [description]
\r
86 topoUtil.initTopoData = function(resp, nodeInstanceData) {
\r
87 if(resp && resp.length > 0) {
\r
93 for(var i=0;i<datas.length;i++){
\r
94 if (datas[i]["containIn"] == "") {
\r
95 datas[i]["containIn"] = "--";
\r
97 //add the property of children for all nodes
\r
98 datas[i]["children"] = [];
\r
99 datas[i]["cp"] = [];
\r
100 //count instances number
\r
101 if (nodeInstanceData) {
\r
102 datas[i]["num"] = topoUtil.getInstanceNum(datas[i], nodeInstanceData);
\r
104 //empty currentLinkNum
\r
105 datas[i]["currentLinkNum"] = 0;
\r
106 //distinguish VL, CP, NETWORK, VNF, VDU, VNFC from nodes to display topology graph
\r
107 var type = datas[i]["type"];
\r
108 if (type.toUpperCase().indexOf(".VL") > -1) {
\r
109 vlanData.push(datas[i]);
\r
110 } else if (type.toUpperCase().indexOf(".CP") > -1) {
\r
111 cpData.push(datas[i]);
\r
112 } else if(type.toUpperCase().indexOf(".NETWORK") > -1) {
\r
113 networks.push(datas[i]);
\r
114 } else if ((type.toUpperCase().indexOf(".VNF") > -1) || (type.toUpperCase().indexOf(".VDU") > -1)
\r
115 || (type.toUpperCase().indexOf(".VNFC") > -1)) {
\r
116 boxData.push(datas[i]);
\r
118 boxData.push(datas[i]);
\r
122 //generate CP nodes
\r
123 topoUtil.generateCpData(cpData, boxData);
\r
124 //generate VNF/NS tree data
\r
125 var rootNode = {"children":[]};
\r
126 topoUtil.topoDatas = boxData;
\r
127 topoUtil.generateSortData("--", rootNode);
\r
128 vm.topologyTab.boxTopoDatas = rootNode.children;
\r
129 //generate NETWORK and VL nodes
\r
130 topoUtil.generateNetworkData(vlanData, networks);
\r
131 vm.topologyTab.networkTopoDatas = networks;
\r
133 //draw topology graph
\r
134 topoUtil.topoDatas = datas;
\r
135 setTimeout("topoUtil.generateLine()", 100);
\r
136 //bind window object events
\r
137 topoUtil.initWindowEvent();
\r
142 * get node instances number
\r
143 * @param {[type]} nodeTemplate [description]
\r
144 * @param {[type]} nodeInstanceData [description]
\r
145 * @return {[type]} [description]
\r
147 topoUtil.getInstanceNum = function(nodeTemplate, nodeInstanceData) {
\r
150 if(nodeTemplate.properties && nodeTemplate.properties.vnfdid) {
\r
151 id = nodeTemplate.properties.vnfdid;
\r
153 id = nodeTemplate.id;
\r
156 if(nodeInstanceData && nodeInstanceData.length > 0) {
\r
157 for (var j=0;j<nodeInstanceData.length;j++) {
\r
158 if(nodeInstanceData[j].nodeTemplateId == id) {
\r
166 topoUtil.getLineOffset = function(index) {
\r
170 * get node y coordinate offset, it is based on the total number of connections and the number of connections to
\r
171 * calculate the Y coordinate offset current connection
\r
172 * here's the connection refers connectsto relationship between VNC and VNC
\r
173 * @param {[type]} node current node object
\r
174 * @param {[type]} height current DOM object cliengtHeight
\r
175 * @return {[type]} Y coordinate offset
\r
177 topoUtil.getNodeOffset = function(node, height) {
\r
178 var toNodeLinkNum = ++node.currentLinkNum;
\r
179 var totalLinkNum = node.inLinks.length + node.outLinks.length;
\r
181 return (height/totalLinkNum)*toNodeLinkNum;
\r
184 * get node object by name
\r
185 * @param {[type]} name node name
\r
186 * @return {[type]} node object data
\r
188 topoUtil.getTopoDataById = function(id) {
\r
190 for(var i=0;i<topoUtil.topoDatas.length;i++) {
\r
191 if(id == topoUtil.topoDatas[i].id) {
\r
192 node = topoUtil.topoDatas[i];
\r
198 topoUtil.pageX = function(elem) {
\r
199 return elem.offsetParent ? (elem.offsetLeft + topoUtil.pageX(elem.offsetParent)) : elem.offsetLeft;
\r
202 topoUtil.pageY = function(elem) {
\r
203 return elem.offsetParent ? (elem.offsetTop + topoUtil.pageY(elem.offsetParent)) : elem.offsetTop;
\r
206 topoUtil.getHorizontalOffset = function(elem, elemArray) {
\r
207 var horizontalOffset = 0;
\r
208 for(var i=0;i<elemArray.length;i++) {
\r
209 var nodeTop = topoUtil.pageY(elemArray[i]);
\r
210 var fromTop = topoUtil.pageY(elem);
\r
211 if(fromTop == nodeTop) {
\r
212 horizontalOffset = topoUtil.getLineOffset(++horizontalIndex);
\r
215 return horizontalOffset;
\r
218 topoUtil.getParentNode = function(elem) {
\r
219 return elem.className == "app" ? topoUtil.getParentNode(elem.offsetParent) : elem.offsetParent;
\r
222 topoUtil.initElementSize = function() {
\r
223 var height=$(".bpContainer").height();
\r
224 $(".vlan").height() < height ? $(".vlan").height(height) : height;
\r
226 var networkWidth = $("#networks").width();
\r
227 var topoWidth = $("#topo").width();
\r
228 var bodyWidth = $("body").width();
\r
229 (networkWidth+topoWidth+50) > bodyWidth ? $("body").width(networkWidth+topoWidth+topoUtil.svgOffsetWidth+10) : $("body").width($("html").width());
\r
231 var containerHeight=$(".container-fluid").height();
\r
232 $(".coordinates").height(containerHeight).width($("body").width());
\r
236 * get the widest VDU or VNF node to generate connect lines
\r
237 * @return {[type]} [description]
\r
239 topoUtil.getMaxNodeRight = function() {
\r
240 var maxNode = {offsetWidth : 0};
\r
241 for(var i=0;i<topoUtil.topoDatas.length;i++) {
\r
242 var node = document.getElementById(topoUtil.topoDatas[i].id);
\r
243 if(node && (maxNode.offsetWidth < node.offsetWidth)) {
\r
247 return topoUtil.pageX(maxNode) + maxNode.offsetWidth;
\r
250 topoUtil.initWindowEvent = function() {
\r
251 $(window.frameElement).attr('scrolling', 'auto');
\r
252 $('body').css('overflow', 'scroll');
\r
253 $(window).scroll(function(){
\r
254 $("#right-menu").css("top",$(window).scrollTop()); //vertical scroll
\r
255 $("#right-menu").css("right",-1*$(window).scrollLeft()); //horizontal scroll
\r
256 }).unload(function(){
\r
257 $(window.frameElement).attr('scrolling', 'no');
\r
259 //$(window).resize(topoUtil.generateLine);
\r
263 * generate topology attachment
\r
264 * connectedto represent the connection between the VNFC and VNFC, virtuallinksto represent the connection between the VLAN and VDU
\r
265 * @return {[type]} [description]
\r
267 topoUtil.generateLine = function() {
\r
268 topoUtil.initElementSize();
\r
273 var fromNodeArray = [];
\r
274 var horizontalIndex = 0;
\r
275 var maxNodeParentRight = topoUtil.getMaxNodeRight();
\r
276 for(var i=0;i<topoUtil.topoDatas.length;i++) {
\r
278 if(topoUtil.topoDatas[i].connectedto !=""){
\r
279 var fromNode = document.getElementById(topoUtil.topoDatas[i].id);
\r
280 var horizontalOffset = 0;
\r
281 for(var k=0;k<fromNodeArray.length;k++) {
\r
282 //VNFC node in the same VDU, coordinate offset
\r
283 var nodeTop = topoUtil.pageY(fromNodeArray[k]);
\r
284 var fromTop = topoUtil.pageY(fromNode);
\r
285 if(fromTop == nodeTop) {
\r
286 horizontalOffset = topoUtil.getLineOffset(++horizontalIndex);
\r
289 fromNodeArray.push(fromNode);
\r
290 var fromNodeParent = topoUtil.getParentNode(fromNode);
\r
291 var toArray = topoUtil.topoDatas[i].connectedto.split(",");
\r
292 for (var j=0;j<toArray.length;j++) {
\r
293 var toNode = document.getElementById(toArray[j]);
\r
294 var toNodeParent = topoUtil.getParentNode(toNode);
\r
295 //Computing connection point and the connection point Y coordinate offset
\r
296 var fromNodeOffset = topoUtil.getNodeOffset(topoUtil.topoDatas[i], fromNode.clientHeight);
\r
297 var toNodeTopoData = topoUtil.getTopoDataById(toArray[j]);
\r
298 var toNodeOffset = topoUtil.getNodeOffset(toNodeTopoData, toNode.clientHeight);
\r
299 //X coordinate offset calculation link
\r
300 var xLineOffset = topoUtil.getLineOffset(++vduIndex);
\r
301 //Get the largest X coordinate offset is used to set the width of the body
\r
302 topoUtil.svgOffsetWidth = Math.max(xLineOffset, topoUtil.svgOffsetWidth);
\r
304 var fromNodeLeft = topoUtil.pageX(fromNode);
\r
305 var fromNodeRight = topoUtil.pageX(fromNode) + fromNode.offsetWidth;
\r
306 var fromNodeTop = topoUtil.pageY(fromNode);
\r
308 var toNodeLeft = topoUtil.pageX(toNode);
\r
309 var toNodeRight = topoUtil.pageX(toNode) + toNode.offsetWidth;
\r
310 var toNodeTop = topoUtil.pageY(toNode);
\r
313 if(fromNodeTop == toNodeTop) {
\r
314 if(fromNodeLeft < toNodeLeft) {
\r
315 coord = "M"+fromNodeRight+","+(fromNodeTop+horizontalOffset+fromNodeOffset)
\r
316 +" L"+toNodeLeft+","+(fromNodeTop+horizontalOffset+fromNodeOffset)
\r
318 coord = "M"+fromNodeLeft+","+(fromNodeTop+horizontalOffset+fromNodeOffset)
\r
319 +" L"+toNodeRight+","+(fromNodeTop+horizontalOffset+fromNodeOffset);
\r
322 var nodeRight = maxNodeParentRight + xLineOffset;
\r
323 coord = "M"+fromNodeRight+","+(fromNodeTop+horizontalOffset+fromNodeOffset)
\r
324 +" L"+nodeRight+","+(fromNodeTop+horizontalOffset+fromNodeOffset)
\r
325 +" L"+nodeRight+","+(toNodeTop+toNodeOffset)
\r
326 +" L"+toNodeRight+","+(toNodeTop+toNodeOffset);
\r
328 vduPath +='<path d="'+coord+'" marker-end="url(#arrowhead)" fill="none" stroke-dasharray="5,5" stroke="#7A7A7A" stroke-width="3px" shape-rendering="geometricPrecision"></path>';
\r
333 if(topoUtil.topoDatas[i].virtuallinksto !=""){
\r
334 var fromNode = document.getElementById(topoUtil.topoDatas[i].id);
\r
335 var toArray = topoUtil.topoDatas[i].virtuallinksto.split(",");
\r
336 for (var j=0;j<toArray.length;j++) {
\r
337 var toNode = document.getElementById(toArray[j]);
\r
339 var yLineOffset = topoUtil.getLineOffset(j);
\r
340 var xLineOffset = topoUtil.getLineOffset(++vlIndex);
\r
342 var fromNodeLeft = topoUtil.pageX(fromNode);
\r
343 var fromNodeTop = topoUtil.pageY(fromNode);
\r
345 var toNodeRight = topoUtil.pageX(toNode) + toNode.offsetWidth;
\r
346 var toNodeTop = topoUtil.pageY(toNode);
\r
349 if(fromNodeTop == toNodeTop) {
\r
350 coord = "M"+fromNodeLeft+","+(fromNodeTop+fromNode.clientHeight/2+xLineOffset)
\r
351 +" L"+toNodeRight+","+(fromNodeTop+fromNode.clientHeight/2+xLineOffset);
\r
353 coord = "M"+fromNodeLeft+","+(fromNodeTop+fromNode.clientHeight/2+yLineOffset)
\r
354 +" L"+toNodeRight+","+(fromNodeTop+fromNode.clientHeight/2+yLineOffset);
\r
356 vlPath +='<path d="'+coord+'" fill="none" stroke="'+toNode.style.backgroundColor+'" stroke-width="4px"></path>';
\r
362 $("#svg_vdu g").html(vduPath);
\r
363 $("#svg_vl g").html(vlPath);
\r
367 * generate node table data
\r
368 * @param {[type]} data [description]
\r
369 * @return {[type]} [description]
\r
371 topoUtil.generateNodeTemplate = function(data) {
\r
372 var nodeTemplate = {};
\r
373 nodeTemplate.id = data.id;
\r
374 nodeTemplate.name = data.name;
\r
375 nodeTemplate.type = data.type;
\r
376 nodeTemplate.parentType = data.parentType;
\r
377 nodeTemplate.vnfdid = ""; //only nested VNF node has value
\r
378 nodeTemplate.properties = data.properties;
\r
379 nodeTemplate.flavors = data.flavors;
\r
380 nodeTemplate.containIn = ""; //containIn relation which the front-end custom is used to display the topo relations of the graph
\r
381 nodeTemplate.containedin = ""; //the relation between VNF and VNFC
\r
382 nodeTemplate.deployedon = ""; //the relation between VDU and VNFC
\r
383 nodeTemplate.connectedto = ""; //the relation between VNFC and VNFC
\r
384 nodeTemplate.virtuallinksto = ""; //the relation between VL and CP or between VL and VDU
\r
385 nodeTemplate.virtualbindsto = ""; //the relation between CP and VDU
\r
386 nodeTemplate.outLinks = []; //a collection of connected nodes connectedto
\r
387 nodeTemplate.inLinks = []; //nodes are connected connectedto relationship collection
\r
388 nodeTemplate.currentLinkNum = 0;
\r
389 var relationShips = data.relationShips || []; //some nodes may not have relationships
\r
390 $.each(relationShips, function(index, obj){
\r
391 if (obj.sourceNodeId == data.name) {
\r
393 case "containedIn" :
\r
394 case "tosca.relationships.nfv.ContainedIn" :
\r
395 case "tosca.relationships.nfv.BelongTo" :
\r
396 nodeTemplate.containedin = obj.targetNodeId;
\r
398 case "deployedOn" :
\r
399 case "tosca.relationships.nfv.DeployedOn" :
\r
400 nodeTemplate.deployedon = obj.targetNodeId;
\r
402 case "connectedTo" :
\r
403 case "tosca.relationships.nfv.ConnectsTo" :
\r
404 nodeTemplate.connectedto += "," + obj.targetNodeId;
\r
405 nodeTemplate.outLinks.push(obj.targetNodeId);
\r
407 case "virtualLinksTo" :
\r
408 case "tosca.relationships.nfv.VirtualLinksTo" :
\r
409 nodeTemplate.virtuallinksto += "," + obj.targetNodeId;
\r
411 case "virtualBindsTo" :
\r
412 case "tosca.relationships.nfv.VirtualBindsTo" :
\r
413 nodeTemplate.virtualbindsto += "," + obj.targetNodeId;
\r
417 if (obj.targetNodeId == data.name) {
\r
419 case "connectedTo" :
\r
420 case "tosca.relationships.nfv.ConnectsTo" :
\r
421 nodeTemplate.inLinks.push(obj.sourceNodeId);
\r
426 nodeTemplate.connectedto = nodeTemplate.connectedto.substring(1);
\r
427 nodeTemplate.virtuallinksto = nodeTemplate.virtuallinksto.substring(1);
\r
428 nodeTemplate.virtualbindsto = nodeTemplate.virtualbindsto.substring(1);
\r
430 if(topoUtil.isVNFType(data.type)) {
\r
431 $.each(data.properties, function(key, value) {
\r
432 if(key == "vnfdid" && value) {
\r
433 nodeTemplate.vnfdid = value;
\r
437 return nodeTemplate;
\r
441 * generate topology data
\r
442 * deployedon is used to display the relation between VNFC and VDU
\r
443 * containedin is used to display the relation between VNFC and VNF
\r
444 * transform relations between VDU and VNF, containIn is used to display the relation between VDU and VNF
\r
445 * @param {[type]} data [description]
\r
446 * @return {[type]} [description]
\r
448 topoUtil.generateTopoTemplate = function(data) {
\r
449 for(var i=0;i<data.length;i++) {
\r
450 if(data[i].containedin){
\r
451 //assignment is designed to compatible with no VDU, only VNF and VNFC situations
\r
452 data[i].containIn = data[i].containedin;
\r
453 for(var j=0;j<data.length;j++) {
\r
454 if(data[i].deployedon == data[j].id) {
\r
455 data[j].containIn = data[i].containedin;
\r
460 //the relationship between VNFC and VDU deployedon replace with containIn
\r
461 if(data[i].deployedon){
\r
462 data[i].containIn = data[i].deployedon;
\r
469 * generate nodetemplate detail
\r
470 * @param {[type]} data [description]
\r
471 * @return {[type]} [description]
\r
473 topoUtil.generateNodeTemplateDetail = function(data) {
\r
474 var nodeTemplateDetail = {};
\r
475 nodeTemplateDetail.properties = [];
\r
476 var properties = data.properties;
\r
477 for(var key in properties) {
\r
479 property.key = key;
\r
480 property.value = properties[key];
\r
481 nodeTemplateDetail.properties.push(property);
\r
483 //add flavor to nodetempalte properties
\r
484 var flavors = data.flavors;
\r
485 if(flavors && flavors.length) {
\r
486 var flavor = flavors[0];
\r
487 for(var key in flavor) {
\r
489 property.key = key;
\r
490 property.value = flavor[key];
\r
491 nodeTemplateDetail.properties.push(property);
\r
495 nodeTemplateDetail.relationShips = data.relationShips;
\r
497 nodeTemplateDetail.general = [];
\r
499 general.key = "name";
\r
500 general.value = data.name;
\r
501 nodeTemplateDetail.general.push(general);
\r
503 general.key = "type";
\r
504 general.value = data.type;
\r
505 nodeTemplateDetail.general.push(general);
\r
507 return nodeTemplateDetail;
\r
510 topoUtil.getCurrentDetailData = function(detailDatas, nodetemplateid) {
\r
512 for(var i=0; i<detailDatas.length; i++) {
\r
513 if (detailDatas[i].id == nodetemplateid) {
\r
514 data = topoUtil.generateNodeTemplateDetail(detailDatas[i]);
\r
522 * generate node instance detail
\r
523 * a node template may correspond to multiple node instances, their properties are not the same
\r
524 * @param {[type]} data [description]
\r
525 * @return {[type]} [description]
\r
527 topoUtil.generateNodeInstanceDetail = function(data) {
\r
528 var nodeInstanceDetail = [];
\r
529 nodeInstanceDetail.properties = [];
\r
530 nodeInstanceDetail.general = [];
\r
532 var properties = data.properties;
\r
533 for(var i=0;i<properties.length;i++) {
\r
534 var nodeDetail = {};
\r
535 var name = data.name;
\r
536 for(var key in properties[i]) {
\r
538 property.key = key;
\r
539 property.value = properties[i][key];
\r
540 nodeDetail.properties.push(property);
\r
542 if(key == "name") {
\r
543 name = properties[i][key];
\r
547 general.key = "name";
\r
548 general.value = name;
\r
549 nodeDetail.general.push(general);
\r
551 general.key = "type";
\r
552 general.value = data.type;
\r
553 nodeDetail.general.push(general);
\r
555 nodeDetail.relationShips = data.relationShips;
\r
556 nodeInstanceDetail.push(nodeDetail);
\r
558 return nodeInstanceDetail;
\r
561 topoUtil.getCurrentNodeInstanceDetail = function(detailDatas, nodetemplateid) {
\r
563 for(var i=0; i<detailDatas.length; i++) {
\r
564 if (detailDatas[i].id == nodetemplateid) {
\r
565 data = topoUtil.generateNodeInstanceDetail(detailDatas[i]);
\r
572 topoUtil.getCidr = function(properties) {
\r
573 for(var key in properties) {
\r
574 if(key == "cidr") {
\r
575 return properties[key];
\r
580 topoUtil.getColor = function(index) {
\r
581 var colors = ['#1F77B4','#FF7F0E','#2CA02C','#D62728','#9467BD','#8C564B','#4b6c8b','#550000','#dc322f','#FF6600'];
\r
582 return colors[index%10];
\r
585 topoUtil.getCpTop = function(index, parentBoxId) {
\r
589 var circle_top = $(".circle").css("top");
\r
590 var circle_height = $(".circle").css("height");
\r
591 var top = circle_top.substring(0, circle_top.length-2) - 0;
\r
592 height = circle_height.substring(0, circle_height.length-2) - 0;
\r
593 newTop = (top+height+10);
\r
595 var circle_top = $(".smallCircle").css("top");
\r
596 var circle_height = $(".smallCircle").css("height");
\r
597 var top = circle_top.substring(0, circle_top.length-2) - 0;
\r
598 height = circle_height.substring(0, circle_height.length-2) - 0;
\r
599 newTop = (top+height*(index));
\r
601 //if the length of cp over the box which cp is virtualbindsto, set the box min-heght attribute
\r
602 var $box = $("#" + parentBoxId);
\r
603 var min_height = $box.css("min-height");
\r
604 var box_min_height = min_height.substring(0, min_height.length-2) - 0;
\r
605 var cp_height = newTop + height;
\r
606 if(cp_height > box_min_height) {
\r
607 $box.css("min-height", cp_height);
\r
610 return newTop + "px";
\r
613 topoUtil.isVNFType = function(type) {
\r
614 if((type.toUpperCase().indexOf(".VNF") > -1) && (type.toUpperCase().indexOf(".VNFC") < 0)) {
\r