mmm_utils.js 15 KB

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