view java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramModel.java @ 0:9418ab7b7f3f

Initial import
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Fri, 16 Dec 2011 17:35:51 +0000
parents
children 9e67171477bc
line wrap: on
line source
/*  
 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
  
 Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package uk.ac.qmul.eecs.ccmi.diagrammodel;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;

import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
import uk.ac.qmul.eecs.ccmi.utils.Pair;

/**
 * This class represent a model as per in the model-view control architecture. 
 * The model is "double sided" in the sense that it can be accessed through either 
 * a CollectionModel or a TreeModel returned by the respective getter methods.
 * The TreeModel is suitable for JTree classes of the swing library, while 
 * the CollectionModel can be used by view classes by registering a CollectionListener 
 * to the CollectionModel itself.    
 * It is important to notice that changes made on one side will reflect on the other, 
 * eventually triggering the registered listeners.
 * The tree model is structured according to a special layout which is suitable for 
 * browsing the tree view via audio interface ( text to speech synthesis and sound).
 *
 * @param <N> a subclass of DiagramNode 
 * @param <E> a subclass of DiagramEdge
 */
public class DiagramModel<N extends DiagramNode, E extends DiagramEdge>{
	/**
	 * Create a model instance starting from some nodes and edges prototypes. 
	 * All subsequently added element must be clones of such prototypes.
	 * @param nodePrototypes
	 * @param edgePrototypes
	 */
	@SuppressWarnings("serial")
	public DiagramModel(N [] nodePrototypes, E [] edgePrototypes) {
		root = new DiagramModelTreeNode(ROOT_LABEL){
			@Override
			public boolean isRoot(){
				return true;
			}
		};
		modified = false;
		
		nodeCounter = 0;
		edgeCounter = 0;
		
		treeModel = new InnerTreeModel(root);
		treeModel.setEventSource(treeModel);/* default event source is the tree itself */
		diagramCollection = new InnerDiagramCollection();
		
		nodes = new ArrayList<N>(INITIAL_NODES_SIZE);
		edges = new ArrayList<E>(INITIAL_EDGES_SIZE);
		elements = new ArrayList<DiagramElement>(INITIAL_NODES_SIZE+INITIAL_EDGES_SIZE);
		
		changeListeners = new LinkedList<ChangeListener>();
		
		notifier = new ElementNotifier(){
			@Override
			public void notifyChange(ElementChangedEvent evt) {
				_change(evt);
				triggerModification(evt.getDiagramElement());
			}
		};
		
		for(N n : nodePrototypes)
			addType(n);
		for(E e : edgePrototypes){
			addType(e);
		}
	}
	
	/**
	 * Returns a CollectionModel for this diagram
	 * 
	 * @return a CollectionModel for this diagram 
	 */
	public CollectionModel<N,E> getDiagramCollection(){
		return diagramCollection;
	}
	
	/**
	 * Returns a TreeModel for this diagram
	 * 
	 * @return a TreeModel for this diagram 
	 */
	public TreeModel<N,E> getTreeModel(){
		return treeModel;
	}	
	
	private void triggerModification(Object source){
		if(modified) // fire the listener only the first time a change happens
			return;
		modified = true;
		fireChangeListeners(source);
	}
	
	private void addChangeListener(ChangeListener l){
		changeListeners.add(l);
	}
	
	private void removeChangeListener(ChangeListener l){
		changeListeners.remove(l);
	}
	
	protected void fireChangeListeners(Object source){
		ChangeEvent changeEvent = new ChangeEvent(source);
		for(ChangeListener l : changeListeners)
			l.stateChanged(changeEvent);
	}
	
	private void addType(DiagramElement element){
		DiagramModelTreeNode typeNode = _lookForChild(root, element.getType());
		if(typeNode == null){
			typeNode = new TypeMutableTreeNode(element);
			treeModel.insertNodeInto(typeNode, root, root.getChildCount());
		}
	}
	
	private class InnerDiagramCollection implements CollectionModel<N,E> {

		public InnerDiagramCollection(){
			listeners = new ArrayList<CollectionListener>();
		}
		
