annotate java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramModel.java @ 8:ea7885bd9bff tip

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