annotate java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramModel.java @ 1:e3935c01cde2 tip

moved license of PdPersistenceManager to the beginning of the file
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 08 Jul 2014 19:52:03 +0100
parents 78b7fc5391a2
children
rev   line source
f@0 1 /*
f@0 2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
f@0 3
f@0 4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
f@0 5
f@0 6 This program is free software: you can redistribute it and/or modify
f@0 7 it under the terms of the GNU General Public License as published by
f@0 8 the Free Software Foundation, either version 3 of the License, or
f@0 9 (at your option) any later version.
f@0 10
f@0 11 This program is distributed in the hope that it will be useful,
f@0 12 but WITHOUT ANY WARRANTY; without even the implied warranty of
f@0 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
f@0 14 GNU General Public License for more details.
f@0 15
f@0 16 You should have received a copy of the GNU General Public License
f@0 17 along with this program. If not, see <http://www.gnu.org/licenses/>.
f@0 18 */
f@0 19 package uk.ac.qmul.eecs.ccmi.diagrammodel;
f@0 20
f@0 21 import java.util.ArrayList;
f@0 22 import java.util.Collection;
f@0 23 import java.util.Collections;
f@0 24 import java.util.Enumeration;
f@0 25 import java.util.LinkedHashMap;
f@0 26 import java.util.LinkedHashSet;
f@0 27 import java.util.LinkedList;
f@0 28 import java.util.List;
f@0 29 import java.util.Map;
f@0 30 import java.util.Set;
f@0 31 import java.util.concurrent.locks.ReentrantLock;
f@0 32
f@0 33 import javax.swing.event.ChangeEvent;
f@0 34 import javax.swing.event.ChangeListener;
f@0 35 import javax.swing.tree.DefaultTreeModel;
f@0 36 import javax.swing.tree.MutableTreeNode;
f@0 37 import javax.swing.tree.TreeNode;
f@0 38
f@0 39 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
f@0 40 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
f@0 41
f@0 42 /**
f@0 43 * This class represent a model as per in the model-view control architecture.
f@0 44 * The model is "double sided" in the sense that it can be accessed through either
f@0 45 * a CollectionModel or a TreeModel returned by the respective getter methods.
f@0 46 * The TreeModel is suitable for JTree classes of the swing library, while
f@0 47 * the CollectionModel can be used by view classes by registering a CollectionListener
f@0 48 * to the CollectionModel itself.
f@0 49 * It is important to notice that changes made on one side will reflect on the other,
f@0 50 * eventually triggering the registered listeners.
f@0 51 * The tree model is structured according to a special layout which is suitable for
f@0 52 * browsing the tree view via audio interface ( text to speech synthesis and sound).
f@0 53 *
f@0 54 * @param <N> a subclass of DiagramNode
f@0 55 * @param <E> a subclass of DiagramEdge
f@0 56 */
f@0 57 public class DiagramModel<N extends DiagramNode, E extends DiagramEdge>{
f@0 58 /**
f@0 59 * Create a model instance starting from some nodes and edges prototypes.
f@0 60 * All subsequently added element must be clones of such prototypes.
f@0 61 * @param nodePrototypes an array of {@code DiagramNode} prototypes, from which
f@0 62 * nodes that will be inserted in this model will be cloned
f@0 63 * @param edgePrototypes an array of {@code DiagramEdge} prototypes, from which
f@0 64 * edges that will be inserted in this model will be cloned
f@0 65 */
f@0 66 @SuppressWarnings("serial")
f@0 67 public DiagramModel(N [] nodePrototypes, E [] edgePrototypes) {
f@0 68 root = new DiagramTreeNode(ROOT_LABEL){
f@0 69 @Override
f@0 70 public boolean isRoot(){
f@0 71 return true;
f@0 72 }
f@0 73 };
f@0 74 modified = false;
f@0 75
f@0 76 nodeCounter = 0;
f@0 77 edgeCounter = 0;
f@0 78
f@0 79 notifier = new ReentrantLockNotifier();
f@0 80
f@0 81 treeModel = new InnerTreeModel(root);
f@0 82 treeModel.setEventSource(treeModel);/* default event source is the tree itself */
f@0 83 diagramCollection = new InnerDiagramCollection();
f@0 84
f@0 85 nodes = new ArrayList<N>(INITIAL_NODES_SIZE);
f@0 86 edges = new ArrayList<E>(INITIAL_EDGES_SIZE);
f@0 87 elements = new ArrayList<DiagramElement>(INITIAL_NODES_SIZE+INITIAL_EDGES_SIZE);
f@0 88
f@0 89 changeListeners = new LinkedList<ChangeListener>();
f@0 90
f@0 91 for(N n : nodePrototypes)
f@0 92 addType(n);
f@0 93 for(E e : edgePrototypes){
f@0 94 addType(e);
f@0 95 }
f@0 96 }
f@0 97
f@0 98 /**
f@0 99 * Returns a CollectionModel for this diagram
f@0 100 *
f@0 101 * @return a CollectionModel for this diagram
f@0 102 */
f@0 103 public CollectionModel<N,E> getDiagramCollection(){
f@0 104 return diagramCollection;
f@0 105 }
f@0 106
f@0 107 /**
f@0 108 * Returns a TreeModel for this diagram
f@0 109 *
f@0 110 * @return a TreeModel for this diagram
f@0 111 */
f@0 112 public TreeModel<N,E> getTreeModel(){
f@0 113 return treeModel;
f@0 114 }
f@0 115
f@0 116 private void handleChangeListeners(Object source){
f@0 117 if(modified) // fire the listener only the first time a change happens
f@0 118 return;
f@0 119 modified = true;
f@0 120 fireChangeListeners(source);
f@0 121 }
f@0 122
f@0 123 private void addChangeListener(ChangeListener l){
f@0 124 changeListeners.add(l);
f@0 125 }
f@0 126
f@0 127 private void removeChangeListener(ChangeListener l){
f@0 128 changeListeners.remove(l);
f@0 129 }
f@0 130
f@0 131 protected void fireChangeListeners(Object source){
f@0 132 ChangeEvent changeEvent = new ChangeEvent(source);
f@0 133 for(ChangeListener l : changeListeners)
f@0 134 l.stateChanged(changeEvent);
f@0 135 }
f@0 136
f@0 137 private void addType(DiagramElement element){
f@0 138 DiagramTreeNode typeNode = _lookForChild(root, element.getType());
f@0 139 if(typeNode == null){
f@0 140 typeNode = new TypeMutableTreeNode(element);
f@0 141 treeModel.insertNodeInto(typeNode, root, root.getChildCount());
f@0 142 }
f@0 143 }
f@0 144
f@0 145 private class InnerDiagramCollection implements CollectionModel<N,E> {
f@0 146
f@0 147 public InnerDiagramCollection(){
f@0 148 listeners = new ArrayList<CollectionListener>();
f@0 149 }
f@0 150
f@0 151 @Override
f@0 152 public boolean insert(N n, Object source){
f@0 153 if(source == null)
f@0 154 source = this;
f@0 155 return _insert(n,source);
f@0 156 }
f@0 157
f@0 158 @Override
f@0 159 public boolean insert(E e, Object source){
f@0 160 if(source == null)
f@0 161 source = this;
f@0 162 return _insert(e,source);
f@0 163 }
f@0 164
f@0 165 @Override
f@0 166 public boolean takeOut(DiagramElement element, Object source){
f@0 167 if(source == null)
f@0 168 source = this;
f@0 169 if(element instanceof DiagramNode)
f@0 170 return _takeOut((DiagramNode)element,source);
f@0 171 if(element instanceof DiagramEdge)
f@0 172 return _takeOut((DiagramEdge)element,source);
f@0 173 return false;
f@0 174 }
f@0 175
f@0 176 @Override
f@0 177 public void addCollectionListener(CollectionListener listener) {
f@0 178 listeners.add(listener);
f@0 179 }
f@0 180
f@0 181 @Override
f@0 182 public void removeCollectionListener(CollectionListener listener) {
f@0 183 listeners.remove(listener);
f@0 184 }
f@0 185
f@0 186 protected void fireElementInserted(Object source, DiagramElement element) {
f@0 187 for(CollectionListener l : listeners){
f@0 188 l.elementInserted(new CollectionEvent(source,element));
f@0 189 }
f@0 190 }
f@0 191
f@0 192 protected void fireElementTakenOut(Object source, DiagramElement element) {
f@0 193 for(CollectionListener l : listeners){
f@0 194 l.elementTakenOut(new CollectionEvent(source,element));
f@0 195 }
f@0 196 }
f@0 197
f@0 198 protected void fireElementChanged(ElementChangedEvent evt){
f@0 199 for(CollectionListener l : listeners){
f@0 200 l.elementChanged(evt);
f@0 201 }
f@0 202 }
f@0 203
f@0 204 @Override
f@0 205 public Collection<N> getNodes() {
f@0 206 return Collections.unmodifiableCollection(nodes);
f@0 207 }
f@0 208
f@0 209 @Override
f@0 210 public Collection<E> getEdges() {
f@0 211 return Collections.unmodifiableCollection(edges);
f@0 212 }
f@0 213
f@0 214 @Override
f@0 215 public Collection<DiagramElement> getElements(){
f@0 216 return Collections.unmodifiableCollection(elements);
f@0 217 }
f@0 218
f@0 219 @Override
f@0 220 public ReentrantLock getMonitor(){
f@0 221 return notifier;
f@0 222 }
f@0 223
f@0 224 @Override
f@0 225 public void addChangeListener(ChangeListener l){
f@0 226 DiagramModel.this.addChangeListener(l);
f@0 227 }
f@0 228
f@0 229 @Override
f@0 230 public void removeChangeListener(ChangeListener l){
f@0 231 DiagramModel.this.removeChangeListener(l);
f@0 232 }
f@0 233
f@0 234 /* sort the collections according to the id of nodes */
f@0 235 public void sort(){
f@0 236 Collections.sort(nodes, DiagramElementComparator.getInstance());
f@0 237 Collections.sort(edges, DiagramElementComparator.getInstance());
f@0 238 }
f@0 239
f@0 240 public boolean isModified(){
f@0 241 return modified;
f@0 242 }
f@0 243
f@0 244 public void setUnmodified(){
f@0 245 modified = false;
f@0 246 }
f@0 247
f@0 248 protected ArrayList<CollectionListener> listeners;
f@0 249
f@0 250 }
f@0 251
f@0 252 @SuppressWarnings("serial")
f@0 253 private class InnerTreeModel extends DefaultTreeModel implements TreeModel<N,E>{
f@0 254
f@0 255 public InnerTreeModel(TreeNode root){
f@0 256 super(root);
f@0 257 bookmarks = new LinkedHashMap<String, DiagramTreeNode>();
f@0 258 diagramTreeNodeListeners = new ArrayList<DiagramTreeNodeListener>();
f@0 259 }
f@0 260
f@0 261 @Override
f@0 262 public boolean insertTreeNode(N treeNode, Object source){
f@0 263 if(source == null)
f@0 264 source = this;
f@0 265 return _insert(treeNode,source);
f@0 266 }
f@0 267
f@0 268 @Override
f@0 269 public boolean insertTreeNode(E treeNode, Object source){
f@0 270 if(source == null)
f@0 271 source = this;
f@0 272 return _insert(treeNode,source);
f@0 273 }
f@0 274
f@0 275 @Override
f@0 276 public boolean takeTreeNodeOut(DiagramElement treeNode, Object source){
f@0 277 if(source == null)
f@0 278 source = this;
f@0 279 boolean result;
f@0 280 if(treeNode instanceof DiagramEdge){
f@0 281 result = _takeOut((DiagramEdge)treeNode,source);
f@0 282 }
f@0 283 else{
f@0 284 result = _takeOut((DiagramNode)treeNode,source);
f@0 285 }
f@0 286 /* remove the bookmarks associated with the just deleted diagram element, if any */
f@0 287 for(String key : treeNode.getBookmarkKeys())
f@0 288 bookmarks.remove(key);
f@0 289 return result;
f@0 290 }
f@0 291
f@0 292 @Override
f@0 293 public DiagramTreeNode putBookmark(String bookmark, DiagramTreeNode treeNode, Object source){
f@0 294 if(bookmark == null)
f@0 295 throw new IllegalArgumentException("bookmark cannot be null");
f@0 296 if(source == null)
f@0 297 source = this;
f@0 298 setEventSource(source);
f@0 299 treeNode.addBookmarkKey(bookmark);
f@0 300 DiagramTreeNode result = bookmarks.put(bookmark, treeNode);
f@0 301 nodeChanged(treeNode);
f@0 302 iLog("bookmark added",bookmark);
f@0 303 DiagramTreeNodeEvent evt = new DiagramTreeNodeEvent(treeNode,bookmark,source);
f@0 304 for(DiagramTreeNodeListener l : diagramTreeNodeListeners){
f@0 305 l.bookmarkAdded(evt);
f@0 306 }
f@0 307 handleChangeListeners(this);
f@0 308 return result;
f@0 309 }
f@0 310
f@0 311 @Override
f@0 312 public DiagramTreeNode getBookmarkedTreeNode(String bookmark) {
f@0 313 return bookmarks.get(bookmark);
f@0 314 }
f@0 315
f@0 316 @Override
f@0 317 public DiagramTreeNode removeBookmark(String bookmark,Object source) {
f@0 318 if(source == null)
f@0 319 source = this;
f@0 320 setEventSource(source);
f@0 321 DiagramTreeNode treeNode = bookmarks.remove(bookmark);
f@0 322 treeNode.removeBookmarkKey(bookmark);
f@0 323 nodeChanged(treeNode);
f@0 324 iLog("bookmark removed",bookmark);
f@0 325 DiagramTreeNodeEvent evt = new DiagramTreeNodeEvent(treeNode,bookmark,source);
f@0 326 for(DiagramTreeNodeListener l : diagramTreeNodeListeners){
f@0 327 l.bookmarkRemoved(evt);
f@0 328 }
f@0 329 handleChangeListeners(this);
f@0 330 return treeNode;
f@0 331 }
f@0 332
f@0 333 @Override
f@0 334 public Set<String> getBookmarks(){
f@0 335 return new LinkedHashSet<String>(bookmarks.keySet());
f@0 336 }
f@0 337
f@0 338 @Override
f@0 339 public void setNotes(DiagramTreeNode treeNode, String notes,Object source){
f@0 340 if(source == null)
f@0 341 source = this;
f@0 342 setEventSource(source);
f@0 343 String oldValue = treeNode.getNotes();
f@0 344 treeNode.setNotes(notes,source);
f@0 345 nodeChanged(treeNode);
f@0 346 iLog("notes set for "+treeNode.getName(),"".equals(notes) ? "empty notes" : notes.replaceAll("\n", "\\\\n"));
f@0 347 DiagramTreeNodeEvent evt = new DiagramTreeNodeEvent(treeNode,oldValue,source);
f@0 348 for(DiagramTreeNodeListener l : diagramTreeNodeListeners){
f@0 349 l.notesChanged(evt);
f@0 350 }
f@0 351 handleChangeListeners(source);
f@0 352 }
f@0 353
f@0 354 private void setEventSource(Object source){
f@0 355 this.src = source;
f@0 356 }
f@0 357
f@0 358 @Override
f@0 359 public ReentrantLock getMonitor(){
f@0 360 return notifier;
f@0 361 }
f@0 362
f@0 363 @Override
f@0 364 public void addDiagramTreeNodeListener(DiagramTreeNodeListener l){
f@0 365 diagramTreeNodeListeners.add(l);
f@0 366 }
f@0 367
f@0 368 @Override
f@0 369 public void removeDiagramTreeNodeListener(DiagramTreeNodeListener l){
f@0 370 diagramTreeNodeListeners.remove(l);
f@0 371 }
f@0 372
f@0 373 /* redefine the fire methods so that they set the source object according */
f@0 374 /* to whether the element was inserted from the graph or from the tree */
f@0 375 @Override
f@0 376 protected void fireTreeNodesChanged(Object source, Object[] path,
f@0 377 int[] childIndices, Object[] children) {
f@0 378 super.fireTreeNodesChanged(src, path, childIndices, children);
f@0 379 }
f@0 380
f@0 381 @Override
f@0 382 protected void fireTreeNodesInserted(Object source, Object[] path,
f@0 383 int[] childIndices, Object[] children) {
f@0 384 super.fireTreeNodesInserted(src, path, childIndices, children);
f@0 385 }
f@0 386
f@0 387 @Override
f@0 388 protected void fireTreeNodesRemoved(Object source, Object[] path,
f@0 389 int[] childIndices, Object[] children) {
f@0 390 super.fireTreeNodesRemoved(src, path, childIndices, children);
f@0 391 }
f@0 392
f@0 393 @Override
f@0 394 protected void fireTreeStructureChanged(Object source, Object[] path,
f@0 395 int[] childIndices, Object[] children) {
f@0 396 super.fireTreeStructureChanged(src, path, childIndices, children);
f@0 397 }
f@0 398
f@0 399 public boolean isModified(){
f@0 400 return modified;
f@0 401 }
f@0 402
f@0 403 public void setUnmodified(){
f@0 404 modified = false;
f@0 405 }
f@0 406
f@0 407 private Object src;
f@0 408 private Map<String, DiagramTreeNode> bookmarks;
f@0 409 private ArrayList<DiagramTreeNodeListener> diagramTreeNodeListeners;
f@0 410 }
f@0 411
f@0 412 @SuppressWarnings("serial")
f@0 413 class ReentrantLockNotifier extends ReentrantLock implements ElementNotifier {
f@0 414 @Override
f@0 415 public void notifyChange(ElementChangedEvent evt) {
f@0 416 _change(evt);
f@0 417 handleChangeListeners(evt.getDiagramElement());
f@0 418 }
f@0 419 }
f@0 420
f@0 421 private boolean _insert(N n, Object source) {
f@0 422 assert(n != null);
f@0 423
f@0 424 /* if id has already been given then sync the counter so that a surely new value is given to the next nodes */
f@0 425 if(n.getId() == DiagramElement.NO_ID)
f@0 426 n.setId(++nodeCounter);
f@0 427 else if(n.getId() > nodeCounter)
f@0 428 nodeCounter = n.getId();
f@0 429
f@0 430 treeModel.setEventSource(source);
f@0 431 nodes.add(n);
f@0 432 elements.add(n);
f@0 433 /* add the node to outer node's (if any) inner nodes */
f@0 434 if(n.getExternalNode() != null)
f@0 435 n.getExternalNode().addInternalNode(n);
f@0 436
f@0 437 /* decide where to insert the node based on whether this is an inner node or not */
f@0 438 MutableTreeNode parent;
f@0 439 if(n.getExternalNode() == null){
f@0 440 DiagramTreeNode typeNode = _lookForChild(root, n.getType());
f@0 441 if(typeNode == null)
f@0 442 throw new IllegalArgumentException("Node type "+n.getType()+" not present in the model");
f@0 443 parent = typeNode;
f@0 444 }else{
f@0 445 parent = n.getExternalNode();
f@0 446 }
f@0 447
f@0 448 /* add to the node one child per property type */
f@0 449 for(String propertyType : n.getProperties().getTypes())
f@0 450 n.insert(new PropertyTypeMutableTreeNode(propertyType,n), n.getChildCount());
f@0 451
f@0 452 /* inject the notifier for managing changes internal to the edge */
f@0 453 n.setNotifier(notifier);
f@0 454
f@0 455 /* insert node into tree which fires tree listeners */
f@0 456 treeModel.insertNodeInto(n, parent, parent.getChildCount());
f@0 457 /* this is necessary to increment the child counter displayed between brackets */
f@0 458 treeModel.nodeChanged(parent);
f@0 459 diagramCollection.fireElementInserted(source,n);
f@0 460 handleChangeListeners(n);
f@0 461
f@0 462 iLog("node inserted",DiagramElement.toLogString(n));
f@0 463 return true;
f@0 464 }
f@0 465
f@0 466 private boolean _takeOut(DiagramNode n, Object source) {
f@0 467 treeModel.setEventSource(source);
f@0 468 /* recursively remove internal nodes of this node */
f@0 469 _removeInternalNodes(n,source);
f@0 470 /* clear external node and clear edges attached to this node and updates other ends of such edges */
f@0 471 _clearNodeReferences(n,source);
f@0 472 /* remove the node from the tree (fires listeners) */
f@0 473 treeModel.removeNodeFromParent(n);
f@0 474 /* this is necessary to increment the child counter displayed between brackets */
f@0 475 treeModel.nodeChanged(n.getParent());
f@0 476 /* remove the nodes from the collection */
f@0 477 nodes.remove(n);
f@0 478 elements.remove(n);
f@0 479 /* notify all the listeners a new node has been removed */
f@0 480 diagramCollection.fireElementTakenOut(source,n);
f@0 481 handleChangeListeners(n);
f@0 482
f@0 483 if(nodes.isEmpty()){
f@0 484 nodeCounter = 0;
f@0 485 }else{
f@0 486 long lastNodeId = nodes.get(nodes.size()-1).getId();
f@0 487 if(n.getId() > lastNodeId)
f@0 488 nodeCounter = lastNodeId;
f@0 489 }
f@0 490 iLog("node removed",DiagramElement.toLogString(n));
f@0 491 return true;
f@0 492 }
f@0 493
f@0 494 private boolean _insert(E e, Object source) {
f@0 495 assert(e != null);
f@0 496 /* executes formal controls over the edge's node, which must be specified from the outer class*/
f@0 497 if(e.getNodesNum() < 2)
f@0 498 throw new MalformedEdgeException("too few (" +e.getNodesNum()+ ") nodes");
f@0 499
f@0 500 /* if id has already been given then sync the counter so that a surely new value is given to the next edges */
f@0 501 if(e.getId() > edgeCounter)
f@0 502 edgeCounter = e.getId();
f@0 503 else
f@0 504 e.setId(++edgeCounter);
f@0 505
f@0 506 treeModel.setEventSource(source);
f@0 507 edges.add(e);
f@0 508 elements.add(e);
f@0 509
f@0 510 /* updates the nodes' edge reference and the edge tree references */
f@0 511 for(int i = e.getNodesNum()-1; i >= 0; i--){
f@0 512 DiagramNode n = e.getNodeAt(i);
f@0 513 assert(n != null);
f@0 514 /* insert first the type of the edge, if not already present */
f@0 515 DiagramTreeNode edgeType = _lookForChild(n, e.getType());
f@0 516 if(edgeType == null){
f@0 517 edgeType = new EdgeReferenceHolderMutableTreeNode(e.getType());
f@0 518 treeModel.insertNodeInto(edgeType, n, 0);
f@0 519 }
f@0 520
f@0 521 /* insert the edge reference under its type tree node, in the node*/
f@0 522 treeModel.insertNodeInto(new EdgeReferenceMutableTreeNode(e,n), edgeType, 0);
f@0 523 /* this is necessary to increment the child counter displayed between brackets */
f@0 524 treeModel.nodeChanged(edgeType);
f@0 525
f@0 526 n.addEdge(e);
f@0 527 /* insert the node reference into the edge tree node */
f@0 528 e.insert(new NodeReferenceMutableTreeNode(n,e), 0);
f@0 529 }
f@0 530
f@0 531 DiagramTreeNode parent = _lookForChild(root, e.getType());
f@0 532 if(parent == null)
f@0 533 throw new IllegalArgumentException("Edge type "+e.getType()+" not present in the model");
f@0 534
f@0 535 /* inject the controller and notifier to manage changes internal to the edge */
f@0 536 e.setNotifier(notifier);
f@0 537
f@0 538 /* c'mon baby light my fire */
f@0 539 treeModel.insertNodeInto(e, parent, parent.getChildCount());
f@0 540 /* this is necessary to increment the child counter displayed between brackets */
f@0 541 treeModel.nodeChanged(parent);
f@0 542 diagramCollection.fireElementInserted(source,e);
f@0 543 handleChangeListeners(e);
f@0 544
f@0 545 StringBuilder builder = new StringBuilder(DiagramElement.toLogString(e));
f@0 546 builder.append(" connecting:");
f@0 547 for(int i=0; i<e.getNodesNum();i++)
f@0 548 builder.append(DiagramElement.toLogString(e.getNodeAt(i))).append(' ');
f@0 549
f@0 550 iLog("edge inserted",builder.toString());
f@0 551 return true;
f@0 552 }
f@0 553
f@0 554 private boolean _takeOut(DiagramEdge e, Object source) {
f@0 555 treeModel.setEventSource(source);
f@0 556 /* update the nodes attached to this edge */
f@0 557 _clearEdgeReferences(e);
f@0 558 /* remove the edge from the collection */
f@0 559 edges.remove(e);
f@0 560 elements.remove(e);
f@0 561 /* remove the edge from the tree (fires tree listeners) */
f@0 562 treeModel.removeNodeFromParent(e);
f@0 563 /* this is necessary to increment the child counter displayed between brackets */
f@0 564 treeModel.nodeChanged(e.getParent());
f@0 565 /* notify listeners for collection */
f@0 566 diagramCollection.fireElementTakenOut(source,e);
f@0 567 handleChangeListeners(e);
f@0 568
f@0 569 if(edges.isEmpty()){
f@0 570 edgeCounter = 0;
f@0 571 }else{
f@0 572 long lastEdgeId = edges.get(edges.size()-1).getId();
f@0 573 if(e.getId() > lastEdgeId)
f@0 574 edgeCounter = lastEdgeId;
f@0 575 }
f@0 576 iLog("edge removed",DiagramElement.toLogString(e));
f@0 577 return true;
f@0 578 }
f@0 579
f@0 580 private void _removeInternalNodes(DiagramNode n, Object source){
f@0 581 for(int i=0; i<n.getInternalNodesNum(); i++){
f@0 582 DiagramNode innerNode = n.getInternalNodeAt(i);
f@0 583 _clearNodeReferences(innerNode, source);
f@0 584 _removeInternalNodes(innerNode, source);
f@0 585 n.removeInternalNode(innerNode);
f@0 586 nodes.remove(n);
f@0 587 }
f@0 588 }
f@0 589
f@0 590 /* removes both inner and tree node references to an edge from nodes it's attached to */
f@0 591 private void _clearEdgeReferences(DiagramEdge e){
f@0 592 for(int i=0; i<e.getNodesNum();i++){
f@0 593 DiagramNode n = e.getNodeAt(i);
f@0 594 EdgeReferenceMutableTreeNode reference;
f@0 595 /* find the category tree node under which our reference is */
f@0 596
f@0 597 reference = _lookForEdgeReference(n, e);
f@0 598 assert(reference != null);
f@0 599
f@0 600 treeModel.removeNodeFromParent(reference);
f@0 601 DiagramTreeNode type = _lookForChild(n, e.getType());
f@0 602 if(type.isLeaf())
f@0 603 treeModel.removeNodeFromParent(type);
f@0 604 n.removeEdge(e);
f@0 605 }
f@0 606 }
f@0 607
f@0 608 /* removes references from node */
f@0 609 private void _clearNodeReferences(DiagramNode n, Object source){
f@0 610 /* remove the node itself from its external node, if any */
f@0 611 if(n.getExternalNode() != null)
f@0 612 n.getExternalNode().removeInternalNode(n);
f@0 613 /* remove edges attached to this node from the collection */
f@0 614 ArrayList<DiagramEdge> edgesToRemove = new ArrayList<DiagramEdge>(edges.size());
f@0 615 for(int i=0; i<n.getEdgesNum(); i++){
f@0 616 DiagramEdge e = n.getEdgeAt(i);
f@0 617 if(e.getNodesNum() == 2){ // deleting a node on a two ends edge means deleting the edge itself
f@0 618 edgesToRemove.add(e);
f@0 619 }else{
f@0 620 e.removeNode(n,source);
f@0 621 DiagramTreeNode nodeTreeReference = _lookForNodeReference(e, n);
f@0 622 treeModel.removeNodeFromParent(nodeTreeReference);
f@0 623 }
f@0 624
f@0 625 }
f@0 626 /* remove the edges that must no longer exist as were two ended edges attached to this node */
f@0 627 for(DiagramEdge e : edgesToRemove)
f@0 628 _takeOut(e, source);
f@0 629 }
f@0 630
f@0 631 /* the tree structure is changed as a consequence of this method, therefore it's synchronized *
f@0 632 * so that external classes accessing the tree can get exclusive access through getMonitor() */
f@0 633 private void _change(ElementChangedEvent evt){
f@0 634 synchronized(this){
f@0 635 String changeType = evt.getChangeType();
f@0 636 /* don't use the event source as it might collide with other threads as
f@0 637 * changes on the collections and inner changes on the node are synch'ed thought different monitors */
f@0 638 /* treeModel.setEventSource(evt.getSource());*/
f@0 639 if("name".equals(changeType)){
f@0 640 if(evt.getDiagramElement() instanceof DiagramNode){
f@0 641 DiagramNode n = (DiagramNode)evt.getDiagramElement();
f@0 642 for(int i=0; i<n.getEdgesNum(); i++){
f@0 643 DiagramEdge e = n.getEdgeAt(i);
f@0 644 treeModel.nodeChanged(_lookForNodeReference(e,n));
f@0 645 treeModel.nodeChanged(_lookForEdgeReference(n,e));
f@0 646 for(int j=0; j<e.getNodesNum(); j++){
f@0 647 DiagramNode n2 = e.getNodeAt(j);
f@0 648 if(n2 != n)
f@0 649 treeModel.nodeChanged(_lookForEdgeReference(n2,e));
f@0 650 }
f@0 651 }
f@0 652 iLog("node name changed",DiagramElement.toLogString(n));
f@0 653 }else{
f@0 654 DiagramEdge e = (DiagramEdge)evt.getDiagramElement();
f@0 655 for(int i=0; i<e.getNodesNum();i++){
f@0 656 DiagramNode n = e.getNodeAt(i);
f@0 657 treeModel.nodeChanged(_lookForEdgeReference(n,e));
f@0 658 }
f@0 659 iLog("edge name changed",DiagramElement.toLogString(e));
f@0 660 }
f@0 661 treeModel.nodeChanged(evt.getDiagramElement());
f@0 662 }else if("properties".equals(changeType)){
f@0 663 DiagramNode n = (DiagramNode)evt.getDiagramElement();
f@0 664 for(String type : n.getProperties().getTypes()){
f@0 665 PropertyTypeMutableTreeNode typeNode = null;
f@0 666 for(int i=0; i<n.getChildCount();i++){
f@0 667 /* find the child treeNode corresponding to the current type */
f@0 668 if(n.getChildAt(i) instanceof PropertyTypeMutableTreeNode)
f@0 669 if(type.equals(((PropertyTypeMutableTreeNode)n.getChildAt(i)).getType())){
f@0 670 typeNode = (PropertyTypeMutableTreeNode)n.getChildAt(i);
f@0 671 break;
f@0 672 }
f@0 673 }
f@0 674
f@0 675 if(typeNode == null)
f@0 676 throw new IllegalArgumentException("Inserted Node property type "+ type + " not present in the tree" );
f@0 677
f@0 678 /* set the name and modifier string of all the children PropertyNodes */
f@0 679 typeNode.setValues(n.getProperties().getValues(type), n.getProperties().getModifiers(type));
f@0 680 }
f@0 681 treeModel.nodeStructureChanged(evt.getDiagramElement());
f@0 682 iLog("node properties changed",n.getProperties().toString());
f@0 683 }else if("properties.clear".equals(changeType)){
f@0 684 DiagramNode n = (DiagramNode)evt.getDiagramElement();
f@0 685 List<String> empty = Collections.emptyList();
f@0 686 for(int i=0; i<n.getChildCount();i++){
f@0 687 /* find the child treeNode corresponding to the current type */
f@0 688 if(n.getChildAt(i) instanceof PropertyTypeMutableTreeNode){
f@0 689 ((PropertyTypeMutableTreeNode)n.getChildAt(i)).setValues(empty, null);
f@0 690 }
f@0 691 }
f@0 692 treeModel.nodeStructureChanged(evt.getDiagramElement());
f@0 693 }else if("property.add".equals(changeType)){
f@0 694 DiagramNode n = (DiagramNode)evt.getDiagramElement();
f@0 695 ElementChangedEvent.PropertyChangeArgs args = (ElementChangedEvent.PropertyChangeArgs)evt.getArguments();
f@0 696 PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,args.getPropertyType());
f@0 697 PropertyMutableTreeNode propertyNode = new PropertyMutableTreeNode(n.getProperties().getValues(args.getPropertyType()).get(args.getPropertyIndex()));
f@0 698 typeNode.add(propertyNode);
f@0 699 treeModel.insertNodeInto(propertyNode, typeNode, args.getPropertyIndex());
f@0 700 /* this is necessary to increment the child counter displayed between brackets */
f@0 701 treeModel.nodeChanged(typeNode);
f@0 702 iLog("property inserted",propertyNode.getName());
f@0 703 }else if("property.set".equals(changeType)){
f@0 704 DiagramNode n = (DiagramNode)evt.getDiagramElement();
f@0 705 ElementChangedEvent.PropertyChangeArgs args = (ElementChangedEvent.PropertyChangeArgs)evt.getArguments();
f@0 706 PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,args.getPropertyType());
f@0 707 ((DiagramTreeNode)typeNode.getChildAt(args.getPropertyIndex()))
f@0 708 .setUserObject(n.getProperties().getValues(args.getPropertyType()).get(args.getPropertyIndex()));
f@0 709 treeModel.nodeChanged((typeNode.getChildAt(args.getPropertyIndex())));
f@0 710 iLog("property changed",n.getProperties().getValues(args.getPropertyType()).get(args.getPropertyIndex()));
f@0 711 }else if("property.remove".equals(changeType)){
f@0 712 DiagramNode n = (DiagramNode)evt.getDiagramElement();
f@0 713 ElementChangedEvent.PropertyChangeArgs args = (ElementChangedEvent.PropertyChangeArgs)evt.getArguments();
f@0 714 PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,args.getPropertyType());
f@0 715 iLog("property removed",((DiagramTreeNode)typeNode.getChildAt(args.getPropertyIndex())).getName()); //must do it before actual removing
f@0 716 treeModel.removeNodeFromParent((DiagramTreeNode)typeNode.getChildAt(args.getPropertyIndex()));
f@0 717 /* remove the bookmark keys associated with this property tree node, if any */
f@0 718 for(String key : treeModel.getBookmarks()){
f@0 719 treeModel.bookmarks.remove(key);
f@0 720 }
f@0 721 }else if("property.modifiers".equals(changeType)){
f@0 722 DiagramNode n = (DiagramNode)evt.getDiagramElement();
f@0 723 ElementChangedEvent.PropertyChangeArgs args = (ElementChangedEvent.PropertyChangeArgs)evt.getArguments();
f@0 724 PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,args.getPropertyType());
f@0 725 PropertyMutableTreeNode propertyNode = ((PropertyMutableTreeNode)typeNode.getChildAt(args.getPropertyIndex()));
f@0 726 StringBuilder builder = new StringBuilder();
f@0 727 Modifiers modifiers = n.getProperties().getModifiers(args.getPropertyType());
f@0 728 for(int index : modifiers.getIndexes(args.getPropertyIndex()))
f@0 729 builder.append(modifiers.getTypes().get(index)).append(' ');
f@0 730 propertyNode.setModifierString(builder.toString());
f@0 731 treeModel.nodeChanged(propertyNode);
f@0 732 }else if("arrowHead".equals(changeType)||"endLabel".equals(changeType)){
f@0 733 /* source is considered to be the node whose end of the edge was changed */
f@0 734 DiagramNode source = (DiagramNode)evt.getArguments();
f@0 735 DiagramEdge e = (DiagramEdge)evt.getDiagramElement();
f@0 736 treeModel.nodeChanged(e);
f@0 737 for(int i=0; i<e.getChildCount();i++){
f@0 738 NodeReferenceMutableTreeNode ref = (NodeReferenceMutableTreeNode)e.getChildAt(i);
f@0 739 if(ref.getNode() == source){
f@0 740 treeModel.nodeChanged(ref);
f@0 741 iLog(("arrowHead".equals(changeType) ? "arrow head changed" :"end label changed"),
f@0 742 "edge:"+DiagramElement.toLogString(e)+" node:"+DiagramElement.toLogString(ref.getNode())+
f@0 743 " value:"+ ("arrowHead".equals(changeType) ? e.getEndDescription(ref.getNode()): e.getEndLabel(ref.getNode())));
f@0 744 break;
f@0 745 }
f@0 746 }
f@0 747 }else if("notes".equals(changeType)){
f@0 748 /* do nothing as the tree update is taken care in the tree itself
f@0 749 * and cannot do it here because it must work for all the diagram tree nodes */
f@0 750
f@0 751 }
f@0 752 }
f@0 753 /* do nothing for other ElementChangedEvents as they only concern the diagram listeners */
f@0 754 /* just forward the event to other listeners which might have been registered */
f@0 755 diagramCollection.fireElementChanged(evt);
f@0 756 }
f@0 757
f@0 758 private static DiagramTreeNode _lookForChild(DiagramTreeNode parentNode, String name){
f@0 759 DiagramTreeNode child = null, temp;
f@0 760 for(@SuppressWarnings("unchecked")
f@0 761 Enumeration<DiagramTreeNode> children = parentNode.children(); children.hasMoreElements();){
f@0 762 temp = children.nextElement();
f@0 763 if(temp.getName().equals(name)){
f@0 764 child = temp;
f@0 765 break;
f@0 766 }
f@0 767 }
f@0 768 return child;
f@0 769 }
f@0 770
f@0 771 private static NodeReferenceMutableTreeNode _lookForNodeReference(DiagramEdge parent, DiagramNode n){
f@0 772 NodeReferenceMutableTreeNode child = null, temp;
f@0 773 for(@SuppressWarnings("unchecked")
f@0 774 Enumeration<DiagramTreeNode> children = parent.children(); children.hasMoreElements();){
f@0 775 temp = (NodeReferenceMutableTreeNode)children.nextElement();
f@0 776 if( ((NodeReferenceMutableTreeNode)temp).getNode().equals(n)){
f@0 777 child = temp;
f@0 778 break;
f@0 779 }
f@0 780 }
f@0 781 return child;
f@0 782 }
f@0 783
f@0 784 private static EdgeReferenceMutableTreeNode _lookForEdgeReference( DiagramNode parentNode, DiagramEdge e){
f@0 785 DiagramTreeNode edgeType = _lookForChild(parentNode, e.getType());
f@0 786 assert(edgeType != null);
f@0 787 EdgeReferenceMutableTreeNode child = null, temp;
f@0 788 for(@SuppressWarnings("unchecked")
f@0 789 Enumeration<DiagramTreeNode> children = edgeType.children(); children.hasMoreElements();){
f@0 790 temp = (EdgeReferenceMutableTreeNode)children.nextElement();
f@0 791 if( ((EdgeReferenceMutableTreeNode)temp).getEdge().equals(e)){
f@0 792 child = temp;
f@0 793 break;
f@0 794 }
f@0 795 }
f@0 796 return child;
f@0 797 }
f@0 798
f@0 799 private void iLog(String action,String args){
f@0 800 InteractionLog.log("MODEL",action,args);
f@0 801 }
f@0 802
f@0 803 private DiagramTreeNode root;
f@0 804 private InnerDiagramCollection diagramCollection;
f@0 805 private ArrayList<N> nodes;
f@0 806 private ArrayList<E> edges;
f@0 807 private ArrayList<DiagramElement> elements;
f@0 808 private InnerTreeModel treeModel;
f@0 809
f@0 810 private long edgeCounter;
f@0 811 private long nodeCounter;
f@0 812
f@0 813 private ReentrantLockNotifier notifier;
f@0 814 private List<ChangeListener> changeListeners;
f@0 815
f@0 816 private boolean modified;
f@0 817
f@0 818 private final static String ROOT_LABEL = "Diagram";
f@0 819 private final static int INITIAL_EDGES_SIZE = 20;
f@0 820 private final static int INITIAL_NODES_SIZE = 30;}