		@Override
		public boolean insert(N n){
			return _insert(n,this);
		}
		
		@Override
		public boolean insert(E e){
			return _insert(e,this);
		}
		
		@Override
		public boolean takeOut(DiagramElement element){
			if(element instanceof DiagramNode)
				return _takeOut((DiagramNode)element,this);
			if(element instanceof DiagramEdge)
				return _takeOut((DiagramEdge)element,this);
			return false;
		}

		@Override
		public void addCollectionListener(CollectionListener listener) {
			listeners.add(listener);
		}

		@Override
		public void removeCollectionListener(CollectionListener listener) {
			listeners.remove(listener);
		}
		
		protected void fireElementInserted(Object source, DiagramElement element) {
			for(CollectionListener l : listeners){
				l.elementInserted(new CollectionEvent(source,element));
			}
		}
		
		protected void fireElementTakenOut(Object source, DiagramElement element) {
			for(CollectionListener l : listeners){
				l.elementTakenOut(new CollectionEvent(source,element));
			}
		}
		
		protected void fireElementChanged(ElementChangedEvent evt){
			for(CollectionListener l : listeners){
				l.elementChanged(evt);
			}
		}

		@Override
		public Collection<N> getNodes() {
			return Collections.unmodifiableCollection(nodes);
		}

		@Override
		public Collection<E> getEdges() {
			return Collections.unmodifiableCollection(edges);
		}
		
		@Override
		public Collection<DiagramElement> getElements(){
			return Collections.unmodifiableCollection(elements);
		}

		@Override
		public Object getMonitor(){
			return DiagramModel.this;
		}
		
		@Override
		public void addChangeListener(ChangeListener l){
			DiagramModel.this.addChangeListener(l);
		}
		
		@Override
		public void removeChangeListener(ChangeListener l){
			DiagramModel.this.removeChangeListener(l);
		}
		
		/* sort the collections according to the id of nodes */
		public void sort(){
			Collections.sort(nodes, DiagramElementComparator.getInstance());
			Collections.sort(edges, DiagramElementComparator.getInstance());
		}
		
		public boolean isModified(){
			return modified;
		}
		
		public void setUnmodified(){
	    	modified = false;
	    }
		
		protected ArrayList<CollectionListener> listeners;

	}
    
    @SuppressWarnings("serial")
	private class InnerTreeModel extends DefaultTreeModel implements TreeModel<N,E>{

    	public InnerTreeModel(TreeNode root){
    		super(root);
    		bookmarks = new LinkedHashMap<String, DiagramModelTreeNode>();
    	}
    	
		@Override
		public boolean insertTreeNode(N treeNode){
			return _insert(treeNode,this);
		}
		
		@Override
		public boolean insertTreeNode(E treeNode){
			return _insert(treeNode,this);
		}

		@Override
		public boolean takeTreeNodeOut(DiagramElement treeNode){
			boolean result;
			if(treeNode instanceof DiagramEdge){
				result = _takeOut((DiagramEdge)treeNode,this);
			}
			else{
				result = _takeOut((DiagramNode)treeNode,this);
			}
			/* remove the bookmarks associated with the just deleted diagram element, if any */
			for(String key : treeNode.getBookmarkKeys())
				bookmarks.remove(key);
			return result;
		}

		@Override
		public DiagramModelTreeNode putBookmark(String bookmark, DiagramModelTreeNode treeNode){
			if(bookmark == null)
				throw new IllegalArgumentException("bookmark cannot be null");
			setEventSource(this);
			treeNode.addBookmarkKey(bookmark);
			DiagramModelTreeNode result =  bookmarks.put(bookmark, treeNode);
			nodeChanged(treeNode);
			iLog("bookmark added",bookmark);
			triggerModification(this);
			return result;
		}

		@Override
		public DiagramModelTreeNode getBookmarkedTreeNode(String bookmark) {
			return bookmarks.get(bookmark);
		}
		
		@Override
		public DiagramModelTreeNode removeBookmark(String bookmark) {
			setEventSource(this);
			DiagramModelTreeNode treeNode = bookmarks.remove(bookmark);
			treeNode.removeBookmarkKey(bookmark);
			nodeChanged(treeNode);
			iLog("bookmark removed",bookmark);
			triggerModification(this);
			return treeNode;
		}
		
