mmmk.js 63 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947
  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. /* NOTES:
  6. atom3 supported pre/post actions and constraints for the 'SAVE' EVENT...
  7. this never really made any sense (e.g., the user could be prevented from
  8. saving and, technically, the effects of post-actions were never saved)...
  9. atom3 supported 'save' events as a hack to enable forcing mm validation...
  10. in atompm, such validation is carried out by _mmmk.validateModel (which
  11. clients can 'call') and thus, we do no support 'save' events... */
  12. const _utils = require('./utils');
  13. const _util = require("util");
  14. const _mt = require("./libmt");
  15. const _styleinfo = require('./styleinfo');
  16. module.exports = {
  17. /********************************* GLOBALS *********************************/
  18. 'metamodels':{},
  19. 'model':{'nodes':{},'edges':[],'metamodels':[]},
  20. 'name':'',
  21. 'next_id':0,
  22. /********************************* ENV SETUP *******************************/
  23. /* produce a bundle of internal state variables sufficient to fully clone
  24. this instance
  25. OR
  26. use a provided bundle to overwrite this instance's internal state */
  27. 'clone' :
  28. function(clone)
  29. {
  30. if( clone )
  31. {
  32. this.metamodels = clone.metamodels;
  33. this.model = clone.model;
  34. this.name = clone.name;
  35. this.next_id = clone.next_id;
  36. this.journal = clone.journal;
  37. this.journalIndex = clone.journalIndex;
  38. this.undoredoJournal = clone.undoredoJournal;
  39. }
  40. else
  41. return _utils.clone(
  42. {'metamodels': this.metamodels,
  43. 'model': this.model,
  44. 'name': this.name,
  45. 'next_id': this.next_id,
  46. 'journal': this.journal,
  47. 'journalIndex': this.journalIndex,
  48. 'undoredoJournal': this.undoredoJournal});
  49. },
  50. /* load a model into this.model
  51. 0. create step-checkpoint
  52. 1. make sure all required metamodels are loaded
  53. 2. if 'insert' is specified,
  54. a) append 'model' to this.model (via __resetm__)
  55. 2. otherwise, load 'model' into this.model and 'name' into this.name
  56. (via __resetm__) */
  57. 'loadModel' :
  58. function(name,model,insert)
  59. {
  60. this.__setStepCheckpoint();
  61. var new_model = eval('('+ model +')');
  62. for( var i in new_model.metamodels )
  63. if( this.metamodels[new_model.metamodels[i]] == undefined )
  64. return {'$err':'metamodel not loaded :: '+new_model.metamodels[i]};
  65. this.__resetm__(name,model,insert);
  66. return {'changelog':this.__changelog()};
  67. },
  68. /* load a metamodel
  69. 0. create a step-checkpoint
  70. 1. load metamodel into this.model.metamodels and this.metamodels (via
  71. __loadmm__) */
  72. 'loadMetamodel' :
  73. function(name,mm)
  74. {
  75. this.__setStepCheckpoint();
  76. this.__loadmm__(name,mm);
  77. return {'changelog':this.__changelog()};
  78. },
  79. /* unload a metamodel and delete all entities from that metamodel
  80. 0. create a step-checkpoint
  81. 1. deletes nodes from specified metamodel
  82. 2. delete edges where deleted nodes appear
  83. 3. remove metamodel from this.model.metamodels and this.metamodels
  84. (via __dumpmm__) */
  85. 'unloadMetamodel' :
  86. function(name)
  87. {
  88. this.__setStepCheckpoint();
  89. for( var i=0; i<this.model.edges.length; i++ )
  90. {
  91. var edge = this.model.edges[i];
  92. if( this.__getMetamodel(this.model.nodes[edge['src']]['$type']) == name ||
  93. this.__getMetamodel(this.model.nodes[edge['dest']]['$type']) == name )
  94. this.__rmedge__(i--);
  95. }
  96. for( var id in this.model.nodes )
  97. if( this.__getMetamodel(this.model.nodes[id]['$type']) == name )
  98. this.__rmnode__(id);
  99. this.__dumpmm__(name);
  100. return {'changelog':this.__changelog()};
  101. },
  102. /******************************** MODEL CRUD *******************************/
  103. /* wraps crud operations with generic boilerplate
  104. 0. setup next_type hack : the next_type variable is used to carry the type
  105. of the to-be-created node for the special case of pre-create handlers
  106. because their target nodes aren't yet in this.model.nodes
  107. 1. create a checkpoint (any failure along the way causes checkpoint
  108. restore)
  109. 2. run all applicable pre-events constraints and actions
  110. 3. perform the specified crud operation
  111. 4. run all applicable post-events actions and constraints
  112. 5. clear unused checkpoint */
  113. '__crudOp' :
  114. function(metamodel,events,eventTargets,op,args)
  115. {
  116. if( this.metamodels[metamodel] == undefined )
  117. return {'$err':'metamodel not loaded :: '+metamodel};
  118. if( _utils.contains(events,'create') )
  119. this.next_type = args.fulltype || args.connectorType;
  120. this.__checkpoint();
  121. var pre_events = events.slice(0).map(function(ev) {return 'pre-'+ev;}),
  122. post_events = events.slice(0).map(function(ev) {return 'post-'+ev;});
  123. if( (err = this.__runEventHandlers(this.metamodels[metamodel]['constraints'], pre_events, eventTargets, 'constraint')) ||
  124. (err = this.__runEventHandlers(this.metamodels[metamodel]['actions'], pre_events, eventTargets, 'action')) ||
  125. (err = this[op](args)) ||
  126. (err = this.__runEventHandlers(this.metamodels[metamodel]['actions'], post_events, eventTargets, 'action')) ||
  127. (err = this.__runEventHandlers(this.metamodels[metamodel]['constraints'], post_events, eventTargets, 'constraint')) )
  128. {
  129. this.__restoreCheckpoint();
  130. return err;
  131. }
  132. this.__clearCheckpoint();
  133. },
  134. /* connect specified nodes with instance of connectorType
  135. __connectNN: (connect 2 nodes)
  136. 1. run pre-connect on end nodes
  137. 2. run __create to create instance and connect it to end nodes
  138. 3. run post-connect on end nodes
  139. __connectCN: (connect 1 node and 1 connector)
  140. 1. add an appropriate edge to this.model.edges
  141. connect:
  142. 0. create a step-checkpoint
  143. 1. verify validity of requested connection (i.e., connection is legal
  144. and max cardinalities haven't been reached)
  145. 2. if one of the nodes is a connector
  146. a) run pre-connect on end nodes
  147. b) create appropriate new edge between them (via __connectCN)
  148. c) run post-connect on end nodes
  149. 2. if both nodes are non-connectors
  150. a) run pre-create on connectorType
  151. b) create connectorType instance and connect it to end nodes (via
  152. __connectNN)
  153. c) run post-create on connectorType
  154. 3. return err or (new or existing) connector's id */
  155. '__connectNN' :
  156. function(args/*id1,id2,connectorType,attrs*/)
  157. {
  158. return this.__crudOp(
  159. this.__getMetamodel(args.connectorType),
  160. ['connect'],
  161. [args.id1,args.id2],
  162. '__create',
  163. {'fulltype':args.connectorType,
  164. 'id1':args.id1,
  165. 'id2':args.id2,
  166. 'attrs':args.attrs});
  167. },
  168. '__connectCN' :
  169. function(args/*id1,id2,connectorId*/)
  170. {
  171. this.__mkedge__(args.id1,args.id2);
  172. },
  173. 'connect' :
  174. function(id1,id2,connectorType,attrs)
  175. {
  176. this.__setStepCheckpoint();
  177. var metamodel = this.__getMetamodel(connectorType),
  178. t1 = this.__getType(this.model.nodes[id1]['$type']),
  179. t2 = this.__getType(this.model.nodes[id2]['$type']),
  180. tc = this.__getType(connectorType),
  181. into = (t1 == tc ? t2 : tc),
  182. from = (t2 == tc ? t1 : tc),
  183. card_into = undefined,
  184. card_from = undefined,
  185. num_id1to = 0,
  186. num_toid2 = 0,
  187. self = this;
  188. [t1,'$*','__p$*'].some(
  189. function(t)
  190. {
  191. for( var i in self.metamodels[metamodel]['cardinalities'][t] )
  192. {
  193. var cardinality = self.metamodels[metamodel]['cardinalities'][t][i];
  194. if( cardinality['type'] == into && cardinality['dir'] == 'out' )
  195. {
  196. card_into = cardinality;
  197. return true;
  198. }
  199. }
  200. });
  201. [t2,'$*','__p$*'].some(
  202. function(t)
  203. {
  204. for( var i in self.metamodels[metamodel]['cardinalities'][t] )
  205. {
  206. var cardinality = self.metamodels[metamodel]['cardinalities'][t][i];
  207. if( cardinality['type'] == from && cardinality['dir'] == 'in' )
  208. {
  209. card_from = cardinality;
  210. return true;
  211. }
  212. }
  213. });
  214. if( card_into == undefined || card_from == undefined )
  215. return {'$err':'can not connect types '+t1+' and '+t2};
  216. else if( card_into['max'] == 0 )
  217. return {'$err':'maximum outbound multiplicity reached for '+t1+' ('+id1+') and type '+into};
  218. else if( card_from['max'] == 0 )
  219. return {'$err':'maximum inbound multiplicity reached for '+t2+' ('+id2+') and type '+from};
  220. for( var i in this.model.edges )
  221. {
  222. var edge = this.model.edges[i];
  223. if( edge['src'] == id1 &&
  224. this.__getType(this.model.nodes[edge['dest']]['$type']) == into &&
  225. ++num_id1to >= card_into['max'] )
  226. return {'$err':'maximum outbound multiplicity reached for '+t1+' ('+id1+') and type '+into};
  227. if( edge['dest'] == id2 &&
  228. this.__getType(this.model.nodes[edge['src']]['$type']) == from &&
  229. ++num_toid2 >= card_from['max'] )
  230. return {'$err':'maximum inbound multiplicity reached for '+t2+' ('+id2+') and type '+from};
  231. }
  232. if( t1 == tc || t2 == tc )
  233. {
  234. var connectorId = (t1 == tc ? id1 : id2),
  235. err = this.__crudOp(
  236. metamodel,
  237. ['connect'],
  238. [id1,id2],
  239. '__connectCN',
  240. {'id1':id1,
  241. 'id2':id2,
  242. 'connectorId':connectorId});
  243. return err ||
  244. {'id':connectorId,
  245. 'changelog':this.__changelog()};
  246. }
  247. else
  248. {
  249. var err = this.__crudOp(
  250. metamodel,
  251. ['create'],
  252. [this.next_id],
  253. '__connectNN',
  254. {'id1':id1,
  255. 'id2':id2,
  256. 'connectorType':connectorType,
  257. 'attrs':attrs});
  258. return err ||
  259. {'id':this.next_id++,
  260. 'changelog':this.__changelog()};
  261. }
  262. },
  263. /* create an instance of fulltype
  264. __create:
  265. 1. create [default] instance using metamodel [and possibly specified
  266. attrs] + init $type
  267. 2. add to current model nodes
  268. [3. if fulltype is a connectorType, create edges between node id1
  269. and new instance and between new instance and node id2
  270. create:
  271. 0. create a step-checkpoint
  272. 1. wrap __create in crudOp
  273. 2. return err or new instance id */
  274. '__create' :
  275. function(args/*fulltype,attrs,[,id1,id2]*/)
  276. {
  277. var metamodel = this.__getMetamodel(args.fulltype),
  278. type = this.__getType(args.fulltype),
  279. typeAttrs = this.metamodels[metamodel]['types'][type],
  280. new_node = {};
  281. if( typeAttrs == undefined )
  282. return {'$err':'can not create instance of unknown type :: '+args.fulltype};
  283. typeAttrs.forEach(
  284. function(attr)
  285. {
  286. var val = (args.attrs && attr['name'] in args.attrs ?
  287. args.attrs[attr['name']] :
  288. attr['default']);
  289. new_node[attr['name']] =
  290. {'type':attr['type'],
  291. 'value':(typeof attr['default'] == 'object' ?
  292. _utils.clone(val) :
  293. val)};
  294. });
  295. new_node['$type'] = args.fulltype;
  296. this.__mknode__(this.next_id,new_node);
  297. if( args.id1 != undefined )
  298. {
  299. this.__mkedge__(args.id1,String(this.next_id));
  300. this.__mkedge__(String(this.next_id),args.id2);
  301. }
  302. },
  303. 'create' :
  304. function(fulltype,attrs)
  305. {
  306. this.__setStepCheckpoint();
  307. var err = this.__crudOp(
  308. this.__getMetamodel(fulltype),
  309. ['create'],
  310. [this.next_id],
  311. '__create',
  312. {'fulltype':fulltype,
  313. 'attrs':attrs});
  314. return err ||
  315. {'id':this.next_id++,
  316. 'changelog':this.__changelog()};
  317. },
  318. /* delete the specified node (and appropriate edges and/or connectors)
  319. __delete:
  320. 1. determine specified node's neighbors
  321. 2. if specified node is a connector (neighbors are non-connectors),
  322. a) run pre-disconnect constraints and actions and on its neighbors
  323. b) delete it and all appropriate edges (via __deleteConnector)
  324. c) run post-disconnect constraints and actions and on its neighbors
  325. 2. if specified node is not a connector (neighbors are connectors),
  326. a) recursively run __delete on each of its neighbors
  327. b) delete it
  328. __deleteConnector:
  329. 1. delete all appropriate edges then delete node
  330. delete:
  331. 0. create a step-checkpoint
  332. 1. wrap __delete in crudOp
  333. 2. return err or nothing */
  334. '__delete' :
  335. function(args/*id*/)
  336. {
  337. var id = args.id,
  338. metamodel = this.__getMetamodel(this.model.nodes[id]['$type']),
  339. type = this.__getType(this.model.nodes[id]['$type']),
  340. isConnector = (this.metamodels[metamodel]['connectorTypes'][type] != undefined),
  341. neighbors = [];
  342. this.model.edges.forEach(
  343. function(edge)
  344. {
  345. if( edge['src'] == id && ! _utils.contains(neighbors,edge['dest']) )
  346. neighbors.push(edge['dest']);
  347. else if( edge['dest'] == id && ! _utils.contains(neighbors,edge['src']) )
  348. neighbors.push(edge['src']);
  349. });
  350. if( isConnector )
  351. {
  352. if( (res = this.__crudOp(
  353. metamodel,
  354. ['disconnect'],
  355. neighbors,
  356. '__deleteConnector',
  357. {'id':id})) )
  358. return res;
  359. }
  360. else
  361. {
  362. for( var i in neighbors )
  363. if( (res = this.__crudOp(
  364. metamodel,
  365. ['delete'],
  366. [neighbors[i]],
  367. '__delete',
  368. {'id':neighbors[i]})) )
  369. return res;
  370. this.__rmnode__(id);
  371. }
  372. },
  373. '__deleteConnector' :
  374. function(args/*id*/)
  375. {
  376. for( var i=0; i<this.model.edges.length; i++ )
  377. {
  378. var edge = this.model.edges[i];
  379. if( edge['src'] == args.id || edge['dest'] == args.id )
  380. this.__rmedge__(i--);
  381. }
  382. this.__rmnode__(args.id);
  383. },
  384. 'delete' :
  385. function(id)
  386. {
  387. this.__setStepCheckpoint();
  388. if( this.model.nodes[id] == undefined )
  389. return {'$err':'invalid id :: '+id};
  390. var err = this.__crudOp(
  391. this.__getMetamodel(this.model.nodes[id]['$type']),
  392. ['delete'],
  393. [id],
  394. '__delete',
  395. {'id':id});
  396. return err ||
  397. {'changelog':this.__changelog()};
  398. },
  399. /* returns the stringified full model, a stringified node, or a copy of an
  400. attribute's value */
  401. 'read' :
  402. function(id,attr)
  403. {
  404. if( id == undefined )
  405. return _utils.jsons(this.model);
  406. else if( this.model.nodes[id] == undefined )
  407. return {'$err':'instance not found :: '+id};
  408. else if( attr == undefined )
  409. return _utils.jsons(this.model.nodes[id]);
  410. else if( attr.match(/.+\/.+/) )
  411. {
  412. var curr = this.model.nodes[id];
  413. for( var i in (path = attr.split('/')) )
  414. if( typeof curr == 'object' && path[i] in curr )
  415. curr = curr[path[i]];
  416. else
  417. return {'$err':'instance '+id+' has no attribute :: '+attr};
  418. }
  419. else if( !(attr in this.model.nodes[id]) )
  420. return {'$err':'instance '+id+' has no attribute :: '+attr};
  421. var attrVal = (curr ? curr['value'] : this.model.nodes[id][attr]['value']);
  422. if( typeof attrVal == 'object' )
  423. return _utils.clone(attrVal);
  424. else
  425. return attrVal;
  426. },
  427. /* returns a copy of one or all metamodels in this.metamodels */
  428. 'readMetamodels' :
  429. function(metamodel)
  430. {
  431. if( metamodel == undefined )
  432. return _utils.jsons(this.metamodels);
  433. else if( this.metamodels[metamodel] == undefined )
  434. return {'$err':'metamodel not found :: '+metamodel};
  435. else
  436. return _utils.jsons(this.metamodels[metamodel]);
  437. },
  438. /* returns this.name */
  439. 'readName' :
  440. function()
  441. {
  442. return this.name;
  443. },
  444. /* runs accesor-code that conforms to the DesignerCode API and returns its
  445. results */
  446. 'runDesignerAccessorCode' :
  447. function(code,desc,id)
  448. {
  449. var res = this.__runDesignerCode(code,desc,'accessor',id);
  450. if( res && res['$err'] )
  451. return res;
  452. return res;
  453. },
  454. /* runs action-code that conforms to the DesignerCode API (of interest is
  455. that this 'operation' is checkpointed and can thus be undone/redone; and
  456. that any exceptions thrown by the code cause a full rollback to before it
  457. was run and are then returned to the querier) */
  458. 'runDesignerActionCode' :
  459. function(code,desc,type,id)
  460. {
  461. this.__setStepCheckpoint();
  462. this.__checkpoint();
  463. if( (err = this.__runDesignerCode(code,desc,type,id)) )
  464. {
  465. this.__restoreCheckpoint();
  466. return err;
  467. }
  468. this.__clearCheckpoint();
  469. return {'changelog':this.__changelog()};
  470. },
  471. /* updates node with specified id
  472. __update:
  473. 1. update instance as per data
  474. 2. return err on unknown attributes
  475. 3. TBA: type verification on new values
  476. update:
  477. 0. create a step-checkpoint
  478. 1. wrap __update in crudOp
  479. 2. return err or nothing */
  480. '__update' :
  481. function(args/*id,data*/)
  482. {
  483. for( var attr in args.data )
  484. if( args.data[attr] == null )
  485. return {'$err':'tried to set attribute '+attr+' to "null"'};
  486. else if( (res = this.read(args.id,attr))['$err'] )
  487. return res;
  488. else
  489. this.__chattr__(args.id,attr,args.data[attr]);
  490. },
  491. 'update' :
  492. function(id,data/*{..., attr_i:val_i, ...}*/)
  493. {
  494. this.__setStepCheckpoint();
  495. if( this.model.nodes[id] == undefined )
  496. return {'$err':'invalid id :: '+id};
  497. var err = this.__crudOp(
  498. this.__getMetamodel(this.model.nodes[id]['$type']),
  499. ['edit'],
  500. [id],
  501. '__update',
  502. {'id':id,
  503. 'data':data});
  504. return err ||
  505. {'changelog':this.__changelog()};
  506. },
  507. /*************************** EVENT HANDLER EXEC ****************************/
  508. /* run the given constraint|action|accessor... when id is specified, we
  509. consider it to be the id of the node that "owns" the current
  510. constraint|action|accessor...
  511. this function is divided in 3 parts
  512. 1. constraints/actions/accessors API definition
  513. 2. safe_eval definition
  514. 3. actual code that runs the handler and handles its output */
  515. '__runDesignerCode' :
  516. function(code,desc,type,id)
  517. {
  518. /* the functions below implement the API available for constraints,
  519. actions and 'accessors'... they can only be called from "designer"
  520. code (i.e., from actions/constraints/accessors written by language
  521. and/or model transformation designers)... the main consequence of
  522. this is our design decision that setAttr() is not treated like a
  523. normal crud operation: it does not go through the crudOp pipeline
  524. of pre/post edit constraints/actions... on one hand, this decision
  525. avoids any weird recursion cases (e.g., setAttr() in pre-edit action
  526. triggers pre-edit action and on and on and on)... on the other hand,
  527. designers should be aware that:
  528. 1. setAttr() may set attributes to values that the user could not
  529. input (e.g., due to constraints)
  530. 2. consequences (specified as edit actions) of a user setting an
  531. attribute A to value 'a' might not take effect if setAttr()
  532. sets attribute A to value 'a' : for instance, if an edit
  533. action says that attribute B should be 'a'+2, setAttr() won't
  534. trigger that action
  535. 3. even though setAttr() bypasses crudOp constraints/actions, its
  536. effects are still immediate: a getAttr() on the next line (of
  537. designer code) reports the updated value
  538. moral of story:
  539. designers must be very careful with setAttr() to avoid putting
  540. the model into otherwise unreachable states
  541. API:
  542. hasAttr(_attr[,_id])
  543. return true if the specified node has an attribute of the
  544. given name
  545. getAttr(_attr[,_id])
  546. return the requested attr of the specified node... to ensure
  547. getAttr can't be used to edit the model, JSON parse+stringify
  548. is used to return a *copy* of the attribute when its value has
  549. an object type (i.e., hash or array)
  550. getAllNodes([_fulltypes])
  551. if _fulltypes is undefined, return the ids all of nodes...
  552. otherwise, return ids of all nodes with specified fulltypes
  553. getNeighbors(_dir[,_type,_id])
  554. return all inbound (_dir = '<'), outbound (_dir = '>') or both
  555. (_dir = '*') neighbor ids for specified type (if any)
  556. print(str)
  557. print something to the console that launched the server
  558. setAttr(_attr,_val[,_id])
  559. ((this function is only available in actions))... update the
  560. requested attr of the specified node using __chattr__ (s.t.
  561. the change is logged)...
  562. TBA:: type-checking on _val
  563. basic checks are made on input parameters to aid in debugging faulty
  564. actions and constraints... for functions with id parameters, if no
  565. id is given, we use the id passed to __runDesignerCode... which is
  566. either the id of the node that "owns" the current constraint|action|
  567. accessor, or undefined if the parameter was omitted */
  568. var self = this;
  569. function getAttr(_attr,_id)
  570. {
  571. if( _id == undefined )
  572. _id = id;
  573. if( self.model.nodes[_id] == undefined )
  574. throw 'invalid getAttr() id :: '+_id;
  575. else if( !(_attr in self.model.nodes[_id]) )
  576. throw 'invalid getAttr() attribute :: '+_attr;
  577. if( _attr.charAt(0) == '$' )
  578. return self.model.nodes[_id][_attr];
  579. else if( typeof self.model.nodes[_id][_attr]['value'] == 'object' )
  580. return _utils.clone(self.model.nodes[_id][_attr]['value']);
  581. else
  582. return self.model.nodes[_id][_attr]['value'];
  583. }
  584. function getAttrNames(_id)
  585. {
  586. if( _id == undefined )
  587. _id = id;
  588. if( self.model.nodes[_id] == undefined )
  589. throw 'invalid getAttrNames() id :: '+_id;
  590. return Object.getOwnPropertyNames(self.model.nodes[_id]);
  591. }
  592. function hasAttr(_attr,_id)
  593. {
  594. if( _id == undefined )
  595. _id = id;
  596. if( self.model.nodes[_id] == undefined )
  597. throw 'invalid getAttr() id :: '+_id;
  598. return _attr in self.model.nodes[_id];
  599. }
  600. function getAllNodes(_fulltypes)
  601. {
  602. if( _fulltypes != undefined && !(_fulltypes instanceof Array) )
  603. throw 'invalid getAllNodes() types array :: '+_fulltypes;
  604. var ids = [];
  605. for( var _id in self.model.nodes )
  606. {
  607. if( _fulltypes == undefined ||
  608. _utils.contains(_fulltypes,self.model.nodes[_id]['$type']) )
  609. ids.push(_id);
  610. }
  611. return ids;
  612. }
  613. function getNeighbors(_dir,_type,_id)
  614. {
  615. if( _id == undefined )
  616. _id = id;
  617. if( _type == undefined )
  618. _type = '*';
  619. if( self.model.nodes[_id] == undefined )
  620. throw 'invalid getNeighbors() id :: '+_id;
  621. var ids = [];
  622. for( var i in self.model.edges )
  623. {
  624. var edge = self.model.edges[i];
  625. if( edge['src'] == _id &&
  626. (_dir == '>' || _dir == '*' || _dir == "out") &&
  627. (_type == '*' || self.model.nodes[edge['dest']]['$type'] == _type) &&
  628. ! _utils.contains(ids,edge['dest']) )
  629. ids.push(edge['dest']);
  630. else if( edge['dest'] == _id &&
  631. (_dir == '<' || _dir == '*' || _dir == "in") &&
  632. (_type == '*' || self.model.nodes[edge['src']]['$type'] == _type) &&
  633. ! _utils.contains(ids,edge['src']) )
  634. ids.push(edge['src']);
  635. }
  636. return ids;
  637. }
  638. function print(str)
  639. {
  640. _util.log(str);
  641. }
  642. function setAttr(_attr,_val,_id)
  643. {
  644. if( type != 'action' )
  645. throw 'setAttr() can only be used within actions';
  646. if( _id == undefined )
  647. _id = id;
  648. if( self.model.nodes[_id] == undefined )
  649. throw 'invalid setAttr() id :: '+_id;
  650. else if( !(_attr in self.model.nodes[_id]) || _attr.charAt(0) == '$' )
  651. throw 'invalid setAttr() attribute :: '+_attr;
  652. self.__chattr__(_id,_attr,_val);
  653. }
  654. /* evaluate provided code without the said code having access to
  655. globals (i.e., model, journal) or to 'self' (which we use above to
  656. allow non-global functions to access globals), and catching any
  657. exceptions it may throw... escaped newlines if any are unescaped */
  658. function safe_eval(code)
  659. {
  660. var self = undefined;
  661. try
  662. {
  663. return eval(code);
  664. }
  665. catch(err)
  666. {
  667. if( err == 'IgnoredConstraint' )
  668. return true;
  669. return {'$err':err};
  670. }
  671. }
  672. var res = safe_eval(code);
  673. if( res != undefined && res['$err'] != undefined )
  674. return {'$err':type+' ('+desc+') crashed on :: '+res['$err']};
  675. /* completed accessor */
  676. else if( type == 'accessor' )
  677. return res;
  678. /* failed constraint */
  679. else if( res == false )
  680. return {'$err':type+' ('+desc+') failed'};
  681. },
  682. /* run actions or constraints for specified events and specified nodes
  683. 1. get types of specified nodes (note that we do a little hack for the
  684. special case of pre-create handlers because this.model.nodes does not
  685. yet contain a node with the to-be-created node's id... thus its type
  686. is read from this.next_type)
  687. 2. identify and run applicable handlers based on events and targetTypes */
  688. '__runEventHandlers' :
  689. function(allHandlers,events,ids,handlerType)
  690. {
  691. var types2ids = {};
  692. for( var i in ids )
  693. {
  694. var id = ids[i];
  695. if( id == this.next_id )
  696. var type = this.__getType(this.next_type);
  697. else if( this.model.nodes[id] == undefined )
  698. continue;
  699. else
  700. var type = this.__getType(this.model.nodes[id]['$type']);
  701. if( types2ids[type] == undefined )
  702. types2ids[type] = [];
  703. types2ids[type].push(id);
  704. }
  705. for (let i in allHandlers) {
  706. let handler = allHandlers[i];
  707. let handled = _utils.contains(events, handler['event']) ||
  708. (_utils.contains(events, "validate") && handler['event'] == ""); //handle legacy events
  709. if (!handled) {
  710. continue;
  711. }
  712. if (handler['targetType'] == '*') {
  713. let result = null;
  714. for (let j in ids) {
  715. result = this.__runDesignerCode(
  716. handler['code'],
  717. handler['event'] + ' ' + handler['name'],
  718. handlerType,
  719. ids[j]);
  720. if (result) {
  721. return result;
  722. }
  723. }
  724. if (ids.length == 0) {
  725. result = this.__runDesignerCode(
  726. handler['code'],
  727. handler['event'] + ' ' + handler['name'],
  728. handlerType);
  729. if (result) {
  730. return result;
  731. }
  732. }
  733. }
  734. else {
  735. for (let j in types2ids[handler['targetType']]) {
  736. let id = types2ids[handler['targetType']][j];
  737. let result = this.__runDesignerCode(
  738. handler['code'],
  739. handler['event'] + ' ' + handler['name'],
  740. handlerType,
  741. id);
  742. if (result) {
  743. return result;
  744. }
  745. }
  746. }
  747. }
  748. },
  749. /**************************** MODEL VALIDATION *****************************/
  750. /* verifies that the current model satisfies (1) the min cardinalities set
  751. by its metamodel(s) and (2) all global eventless constraints... returns
  752. the first encountered discrepancy or nothing
  753. 1. count incoming and outgoing connections of each type for each node
  754. 2. compare the above to the min cardinalities
  755. 3. run all global eventless constraints */
  756. 'validateModel' :
  757. function(model)
  758. {
  759. var inCounts = {},
  760. outCounts = {},
  761. model = (model == undefined ? this.model : model),
  762. outContainments = {},
  763. containmentTargets = {};
  764. if( model.nodes == undefined ||
  765. model.edges == undefined ||
  766. model.metamodels == undefined ||
  767. model.metamodels.length == 0 )
  768. return {'$err':'provided model is either empty or not an atompm model'};
  769. for( var i in model.edges )
  770. {
  771. var edge = model.edges[i],
  772. srcType = this.__getType(model.nodes[edge['src']]['$type']),
  773. destType = this.__getType(model.nodes[edge['dest']]['$type']),
  774. srcMetamodel = this.__getMetamodel(model.nodes[edge['src']]['$type']),
  775. destMetamodel = this.__getMetamodel(model.nodes[edge['dest']]['$type']);
  776. if( inCounts[edge['dest']] == undefined )
  777. inCounts[edge['dest']] = {};
  778. if( inCounts[edge['dest']][srcType] == undefined )
  779. inCounts[edge['dest']][srcType] = 0;
  780. inCounts[edge['dest']][srcType]++;
  781. if( outCounts[edge['src']] == undefined )
  782. outCounts[edge['src']] = {};
  783. if( outCounts[edge['src']][destType] == undefined )
  784. outCounts[edge['src']][destType] = 0;
  785. outCounts[edge['src']][destType]++;
  786. if ( outContainments[edge['src']] == undefined ) {
  787. outContainments[edge['src']] = [];
  788. }
  789. if (destType in this.metamodels[destMetamodel]['connectorTypes'] && this.metamodels[destMetamodel]['connectorTypes'][destType] == 'containment') {
  790. outContainments[edge['src']].push(edge['dest']);
  791. }
  792. if ( containmentTargets[edge['src']] == undefined ) {
  793. containmentTargets[edge['src']] = [];
  794. }
  795. if (srcType in this.metamodels[srcMetamodel]['connectorTypes'] && this.metamodels[srcMetamodel]['connectorTypes'][srcType] == 'containment') {
  796. containmentTargets[edge['src']].push(edge['dest']);
  797. }
  798. }
  799. var checked_for_loops = [];
  800. for( var id in model.nodes )
  801. {
  802. var metamodel = this.__getMetamodel(model.nodes[id]['$type']),
  803. type = this.__getType(model.nodes[id]['$type']);
  804. for( var i in this.metamodels[metamodel]['cardinalities'][type] )
  805. {
  806. var cardinality = this.metamodels[metamodel]['cardinalities'][type][i],
  807. tc = cardinality['type'];
  808. if( cardinality['dir'] == 'out' &&
  809. cardinality['min'] > (outCounts[id] == undefined || outCounts[id][tc] == undefined ? 0 : outCounts[id][tc]) )
  810. return {'$err':'insufficient outgoing connections of type '+tc+' for '+model.nodes[id]['$type']+'/'+id};
  811. else if( cardinality['dir'] == 'in' &&
  812. cardinality['min'] > (inCounts[id] == undefined || inCounts[id][tc] == undefined ? 0 : inCounts[id][tc]) )
  813. return {'$err':'insufficient incoming connections of type '+tc+' for '+model.nodes[id]['$type']+'/'+id};
  814. }
  815. if (checked_for_loops.indexOf(id) < 0 && !(type in this.metamodels[metamodel]['connectorTypes'])) {
  816. var visited = [],
  817. tv = [id];
  818. // eslint-disable-next-line no-inner-declarations
  819. function dfs(to_visit) {
  820. var curr = to_visit.pop();
  821. if( curr == undefined )
  822. return undefined; // no more to check
  823. else if( visited.indexOf(curr) > -1 )
  824. return {'$err':'containment loop found for ' + model.nodes[id]['$type']+'/'+id}; // error: loop found!
  825. else {
  826. visited.push(curr);
  827. // find all (containment) associations linked to the object, and add their targets to the to_visit list.
  828. for ( var oc_idx in outContainments[curr] ) {
  829. to_visit = to_visit.concat(containmentTargets[outContainments[curr][oc_idx]]);
  830. }
  831. return dfs( to_visit );
  832. }
  833. }
  834. var res = dfs(tv);
  835. if (res != undefined) {
  836. return res;
  837. }
  838. checked_for_loops= checked_for_loops.concat(visited);
  839. }
  840. }
  841. for (let metamodel in this.metamodels) {
  842. let err = this.__runEventHandlers(this.metamodels[metamodel]['constraints'], ['validate'], [], 'constraint');
  843. if (err)
  844. return err;
  845. }
  846. },
  847. /**************************** MODEL COMPILATION ****************************/
  848. /* compile the current model and the given CS model into an icon definition
  849. metamodel
  850. 0. the entire function body is wrapped in a try/catch... this is our lazy
  851. approach to verifying that the current model is indeed a valid model of
  852. an icon definition metamodel
  853. 1. if the current model is missing the CS formalism, return error
  854. 2. extract information about types from current model
  855. a) find all ConcreteSyntax/Icons and ConcreteSyntax/Links
  856. b) map all CS/Icons to their IconIcon in the CS model (argument)
  857. c) map all CS/Icons to the nodes they're [transitively] connected to
  858. (except their IconContents links)
  859. d) save all edges between contained nodes from step c)
  860. e) enhance every contained node (from step c)) with information about
  861. its associated IconIcon (e.g., position, orientation)... this is
  862. needed so that the final '$contents' attributes of each generated
  863. *Icon hold sufficient information to render icons as the user
  864. specified them... note that position attributes are adjusted to make
  865. them relative to the containing IconIcon's top-left corner
  866. e*) enhance nodes contained within Links with link decorator
  867. positioning information (e.g., xratio, yoffset)
  868. f) when pre-defined arrowheads/tails have been selected by the user,
  869. pretend the user has actually drawn them s.t. they get handled by
  870. link decorator positioning code during modelling... in practice:
  871. i. identify pre-defined arrowheads/tails
  872. ii. locate corresponding drawings within relevant Link's
  873. LinkIcon $contents
  874. iii. copy them into relevant Link's compiled $contents
  875. iv. enhance them with link decorator information (c.f., step e*)
  876. 3. construct mm.types based on information from step 2... the resulting
  877. mm.types wil look very much like ConcreteSyntax.types, with a few added
  878. 'special' attributes (e.g., $asuri, $contents, etc.)
  879. 4. check whether all non-abstract types have an icon, and no abstract types have an icon
  880. 5. return mm stringified (ensures no references to objects in this.model
  881. are returned) */
  882. 'compileToIconDefinitionMetamodel' :
  883. function(csm, asmm)
  884. {
  885. var CS = '/Formalisms/__LanguageSyntax__/ConcreteSyntax/ConcreteSyntax';
  886. try
  887. {
  888. /* 1 */
  889. if( ! _utils.contains(this.model.metamodels,CS) )
  890. throw 'icon definition models must have the '+CS+' formalism loaded';
  891. else
  892. var model = _utils.jsonp(this.read());
  893. nodes = {};
  894. for (var id in model.nodes) {
  895. if (model.nodes[id]['$type'].slice(0, CS.length) == CS) {
  896. nodes[id] = model.nodes[id];
  897. }
  898. }
  899. model.nodes = nodes;
  900. /* 2 */
  901. var mm =
  902. {'types':{},
  903. 'constraints':[],
  904. 'actions':[],
  905. 'cardinalities':{},
  906. 'legalConnections':{},
  907. 'connectorTypes':{},
  908. 'types2parentTypes':{}},
  909. iids = [],
  910. iids2contents = {},
  911. ids2csids = {},
  912. self = this,
  913. outNeighbors =
  914. /* returns the given node's outbound neighbors */
  915. function(source)
  916. {
  917. return model.edges.filter(function(edge) {return edge['src'] == source && model.nodes[edge['dest']] != undefined;}).
  918. map(function(edge) {return edge['dest'];});
  919. },
  920. getConnectedNodes =
  921. /* compute the [transitive] contents of 'container'... this
  922. function is a bit of an oversimplification: it makes the
  923. reasonable but not necessarily correct assumption that
  924. anything that is [transitively] connected to a CS/Icon or
  925. CS/Link is inside it */
  926. function(container,contents)
  927. {
  928. var _contents = {};
  929. outNeighbors(container).forEach(
  930. function(n)
  931. {
  932. if( !(n in contents) )
  933. _contents[n] = 1;
  934. });
  935. if( _utils.keys(_contents).length == 0 )
  936. return contents;
  937. contents = _utils.mergeDicts([contents,_contents]);
  938. return _utils.mergeDicts(
  939. _utils.keys(_contents).map(
  940. function(_c) {return getConnectedNodes(_c,contents);} ));
  941. };
  942. /* 2a */
  943. for( var id in model.nodes )
  944. if( model.nodes[id]['$type'] == CS+'/Icon' ||
  945. model.nodes[id]['$type'] == CS+'/Link' )
  946. {
  947. iids.push(id);
  948. iids2contents[id] = {'nodes':{},'edges':[]};
  949. }
  950. /* 2b */
  951. csm = _utils.jsonp(csm);
  952. for( var csid in csm.nodes )
  953. {
  954. var id = csm.nodes[csid]['$asuri']['value'].match(/.*\/(.*)\.instance$/)[1];
  955. ids2csids[id] = csid;
  956. }
  957. iids.forEach(
  958. function(iid)
  959. {
  960. /* 2c */
  961. _utils.keys(getConnectedNodes(iid,{})).filter(
  962. function(id)
  963. {
  964. return model.nodes[id]['$type'] != CS+'/IconContents';
  965. }).forEach(
  966. function(id)
  967. {
  968. iids2contents[iid].nodes[id] = model.nodes[id];
  969. });
  970. /* 2d */
  971. model.edges.forEach(
  972. function(edge)
  973. {
  974. if( iids2contents[iid].nodes[edge['src']] != undefined &&
  975. iids2contents[iid].nodes[edge['dest']] != undefined )
  976. iids2contents[iid].edges.push(edge);
  977. });
  978. /* 2e */
  979. var iidCSIcon = csm.nodes[ ids2csids[iid] ];
  980. for( var vid in iids2contents[iid].nodes )
  981. {
  982. var vidCSIcon = csm.nodes[ ids2csids[vid] ],
  983. vidContentsNode = iids2contents[iid].nodes[vid];
  984. ['position','orientation','scale','link-style'].forEach(
  985. function(_) {vidContentsNode[_] = vidCSIcon[_];});
  986. var vidContentsNodePosition = vidContentsNode['position']['value'],
  987. iidCSIconPosition = iidCSIcon['position']['value'],
  988. vidContentsNodeRelX = vidContentsNodePosition[0] - iidCSIconPosition[0],
  989. vidContentsNodeRelY = vidContentsNodePosition[1] - iidCSIconPosition[1];
  990. vidContentsNode['position']['value'] = [vidContentsNodeRelX,vidContentsNodeRelY];
  991. /* 2e* */
  992. if( model.nodes[iid]['$type'] == CS+'/Link' )
  993. {
  994. var sx = iidCSIcon['scale']['value'][0],
  995. sy = iidCSIcon['scale']['value'][1],
  996. linkPathBBox =
  997. {'x':sx*35,
  998. 'y':sy*77,
  999. 'width': sx*198,
  1000. 'height': sy*(model.nodes[iid]['link-style']['stroke-width'] || 1)};
  1001. vidContentsNode['position']['value'] = [0,0];
  1002. vidContentsNode['$linkDecoratorInfo'] =
  1003. {'type':'map<string,double>',
  1004. 'value':
  1005. {'xratio' :(vidContentsNodeRelX-linkPathBBox.x) / (linkPathBBox.width-linkPathBBox.x),
  1006. 'yoffset':vidContentsNodeRelY - (linkPathBBox.y+linkPathBBox.height/2)}};
  1007. }
  1008. }
  1009. /* 2f */
  1010. if( model.nodes[iid]['$type'] == CS+'/Link' )
  1011. {
  1012. var contents = csm.nodes[ids2csids[iid]]['$contents']['value'].nodes,
  1013. sy = iidCSIcon['scale']['value'][1];
  1014. ['arrowHead','arrowTail'].forEach(
  1015. function(at)
  1016. {
  1017. if( !(at in model.nodes[iid]) )
  1018. throw 'migrate to new Link specification means to compile';
  1019. var a = model.nodes[iid][at]['value'];
  1020. if( a != 'custom' )
  1021. for( var vid in contents )
  1022. if( 'mapper' in contents[vid] &&
  1023. ( _styleinfo[a + ':' + at] ) && (matches = contents[vid]['mapper']['value'].match("^'"+a+":"+at+":(.*)';")) )
  1024. {
  1025. iids2contents[iid].nodes[vid] = contents[vid];
  1026. iids2contents[iid].nodes[vid]['mapper']['value'] = '';
  1027. iids2contents[iid].nodes[vid]['position']['value'] = [0,0];
  1028. iids2contents[iid].nodes[vid]['$linkDecoratorInfo'] =
  1029. {'type':'map<string,double>',
  1030. 'value':
  1031. {'xratio' :(at == 'arrowHead' ? -1 : 1),
  1032. 'yoffset':-_styleinfo[a + ':' + at]/2*sy}};
  1033. break;
  1034. }
  1035. });
  1036. }
  1037. /* 3 */
  1038. var node = model.nodes[iid];
  1039. type = node['typename']['value'];
  1040. isConnectorType = 'link-style' in node;
  1041. mm.types[type] = [];
  1042. self.metamodels[CS].
  1043. types[(isConnectorType ? 'Link' : 'Icon')].forEach(
  1044. function(attr)
  1045. {
  1046. if( _utils.contains(['link-style','typename','mapper','parser','position'],attr['name']) )
  1047. mm.types[type].push(
  1048. {'name': attr['name'],
  1049. 'type': node[attr['name']]['type'],
  1050. 'default': node[attr['name']]['value']});
  1051. else
  1052. mm.types[type].push(attr);
  1053. });
  1054. mm.types[type].push(
  1055. {'name': '$contents',
  1056. 'type': 'map<string,*>',
  1057. 'default': iids2contents[iid]},
  1058. {'name': '$asuri',
  1059. 'type': 'string',
  1060. 'default': '-1'});
  1061. if( isConnectorType )
  1062. mm.types[type].push(
  1063. {'name': '$segments',
  1064. 'type': 'map<string,list<string>>',
  1065. 'default': {}});
  1066. mm.cardinalities[type] = [];
  1067. mm.types2parentTypes[type] = [];
  1068. });
  1069. /* 4 */
  1070. var types = [],
  1071. abstractTypes = [];
  1072. for (var idx in asmm["constraints"]) {
  1073. var curr_constraint = asmm["constraints"][idx];
  1074. if (curr_constraint["name"] == "noAbstractInstances") {
  1075. abstractTypes.push(curr_constraint["targetType"]);
  1076. }
  1077. }
  1078. for (var curr_type in asmm["types"]) {
  1079. if ((curr_type + 'Link' in mm["types"]) || (curr_type + 'Icon' in mm["types"])) {
  1080. if (abstractTypes.indexOf(curr_type) >= 0) {
  1081. return {'$err':'abstract type '+curr_type+' cannot have a visual representation'};
  1082. }
  1083. } else {
  1084. if (abstractTypes.indexOf(curr_type) < 0) {
  1085. return {'$err':'concrete type '+curr_type+' needs to have a visual representation'};
  1086. }
  1087. }
  1088. }
  1089. for (var curr_type in mm["types"]) {
  1090. if (!(curr_type.slice(0, -4) in asmm["types"])) {
  1091. return {'$err':'type '+curr_type.slice(0, -4)+' not found in the abstract syntax metamodel, visual representation ' + curr_type + ' invalid'};
  1092. }
  1093. }
  1094. /* 5 */
  1095. return _utils.jsons(mm,null,"\t");
  1096. }
  1097. catch(err)
  1098. {
  1099. return {'$err':'invalid metamodel model, crashed on :: '+err};
  1100. }
  1101. },
  1102. /* compile the current model into a metamodel
  1103. 0. the entire function body is wrapped in a try/catch... this is our lazy
  1104. approach to verifying that the current model is indeed a valid model of
  1105. a metamodel
  1106. 1. if the current model is not an ER or a SCD model, return error
  1107. 2. if the current model is a SCD model, transform it into an ER model
  1108. before beginning compilation (via _mt.transform)
  1109. 3. copy information about types, constraints, actions, cardinalities,
  1110. connectorTypes and types2parentTypes from current model to mm
  1111. 4. add any missing cardinalities (relationships between entities define
  1112. legal connections but the user might have omitted to specify their
  1113. cardinalities), then construct legalConnections and store it in mm
  1114. 5. return mm stringified (ensures no references to objects in this.model
  1115. are returned) */
  1116. 'compileToMetamodel' :
  1117. function()
  1118. {
  1119. var ER = '/Formalisms/__LanguageSyntax__/EntityRelationship/EntityRelationship',
  1120. SCD = '/Formalisms/__LanguageSyntax__/SimpleClassDiagram/SimpleClassDiagram',
  1121. isolateMMModel =
  1122. /* remove all non-ER/SCD entities from the provided model...
  1123. doing so considerably eases compilation */
  1124. function(m)
  1125. {
  1126. m = _utils.jsonp(m);
  1127. for( var id in m.nodes )
  1128. if( ! m.nodes[id]['$type'].match('^'+ER) &&
  1129. ! m.nodes[id]['$type'].match('^'+SCD) )
  1130. delete m.nodes[id];
  1131. var keepEdges = [];
  1132. m.edges.forEach(
  1133. function(edge,i)
  1134. {
  1135. if( edge['src'] in m.nodes && edge['dest'] in m.nodes )
  1136. keepEdges.push(edge);
  1137. });
  1138. m.edges = keepEdges;
  1139. return m;
  1140. };
  1141. try
  1142. {
  1143. /* 1-2 */
  1144. if( _utils.contains(this.model.metamodels,ER) &&
  1145. _utils.contains(this.model.metamodels,SCD) )
  1146. throw 'metamodel models should not have more than one loaded metametamodel';
  1147. else if( _utils.contains(this.model.metamodels,ER) )
  1148. var model = isolateMMModel(this.read());
  1149. else if( _utils.contains(this.model.metamodels,SCD) )
  1150. var model = _mt.transform(
  1151. _utils.jsons(isolateMMModel(this.read())),
  1152. 'SimpleClassDiagram-2-EntityRelationship');
  1153. else
  1154. throw 'metamodel models should have at least one loaded metametamodel';
  1155. /* 3 */
  1156. var mm =
  1157. {'types':{},
  1158. 'constraints':[],
  1159. 'actions':[],
  1160. 'cardinalities':{},
  1161. 'legalConnections':{},
  1162. 'connectorTypes':{},
  1163. 'types2parentTypes':{}};
  1164. for( var id in model.nodes )
  1165. {
  1166. var node = model.nodes[id];
  1167. if( node['$type'] == ER+'/Entity' ||
  1168. node['$type'] == ER+'/Relationship' )
  1169. {
  1170. var type = node['name']['value'];
  1171. mm.types[type] = [];
  1172. node['attributes']['value'].forEach(
  1173. function(attr) {mm.types[type].push(attr);});
  1174. node['constraints']['value'].forEach(
  1175. function(constraint)
  1176. {
  1177. constraint['targetType'] = type;
  1178. mm.constraints.push(constraint);
  1179. });
  1180. node['actions']['value'].forEach(
  1181. function(action)
  1182. {
  1183. action['targetType'] = type;
  1184. mm.actions.push(action);
  1185. });
  1186. mm.cardinalities[type] = node['cardinalities']['value'];
  1187. if( node['linktype'] != undefined )
  1188. mm.connectorTypes[type] = node['linktype']['value'];
  1189. }
  1190. else if( node['$type'] == ER+'/GlobalConstraint' )
  1191. mm.constraints.push(
  1192. {'name':node['name']['value'],
  1193. 'event':node['event']['value'],
  1194. 'targetType':'*',
  1195. 'code':node['code']['value']});
  1196. else if( node['$type'] == ER+'/GlobalAction' )
  1197. mm.actions.push(
  1198. {'name':node['name']['value'],
  1199. 'event':node['event']['value'],
  1200. 'targetType':'*',
  1201. 'code':node['code']['value']});
  1202. else
  1203. throw 'node "'+id+'" does not conform to the '+ER+' metamodel';
  1204. }
  1205. mm.types2parentTypes = model.types2parentTypes || {};
  1206. /* 4 */
  1207. var types2legalNeighborTypes = {},
  1208. addMissingCardinalities =
  1209. function(t1,t2,dir)
  1210. {
  1211. /* if there is no cardinality between t1 and t2 for dir, add a default cardinality...
  1212. 1:1 for links
  1213. 0:Infinity for nodes */
  1214. if( ! mm.cardinalities[t1].some( function(c) {return c['type'] == t2 && c['dir'] == dir;} ) )
  1215. {
  1216. if( mm.connectorTypes[t1] )
  1217. mm.cardinalities[t1].push(
  1218. {'dir':dir,
  1219. 'type':t2,
  1220. 'min':'0',
  1221. 'max':'1'});
  1222. else
  1223. mm.cardinalities[t1].push(
  1224. {'dir':dir,
  1225. 'type':t2,
  1226. 'min':'0',
  1227. 'max':'Infinity'});
  1228. }
  1229. };
  1230. model.edges.forEach(
  1231. function(edge)
  1232. {
  1233. var srcType = model.nodes[edge['src']]['name']['value'],
  1234. destType = model.nodes[edge['dest']]['name']['value'];
  1235. addMissingCardinalities(srcType,destType,'out',mm.connectorTypes[srcType]);
  1236. addMissingCardinalities(destType,srcType,'in',mm.connectorTypes[destType]);
  1237. });
  1238. for( var type in mm.types )
  1239. {
  1240. if( types2legalNeighborTypes[type] == undefined )
  1241. types2legalNeighborTypes[type] = [];
  1242. mm.cardinalities[type].forEach(
  1243. function(cardinality)
  1244. {
  1245. if( cardinality['dir'] == 'out' )
  1246. types2legalNeighborTypes[type].push(cardinality['type']);
  1247. });
  1248. }
  1249. for( var type in types2legalNeighborTypes )
  1250. {
  1251. if( mm.connectorTypes[type] != undefined )
  1252. continue;
  1253. types2legalNeighborTypes[type].forEach(
  1254. function(ntype)
  1255. {
  1256. if (types2legalNeighborTypes[ntype] == undefined){
  1257. let msg = "Error! Problem with edges for class: " + type +"\nFound constraints: " + JSON.stringify(types2legalNeighborTypes[type]);
  1258. throw msg;
  1259. }
  1260. types2legalNeighborTypes[ntype].forEach(
  1261. function(nntype)
  1262. {
  1263. if( mm.legalConnections[type] == undefined )
  1264. mm.legalConnections[type] = {};
  1265. if( mm.legalConnections[type][nntype] == undefined )
  1266. mm.legalConnections[type][nntype] = [];
  1267. mm.legalConnections[type][nntype].push(ntype);
  1268. });
  1269. });
  1270. }
  1271. /* 5 */
  1272. return _utils.jsons(mm,null,"\t");
  1273. }
  1274. catch(err)
  1275. {
  1276. return {'$err':'invalid metamodel model, crashed on :: ' + err};
  1277. }
  1278. },
  1279. /************************* JOURNALING + UNDO/REDO **************************/
  1280. 'journal':[],
  1281. 'journalIndex':0,
  1282. /* NOTE: on this.undoredoJournal
  1283. this.undoredoJournal contains cud operations performed during the last
  1284. undo()/redo() call provided no user-operations was performed since the
  1285. said call (in which case this.undoredoJournal is empty)... undo/redo
  1286. ops need to be logged for __changelog() to be able to return their
  1287. effects... however, they should not be logged in the main journal since
  1288. all they conceptually do is move a cursor in it... in practice,
  1289. this.undoredoJournal is emptied on every call to undo(), redo() and
  1290. __setStepCheckpoint() */
  1291. /* create a checkpoint : add an entry in the log used as a delimiter to know
  1292. where to stop when restoring (i.e., undoing failed pre-/post-actions or
  1293. crud ops) */
  1294. '__checkpoint' :
  1295. function()
  1296. {
  1297. this.__log({'op':'MKCHKPT'});
  1298. },
  1299. /* deletes the last checkpoint of the current model (other than tidying the
  1300. journal, there's no reason for ever clearing unused checkpoints) */
  1301. '__clearCheckpoint' :
  1302. function()
  1303. {
  1304. for( var i=this.journal.length-1; i>=0; i-- )
  1305. if( this.journal[i]['op'] == 'MKCHKPT' )
  1306. {
  1307. this.journal.splice(i,1);
  1308. this.journalIndex--;
  1309. break;
  1310. }
  1311. },
  1312. /* case 1: 'this.undoredoJournal is defined (possibly empty)'
  1313. returns the operations performed by the last undo()/redo()
  1314. case 2: 'this.undoredoJournal = undefined'
  1315. returns a copy of the portion of the journal that describes the changes
  1316. made by the last user-operation... note that user-operations always call
  1317. __setStepCheckpoint before running */
  1318. '__changelog' :
  1319. function()
  1320. {
  1321. if( this.undoredoJournal != undefined )
  1322. return _utils.clone( this.undoredoJournal.splice(0) );
  1323. var ji = this.journalIndex;
  1324. while( ji > 0 )
  1325. if( this.journal[--ji]['op'] == 'MKSTPCHKPT' )
  1326. break;
  1327. return _utils.clone( this.journal.slice(ji+1,this.journalIndex) );
  1328. },
  1329. /* case 1: 'log=undefined'
  1330. logs an internal cud operation into the journal... if the current index in
  1331. the journal is anything but the end of the journal, clear everything after
  1332. the index (this effectively erases the command "future-history" when
  1333. editing an "undone" model)
  1334. case 2: 'log="UNDOREDO"'
  1335. logs an internal cud operation into this.undoredoJournal
  1336. case 3: 'log="DONTLOG"'
  1337. do nothing
  1338. legal logging commands:
  1339. MKNODE id,node
  1340. RMNODE id,node
  1341. MKEDGE id,id
  1342. RMEDGE id,id
  1343. CHATTR id,attr,new_val,old_val
  1344. LOADMM name,mm
  1345. DUMPMM name,mm
  1346. RESETM name,model
  1347. MKCHKPT
  1348. MKSTPCHKPT
  1349. MKUSRCHKPT */
  1350. '__log' :
  1351. function(step,log)
  1352. {
  1353. if( log == undefined )
  1354. {
  1355. if( this.journalIndex != this.journal.length )
  1356. this.journal.splice(this.journalIndex);
  1357. this.journal.push(step);
  1358. this.journalIndex++;
  1359. }
  1360. else if( log == 'UNDOREDO' )
  1361. this.undoredoJournal.push(step);
  1362. //else if( log == 'DONTLOG' )
  1363. // ;
  1364. },
  1365. /* redo a single step
  1366. 1. identify the nature of the logged operation
  1367. 2. reproduce its effects (these are logged in this.undoredoJournal) */
  1368. '__redo' :
  1369. function(step)
  1370. {
  1371. var log = 'UNDOREDO';
  1372. if( step['op'] == 'CHATTR' ) this.__chattr__(step['id'],step['attr'],step['new_val'],log);
  1373. else if( step['op'] == 'DUMPMM' ) this.__dumpmm__(step['name'],log);
  1374. else if( step['op'] == 'LOADMM' ) this.__loadmm__(step['name'],step['mm'],log);
  1375. else if( step['op'] == 'MKEDGE' ) this.__mkedge__(step['id1'],step['id2'],step['i'],log);
  1376. else if( step['op'] == 'MKNODE' ) this.__mknode__(step['id'],_utils.jsonp(step['node']),log);
  1377. else if( step['op'] == 'RESETM' ) this.__resetm__(step['new_name'],step['new_model'],false,log);
  1378. else if( step['op'] == 'RMEDGE' ) this.__rmedge__(step['i'],log);
  1379. else if( step['op'] == 'RMNODE' ) this.__rmnode__(step['id'],log);
  1380. },
  1381. /* redo all of the changes until the next step-checkpoint or until after the
  1382. specified user-checkpoint, if any... when complete the journal index is
  1383. after the redone MKSTPCHKPT/MKUSRCHKPT entry... redoing when the journal
  1384. index is at the end of the journal will have no effect */
  1385. 'redo' :
  1386. function(uchkpt)
  1387. {
  1388. this.undoredoJournal = [];
  1389. var stopMarkerReached =
  1390. (uchkpt == undefined ?
  1391. function(step) {return step['op'] == 'MKSTPCHKPT';} :
  1392. function(step) {return uchkptEncountered && step['op'] == 'MKUSRCHKPT';}),
  1393. self = this,
  1394. uchkptEncountered = false,
  1395. uchkptReached = function(step) {return step['op'] == 'MKUSRCHKPT' && step['name'] == uchkpt;},
  1396. uchkptFound =
  1397. function(i)
  1398. {
  1399. while( i < self.journal.length )
  1400. if( uchkptReached(self.journal[i++]) )
  1401. return true;
  1402. return false;
  1403. };
  1404. if( uchkpt == undefined || uchkptFound(this.journalIndex) )
  1405. while( this.journalIndex < this.journal.length )
  1406. {
  1407. if( uchkpt != undefined &&
  1408. ! uchkptEncountered &&
  1409. uchkptReached(this.journal[this.journalIndex]) )
  1410. uchkptEncountered = true;
  1411. if( this.journal[++this.journalIndex] == undefined ||
  1412. stopMarkerReached( this.journal[this.journalIndex] ) )
  1413. break;
  1414. else
  1415. this.__redo(this.journal[this.journalIndex]);
  1416. }
  1417. return {'changelog':this.__changelog()};
  1418. },
  1419. /* undo every logged operation until a MKCHKPT is reached (and remove them
  1420. and the said MKCHKPT from the journal)... note that this operation is only
  1421. called internally and that the journalIndex will always be at the end of
  1422. the journal when it's called (and after its called) */
  1423. '__restoreCheckpoint' :
  1424. function()
  1425. {
  1426. while( this.journal.length > 0 )
  1427. {
  1428. var step = this.journal.pop();
  1429. if(step['op'] == 'MKCHKPT' )
  1430. break;
  1431. else
  1432. this.__undo(step,'DONTLOG');
  1433. }
  1434. this.journalIndex = this.journal.length;
  1435. },
  1436. /* create a step-checkpoint : add an entry in the log used as a delimiter to
  1437. know where to stop when undoing/redoing (i.e., on client undo/redo)
  1438. 1. create new step-checkpoint or re-use a 'zombie' step-checkpoint (zombie
  1439. step-checkpoints (SC) are SCs associated to failed or effectless user
  1440. operations... they are recognizable as SCs with no following log
  1441. entries... there's at most 1 zombie SC in the log at any given time) */
  1442. '__setStepCheckpoint' :
  1443. function()
  1444. {
  1445. this.undoredoJournal = undefined;
  1446. if( this.journal.length == 0 ||
  1447. this.journal[this.journal.length-1]['op'] != 'MKSTPCHKPT' )
  1448. this.__log({'op':'MKSTPCHKPT'});
  1449. },
  1450. /* create a user-checkpoint : add an entry in the log used as a delimiter to
  1451. enable undoing/redoing until a specified marker
  1452. 1. create new step-checkpoint or re-use a 'zombie' user-checkpoint (zombie
  1453. user-checkpoints (UC) are UCs associated to failed or effectless user
  1454. operations... they are recognizable as same-name UCs with no following
  1455. log entries... there's at most 1 zombie UC per name in the log at any
  1456. given time) */
  1457. 'setUserCheckpoint' :
  1458. function(name)
  1459. {
  1460. this.undoredoJournal = undefined;
  1461. if( this.journal.length == 0 ||
  1462. this.journal[this.journal.length-1]['op'] != 'MKUSRCHKPT' ||
  1463. this.journal[this.journal.length-1]['name'] != name )
  1464. this.__log({'op':'MKUSRCHKPT','name':name});
  1465. },
  1466. /* undo a single step
  1467. 1. identify the nature of the logged operation
  1468. 2. invert its effects (these may be ignored (log = 'DONTLOG') or logged in
  1469. this.undoredoJournal (log = 'UNDOREDO') */
  1470. '__undo' :
  1471. function(step,log)
  1472. {
  1473. if( step['op'] == 'CHATTR' ) this.__chattr__(step['id'],step['attr'],step['old_val'],log);
  1474. else if( step['op'] == 'DUMPMM' ) this.__loadmm__(step['name'],step['mm'],log);
  1475. else if( step['op'] == 'LOADMM' ) this.__dumpmm__(step['name'],log);
  1476. else if( step['op'] == 'MKEDGE' ) this.__rmedge__(step['i'],log);
  1477. else if( step['op'] == 'MKNODE' ) this.__rmnode__(step['id'],log);
  1478. else if( step['op'] == 'RESETM' ) this.__resetm__(step['old_name'],step['old_model'],false,log);
  1479. else if( step['op'] == 'RMEDGE' ) this.__mkedge__(step['id1'],step['id2'],step['i'],log);
  1480. else if( step['op'] == 'RMNODE' ) this.__mknode__(step['id'],_utils.jsonp(step['node']),log);
  1481. },
  1482. /* undo all of the changes since the last step-checkpoint or since the
  1483. specified user-checkpoint, if any... when complete the journal index is on
  1484. the undone MKSTPCHKPT/MKUSRCHKPT entry... undoing when the journal index is 0
  1485. or when a non-existing user-checkpoint is given will have no effect */
  1486. 'undo':
  1487. function(uchkpt)
  1488. {
  1489. this.undoredoJournal = [];
  1490. var stopMarkerReached =
  1491. (uchkpt == undefined ?
  1492. function(step) {return step['op'] == 'MKSTPCHKPT';} :
  1493. function(step) {return step['op'] == 'MKUSRCHKPT' && step['name'] == uchkpt;}),
  1494. self = this,
  1495. stopMarkerFound =
  1496. function(i)
  1497. {
  1498. while( --i >= 0 )
  1499. if( stopMarkerReached(self.journal[i]) )
  1500. return true;
  1501. return false;
  1502. };
  1503. if( uchkpt == undefined || stopMarkerFound(this.journalIndex) )
  1504. while( this.journalIndex > 0 )
  1505. if( stopMarkerReached( this.journal[--this.journalIndex] ) )
  1506. break;
  1507. else
  1508. this.__undo(this.journal[this.journalIndex],'UNDOREDO');
  1509. return {'changelog':this.__changelog()};
  1510. },
  1511. /****************************** INTERNAL CUD *******************************/
  1512. /* the following functions are super basic and low-level, they offer cud (no
  1513. read) commands on this' internal data structures... their main purposes
  1514. are (1) to localize the said cud operations, and (2) to log everything
  1515. they do... logging enables undoing and redoing (on constraint/action/...
  1516. failure or on client requests) and facilitates change pushing (i.e., push
  1517. a short change log rather the full model)... note that it is assumed that
  1518. only valid parameters are passed to these functions... last but not least,
  1519. the optional 'log' parameter is used when undoing/redoing to log undoing/
  1520. redoing cud ops elsewhere than in this.journal
  1521. __chattr__ change an attribute's value
  1522. > log id,attr,new_val,old_val
  1523. __dumpmm__ remove mm from this.model.metamodels and this.metamodels
  1524. > log name,mm
  1525. __loadmm__ add a mm to this.model.metamodels and this.metamodels
  1526. > log name,mm
  1527. __mkedge__ add an edge to this.model.edges... optional 'i' parameter
  1528. specifies index of new edge in this.model.edges
  1529. > log id1,id2,i
  1530. __mknode__ add a node to this.model.nodes
  1531. > log id,node
  1532. __resetm__ when the 'insert' parameter is false, replaces the current
  1533. model with another + updates this.next_id to account for ids
  1534. in loaded model + updates model.metamodels to account for
  1535. metamodels loaded before the model
  1536. when the 'insert' parameter is true, inserts the given model
  1537. alongside the current model + alters the given model's ids to
  1538. avoid clashes with existing ids + updates this.next_id... the
  1539. logged value of 'insert' ends up being the offset we applied
  1540. to the provided model's ids
  1541. > log new_name,new_model,old_name,old_model,insert
  1542. __rmedge__ remove an edge from this.model.edges
  1543. > log id1,id2,i
  1544. __rmnode__ remove a node from this.model.nodes
  1545. > log id,node
  1546. note: these functions never log any 'live' data into the log (i.e., any
  1547. references that could be altered elsewhere thereby altering the
  1548. journal's contents) */
  1549. '__chattr__' :
  1550. function(id,attr,new_val,log)
  1551. {
  1552. var getattr = undefined,
  1553. setattr = undefined,
  1554. attrval = function(v) {return (v == undefined ? v : _utils.jsonp(v));},
  1555. self = this;
  1556. if( attr.match(/.+\/.+/) )
  1557. {
  1558. var curr = this.model.nodes[id];
  1559. for( var i in (path = attr.split('/')) )
  1560. curr = curr[path[i]];
  1561. getattr = function() {return curr['value'];};
  1562. setattr = function(v) {curr['value'] = v;};
  1563. }
  1564. else
  1565. {
  1566. getattr = function() {return self.model.nodes[id][attr]['value'];};
  1567. setattr = function(v) {self.model.nodes[id][attr]['value'] = v;};
  1568. }
  1569. var _old_val = _utils.jsons(getattr()),
  1570. _new_val = _utils.jsons(new_val);
  1571. if( _old_val == _new_val )
  1572. return;
  1573. setattr( attrval(_new_val) );
  1574. this.__log(
  1575. {'op':'CHATTR',
  1576. 'id':id,
  1577. 'attr':attr,
  1578. 'new_val': attrval(_new_val),
  1579. 'old_val': attrval(_old_val)},
  1580. log);
  1581. },
  1582. '__dumpmm__' :
  1583. function(name,log)
  1584. {
  1585. for( var i in this.model.metamodels )
  1586. if( this.model.metamodels[i] == name )
  1587. {
  1588. this.model.metamodels.splice(i,1);
  1589. break;
  1590. }
  1591. var mm = this.metamodels[name];
  1592. delete this.metamodels[name];
  1593. this.__log(
  1594. {'op':'DUMPMM',
  1595. 'name':name,
  1596. 'mm':_utils.jsons(mm)},
  1597. log);
  1598. },
  1599. '__loadmm__' :
  1600. function(name,mm,log)
  1601. {
  1602. this.metamodels[name] = eval('('+ mm +')');
  1603. if( ! _utils.contains(this.model.metamodels,name) )
  1604. this.model.metamodels.push(name);
  1605. this.__log(
  1606. {'op':'LOADMM',
  1607. 'name':name,
  1608. 'mm':mm},
  1609. log);
  1610. },
  1611. '__mkedge__' :
  1612. function(id1,id2,i,log)
  1613. {
  1614. if( i == undefined )
  1615. i = this.model.edges.push({'src':id1, 'dest':id2})-1;
  1616. else
  1617. this.model.edges.splice(i,0,{'src':id1, 'dest':id2});
  1618. this.__log(
  1619. {'op':'MKEDGE',
  1620. 'id1':id1,
  1621. 'id2':id2,
  1622. 'i':i},
  1623. log);
  1624. },
  1625. '__mknode__' :
  1626. function(id,node,log)
  1627. {
  1628. this.model.nodes[id] = node;
  1629. this.__log(
  1630. {'op':'MKNODE',
  1631. 'id':id,
  1632. 'node':_utils.jsons(node)},
  1633. log);
  1634. },
  1635. '__resetm__' :
  1636. function(new_name,new_model,insert,log)
  1637. {
  1638. var old_model = this.read(),
  1639. old_name = this.name;
  1640. if( insert )
  1641. {
  1642. var _new_model = eval('('+ new_model +')');
  1643. for( var id in _new_model.nodes )
  1644. this.model.nodes[parseInt(id)+this.next_id] = _new_model.nodes[id];
  1645. _new_model.edges.forEach(
  1646. function(edge)
  1647. {
  1648. this.model.edges.push(
  1649. {'src': parseInt(edge.src)+this.next_id,
  1650. 'dest': parseInt(edge.dest)+this.next_id});
  1651. }, this);
  1652. new_model = this.read();
  1653. insert = this.next_id;
  1654. }
  1655. else
  1656. {
  1657. this.model = eval('('+ new_model +')');
  1658. for( var mm in this.metamodels )
  1659. if( ! _utils.contains(this.model.metamodels,mm) )
  1660. this.model.metamodels.push(mm);
  1661. }
  1662. this.name = new_name;
  1663. for( var id in this.model.nodes )
  1664. if( id >= this.next_id )
  1665. this.next_id = parseInt(id)+1;
  1666. this.__log(
  1667. {'op':'RESETM',
  1668. 'new_name': new_name,
  1669. 'new_model':new_model,
  1670. 'old_name': old_name,
  1671. 'old_model':old_model,
  1672. 'insert':insert},
  1673. log);
  1674. },
  1675. '__rmedge__' :
  1676. function(i,log)
  1677. {
  1678. var edge = this.model.edges.splice(i,1).pop();
  1679. this.__log(
  1680. {'op':'RMEDGE',
  1681. 'i':i,
  1682. 'id1':edge['src'],
  1683. 'id2':edge['dest']},
  1684. log);
  1685. },
  1686. '__rmnode__' :
  1687. function(id,log)
  1688. {
  1689. node = this.model.nodes[id];
  1690. delete this.model.nodes[id];
  1691. this.__log(
  1692. {'op':'RMNODE',
  1693. 'id':id,
  1694. 'node':_utils.jsons(node)},
  1695. log);
  1696. },
  1697. /***************************** INTERNAL UTILS ******************************/
  1698. /* splits a full type of the form '/path/to/metamodel/type' and returns
  1699. '/path/to/metamodel' */
  1700. '__getMetamodel' :
  1701. function(fulltype)
  1702. {
  1703. return fulltype.match(/(.*)\/.*/)[1];
  1704. },
  1705. /* splits a full type of the form '/path/to/metamodel/type' and returns
  1706. 'type' */
  1707. '__getType' :
  1708. function(fulltype)
  1709. {
  1710. return fulltype.match(/.*\/(.*)/)[1];
  1711. }
  1712. };