mmm_utils.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. /* This file is part of AToMPM - A Tool for Multi-Paradigm Modelling
  2. * Copyright 2011 by the AToMPM team and licensed under the LGPL
  3. * See COPYING.lesser and README.md in the root of this project for full details
  4. */
  5. /**
  6. * Gets the metamodel at the current URI
  7. */
  8. function __getMetamodel(uri)
  9. {
  10. return uri.match(/(.*)\/.*\/.*\.instance/)[1];
  11. }
  12. /**
  13. * Compiles? the current icon metamodel into a metamodel
  14. * @param imm
  15. * @returns
  16. */
  17. function __iconMetamodelToMetamodel(imm)
  18. {
  19. var matches = imm.match(/(.*)\..*Icons(\.pattern){0,1}(\.metamodel)$/);
  20. return matches[1]+(matches[2] || '')+matches[3];
  21. }
  22. /**
  23. * Returns whether or not the input model is a button model
  24. * @param str
  25. * @returns
  26. */
  27. function __isButtonModel(str)
  28. {
  29. return str.match(/\.buttons.model$/);
  30. }
  31. /* returns true if the given uri describes a Link */
  32. function __isConnectionType(uri)
  33. {
  34. return uri.match(/Link\/[0-9]*\.instance$/) &&
  35. ! uri.match(/(.*\.instance)--(.*\.instance)/);
  36. }
  37. /* returns true if the given Link (specified via uri) is a containment link */
  38. function __isContainmentConnectionType(linkuri)
  39. {
  40. var matches = linkuri.match(/(.*)\..*Icons(\.pattern){0,1}\/(.*)Link/),
  41. asmm = matches[1]+(matches[2] || '')+'.metamodel',
  42. type = matches[3];
  43. return __loadedToolbars[asmm].connectorTypes[type] == __CONTAINMENT_LINK;
  44. }
  45. /**
  46. * Returns whether or not this is a metamodel
  47. * @param str
  48. * @returns {Boolean}
  49. */
  50. function __isAbstractSyntaxMetamodel(str)
  51. {
  52. return str.match(/\.metamodel$/) && !__isIconMetamodel(str);
  53. }
  54. /**
  55. * Returns whether or not this is an icon metamodel or
  56. * a regular metamodel
  57. * @param str
  58. * @returns {Boolean}
  59. */
  60. function __isIconMetamodel(str)
  61. {
  62. return str.match(/\..*Icons\.metamodel$/) ||
  63. str.match(/\..*Icons\.pattern\.metamodel$/);
  64. }
  65. /**
  66. * Returns whether or not this is a model
  67. * @param str
  68. * @returns
  69. */
  70. function __isModel(str)
  71. {
  72. return str.match(/\.model$/);
  73. }
  74. /*--------------------------- PARSING [META]MODELS ---------------------------*/
  75. /* update the client's state given the results of a 'csworker GET current.state'
  76. query
  77. 1. retrieve all missing asmm information (information which we normally get
  78. as a side-effect of loading an icon definition metamodel but which isn't
  79. stored in csworker state, and thus not returned by 'GET current.state')
  80. 2. force the AS and CS next sequence numbers to the values from the given
  81. state
  82. 3. construct a changelog mimicking the model and metamodel loads associated
  83. with the given state (via state2chlog())
  84. 4. apply it */
  85. function __handleState(state,csn)
  86. {
  87. function state2chlog(csmms,asmms,m)
  88. {
  89. var chlog = [];
  90. for( var csmm in csmms )
  91. chlog.push( {'op':'LOADMM',
  92. 'name':csmm,
  93. 'mm':utils.jsons(csmms[csmm])} );
  94. for( var asmm in asmms )
  95. chlog.push( {'op':'LOADASMM',
  96. 'name':asmm,
  97. 'mm':asmms[asmm]} );
  98. for( var id in m.nodes )
  99. {
  100. m.nodes[ m.nodes[id]['$type']+'/'+id+'.instance' ] = m.nodes[id];
  101. delete m.nodes[id];
  102. }
  103. chlog.push( {'op':'RESETM',
  104. 'new_model':utils.jsons(m)} );
  105. return chlog;
  106. }
  107. var mms = utils.jsonp(state['mms']),
  108. m = utils.jsonp(state['m']),
  109. asmms = {},
  110. nbmms = utils.keys(mms).length;
  111. __aswid = state['asw'];
  112. utils.keys(mms).forEach(
  113. function(csmm)
  114. {
  115. var asmm = __iconMetamodelToMetamodel(csmm+'.metamodel');
  116. HttpUtils.httpReq(
  117. 'GET',
  118. HttpUtils.url(asmm,__NO_WID),
  119. undefined,
  120. function(_statusCode,resp)
  121. {
  122. asmms[asmm.substring(0,asmm.length-'.metamodel'.length)] = resp;
  123. if( utils.keys(asmms).length < nbmms )
  124. return;
  125. __forceNextCSWSequenceNumber(csn);
  126. __forceNextASWSequenceNumber(state['asn']);
  127. __handleChangelog(
  128. state2chlog(mms,asmms,m),
  129. csn,
  130. undefined);
  131. });
  132. });
  133. }
  134. /* create and return an edge
  135. 0. do nothing if provided edge ends are invalid (this happens during CS
  136. switches)
  137. 1. draw the path, save it in __edges and style it according to the given
  138. style (via CompileUtils.compileAndDrawEdge(..))
  139. 2. remember the associated linktype's uri in the edge
  140. 3. remember incoming and outgoing edges within __icons
  141. NOTE:: step 3 is redundant but allows for constant-time operations rather
  142. than O(|E|), specifically for implementing edge end following during
  143. connected icon transformations */
  144. function __createEdge(segments,style,edgeId,linkuri)
  145. {
  146. var ids = __edgeId2ends(edgeId);
  147. if( !(ids[0] in __icons) || !(ids[1] in __icons) )
  148. return;
  149. var edge = CompileUtils.compileAndDrawEdge(
  150. segments,
  151. style,
  152. ids[0],
  153. ids[1],
  154. linkuri);
  155. __icons[ids[0]]['edgesOut'].push(edgeId);
  156. __icons[ids[1]]['edgesIn'].push(edgeId);
  157. // sort and draw
  158. for (var id in __icons) {
  159. __icons[id]['ordernr'] = undefined;
  160. }
  161. function getOrderNr(id,visited) {
  162. var icon = __icons[id];
  163. if (visited.indexOf(icon) > 0) return;
  164. if (icon['ordernr']) return;
  165. visited.push(icon);
  166. if (__isConnectionType(id)) {
  167. icon['ordernr'] = 9999;
  168. } else if (icon['edgesIn'].length > 0) {
  169. for (var edgeId in icon['edgesIn']) {
  170. var associationid = __edges[icon['edgesIn'][edgeId]]['start'];
  171. if (__isContainmentConnectionType(associationid)) {
  172. getOrderNr(__edges[__icons[associationid]['edgesIn'][0]]['start'], visited);
  173. icon['ordernr'] = __icons[__edges[__icons[associationid]['edgesIn'][0]]['start']]['ordernr'] + 1;
  174. }
  175. }
  176. if (!icon['ordernr']) icon['ordernr'] = 0;
  177. } else {
  178. icon['ordernr'] = 0;
  179. }
  180. }
  181. for (var id in __icons) {
  182. getOrderNr(id, []);
  183. }
  184. Object.keys(__icons).concat().sort(function(a, b) {return __icons[a]['ordernr'] - __icons[b]['ordernr'];}).forEach(function(el) {
  185. __icons[el]['icon'].toFront();
  186. });
  187. Object.keys(__edges).forEach(function(el) {
  188. __edges[el]['icon'].toFront();
  189. });
  190. return edge;
  191. }
  192. /* create and return an icon
  193. 1. draw the icon and save it in __icons (via CompileUtils.compileAndDrawIconModel(..))
  194. ... if the icon is a linktype, we also give it a __linkStyle attribute */
  195. function __createIcon(node,id)
  196. {
  197. var attrs = utils.mergeDicts(
  198. [{'__asuri' :node['$asuri']['value'],
  199. '__r' :node['orientation']['value'],
  200. '__sx' :node['scale']['value'][0],
  201. '__sy' :node['scale']['value'][1]},
  202. ('link-style' in node ?
  203. {'__linkStyle':utils.jsons(node['link-style']['value'])} :
  204. {})] ),
  205. icon = CompileUtils.compileAndDrawIconModel(
  206. node['$contents']['value'],
  207. __canvas,
  208. {'id':id,
  209. 'attrs':attrs,
  210. 'behaviours':true}),
  211. bbox = icon.getBBox(),
  212. pos = node['position']['value'];
  213. icon.setAttr('__x',__getAbsoluteCoordinate(pos[0],bbox.width));
  214. icon.setAttr('__y',__getAbsoluteCoordinate(pos[1],bbox.height));
  215. icon.setAttr('vector-effect','inherit');
  216. __setIconTransform(id);
  217. return icon;
  218. }
  219. /* icon positions may be set to [x,a%,y,b%], meaning that the point at a% of the
  220. icon's width and at b% of the icon's height should be located at x,y... for
  221. instance, if a and b == 50, the icon is centered on x,y... given a coordinate
  222. of the form 'c,a%', this function returns an equivalent coordinate of the
  223. form 'c' (returns unchanged input on coordinates of the form 'c') */
  224. function __getAbsoluteCoordinate(c,full)
  225. {
  226. if( (matches = String(c).match(/(.*),(.*)%/)) )
  227. return matches[1] - full*matches[2]/100.0;
  228. return c;
  229. }
  230. /* vobject geometric attribute (i.e., position, etc.) values contain the
  231. attribute's initial and latest values... this is necessary to properly render
  232. and transform them... this function returns a parsed form of these values,
  233. which look like: aa;bbbb or aa */
  234. function __getVobjGeomAttrVal(val)
  235. {
  236. if( (matches = String(val).match(/^(.*?)(;(.*)){0,1}$/)) )
  237. return {'initial' : parseFloat(matches[1]),
  238. 'latest': matches[3]};
  239. else
  240. return {'initial' : val};
  241. }
  242. /* returns a list of fulltype legal connection types between specified nodes
  243. 1. extract info from ids
  244. a) csmm name
  245. b) asmm name
  246. c) astype
  247. d) asmm.legalconnections
  248. 2. go through asmm1 and asmm2 adding all encoutered legal connections
  249. a) t1 -> t2 (when t1 and t2 are of the same mm)
  250. b) $* -> t2
  251. c) t1 -> t2
  252. d) $* -> $*
  253. if 'ctype' is specified (either CONTAINMENT_LINK or VISUAL_LINK), only
  254. add connections of the given ctype
  255. 3. flatten and return resulting array of arrays
  256. TBA/TBI
  257. add support for hyperedges (i.e., when uri1/uri2/?both? are links)...
  258. this might cause problems with the selection mechanism which is untested
  259. for anything else than edges connecting icons... will probably also need
  260. to make changes in csworker MKNODE and in worker urize-hack... copy-paste
  261. might also need to be updated to support more than 2 segments per Link */
  262. function __legalConnections(uri1,uri2,ctype)
  263. {
  264. /* helper function that takes an array of basic types (e.g., Below) and
  265. returns an array of fulltypes (e.g., /Formalisms/.../Below) */
  266. function t2ft(types,mm)
  267. {
  268. return types.map(
  269. function(t)
  270. {
  271. return mm+'/'+t;
  272. });
  273. }
  274. /* helper function that takes an array of basic types, and, if 'ctype' is
  275. defined, removes non-ctype types */
  276. function ctypes(types,connectorTypes)
  277. {
  278. return (ctype == undefined ?
  279. types :
  280. types.filter( function(t) {return connectorTypes[t]==ctype;}) );
  281. }
  282. var matches1 = uri1.match(/(.*)\/(.*)\/.*\.instance/),
  283. csmm1 = matches1[1],
  284. asmm1 = __iconMetamodelToMetamodel(matches1[1]+'.metamodel'),
  285. asmatch1 = matches1[2].match(/(.*)Icon/),
  286. astype1 = asmatch1 == null ? null : asmatch1[1],
  287. lc1 = __loadedToolbars[asmm1].legalConnections,
  288. ct1 = __loadedToolbars[asmm1].connectorTypes,
  289. matches2 = uri2.match(/(.*)\/(.*)\/.*\.instance/),
  290. csmm2 = matches2[2],
  291. asmm2 = __iconMetamodelToMetamodel(matches2[1]+'.metamodel'),
  292. asmatch2 = matches2[2].match(/(.*)Icon/),
  293. astype2 = asmatch2 == null ? null : asmatch2[1],
  294. lc2 = __loadedToolbars[asmm2].legalConnections,
  295. ct2 = __loadedToolbars[asmm2].connectorTypes,
  296. legalConnections = [];
  297. if (astype1 == null || astype2 == null) {
  298. return [];
  299. }
  300. if( asmm1 == asmm2 )
  301. // MM1=MM2: t1 -> t2
  302. // MM1=MM2: $* -> t2
  303. // MM1=MM2: __p$* -> t2
  304. [astype1,'$*','__p$*'].forEach(
  305. function(t)
  306. {
  307. if( t in lc1 && astype2 in lc1[t] )
  308. legalConnections.push(
  309. t2ft( ctypes(lc1[t][astype2],ct1), csmm1) );
  310. });
  311. else
  312. // MM2: $* -> t2
  313. // MM2: __p$* -> t2
  314. ['$*','__p$*'].forEach(
  315. function(t)
  316. {
  317. if( t in lc2 && astype2 in lc2[t] )
  318. legalConnections.push(
  319. t2ft( ctypes(lc2[t][astype2],ct2), csmm2) );
  320. });
  321. // MM1: t1 -> $*
  322. // MM1: t1 -> __p$*
  323. ['$*','__p$*'].forEach(
  324. function(t)
  325. {
  326. if( astype1 in lc1 && t in lc1[astype1] )
  327. legalConnections.push(
  328. t2ft( ctypes(lc1[astype1][t],ct1), csmm1) );
  329. });
  330. for( var tb in __loadedToolbars )
  331. if( __isIconMetamodel(tb) &&
  332. (asmm=__iconMetamodelToMetamodel(tb)) != asmm1 &&
  333. asmm != asmm2 )
  334. ['$*','__p$*'].forEach(
  335. function(t)
  336. {
  337. if( t in __loadedToolbars[asmm].legalConnections &&
  338. t in __loadedToolbars[asmm].legalConnections[t] )
  339. legalConnections.push(
  340. t2ft(
  341. ctypes(
  342. __loadedToolbars[asmm].legalConnections[t][t],
  343. __loadedToolbars[asmm].connectorTypes),
  344. tb.match(/(.*)\.metamodel/)[1]) );
  345. });
  346. return utils.flatten(legalConnections);
  347. }
  348. /*----------------------------- CONTAINMENT... -------------------------------*/
  349. /* returns the uris of the icons contained in the given container (specified via
  350. uri)
  351. 1. retrieve every icon that is explicitly contained (i.e., that has a
  352. containment link from the given container or from any of its [recursive]
  353. contents)
  354. a. if container is a Link, return []
  355. b. foreach of the container's outgoing edges
  356. i. if the edge's Link is not a containment Link, return []
  357. ii. otherwise, return all the ends of that Link's outgoing edges along
  358. with the Link
  359. c. flatten the resulting array of arrays
  360. d. recurse back to step 1a on every item returned by step 1c (i.e., on
  361. all of the computed contents) and append results to those of step 1c
  362. 2. retrieve icons that are implicity contained (i.e., icons of Links that
  363. connect contained icons)... this achieved by iterating through every known
  364. Link and keeping aside those whose ends are contained
  365. 3. return 'setified' concatenation of results from steps 1 and 2 */
  366. function __getIconsInContainer(container)
  367. {
  368. function getExplicitContents(container, explored)
  369. {
  370. if( __isConnectionType(container) )
  371. return [];
  372. if( explored.indexOf(container) > -1 ) {
  373. return [];
  374. }
  375. var contents =
  376. utils.flatten(__icons[container]['edgesOut'].map(
  377. function(edgeId)
  378. {
  379. var linkuri = __edgeId2linkuri(edgeId);
  380. if( ! __isContainmentConnectionType(linkuri) )
  381. return [];
  382. return __icons[linkuri]['edgesOut'].map(
  383. function(_edgeId)
  384. {
  385. return __edges[_edgeId]['end'];
  386. }).concat([linkuri]);
  387. }));
  388. explored.push(container);
  389. for (var ct_idx in contents) {
  390. var to_concat = utils.flatten(getExplicitContents(contents[ct_idx], explored));
  391. contents = contents.concat(to_concat);
  392. }
  393. return utils.toSet(contents);
  394. }
  395. var explicitContents = getExplicitContents(container, []),
  396. implicitContents = [];
  397. for( var uri in __icons )
  398. if( __isConnectionType(uri) &&
  399. __icons[uri]['edgesIn'].every(
  400. function(edgeId)
  401. {
  402. return utils.contains(explicitContents,__edges[edgeId]['start']);
  403. }) &&
  404. __icons[uri]['edgesOut'].every(
  405. function(edgeId)
  406. {
  407. return utils.contains(explicitContents,__edges[edgeId]['end']);
  408. }) )
  409. implicitContents.push(uri);
  410. return utils.toSet(explicitContents.concat(implicitContents));
  411. }
  412. /* returns true if the given container (specified via uri) has a containment
  413. Link to the given icon (also specified via uri)
  414. 1. foreach of the icon's incoming edges
  415. a. if the edge's Link is not a containment Link, loop to 1.
  416. b. if the edge's Link has an incoming edge from container, break and
  417. return true
  418. 2. return false */
  419. function __isDirectlyContainedIn(icon,container)
  420. {
  421. return container != undefined &&
  422. ! __isConnectionType(container) &&
  423. __icons[icon]['edgesIn'].some(
  424. function(edgeId)
  425. {
  426. var linkuri = __edgeId2linkuri(edgeId);
  427. if( ! __isContainmentConnectionType(linkuri) )
  428. return false;
  429. return __icons[linkuri]['edgesIn'].some(
  430. function(_edgeId)
  431. {
  432. return __edges[_edgeId]['start'] == container;
  433. });
  434. });
  435. }