		@Override
		public Set<String> getBookmarks(){
			return new LinkedHashSet<String>(bookmarks.keySet());
		}
		
		@Override
		public void setNotes(DiagramModelTreeNode treeNode, String notes){
			setEventSource(this);
			treeNode.setNotes(notes);
			nodeChanged(treeNode);
			iLog("notes set for "+treeNode.getName(),"".equals(notes) ? "empty notes" : notes.replaceAll("\n", "\\\\n"));
			triggerModification(this);
		}
		
		private void setEventSource(Object source){
			this.src = source;
		}
		
		@Override
		public Object getMonitor(){
			return DiagramModel.this;
		}
		
		@Override
		public void addChangeListener(ChangeListener l){
			DiagramModel.this.addChangeListener(l);
		}
		
		@Override
		public void removeChangeListener(ChangeListener l){
			DiagramModel.this.removeChangeListener(l);
		}
		
		/* redefine the fire methods so that they set the source object according */
		/* to whether the element was inserted from the graph or from the tree    */
		@Override
		protected void fireTreeNodesChanged(Object source, Object[] path,
				int[] childIndices, Object[] children) {
			super.fireTreeNodesChanged(src, path, childIndices, children);
		}

		@Override
		protected void fireTreeNodesInserted(Object source, Object[] path,
				int[] childIndices, Object[] children) {
			super.fireTreeNodesInserted(src, path, childIndices, children);
		}

		@Override
		protected void fireTreeNodesRemoved(Object source, Object[] path,
				int[] childIndices, Object[] children) {
			super.fireTreeNodesRemoved(src, path, childIndices, children);
		}

		@Override
		protected void fireTreeStructureChanged(Object source, Object[] path,
				int[] childIndices, Object[] children) {
			super.fireTreeStructureChanged(src, path, childIndices, children);
		}
		
		public boolean isModified(){
			return modified;
		}
		
		public void setUnmodified(){
	    	modified = false;
	    }
		
		private Object src;
		private Map<String, DiagramModelTreeNode> bookmarks;		
    }
    
    private synchronized boolean _insert(N n, Object source) {
    	assert(n != null);

    	/* if id has already been given then sync the counter so that a surely new value is given to the next nodes */
    	if(n.getId() == DiagramElement.NO_ID)
    		n.setId(++nodeCounter);
    	else if(n.getId() > nodeCounter)
    		nodeCounter = n.getId();
    	
    	treeModel.setEventSource(source);
		nodes.add(n);	
		elements.add(n);
		/* add the node to outer node's (if any) inner nodes */
		if(n.getExternalNode() != null)
			n.getExternalNode().addInternalNode(n);
		
		/* decide where to insert the node based on whether this is an inner node or not */
		MutableTreeNode parent;
		if(n.getExternalNode() == null){
			DiagramModelTreeNode typeNode = _lookForChild(root, n.getType());
			if(typeNode == null)
				throw new IllegalArgumentException("Node type "+n.getType()+" not present in the model");
			parent = typeNode;
		}else{
			parent = n.getExternalNode();
		}
		
		/* add to the node one child per property type */
		for(String propertyType : n.getProperties().getTypes())
			n.insert(new PropertyTypeMutableTreeNode(propertyType,n), n.getChildCount());
		
		/* inject the notifier for managing changes internal to the edge */
		n.setNotifier(notifier);

		/* insert node into tree which fires tree listeners */
		treeModel.insertNodeInto(n, parent, parent.getChildCount());
		/* this is necessary to increment the child counter displayed between brackets */
		treeModel.nodeChanged(parent);
		diagramCollection.fireElementInserted(source,n);
		triggerModification(n);
		
		iLog("node inserted",DiagramElement.toLogString(n));
		return true;
    }
    
    private synchronized boolean _takeOut(DiagramNode n, Object source) {
    	treeModel.setEventSource(source);
    	/* recursively remove internal nodes of this node */
		_removeInternalNodes(n,source);
		/* clear external node and clear edges attached to this node and updates other ends of such edges */
		_clearNodeReferences(n,source);
		/* remove the node from the tree (fires listeners) */
		treeModel.removeNodeFromParent(n);
		/* this is necessary to increment the child counter displayed between brackets */
		treeModel.nodeChanged(n.getParent());
		/* remove the nodes from the collection */
		nodes.remove(n);
		elements.remove(n);
		/* notify all the listeners a new node has been removed */
		diagramCollection.fireElementTakenOut(source,n);
		triggerModification(n);
		
		if(nodes.isEmpty()){
			nodeCounter = 0;
		}else{
			long lastNodeId = nodes.get(nodes.size()-1).getId(); 
			if(n.getId() > lastNodeId)
				nodeCounter = lastNodeId;
		}
		iLog("node removed",DiagramElement.toLogString(n));
		return true;
    }
    
    private  synchronized boolean _insert(E e, Object source)  {
    	assert(e != null);
    	/* executes formal controls over the edge's node, which must be specified from the outer class*/
    	if(e.getNodesNum() < 2)
    		throw new MalformedEdgeException(e,"too few (" +e.getNodesNum()+ ") nodes");
    	
    	/* if id has already been given then sync the counter so that a surely new value is given to the next edges */
    	if(e.getId() > edgeCounter)
    		edgeCounter = e.getId();
    	else
    		e.setId(++edgeCounter);
    	
    	treeModel.setEventSource(source);
    	edges.add(e);
    	elements.add(e);
    	
    	/* updates the nodes' edge reference and the edge tree references */ 
		for(int i = e.getNodesNum()-1; i >= 0; i--){
			DiagramNode n = e.getNodeAt(i);
			assert(n != null);
			/* insert first the type of the edge, if not already present */
			DiagramModelTreeNode edgeType = _lookForChild(n, e.getType());
			if(edgeType == null){
				edgeType = new EdgeReferenceHolderMutableTreeNode(e.getType());
				treeModel.insertNodeInto(edgeType, n, 0);
			}
			
			/* insert the edge reference under its type tree node, in the Node*/
			treeModel.insertNodeInto(new EdgeReferenceMutableTreeNode(e,n), edgeType, 0);
			/* this is necessary to increment the child counter displayed between brackets */
    		treeModel.nodeChanged(edgeType);

			n.addEdge(e);
			/* insert the node reference into the edge tree node */
			e.insert(new NodeReferenceMutableTreeNode(n,e), 0);
		}
		
		DiagramModelTreeNode parent = _lookForChild(root, e.getType());
		if(parent == null)
			throw new IllegalArgumentException("Edge type "+e.getType()+" not present in the model");
	
		/* inject the controller and notifier to manage changes internal to the edge */
		e.setNotifier(notifier);
		
		/* c'mon baby light my fire */
		treeModel.insertNodeInto(e, parent, parent.getChildCount());
		/* this is necessary to increment the child counter displayed between brackets */
		treeModel.nodeChanged(parent);
		diagramCollection.fireElementInserted(source,e);
		triggerModification(e);
		
		StringBuilder builder = new StringBuilder(DiagramElement.toLogString(e));
		builder.append(" connecting:");
		for(int i=0; i<e.getNodesNum();i++)
			builder.append(DiagramElement.toLogString(e.getNodeAt(i))).append(' ');
		
		iLog("edge inserted",builder.toString());
		return true;
    }
    
    private synchronized boolean _takeOut(DiagramEdge e, Object source) {
    	treeModel.setEventSource(source);
		/* update the nodes attached to this edge */
    	_clearEdgeReferences(e);    		
    	/* remove the edge from the collection */	
    	edges.remove(e);
    	elements.remove(e);
    	/* remove the edge from the tree (fires tree listeners) */
    	treeModel.removeNodeFromParent(e);
    	/* this is necessary to increment the child counter displayed between brackets */
		treeModel.nodeChanged(e.getParent());
    	/* notify listeners for collection */
    	diagramCollection.fireElementTakenOut(source,e);
    	triggerModification(e);
    	
    	if(edges.isEmpty()){
    		edgeCounter = 0;
    	}else{
    		long lastEdgeId = edges.get(edges.size()-1).getId(); 
			if(e.getId() > lastEdgeId)
				edgeCounter = lastEdgeId;
    	}
    	iLog("edge removed",DiagramElement.toLogString(e));
    	return true;
    }

    private void _removeInternalNodes(DiagramNode n, Object source){
    	for(int i=0; i<n.getInternalNodesNum(); i++){
    		DiagramNode innerNode = n.getInternalNodeAt(i);
    		_clearNodeReferences(innerNode, source);
    		_removeInternalNodes(innerNode, source);
    		n.removeInternalNode(innerNode);
    		nodes.remove(n);
    	}   	
    }
	
    /* removes both inner and tree node references to an edge from nodes it's attached to */
    private void _clearEdgeReferences(DiagramEdge e){
    	for(int i=0; i<e.getNodesNum();i++){
    		DiagramNode n = e.getNodeAt(i);
    		EdgeReferenceMutableTreeNode reference;
    		/* find the category tree node under which our reference is */ 
    		
    		reference = _lookForEdgeReference(n, e);
    		assert(reference != null);
    		
    		treeModel.removeNodeFromParent(reference);
    		DiagramModelTreeNode type = _lookForChild(n, e.getType());
    		if(type.isLeaf())
    			treeModel.removeNodeFromParent(type);   		
    		n.removeEdge(e);
    	}
    }
    
    /* removes references from node */
    private void _clearNodeReferences(DiagramNode n, Object source){
    	/* remove the node itself from its external node, if any */
		if(n.getExternalNode() != null)
			n.getExternalNode().removeInternalNode(n);
		/* remove edges attached to this node from the collection */
		ArrayList<DiagramEdge> edgesToRemove = new ArrayList<DiagramEdge>(edges.size());
		for(int i=0; i<n.getEdgesNum(); i++){
			DiagramEdge e = n.getEdgeAt(i);
			if(e.getNodesNum() == 2){
				edgesToRemove.add(e);
			}else{
				e.removeNode(n);
				DiagramModelTreeNode nodeTreeReference = _lookForNodeReference(e, n);
				treeModel.removeNodeFromParent(nodeTreeReference);
			}
			
		}
		/* remove the edges that must no longer exist as were two ended edges attached to this node */
		for(DiagramEdge e : edgesToRemove)
			_takeOut(e, source);
    }
    
    private void _change(ElementChangedEvent evt){
    	String changeType = evt.getChangeType();
    	/* don't use the event source as it might collide with other threads as 
    	 * changes on the collections and inner changes on the node are synch'ed thought different monitors */
    	/* treeModel.setEventSource(evt.getSource());*/
    	if("name".equals(changeType)){
    		if(evt.getSource() instanceof DiagramNode){
    			DiagramNode n = (DiagramNode)evt.getSource();
    			for(int i=0; i<n.getEdgesNum(); i++){
    				DiagramEdge e = n.getEdgeAt(i);
    				treeModel.nodeChanged(_lookForNodeReference(e,n));
    				treeModel.nodeChanged(_lookForEdgeReference(n,e));
    				for(int j=0; j<e.getNodesNum(); j++){
    					DiagramNode n2 = e.getNodeAt(j);
    					if(n2 != n)
    						treeModel.nodeChanged(_lookForEdgeReference(n2,e));
    				}
    			}
    			iLog("node name changed",DiagramElement.toLogString(n));
    		}else{
    			DiagramEdge e = (DiagramEdge)evt.getSource();
    			for(int i=0; i<e.getNodesNum();i++){
    				DiagramNode n = e.getNodeAt(i);
    				treeModel.nodeChanged(_lookForEdgeReference(n,e));
    			}
    			iLog("edge name changed",DiagramElement.toLogString(e));
    		}
    		treeModel.nodeChanged(evt.getDiagramElement());
    	}else if("properties".equals(changeType)){
    		DiagramNode n = (DiagramNode)evt.getDiagramElement();
    		for(String type : n.getProperties().getTypes()){
    			PropertyTypeMutableTreeNode typeNode = null;
    			for(int i=0; i<n.getChildCount();i++){
    				/* find the child treeNode corresponding to the current type */
    				if(n.getChildAt(i) instanceof PropertyTypeMutableTreeNode)
    					if(type.equals(((PropertyTypeMutableTreeNode)n.getChildAt(i)).getType())){
    						typeNode = (PropertyTypeMutableTreeNode)n.getChildAt(i);
    						break;
    					}
    			}
    			
    			if(typeNode == null) 
    				throw new IllegalArgumentException("Inserted Node property type "+ type + " not present in the tree" );
    			
    			/* set the name and modifier string of all the children PropertyNodes */
    			typeNode.setValues(n.getProperties().getValues(type), n.getProperties().getModifiers(type));
    		}
    		treeModel.nodeStructureChanged(evt.getDiagramElement());
    		iLog("node properties changed",n.getProperties().toString());
    	}else if("properties.clear".equals(changeType)){
    		DiagramNode n = (DiagramNode)evt.getDiagramElement();
			List<String> empty = Collections.emptyList();
			for(int i=0; i<n.getChildCount();i++){
				/* find the child treeNode corresponding to the current type */
				if(n.getChildAt(i) instanceof PropertyTypeMutableTreeNode){
					((PropertyTypeMutableTreeNode)n.getChildAt(i)).setValues(empty, null);
				}
			}
    		treeModel.nodeStructureChanged(evt.getDiagramElement());
    	}else if("property.add".equals(changeType)){
    		DiagramNode n = (DiagramNode)evt.getDiagramElement();
    		@SuppressWarnings("unchecked")
			Pair<String,Integer> p = (Pair<String,Integer>)evt.getSource();
    		PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,p.first);
    		PropertyMutableTreeNode propertyNode = new PropertyMutableTreeNode(n.getProperties().getValues(p.first).get(p.second));
    		typeNode.add(propertyNode);
    		treeModel.insertNodeInto(propertyNode, typeNode, p.second);
    		/* this is necessary to increment the child counter displayed between brackets */
    		treeModel.nodeChanged(typeNode);
    		iLog("property inserted",propertyNode.getName());
    	}else if("property.set".equals(changeType)){
    		DiagramNode n = (DiagramNode)evt.getDiagramElement();
    		@SuppressWarnings("unchecked")
			Pair<String,Integer> p = (Pair<String,Integer>)evt.getSource();
    		PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,p.first);
    		((DiagramModelTreeNode)typeNode.getChildAt(p.second)).setUserObject(n.getProperties().getValues(p.first).get(p.second));
    		treeModel.nodeChanged((typeNode.getChildAt(p.second)));
    		iLog("property changed",n.getProperties().getValues(p.first).get(p.second));
    	}else if("property.remove".equals(changeType)){
    		DiagramNode n = (DiagramNode)evt.getDiagramElement();
    		@SuppressWarnings("unchecked")
			Pair<String,Integer> p = (Pair<String,Integer>)evt.getSource();
    		PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,p.first);
    		iLog("property removed",((DiagramModelTreeNode)typeNode.getChildAt(p.second)).getName()); //must do it before actual removing
    		treeModel.removeNodeFromParent((DiagramModelTreeNode)typeNode.getChildAt(p.second));
    		/* remove the bookmark keys associated with this property tree node, if any */
    		for(String key : treeModel.getBookmarks()){
    			treeModel.bookmarks.remove(key);
    		}
    	}else if("property.modifiers".equals(changeType)){
    		DiagramNode n = (DiagramNode)evt.getDiagramElement();
    		@SuppressWarnings("unchecked")
			Pair<String,Integer> p = (Pair<String,Integer>)evt.getSource();
    		PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)_lookForChild(n,p.first);
    		PropertyMutableTreeNode propertyNode = ((PropertyMutableTreeNode)typeNode.getChildAt(p.second));
    		StringBuilder builder = new StringBuilder();
			Modifiers modifiers = n.getProperties().getModifiers(p.first);
			for(int index : modifiers.getIndexes(p.second))
				builder.append(modifiers.getTypes().get(index)).append(' ');
    		propertyNode.setModifierString(builder.toString());
    		treeModel.nodeChanged(propertyNode);
    	}else if("arrowHead".equals(changeType)||"endLabel".equals(changeType)){
    		/* source is considered to be the node whose end of the edge was changed */
    		DiagramNode source = (DiagramNode)evt.getSource();
    		DiagramEdge e = (DiagramEdge)evt.getDiagramElement();
    		treeModel.nodeChanged(e);
    		for(int i=0; i<e.getChildCount();i++){
    			NodeReferenceMutableTreeNode ref = (NodeReferenceMutableTreeNode)e.getChildAt(i);
    			if(ref.getNode() == source){
    				treeModel.nodeChanged(ref);
    				iLog(("arrowHead".equals(changeType) ? "arrow head changed" :"end label changed"),
    						"edge:"+DiagramElement.toLogString(e)+" node:"+DiagramElement.toLogString(ref.getNode())+
    						" value:"+ ("arrowHead".equals(changeType) ? e.getEndDescription(ref.getNode()): e.getEndLabel(ref.getNode())));
    				break;
    			}
    		}
    	}else if("notes".equals(changeType)){
    		/* do nothing as the tree update is taken care in the tree itself
    		 * and cannot do it here because it must work for all the diagram tree nodes */
    		
    	}
    	/* do nothing for other ElementChangedEvents as the position only concerns the diagram listeners */
    	/* just forward the event to other listeners which might have been registered                     */ 
    	diagramCollection.fireElementChanged(evt);
    }
    
    private static DiagramModelTreeNode _lookForChild(DiagramModelTreeNode parentNode, String name){
    	DiagramModelTreeNode child = null, temp;
    	for(@SuppressWarnings("unchecked")
		Enumeration<DiagramModelTreeNode> children = parentNode.children(); children.hasMoreElements();){
			temp = children.nextElement();
			if(temp.getName().equals(name)){
				 child = temp;
				 break;
			}
		}
    	return child;
    }
    
    private static NodeReferenceMutableTreeNode _lookForNodeReference(DiagramEdge parent, DiagramNode n){
    	NodeReferenceMutableTreeNode child = null, temp;
    	for(@SuppressWarnings("unchecked")
		Enumeration<DiagramModelTreeNode> children = parent.children(); children.hasMoreElements();){
    		temp = (NodeReferenceMutableTreeNode)children.nextElement();
    		if( ((NodeReferenceMutableTreeNode)temp).getNode().equals(n)){
    			child = temp;
    			break;
    		}
    	}
    	return child;
    }
    
    private static EdgeReferenceMutableTreeNode _lookForEdgeReference( DiagramNode parentNode, DiagramEdge e){
    	DiagramModelTreeNode edgeType = _lookForChild(parentNode, e.getType());
    	assert(edgeType != null);
    	EdgeReferenceMutableTreeNode child = null, temp;
    	for(@SuppressWarnings("unchecked")
		Enumeration<DiagramModelTreeNode> children = edgeType.children(); children.hasMoreElements();){
    		temp = (EdgeReferenceMutableTreeNode)children.nextElement();
    		if( ((EdgeReferenceMutableTreeNode)temp).getEdge().equals(e)){
    			child = temp;
    			break;
    		}
    	}
    	return child;
    }
    
    private void iLog(String action,String args){
 		   InteractionLog.log("MODEL",action,args);
    }
    
	private DiagramModelTreeNode root;
	InnerDiagramCollection diagramCollection;
	private ArrayList<N> nodes;
	private ArrayList<E> edges;
	private ArrayList<DiagramElement> elements;
	private InnerTreeModel treeModel;
	
	private long edgeCounter;
	private long nodeCounter;
	
	private ElementNotifier notifier;
	private List<ChangeListener> changeListeners;
	
	private boolean modified;
	
	private final static String ROOT_LABEL = "Diagram";
	private final static int INITIAL_EDGES_SIZE = 20;
	private final static int INITIAL_NODES_SIZE = 30;}