]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/GraphToDiagramSynchronizer.java
Fix async NPE with GraphToDiagramSynchronizer
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / adapter / GraphToDiagramSynchronizer.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2018 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.adapter;
13
14 import java.awt.geom.AffineTransform;
15 import java.lang.reflect.InvocationTargetException;
16 import java.util.ArrayDeque;
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.Comparator;
22 import java.util.Deque;
23 import java.util.EnumSet;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Queue;
29 import java.util.Set;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.ConcurrentMap;
32 import java.util.concurrent.atomic.AtomicBoolean;
33 import java.util.concurrent.locks.Condition;
34 import java.util.concurrent.locks.ReentrantLock;
35
36 import org.eclipse.core.runtime.IProgressMonitor;
37 import org.eclipse.core.runtime.SubMonitor;
38 import org.simantics.db.AsyncReadGraph;
39 import org.simantics.db.ReadGraph;
40 import org.simantics.db.RequestProcessor;
41 import org.simantics.db.Resource;
42 import org.simantics.db.Session;
43 import org.simantics.db.common.ResourceArray;
44 import org.simantics.db.common.exception.DebugException;
45 import org.simantics.db.common.procedure.adapter.AsyncProcedureAdapter;
46 import org.simantics.db.common.procedure.adapter.CacheListener;
47 import org.simantics.db.common.procedure.adapter.ListenerSupport;
48 import org.simantics.db.common.procedure.adapter.ProcedureAdapter;
49 import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;
50 import org.simantics.db.common.request.AsyncReadRequest;
51 import org.simantics.db.common.request.ReadRequest;
52 import org.simantics.db.common.session.SessionEventListenerAdapter;
53 import org.simantics.db.common.utils.NameUtils;
54 import org.simantics.db.exception.CancelTransactionException;
55 import org.simantics.db.exception.DatabaseException;
56 import org.simantics.db.exception.NoSingleResultException;
57 import org.simantics.db.exception.ServiceException;
58 import org.simantics.db.procedure.AsyncListener;
59 import org.simantics.db.procedure.AsyncProcedure;
60 import org.simantics.db.procedure.Listener;
61 import org.simantics.db.procedure.Procedure;
62 import org.simantics.db.request.Read;
63 import org.simantics.db.service.SessionEventSupport;
64 import org.simantics.diagram.connection.ConnectionSegmentEnd;
65 import org.simantics.diagram.content.Change;
66 import org.simantics.diagram.content.ConnectionUtil;
67 import org.simantics.diagram.content.DesignatedTerminal;
68 import org.simantics.diagram.content.DiagramContentChanges;
69 import org.simantics.diagram.content.DiagramContents;
70 import org.simantics.diagram.content.EdgeResource;
71 import org.simantics.diagram.content.ResourceTerminal;
72 import org.simantics.diagram.internal.DebugPolicy;
73 import org.simantics.diagram.internal.timing.GTask;
74 import org.simantics.diagram.internal.timing.Timing;
75 import org.simantics.diagram.profile.ProfileKeys;
76 import org.simantics.diagram.synchronization.CollectingModificationQueue;
77 import org.simantics.diagram.synchronization.CompositeModification;
78 import org.simantics.diagram.synchronization.CopyAdvisor;
79 import org.simantics.diagram.synchronization.ErrorHandler;
80 import org.simantics.diagram.synchronization.IHintSynchronizer;
81 import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;
82 import org.simantics.diagram.synchronization.IModification;
83 import org.simantics.diagram.synchronization.LogErrorHandler;
84 import org.simantics.diagram.synchronization.ModificationAdapter;
85 import org.simantics.diagram.synchronization.SynchronizationHints;
86 import org.simantics.diagram.synchronization.graph.AddElement;
87 import org.simantics.diagram.synchronization.graph.BasicResources;
88 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
89 import org.simantics.diagram.synchronization.graph.ElementLoader;
90 import org.simantics.diagram.synchronization.graph.ElementReorder;
91 import org.simantics.diagram.synchronization.graph.ElementWriter;
92 import org.simantics.diagram.synchronization.graph.GraphSynchronizationContext;
93 import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;
94 import org.simantics.diagram.synchronization.graph.ModificationQueue;
95 import org.simantics.diagram.synchronization.graph.TagChange;
96 import org.simantics.diagram.synchronization.graph.TransformElement;
97 import org.simantics.diagram.synchronization.graph.layer.GraphLayer;
98 import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;
99 import org.simantics.diagram.ui.DiagramModelHints;
100 import org.simantics.g2d.canvas.Hints;
101 import org.simantics.g2d.canvas.ICanvasContext;
102 import org.simantics.g2d.connection.ConnectionEntity;
103 import org.simantics.g2d.connection.EndKeyOf;
104 import org.simantics.g2d.connection.TerminalKeyOf;
105 import org.simantics.g2d.diagram.DiagramClass;
106 import org.simantics.g2d.diagram.DiagramHints;
107 import org.simantics.g2d.diagram.DiagramMutator;
108 import org.simantics.g2d.diagram.DiagramUtils;
109 import org.simantics.g2d.diagram.IDiagram;
110 import org.simantics.g2d.diagram.IDiagram.CompositionListener;
111 import org.simantics.g2d.diagram.IDiagram.CompositionVetoListener;
112 import org.simantics.g2d.diagram.handler.DataElementMap;
113 import org.simantics.g2d.diagram.handler.ElementFactory;
114 import org.simantics.g2d.diagram.handler.Relationship;
115 import org.simantics.g2d.diagram.handler.RelationshipHandler;
116 import org.simantics.g2d.diagram.handler.SubstituteElementClass;
117 import org.simantics.g2d.diagram.handler.Topology;
118 import org.simantics.g2d.diagram.handler.Topology.Connection;
119 import org.simantics.g2d.diagram.handler.Topology.Terminal;
120 import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType;
121 import org.simantics.g2d.diagram.impl.Diagram;
122 import org.simantics.g2d.diagram.participant.ElementPainter;
123 import org.simantics.g2d.element.ElementClass;
124 import org.simantics.g2d.element.ElementHints;
125 import org.simantics.g2d.element.ElementHints.DiscardableKey;
126 import org.simantics.g2d.element.ElementUtils;
127 import org.simantics.g2d.element.IElement;
128 import org.simantics.g2d.element.IElementClassProvider;
129 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
130 import org.simantics.g2d.element.handler.ElementHandler;
131 import org.simantics.g2d.element.handler.ElementLayerListener;
132 import org.simantics.g2d.element.handler.TerminalTopology;
133 import org.simantics.g2d.element.impl.Element;
134 import org.simantics.g2d.layers.ILayer;
135 import org.simantics.g2d.layers.ILayersEditor;
136 import org.simantics.g2d.routing.RouterFactory;
137 import org.simantics.scenegraph.INode;
138 import org.simantics.scenegraph.profile.DataNodeConstants;
139 import org.simantics.scenegraph.profile.DataNodeMap;
140 import org.simantics.scenegraph.profile.common.ProfileObserver;
141 import org.simantics.structural2.modelingRules.IModelingRules;
142 import org.simantics.utils.datastructures.ArrayMap;
143 import org.simantics.utils.datastructures.MapSet;
144 import org.simantics.utils.datastructures.Pair;
145 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
146 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
147 import org.simantics.utils.datastructures.hints.IHintContext.Key;
148 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
149 import org.simantics.utils.datastructures.hints.IHintListener;
150 import org.simantics.utils.datastructures.hints.IHintObservable;
151 import org.simantics.utils.datastructures.map.AssociativeMap;
152 import org.simantics.utils.datastructures.map.Associativity;
153 import org.simantics.utils.datastructures.map.Tuple;
154 import org.simantics.utils.strings.EString;
155 import org.simantics.utils.threads.ThreadUtils;
156 import org.simantics.utils.threads.logger.ITask;
157 import org.simantics.utils.threads.logger.ThreadLogger;
158 import org.slf4j.Logger;
159 import org.slf4j.LoggerFactory;
160
161 import gnu.trove.map.hash.TObjectIntHashMap;
162 import gnu.trove.set.hash.THashSet;
163
164 /**
165  * This class loads a diagram contained in the graph database into the runtime
166  * diagram model and synchronizes changes in the graph into the run-time
167  * diagram. Any modifications to the graph model will be reflected to the
168  * run-time diagram model. Hence the name GraphToDiagramSynchronizer.
169  * 
170  * <p>
171  * This class does not in itself support modification of the graph diagram
172  * model. This manipulation is meant to be performed through a
173  * {@link DiagramMutator} implementation that is installed into the diagram
174  * using the {@link DiagramHints#KEY_MUTATOR} hint key.
175  * 
176  * This implementations is built to only support diagrams defined in the graph
177  * model as indicated in <a
178  * href="https://www.simantics.org/wiki/index.php/Org.simantics.diagram" >this
179  * </a> document.
180  * 
181  * <p>
182  * The synchronizer in itself is an {@link IDiagramLoader} which means that it
183  * can be used for loading a diagram from the graph. In order for the
184  * synchronizer to keep tracking the graph diagram model for changes the diagram
185  * must be loaded with it. The tracking is implemented using graph database
186  * queries. If you just want to load the diagram but detach it from the
187  * synchronizer's tracking mechanisms, all you need to do is to dispose the
188  * synchronizer after loading the diagram.
189  * 
190  * <p>
191  * This class guarantees that a single diagram element (IElement) representing a
192  * single back-end object ({@link ElementHints#KEY_OBJECT}) will stay the same
193  * object for the same back-end object.
194  * 
195  * <p>
196  * TODO: Currently it just happens that {@link GraphToDiagramSynchronizer}
197  * contains {@link DefaultDiagramMutator} which depends on some internal details
198  * of {@link GraphToDiagramSynchronizer} but it should be moved out of here by
199  * introducing new interfaces.
200  * 
201  * <h2>Basic usage example</h2>
202  * <p>
203  * This example shows how to initialize {@link GraphToDiagramSynchronizer} for a
204  * specified {@link ICanvasContext} and load a diagram from a specified diagram
205  * resource in the graph.
206  * 
207  * <pre>
208  * IDiagram loadDiagram(final ICanvasContext canvasContext, RequestProcessor processor, Resource diagramResource,
209  *         ResourceArray structuralPath) throws DatabaseException {
210  *     GraphToDiagramSynchronizer synchronizer = processor.syncRequest(new Read&lt;GraphToDiagramSynchronizer&gt;() {
211  *         public GraphToDiagramSynchronizer perform(ReadGraph graph) throws DatabaseException {
212  *             return new GraphToDiagramSynchronizer(graph, canvasContext, createElementClassProvider(graph));
213  *         }
214  *     });
215  *     IDiagram d = requestProcessor
216  *             .syncRequest(new DiagramLoadQuery(diagramResource, structuralPath, synchronizer, null));
217  *     return d;
218  * }
219  * 
220  * protected IElementClassProvider createElementClassProvider(ReadGraph graph) {
221  *     DiagramResource dr = DiagramResource.getInstance(graph);
222  *     return ElementClassProviders.mappedProvider(ElementClasses.CONNECTION, DefaultConnectionClassFactory.CLASS
223  *             .newClassWith(new ResourceAdapterImpl(dr.Connection)), ElementClasses.FLAG, FlagClassFactory
224  *             .createFlagClass(dr.Flag));
225  * }
226  * </pre>
227  * 
228  * <p>
229  * TODO: make GraphToDiagramSynchronizer a canvas participant to make it more
230  * uniform with the rest of the canvas system. This does not mean that G2DS must
231  * be attached to an ICanvasContext in order to be used, rather that it can be
232  * attached to one.
233  * 
234  * <p>
235  * TODO: test that detaching the synchronizer via {@link #dispose()} actually
236  * works.
237  * <p>
238  * TODO: remove {@link DefaultDiagramMutator} and all {@link DiagramMutator}
239  * stuff altogether
240  * 
241  * <p>
242  * TODO: diagram connection loading has no listener
243  * 
244  * @author Tuukka Lehtonen
245  * 
246  * @see GraphElementClassFactory
247  * @see GraphElementFactory
248  * @see ElementLoader
249  * @see ElementWriter
250  * @see IHintSynchronizer
251  * @see CopyAdvisor
252  */
253 public class GraphToDiagramSynchronizer extends AbstractDisposable implements IDiagramLoader, IModifiableSynchronizationContext {
254
255     private static final Logger LOGGER = LoggerFactory.getLogger(GraphToDiagramSynchronizer.class);
256
257     /**
258      * Controls whether the class adds hint listeners to each diagram element
259      * that try to perform basic sanity checks on changes happening in element
260      * hints. Having this will immediately inform you of bugs that corrupt the
261      * diagram model within the element hints in some way.
262      */
263     private static final boolean USE_ELEMENT_VALIDATING_LISTENERS = false;
264
265     /**
266      * These keys are used to hang on to Connection instances of edges that will
267      * be later installed as EndKeyOf/TerminalKeyOf hints into the loaded
268      * element during the "graph to diagram update transaction".
269      */
270     private static final Key     KEY_CONNECTION_BEGIN_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_BEGIN_PLACEHOLDER");
271     private static final Key     KEY_CONNECTION_END_PLACEHOLDER   = new KeyOf(PlaceholderConnection.class, "CONNECTION_END_PLACEHOLDER");
272
273     /**
274      * Stored into an edge node during connection edge requests using the
275      * KEY_CONNECTION_BEGIN_PLACEHOLDER and KEY_CONNECTION_END_PLACEHOLDER keys.
276      */
277     static class PlaceholderConnection {
278         public final EdgeEnd end;
279         public final Object node;
280         public final Terminal terminal;
281         public PlaceholderConnection(EdgeEnd end, Object node, Terminal terminal) {
282             this.end = end;
283             this.node = node;
284             this.terminal = terminal;
285         }
286     }
287
288     /**
289      * Indicates to the diagram CompositionListener of this synchronizer that is
290      * should deny all relationships for the element this hint is attached to.
291      */
292     private static final Key        KEY_REMOVE_RELATIONSHIPS = new KeyOf(Boolean.class, "REMOVE_RELATIONSHIPS");
293
294     static ErrorHandler             errorHandler             = LogErrorHandler.INSTANCE;
295
296     /**
297      * The canvas context which is being synchronized with the graph. Received
298      * during construction.
299      * 
300      * <p>
301      * Is not nulled during disposal of this class since internal listener's
302      * life-cycles depend on canvas.isDisposed.
303      */
304     ICanvasContext                  canvas;
305
306     /**
307      * The session used by this synchronizer. Received during construction.
308      */
309     Session                         session;
310
311     /**
312      * Locked while updating diagram contents from the graph.
313      */
314     ReentrantLock                   diagramUpdateLock        = new ReentrantLock();
315
316     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
317     // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING BEGIN
318     // ------------------------------------------------------------------------
319
320     /**
321      * Holds a GraphToDiagramUpdater instance while a diagram content update is
322      * currently in progress.
323      *
324      * <p>
325      * Basically this is a hack solution to the problem of properly finding
326      * newly added Resource<->IElement mappings while loading the diagram
327      * contents. See {@link DataElementMapImpl} for why this is necessary.
328      */
329     GraphToDiagramUpdater                   currentUpdater                   = null;
330
331     /**
332      * A map from data objects to elements. Elements should already contain the
333      * data objects as {@link ElementHints#KEY_OBJECT} hints.
334      */
335     ConcurrentMap<Object, IElement>         dataElement                      = new ConcurrentHashMap<Object, IElement>();
336
337     /**
338      * Temporary structure for single-threaded use in #{@link DiagramUpdater}.
339      */
340     Collection<Connection>                  tempConnections = new ArrayList<Connection>();
341
342     /**
343      * A dummy class of which an instance will be given to each new edge element
344      * to make {@link TerminalKeyOf} keys unique for each edge.
345      */
346     static class TransientElementObject {
347         @Override
348         public String toString() {
349             return "MUTATOR GENERATED (hash=" + System.identityHashCode(this) + ")";
350         }
351     }
352
353     private static class ConnectionChildren {
354         public Set<IElement> branchPoints;
355         public Set<IElement> segments;
356
357         public ConnectionChildren(Set<IElement> branchPoints, Set<IElement> segments) {
358             this.branchPoints = branchPoints;
359             this.segments = segments;
360         }
361     }
362
363     ListenerSupport canvasListenerSupport = new ListenerSupport() {
364         @Override
365         public void exception(Throwable t) {
366             error(t);
367         }
368
369         @Override
370         public boolean isDisposed() {
371             return !isAlive() || canvas.isDisposed();
372         }
373     };
374
375     /**
376      * @see ElementHints#KEY_CONNECTION_ENTITY
377      */
378     class ConnectionEntityImpl implements ConnectionEntity {
379
380         /**
381          * The connection instance resource in the graph backend.
382          *
383          * May be <code>null</code> if the connection has not been synchronized
384          * yet.
385          */
386         Resource                 connection;
387
388         /**
389          * The connection type resource in the graph backend.
390          *
391          * May be <code>null</code> if the connection has not been synchronized
392          * yet.
393          */
394         Resource                 connectionType;
395
396         /**
397          * The connection entity element which is a part of the diagram.
398          */
399         IElement                 connectionElement;
400
401         /**
402          * List of backend-synchronized branch points that are part of this
403          * connection.
404          */
405         Collection<Resource>     branchPoints        = Collections.emptyList();
406
407         /**
408          * List of backend-synchronized edges that are part of this connection.
409          */
410         Collection<EdgeResource> segments            = Collections.emptyList();
411
412         Set<Object>              removedBranchPoints = new HashSet<Object>(4);
413
414         Set<Object>              removedSegments     = new HashSet<Object>(4);
415
416         /**
417          * List of non-backend-synchronized branch point element that are part
418          * of this connection.
419          */
420         List<IElement>           branchPointElements = new ArrayList<IElement>(1);
421
422         /**
423          * List of non-backend-synchronized edge element that are part of this
424          * connection.
425          */
426         List<IElement>           segmentElements     = new ArrayList<IElement>(2);
427
428         ConnectionListener       listener;
429
430         ConnectionEntityImpl(Resource connection, Resource connectionType, IElement connectionElement) {
431             this.connection = connection;
432             this.connectionType = connectionType;
433             this.connectionElement = connectionElement;
434         }
435
436         ConnectionEntityImpl(Resource connectionType, IElement connectionElement) {
437             this.connectionType = connectionType;
438             this.connectionElement = connectionElement;
439         }
440
441         ConnectionEntityImpl(ReadGraph graph, Resource connection, IElement connectionElement)
442         throws NoSingleResultException, ServiceException {
443             this.connection = connection;
444             this.connectionType = graph.getSingleType(connection, br.DIA.Connection);
445             this.connectionElement = connectionElement;
446         }
447
448         @Override
449         public IElement getConnection() {
450             return connectionElement;
451         }
452
453         public Object getConnectionObject() {
454             return connection;
455         }
456
457         public IElement getConnectionElement() {
458             if (connectionElement == null)
459                 return getMappedConnectionElement();
460             return connectionElement;
461         }
462
463         private IElement getMappedConnectionElement() {
464             IElement ce = null;
465             if (connection != null)
466                 ce = getMappedElement(connection);
467             return ce == null ? connectionElement : ce;
468         }
469
470         void fix() {
471             Collection<IElement> segments = getSegments(null);
472
473             // Remove all TerminalKeyOf hints from branch points that do not
474             // match
475             ArrayList<TerminalKeyOf> pruned = null;
476             for (IElement bp : getBranchPoints(null)) {
477                 if (pruned == null)
478                     pruned = new ArrayList<TerminalKeyOf>(4);
479                 pruned.clear();
480                 for (Map.Entry<TerminalKeyOf, Object> entry : bp.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
481                     // First check that the terminal matches.
482                     Connection c = (Connection) entry.getValue();
483                     if (!segments.contains(c.edge))
484                         pruned.add(entry.getKey());
485                 }
486                 removeNodeTopologyHints((Element) bp, pruned);
487             }
488         }
489
490         public ConnectionChildren getConnectionChildren() {
491             Set<IElement> bps = Collections.emptySet();
492             Set<IElement> segs = Collections.emptySet();
493             if (!branchPoints.isEmpty()) {
494                 bps = new HashSet<IElement>(branchPoints.size());
495                 for (Resource bp : branchPoints) {
496                     IElement e = getMappedElement(bp);
497                     if (e != null)
498                         bps.add(e);
499                 }
500             }
501             if (!segments.isEmpty()) {
502                 segs = new HashSet<IElement>(segments.size());
503                 for (EdgeResource seg : segments) {
504                     IElement e = getMappedElement(seg);
505                     if (e != null)
506                         segs.add(e);
507                 }
508             }
509             return new ConnectionChildren(bps, segs);
510         }
511
512         public void setData(Collection<EdgeResource> segments, Collection<Resource> branchPoints) {
513             // System.out.println("setData " + segments.size());
514             this.branchPoints = branchPoints;
515             this.segments = segments;
516
517             // Reset the added/removed state of segments and branchpoints.
518             this.removedBranchPoints = new HashSet<Object>(4);
519             this.removedSegments = new HashSet<Object>(4);
520             this.branchPointElements = new ArrayList<IElement>(4);
521             this.segmentElements = new ArrayList<IElement>(4);
522         }
523
524         public void fireListener(ConnectionChildren old, ConnectionChildren current) {
525             if (listener != null) {
526                 List<IElement> removed = new ArrayList<IElement>();
527                 List<IElement> added = new ArrayList<IElement>();
528
529                 for (IElement oldBp : old.branchPoints)
530                     if (!current.branchPoints.contains(oldBp))
531                         removed.add(oldBp);
532                 for (IElement oldSeg : old.segments)
533                     if (!current.segments.contains(oldSeg))
534                         removed.add(oldSeg);
535
536                 for (IElement bp : current.branchPoints)
537                     if (!old.branchPoints.contains(bp))
538                         added.add(bp);
539                 for (IElement seg : current.segments)
540                     if (!old.segments.contains(seg))
541                         added.add(seg);
542
543                 if (!removed.isEmpty() || !added.isEmpty()) {
544                     listener.connectionChanged(new ConnectionEvent(this.connectionElement, removed, added));
545                 }
546             }
547         }
548
549         @Override
550         public Collection<IElement> getBranchPoints(Collection<IElement> result) {
551             if (result == null)
552                 result = new ArrayList<IElement>(branchPoints.size());
553             for (Resource bp : branchPoints) {
554                 if (!removedBranchPoints.contains(bp)) {
555                     IElement e = getMappedElement(bp);
556                     if (e != null)
557                         result.add(e);
558                 }
559             }
560             result.addAll(branchPointElements);
561             return result;
562         }
563
564         @Override
565         public Collection<IElement> getSegments(Collection<IElement> result) {
566             if (result == null)
567                 result = new ArrayList<IElement>(segments.size());
568             for (EdgeResource seg : segments) {
569                 if (!removedSegments.contains(seg)) {
570                     IElement e = getMappedElement(seg);
571                     if (e != null)
572                         result.add(e);
573                 }
574             }
575             result.addAll(segmentElements);
576             return result;
577         }
578
579         @Override
580         public Collection<Connection> getTerminalConnections(Collection<Connection> result) {
581             if (result == null)
582                 result = new ArrayList<Connection>(segments.size() * 2);
583             Set<org.simantics.utils.datastructures.Pair<IElement, Terminal>> processed = new HashSet<org.simantics.utils.datastructures.Pair<IElement, Terminal>>();
584             for (EdgeResource seg : segments) {
585                 IElement edge = getMappedElement(seg);
586                 if (edge != null) {
587                     for (EndKeyOf key : EndKeyOf.KEYS) {
588                         Connection c = edge.getHint(key);
589                         if (c != null && (c.terminal instanceof ResourceTerminal) && processed.add(Pair.make(c.node, c.terminal)))
590                             result.add(c);
591                     }
592                 }
593             }
594             return result;
595         }
596
597         @Override
598         public void setListener(ConnectionListener listener) {
599             this.listener = listener;
600         }
601
602         @Override
603         public String toString() {
604             return getClass().getSimpleName() + "[resource=" + connection + ", branch points=" + branchPoints
605             + ", segments=" + segments + ", connectionElement=" + connectionElement
606             + ", branch point elements=" + branchPointElements + ", segment elements=" + segmentElements
607             + ", removed branch points=" + removedBranchPoints + ", removed segments=" + removedSegments + "]";
608         }
609
610     }
611
612     /**
613      * A map from connection data objects to connection entities. The connection
614      * part elements should already contain the data objects as
615      * {@link ElementHints#KEY_OBJECT} hints.
616      */
617     ConcurrentMap<Object, ConnectionEntityImpl> dataConnection = new ConcurrentHashMap<Object, ConnectionEntityImpl>();
618
619     /**
620      * @param data
621      * @param element
622      */
623     void mapElement(final Object data, final IElement element) {
624         if (!(element instanceof Element)) {
625             throw new IllegalArgumentException("mapElement: expected instance of Element, got " + element + " with data " + data);
626         }
627         assert data != null;
628         assert element != null;
629         if (DebugPolicy.DEBUG_MAPPING)
630             new Exception(Thread.currentThread() + " MAPPING: " + data + " -> " + element).printStackTrace();
631         dataElement.put(data, element);
632     }
633
634     /**
635      * @param data
636      * @return
637      */
638     IElement getMappedElement(final Object data) {
639         assert (data != null);
640         IElement element = dataElement.get(data);
641         return element;
642     }
643
644     IElement getMappedElementByElementObject(IElement e) {
645         if (e == null)
646             return null;
647         Object o = e.getHint(ElementHints.KEY_OBJECT);
648         if (o == null)
649             return null;
650         return getMappedElement(o);
651     }
652
653     /**
654      * @param data
655      * @return
656      */
657     IElement assertMappedElement(final Object data) {
658         IElement element = dataElement.get(data);
659         assert element != null;
660         return element;
661     }
662
663     /**
664      * @param data
665      * @return
666      */
667     IElement unmapElement(final Object data) {
668         IElement element = dataElement.remove(data);
669         if (DebugPolicy.DEBUG_MAPPING)
670             new Exception(Thread.currentThread() + " UN-MAPPED: " + data + " -> " + element).printStackTrace();
671         return element;
672     }
673
674     /**
675      * @param data
676      * @param element
677      */
678     void mapConnection(final Object data, final ConnectionEntityImpl connection) {
679         assert data != null;
680         assert connection != null;
681         if (DebugPolicy.DEBUG_MAPPING)
682             System.out.println(Thread.currentThread() + " MAPPING CONNECTION: " + data + " -> " + connection);
683         dataConnection.put(data, connection);
684     }
685
686     /**
687      * @param data
688      * @return
689      */
690     ConnectionEntityImpl getMappedConnection(final Object data) {
691         ConnectionEntityImpl connection = dataConnection.get(data);
692         return connection;
693     }
694
695     /**
696      * @param data
697      * @return
698      */
699     ConnectionEntityImpl assertMappedConnection(final Object data) {
700         ConnectionEntityImpl connection = getMappedConnection(data);
701         assert connection != null;
702         return connection;
703     }
704
705     /**
706      * @param data
707      * @return
708      */
709     ConnectionEntityImpl unmapConnection(final Object data) {
710         ConnectionEntityImpl connection = dataConnection.remove(data);
711         if (DebugPolicy.DEBUG_MAPPING)
712             System.out.println(Thread.currentThread() + " UN-MAPPED CONNECTION: " + data + " -> " + connection);
713         return connection;
714     }
715
716     class DataElementMapImpl implements DataElementMap {
717         @Override
718         public Object getData(IDiagram d, IElement element) {
719             if (d == null)
720                 throw new NullPointerException("null diagram");
721             if (element == null)
722                 throw new NullPointerException("null element");
723
724             assert ElementUtils.getDiagram(element) == d;
725             return element.getHint(ElementHints.KEY_OBJECT);
726         }
727
728         @Override
729         public IElement getElement(IDiagram d, Object data) {
730             if (d == null)
731                 throw new NullPointerException("null diagram");
732             if (data == null)
733                 throw new NullPointerException("null data");
734
735             GraphToDiagramUpdater updater = currentUpdater;
736             if (updater != null) {
737                 // This HACK is for allowing GraphElementFactory implementations
738                 // to find the IElements they are related to.
739                 IElement e = updater.addedElementMap.get(data);
740                 if (e != null)
741                     return e;
742             }
743
744             IElement e = getMappedElement(data);
745             if (e != null)
746                 return e;
747             return null;
748         }
749     }
750
751     class SubstituteElementClassImpl implements SubstituteElementClass {
752         @Override
753         public ElementClass substitute(IDiagram d, ElementClass ec) {
754             if (d != diagram)
755                 throw new IllegalArgumentException("specified diagram does not have this SubstituteElementClass handler");
756
757             // If the element class is our own, there's no point in creating
758             // a copy of it.
759             if (ec.contains(elementLayerListener))
760                 return ec;
761
762             List<ElementHandler> all = ec.getAll();
763             List<ElementHandler> result = new ArrayList<ElementHandler>(all.size());
764             for (ElementHandler eh : all) {
765                 if (eh instanceof ElementLayerListenerImpl)
766                     result.add(elementLayerListener);
767                 else
768                     result.add(eh);
769             }
770             return ElementClass.compile(result, false).setId(ec.getId());
771         }
772     }
773
774     final DataElementMapImpl         dataElementMap         = new DataElementMapImpl();
775
776     final SubstituteElementClassImpl substituteElementClass = new SubstituteElementClassImpl();
777
778     // ------------------------------------------------------------------------
779     // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING END
780     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
781
782     void warning(String message, Exception e) {
783         errorHandler.warning(message, e);
784     }
785
786     void warning(Exception e) {
787         errorHandler.warning(e.getMessage(), e);
788     }
789
790     void error(String message, Throwable e) {
791         errorHandler.error(message, e);
792     }
793
794     void error(Throwable e) {
795         errorHandler.error(e.getMessage(), e);
796     }
797
798     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
799     // GRAPH MODIFICATION QUEUE BEGIN
800     // ------------------------------------------------------------------------
801
802     ModificationQueue                 modificationQueue;
803     IModifiableSynchronizationContext synchronizationContext;
804
805     @Override
806     public <T> T set(Key key, Object value) {
807         if (synchronizationContext == null)
808             return null;
809         return synchronizationContext.set(key, value);
810     }
811
812     @Override
813     public <T> T get(Key key) {
814         if (synchronizationContext == null)
815             return null;
816         return synchronizationContext.get(key);
817     }
818
819     // ------------------------------------------------------------------------
820     // GRAPH MODIFICATION QUEUE END
821     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
822
823     /**
824      * The previously loaded version of the diagram content. This is needed to
825      * calculate the difference between new and old content on each
826      * {@link #diagramGraphUpdater(DiagramContents)} invocation.
827      */
828     DiagramContents       previousContent;
829
830     /**
831      * The diagram instance that this synchronizer is synchronizing with the
832      * graph.
833      */
834     IDiagram              diagram;
835
836     /**
837      * An observer for diagram profile entries. Has a life-cycle that must be
838      * bound to the life-cycle of this GraphToDiagramSynchronizer instance.
839      * Disposed if synchronizer is detached in {@link #doDispose()} or finally
840      * when the canvas is disposed.
841      */
842     ProfileObserver       profileObserver;
843
844     IElementClassProvider elementClassProvider;
845
846     BasicResources        br;
847
848     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
849     // Internal state machine handling BEGIN
850     // ------------------------------------------------------------------------
851
852     /**
853      * An indicator for the current state of this synchronizer. This is a simple
854      * state machine with the following possible state transitions:
855      *
856      * <ul>
857      * <li>INITIAL -> LOADING, DISPOSED</li>
858      * <li>LOADING -> IDLE</li>
859      * <li>IDLE -> UPDATING_DIAGRAM, DISPOSED</li>
860      * <li>UPDATING_DIAGRAM -> IDLE</li>
861      * </ul>
862      * 
863      * Start states: INITIAL
864      * End states: DISPOSED
865      */
866     static enum State {
867         /**
868          * The initial state of the synchronizer.
869          */
870         INITIAL,
871         /**
872          * The synchronizer is performing load-time initialization. During this
873          * time no canvas refreshes should be forced.
874          */
875         LOADING,
876         /**
877          * The synchronizer is performing updates to the diagram model. This
878          * process goes on in the canvas context thread.
879          */
880         UPDATING_DIAGRAM,
881         /**
882          * The synchronizer is doing nothing.
883          */
884         IDLE,
885         /**
886          * The synchronized diagram is being disposed, which means that this
887          * synchronizer should not accept any further actions.
888          */
889         DISPOSED,
890     }
891
892     public static final EnumSet<State> FROM_INITIAL          = EnumSet.of(State.LOADING, State.DISPOSED);
893     public static final EnumSet<State> FROM_LOADING          = EnumSet.of(State.IDLE);
894     public static final EnumSet<State> FROM_UPDATING_DIAGRAM = EnumSet.of(State.IDLE);
895     public static final EnumSet<State> FROM_IDLE             = EnumSet.of(State.UPDATING_DIAGRAM, State.DISPOSED);
896     public static final EnumSet<State> NO_STATES             = EnumSet.noneOf(State.class);
897
898     private EnumSet<State> validTargetStates(State start) {
899         switch (start) {
900             case INITIAL: return FROM_INITIAL;
901             case LOADING: return FROM_LOADING;
902             case UPDATING_DIAGRAM: return FROM_UPDATING_DIAGRAM;
903             case IDLE: return FROM_IDLE;
904             case DISPOSED: return NO_STATES;
905         }
906         throw new IllegalArgumentException("unrecognized state " + start);
907     }
908
909     private String validateStateChange(State start, State end) {
910         EnumSet<State> validTargets = validTargetStates(start);
911         if (!validTargets.contains(end))
912             return "Cannot transition from " + start + " state to " + end + ".";
913         return null;
914     }
915
916     /**
917      * The current state of the synchronizer. At start it is
918      * {@link State#INITIAL} and after loading it is {@link State#IDLE}.
919      */
920     State                              synchronizerState     = State.INITIAL;
921
922     /**
923      * A condition variable used to synchronize synchronizer state changes.
924      */
925     ReentrantLock                      stateLock             = new ReentrantLock();
926
927     /**
928      * A condition that is signaled when the synchronizer state changes to IDLE.
929      */
930     Condition                          idleCondition         = stateLock.newCondition();
931
932     State getState() {
933         return synchronizerState;
934     }
935
936     /**
937      * Activates the desired state after making sure that the synchronizer has
938      * been IDLE in between its current state and this invocation.
939      *
940      * @param newState the new state to activate
941      * @throws InterruptedException if waiting for IDLE state gets interrupted
942      * @throws IllegalStateException if the requested transition from the
943      *         current state to the desired state would be illegal.
944      */
945     void activateState(State newState, boolean waitForIdle) throws InterruptedException {
946         stateLock.lock();
947         try {
948             // Wait until the state of the synchronizer IDLEs if necessary.
949             if (waitForIdle && synchronizerState != State.IDLE) {
950                 String error = validateStateChange(synchronizerState, State.IDLE);
951                 if (error != null)
952                     throw new IllegalStateException(error);
953
954                 while (synchronizerState != State.IDLE) {
955                     if (DebugPolicy.DEBUG_STATE)
956                         System.out.println(Thread.currentThread() + " waiting for IDLE state, current="
957                                 + synchronizerState);
958                     idleCondition.await();
959                 }
960             }
961
962             String error = validateStateChange(synchronizerState, newState);
963             if (error != null)
964                 throw new IllegalStateException(error);
965
966             if (DebugPolicy.DEBUG_STATE)
967                 System.out.println(Thread.currentThread() + " activated state " + newState);
968             this.synchronizerState = newState;
969
970             if (newState == State.IDLE)
971                 idleCondition.signalAll();
972         } finally {
973             stateLock.unlock();
974         }
975     }
976
977     void idle() throws IllegalStateException, InterruptedException {
978         activateState(State.IDLE, false);
979     }
980
981     static interface StateRunnable extends Runnable {
982         void execute() throws InvocationTargetException;
983
984         public abstract class Stub implements StateRunnable {
985             @Override
986             public void run() {
987             }
988
989             @Override
990             public final void execute() throws InvocationTargetException {
991                 try {
992                     perform();
993                 } catch (Exception e) {
994                     throw new InvocationTargetException(e);
995                 } catch (LinkageError e) {
996                     throw new InvocationTargetException(e);
997                 }
998             }
999
1000             protected abstract void perform() throws Exception;
1001         }
1002     }
1003
1004     protected void runInState(State state, StateRunnable runnable) throws InvocationTargetException {
1005         try {
1006             activateState(state, true);
1007             try {
1008                 runnable.execute();
1009             } finally {
1010                 idle();
1011             }
1012         } catch (IllegalStateException e) {
1013             throw new InvocationTargetException(e);
1014         } catch (InterruptedException e) {
1015             throw new InvocationTargetException(e);
1016         }
1017     }
1018
1019     protected void safeRunInState(State state, StateRunnable runnable) {
1020         try {
1021             runInState(state, runnable);
1022         } catch (InvocationTargetException e) {
1023             error("Failed to run runnable " + runnable + " in state " + state + ". See exception for details.", e
1024                     .getCause());
1025         }
1026     }
1027
1028     // ------------------------------------------------------------------------
1029     // Internal state machine handling END
1030     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1031
1032     /**
1033      * @param processor
1034      * @param canvas
1035      * @param elementClassProvider
1036      * @throws DatabaseException
1037      */
1038     public GraphToDiagramSynchronizer(RequestProcessor processor, ICanvasContext canvas, IElementClassProvider elementClassProvider) throws DatabaseException {
1039         if (processor == null)
1040             throw new IllegalArgumentException("null processor");
1041         if (canvas == null)
1042             throw new IllegalArgumentException("null canvas");
1043         if (elementClassProvider == null)
1044             throw new IllegalArgumentException("null element class provider");
1045
1046         this.session = processor.getSession();
1047         this.canvas = canvas;
1048         this.modificationQueue = new ModificationQueue(session, errorHandler);
1049
1050         processor.syncRequest(new ReadRequest() {
1051             @Override
1052             public void run(ReadGraph graph) throws DatabaseException {
1053                 initializeResources(graph);
1054             }
1055         });
1056
1057         this.elementClassProvider = elementClassProvider;
1058         synchronizationContext.set(SynchronizationHints.ELEMENT_CLASS_PROVIDER, elementClassProvider);
1059
1060         attachSessionListener(processor.getSession());
1061     }
1062
1063     /**
1064      * @return
1065      */
1066     public IElementClassProvider getElementClassProvider() {
1067         return elementClassProvider;
1068     }
1069
1070     public Session getSession() {
1071         return session;
1072     }
1073
1074     public ICanvasContext getCanvasContext() {
1075         return canvas;
1076     }
1077
1078     public IDiagram getDiagram() {
1079         return diagram;
1080     }
1081
1082     void setCanvasDirty() {
1083         ICanvasContext c = canvas;
1084         if (synchronizerState != State.LOADING && c != null && !c.isDisposed()) {
1085             // TODO: Consider adding an invocation limiter here, to prevent
1086             // calling setDirty too often if enough time hasn't passed yet since
1087             // the last invocation.
1088             c.getContentContext().setDirty();
1089         }
1090     }
1091
1092     /**
1093      * @param elementType
1094      * @return
1095      * @throws DatabaseException if ElementClass cannot be retrieved
1096      */
1097     public ElementClass getNodeClass(Resource elementType) throws DatabaseException {
1098         return getNodeClass(session, elementType);
1099     }
1100
1101     public ElementClass getNodeClass(RequestProcessor processor, Resource elementType) throws DatabaseException {
1102         ElementClass ec = processor.syncRequest(new NodeClassRequest(canvas, diagram, elementType, true));
1103         return ec;
1104     }
1105
1106     @Override
1107     protected void doDispose() {
1108         try {
1109             try {
1110                 stateLock.lock();
1111                 boolean isInitial = getState() == State.INITIAL;
1112                 activateState(State.DISPOSED, !isInitial);
1113             } finally {
1114                 stateLock.unlock();
1115             }
1116         } catch (InterruptedException e) {
1117             // Shouldn't happen.
1118             LOGGER.error("Dispose interrupted!", e);
1119         } finally {
1120             detachSessionListener();
1121
1122             if (profileObserver != null) {
1123                 profileObserver.dispose();
1124                 profileObserver = null;
1125             }
1126
1127             if (diagram != null) {
1128                 diagram.removeCompositionListener(diagramListener);
1129                 diagram.removeCompositionVetoListener(diagramListener);
1130             }
1131
1132             // TODO: we should probably leave the dataElement map as is since DataElementMap needs it even after the synchronizer has been disposed.
1133             // Currently the diagram's DataElementMap will be broken after disposal.
1134 //            dataElement.clear();
1135 //            dataConnection.clear();
1136
1137             if (layerManager != null) {
1138                 layerManager.dispose();
1139             }
1140
1141             // Let GC work.
1142             modificationQueue.dispose();
1143         }
1144     }
1145
1146     void initializeResources(ReadGraph graph) {
1147         this.br = new BasicResources(graph);
1148
1149         // Initialize synchronization context
1150         synchronizationContext = new GraphSynchronizationContext(graph, modificationQueue);
1151         synchronizationContext.set(SynchronizationHints.ERROR_HANDLER, errorHandler);
1152     }
1153
1154     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1155     // LAYERS BEGIN
1156     // ------------------------------------------------------------------------
1157
1158     GraphLayerManager layerManager;
1159
1160     /**
1161      * A common handler for all elements that is used to listen to changes in
1162      * element visibility and focusability on diagram layers.
1163      */
1164     class ElementLayerListenerImpl implements ElementLayerListener {
1165         private static final long serialVersionUID = -3410052116598828129L;
1166
1167         @Override
1168         public void visibilityChanged(IElement e, ILayer layer, boolean visible) {
1169             if (!isAlive())
1170                 return;
1171             if (DebugPolicy.DEBUG_LAYERS)
1172                 System.out.println("visibility changed: " + e + ", " + layer + ", " + visible);
1173             GraphLayer gl = layerManager.getGraphLayer(layer.getName());
1174             if (gl != null) {
1175                 changeTag(e, gl.getVisible(), visible);
1176             }
1177         }
1178
1179         @Override
1180         public void focusabilityChanged(IElement e, ILayer layer, boolean focusable) {
1181             if (!isAlive())
1182                 return;
1183             if (DebugPolicy.DEBUG_LAYERS)
1184                 System.out.println("focusability changed: " + e + ", " + layer + ", " + focusable);
1185             GraphLayer gl = layerManager.getGraphLayer(layer.getName());
1186             if (gl != null) {
1187                 changeTag(e, gl.getFocusable(), focusable);
1188             }
1189         }
1190
1191         void changeTag(IElement e, Resource tag, boolean set) {
1192             Object object = e.getHint(ElementHints.KEY_OBJECT);
1193             Resource tagged = null;
1194             if (object instanceof Resource) {
1195                 tagged = (Resource) object;
1196             } else if (object instanceof EdgeResource) {
1197                 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
1198                 if (ce instanceof ConnectionEntityImpl) {
1199                     tagged = ((ConnectionEntityImpl) ce).connection;
1200                 }
1201             }
1202             if (tagged == null)
1203                 return;
1204
1205             modificationQueue.async(new TagChange(tagged, tag, set), null);
1206         }
1207     };
1208
1209     ElementLayerListenerImpl elementLayerListener = new ElementLayerListenerImpl();
1210
1211     // ------------------------------------------------------------------------
1212     // LAYERS END
1213     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1214
1215     @Override
1216     public IDiagram loadDiagram(IProgressMonitor progressMonitor, ReadGraph g, final String modelURI, final Resource diagram, final Resource runtime, final ResourceArray structuralPath,
1217             IHintObservable initialHints) throws DatabaseException {
1218         if (DebugPolicy.DEBUG_LOAD)
1219             System.out.println(Thread.currentThread() + " loadDiagram: " + NameUtils.getSafeName(g, diagram));
1220
1221         SubMonitor monitor = SubMonitor.convert(progressMonitor, "Load Diagram", 100);
1222
1223         Object loadTask = Timing.BEGIN("GDS.loadDiagram");
1224         try {
1225             try {
1226                 activateState(State.LOADING, false);
1227             } catch (IllegalStateException e) {
1228                 // Disposed already before loading even began.
1229                 this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
1230                 return this.diagram;
1231             }
1232             try {
1233                 // Query for diagram class
1234                 Resource diagramClassResource = g.getPossibleType(diagram, br.DIA.Composite);
1235                 if (diagramClassResource != null) {
1236                     // Spawn new diagram
1237                     Object task = Timing.BEGIN("GDS.DiagramClassRequest");
1238                     final DiagramClass diagramClass = g.syncRequest(new DiagramClassRequest(diagram));
1239                     Timing.END(task);
1240                     final IDiagram d = Diagram.spawnNew(diagramClass);
1241                     {
1242                         d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, diagram);
1243                         if (runtime != null)
1244                             d.setHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE, runtime);
1245                         if (modelURI != null)
1246                             d.setHint(DiagramModelHints.KEY_DIAGRAM_MODEL_URI, modelURI);
1247                         d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE_ARRAY, structuralPath);
1248
1249                         // Set dumb default routing when DiagramClass does not
1250                         // predefine the default connection routing for the diagram.
1251                         if (!d.containsHint(DiagramHints.ROUTE_ALGORITHM))
1252                             d.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, false));
1253
1254                         d.setHint(SynchronizationHints.CONTEXT, this);
1255
1256                         // Initialize hints with hints from initialHints if given
1257                         if (initialHints != null) {
1258                             d.setHints(initialHints.getHints());
1259                         }
1260                     }
1261
1262                     // ITask task2 = ThreadLogger.getInstance().begin("loadLayers");
1263                     monitor.subTask("Layers");
1264                     {
1265                         this.layerManager = new GraphLayerManager(g, modificationQueue, diagram);
1266                         synchronizationContext.set(GraphSynchronizationHints.GRAPH_LAYER_MANAGER, this.layerManager);
1267                         ILayersEditor layers = layerManager.loadLayers(d, g, diagram);
1268                         // task2.finish();
1269
1270                         d.setHint(DiagramHints.KEY_LAYERS, layers);
1271                         d.setHint(DiagramHints.KEY_LAYERS_EDITOR, layers);
1272
1273                         d.addCompositionVetoListener(diagramListener);
1274                         d.addCompositionListener(diagramListener);
1275
1276                         this.diagram = d;
1277
1278                         d.setHint(DiagramHints.KEY_MUTATOR, new DefaultDiagramMutator(d, diagram, synchronizationContext));
1279
1280                         // Add default layer if no layers exist.
1281                         // NOTE: this must be done after this.diagram has been set
1282                         // as it will trigger a graph modification which needs the
1283                         // diagram resource.
1284                         // ITask task3 = ThreadLogger.getInstance().begin("addDefaultLayer");
1285 //                        if (layers.getLayers().isEmpty()) {
1286 //                            if (DebugPolicy.DEBUG_LAYERS)
1287 //                                System.out.println("No layers, creating default layer '"
1288 //                                        + DiagramConstants.DEFAULT_LAYER_NAME + "'");
1289 //                            SimpleLayer defaultLayer = new SimpleLayer(DiagramConstants.DEFAULT_LAYER_NAME);
1290 //                            layers.addLayer(defaultLayer);
1291 //                            layers.activate(defaultLayer);
1292 //                        }
1293 //                        // task3.finish();
1294                     }
1295                     monitor.worked(10);
1296
1297                     monitor.subTask("Contents");
1298                     // Discover the plain resources that form the content of the
1299                     // diagram through a separate query. This allows us to
1300                     // separately
1301                     // track changes to the diagram structure itself, not the
1302                     // substructures contained by the structure elements.
1303                     ITask task4 = ThreadLogger.getInstance().begin("DiagramContentRequest1");
1304                     DiagramContentRequest query = new DiagramContentRequest(canvas, diagram, errorHandler);
1305                     g.syncRequest(query, new DiagramContentListener(diagram));
1306                     task4.finish();
1307                     // ITask task5 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
1308                     ITask task42 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
1309                     DiagramContents contents = g.syncRequest(query, TransientCacheAsyncListener.instance());
1310                     //System.err.println("contents: " + contents);
1311                     task42.finish();
1312                     // task5.finish();
1313                     monitor.worked(10);
1314
1315                     monitor.subTask("Graphical elements");
1316                     {
1317                         Object applyDiagramContents = Timing.BEGIN("GDS.applyDiagramContents");
1318                         ITask task6 = ThreadLogger.getInstance().begin("applyDiagramContents");
1319                         processGraphUpdates(g, Collections.singleton(diagramGraphUpdater(contents)));
1320                         task6.finish();
1321                         Timing.END(applyDiagramContents);
1322                     }
1323                     monitor.worked(80);
1324
1325                     DataNodeMap dn = new DataNodeMap() {
1326                         @Override
1327                         public INode getNode(Object data) {
1328                             if (DataNodeConstants.CANVAS_ROOT == data)
1329                                 return canvas.getCanvasNode();
1330                             if (DataNodeConstants.DIAGRAM_ELEMENT_PARENT == data) {
1331                                 ElementPainter ep = canvas.getAtMostOneItemOfClass(ElementPainter.class);
1332                                 return ep != null ? ep.getDiagramElementParentNode() : null;
1333                             }
1334
1335                             DataElementMap emap = GraphToDiagramSynchronizer.this.diagram.getDiagramClass().getSingleItem(DataElementMap.class);
1336                             IElement element = emap.getElement(GraphToDiagramSynchronizer.this.diagram, data);
1337                             if(element == null) return null;
1338                             return element.getHint(ElementHints.KEY_SG_NODE);
1339                         }
1340                     };
1341
1342                     profileObserver = new ProfileObserver(g.getSession(), runtime,
1343                             canvas.getThreadAccess(), canvas, canvas.getSceneGraph(), diagram, 
1344                             ArrayMap.keys(ProfileKeys.DIAGRAM, ProfileKeys.CANVAS, ProfileKeys.NODE_MAP).values(GraphToDiagramSynchronizer.this.diagram, canvas, dn),
1345                             new CanvasNotification(canvas));
1346
1347                     g.getSession().asyncRequest(new AsyncReadRequest() {
1348                         @Override
1349                         public void run(AsyncReadGraph graph) {
1350                           ProfileObserver po = profileObserver;
1351                             if (po != null)
1352                                 po.listen(graph, GraphToDiagramSynchronizer.this);
1353                             else
1354                                 LOGGER.info("profileObserver has been disposed already!");
1355                         }
1356                     });
1357
1358                     return d;
1359
1360                 }
1361
1362                 this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
1363                 return this.diagram;
1364
1365             } finally {
1366                 idle();
1367             }
1368         } catch (InterruptedException e) {
1369             throw new RuntimeException(e);
1370         } catch (IllegalStateException e) {
1371             // If the synchronizer was disposed ahead of time, it was done
1372             // for a reason, such as the user having closed the owner editor.
1373             if (!isAlive())
1374                 throw new CancelTransactionException(e);
1375             throw new RuntimeException(e);
1376         } finally {
1377             Timing.END(loadTask);
1378         }
1379     }
1380
1381     static class CanvasNotification implements Runnable {
1382
1383         final private ICanvasContext canvas;
1384
1385         public CanvasNotification(ICanvasContext canvas) {
1386             this.canvas = canvas;
1387         }
1388
1389         public void run() {
1390             canvas.getContentContext().setDirty();
1391         }
1392
1393     }
1394
1395     ArrayList<IModification>        pendingModifications = new ArrayList<IModification>();
1396     MapSet<IElement, IModification> modificationIndex    = new MapSet.Hash<IElement, IModification>();
1397
1398     void addModification(IElement element, IModification modification) {
1399         pendingModifications.add(modification);
1400         if (element != null)
1401             modificationIndex.add(element, modification);
1402
1403     }
1404     class DefaultDiagramMutator implements DiagramMutator {
1405
1406         Map<IElement, Resource> creation = new HashMap<IElement, Resource>();
1407
1408         IDiagram d;
1409         Resource diagram;
1410
1411         IModifiableSynchronizationContext synchronizationContext;
1412
1413         public DefaultDiagramMutator(IDiagram d, Resource diagram, IModifiableSynchronizationContext synchronizationContext) {
1414             this.d = d;
1415             this.diagram = diagram;
1416             this.synchronizationContext = synchronizationContext;
1417
1418             if (synchronizationContext.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER) == null)
1419                 throw new IllegalArgumentException("SynchronizationHints.ELEMENT_CLASS_PROVIDER not available");
1420         }
1421
1422         void assertNotDisposed() {
1423             if (!isAlive())
1424                 throw new IllegalStateException(getClass().getSimpleName() + " is disposed");
1425         }
1426
1427         @Override
1428         public IElement newElement(ElementClass clazz) {
1429             assertNotDisposed();
1430             ElementFactory ef = d.getDiagramClass().getAtMostOneItemOfClass(ElementFactory.class);
1431             IElement element = null;
1432             if (ef != null)
1433                 element = ef.spawnNew(clazz);
1434             else
1435                 element = Element.spawnNew(clazz);
1436
1437             element.setHint(ElementHints.KEY_OBJECT, new TransientElementObject());
1438
1439             addModification(element, new AddElement(synchronizationContext, d, element));
1440
1441             return element;
1442         }
1443
1444         @Override
1445         public void commit() {
1446             assertNotDisposed();
1447             if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
1448                 System.out.println("DiagramMutator is about to commit changes:");
1449                 for (IModification mod : pendingModifications)
1450                     System.out.println("\t- " + mod);
1451             }
1452
1453             Collections.sort(pendingModifications);
1454
1455             if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
1456                 if (pendingModifications.size() > 1) {
1457                     System.out.println("* changes were re-ordered to:");
1458                     for (IModification mod : pendingModifications)
1459                         System.out.println("\t" + mod);
1460                 }
1461             }
1462
1463             Timing.safeTimed(errorHandler, "QUEUE AND WAIT FOR MODIFICATIONS TO FINISH", new GTask() {
1464                 @Override
1465                 public void run() throws DatabaseException {
1466                     // Performs a separate write request and query result update
1467                     // for each modification
1468 //                    for (IModification mod : pendingModifications) {
1469 //                        try {
1470 //                            modificationQueue.sync(mod);
1471 //                        } catch (InterruptedException e) {
1472 //                            error("Pending diagram modification " + mod
1473 //                                    + " was interrupted. See exception for details.", e);
1474 //                        }
1475 //                    }
1476
1477                     // NOTE: this is still under testing, the author is not
1478                     // truly certain that it should work in all cases ATM.
1479
1480                     // Performs all modifications with in a single write request
1481                     for (IModification mod : pendingModifications) {
1482                         modificationQueue.offer(mod, null);
1483                     }
1484                     try {
1485                         // Perform the modifications in a single request.
1486                         modificationQueue.finish();
1487                     } catch (InterruptedException e) {
1488                         errorHandler.error("Diagram modification finishing was interrupted. See exception for details.", e);
1489                     }
1490                 }
1491             });
1492             pendingModifications.clear();
1493             modificationIndex.clear();
1494             creation.clear();
1495             if (DebugPolicy.DEBUG_MUTATOR_COMMIT)
1496                 System.out.println("DiagramMutator has committed");
1497         }
1498
1499         @Override
1500         public void clear() {
1501             assertNotDisposed();
1502             pendingModifications.clear();
1503             modificationIndex.clear();
1504             creation.clear();
1505             if (DebugPolicy.DEBUG_MUTATOR)
1506                 System.out.println("DiagramMutator has been cleared");
1507         }
1508
1509         @Override
1510         public void modifyTransform(IElement element) {
1511             assertNotDisposed();
1512             Resource resource = backendObject(element);
1513             AffineTransform tr = element.getHint(ElementHints.KEY_TRANSFORM);
1514             if (resource != null && tr != null) {
1515                 addModification(element, new TransformElement(resource, tr));
1516             }
1517         }
1518
1519         @Override
1520         public void synchronizeHintsToBackend(IElement element) {
1521             assertNotDisposed();
1522             IHintSynchronizer synchronizer = element.getHint(SynchronizationHints.HINT_SYNCHRONIZER);
1523             if (synchronizer != null) {
1524                 CollectingModificationQueue queue = new CollectingModificationQueue();
1525                 synchronizer.synchronize(synchronizationContext, element);
1526                 addModification(element, new CompositeModification(ModificationAdapter.LOW_PRIORITY, queue.getQueue()));
1527             }
1528         }
1529
1530         @Override
1531         public void synchronizeElementOrder() {
1532             assertNotDisposed();
1533             List<IElement> snapshot = d.getSnapshot();
1534             addModification(null, new ElementReorder(d, snapshot));
1535         }
1536
1537         @Override
1538         public void register(IElement element, Object object) {
1539             creation.put(element, (Resource) object);
1540         }
1541
1542         @SuppressWarnings("unchecked")
1543         @Override
1544         public <T> T backendObject(IElement element) {
1545             Object object = ElementUtils.getObject(element);
1546             if (object instanceof Resource)
1547                 return (T) object;
1548             else
1549                 return (T) creation.get(element);
1550         }
1551
1552     }
1553
1554     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1555     // GRAPH TO DIAGRAM SYCHRONIZATION LOGIC BEGIN
1556     // ------------------------------------------------------------------------
1557
1558     static class ConnectionData {
1559         ConnectionEntityImpl impl;
1560         List<Resource>       branchPoints = new ArrayList<Resource>();
1561         List<EdgeResource>   segments     = new ArrayList<EdgeResource>();
1562
1563         ConnectionData(ConnectionEntityImpl ce) {
1564             this.impl = ce;
1565         }
1566
1567         void addBranchPoint(Resource bp) {
1568             branchPoints.add(bp);
1569         }
1570
1571         void addSegment(EdgeResource seg) {
1572             segments.add(seg);
1573         }
1574     }
1575
1576     class GraphToDiagramUpdater {
1577         DiagramContents                                 lastContent;
1578         DiagramContents                                 content;
1579         DiagramContentChanges                           changes;
1580
1581         final List<IElement>                            addedElements;
1582         final List<IElement>                            removedElements;
1583
1584         final List<IElement>                            addedConnectionSegments;
1585         final List<IElement>                            removedConnectionSegments;
1586
1587         final List<IElement>                            addedBranchPoints;
1588         final List<IElement>                            removedBranchPoints;
1589
1590         final Map<Object, IElement>                     addedElementMap;
1591         final Map<Resource, IElement>                   addedConnectionMap;
1592         final Map<Resource, ConnectionEntityImpl>       addedConnectionEntities;
1593         final List<Resource>                            removedConnectionEntities;
1594         final Map<ConnectionEntityImpl, ConnectionData> changedConnectionEntities;
1595
1596         final Map<Resource, IElement>                   addedRouteGraphConnectionMap;
1597         final List<IElement>                            removedRouteGraphConnections;
1598
1599
1600         GraphToDiagramUpdater(DiagramContents lastContent, DiagramContents content, DiagramContentChanges changes) {
1601             this.lastContent = lastContent;
1602             this.content = content;
1603             this.changes = changes;
1604
1605             this.addedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
1606             this.removedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
1607             this.addedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
1608             this.removedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
1609             this.addedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
1610             this.removedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
1611             this.addedElementMap = new HashMap<Object, IElement>();
1612             this.addedConnectionMap = new HashMap<Resource, IElement>();
1613             this.addedConnectionEntities = new HashMap<Resource, ConnectionEntityImpl>();
1614             this.removedConnectionEntities = new ArrayList<Resource>(changes.connections.size());
1615             this.changedConnectionEntities = new HashMap<ConnectionEntityImpl, ConnectionData>();
1616             this.addedRouteGraphConnectionMap = new HashMap<Resource, IElement>();
1617             this.removedRouteGraphConnections = new ArrayList<IElement>(changes.routeGraphConnections.size());
1618         }
1619
1620         public void clear() {
1621             // Prevent DiagramContents leakage through DisposableListeners.
1622             lastContent = null;
1623             content = null;
1624             changes = null;
1625
1626             this.addedElements.clear();
1627             this.removedElements.clear();
1628             this.addedConnectionSegments.clear();
1629             this.removedConnectionSegments.clear();
1630             this.addedBranchPoints.clear();
1631             this.removedBranchPoints.clear();
1632             this.addedElementMap.clear();
1633             this.addedConnectionMap.clear();
1634             this.addedConnectionEntities.clear();
1635             this.removedConnectionEntities.clear();
1636             this.changedConnectionEntities.clear();
1637             this.addedRouteGraphConnectionMap.clear();
1638             this.removedRouteGraphConnections.clear();
1639         }
1640
1641         void processNodes(ReadGraph graph) throws DatabaseException {
1642
1643             for (Map.Entry<Resource, Change> entry : changes.elements.entrySet()) {
1644
1645                 final Resource element = entry.getKey();
1646                 Change change = entry.getValue();
1647
1648                 switch (change) {
1649                     case ADDED: {
1650                         IElement mappedElement = getMappedElement(element);
1651                         if (mappedElement == null) {
1652                             if (DebugPolicy.DEBUG_NODE_LOAD)
1653                                 graph.syncRequest(new ReadRequest() {
1654                                     @Override
1655                                     public void run(ReadGraph graph) throws DatabaseException {
1656                                         System.out.println("    EXTERNALLY ADDED ELEMENT: "
1657                                                 + NameUtils.getSafeName(graph, element) + " ("
1658                                                 + element.getResourceId() + ")");
1659                                     }
1660                                 });
1661
1662                             if (content.connectionSet.contains(element)) {
1663
1664                                 // TODO: Connection loading has no listening, changes :Connection will not be noticed by this code!
1665                                 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1666
1667                                     boolean firstTime = true;
1668
1669                                     @Override
1670                                     public String toString() {
1671                                         return "Connection load listener for " + element;
1672                                     }
1673                                     @Override
1674                                     public void execute(IElement loaded) {
1675                                         // Invoked when the element has been loaded.
1676                                         if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1677                                             System.out.println("CONNECTION LoadListener for " + loaded);
1678
1679                                         if (loaded == null) {
1680                                             disposeListener();
1681                                             return;
1682                                         }
1683
1684                                         if (firstTime) {
1685
1686                                             mapElement(element, loaded);
1687                                             synchronized (GraphToDiagramUpdater.this) {
1688                                                 addedElements.add(loaded);
1689                                                 addedElementMap.put(element, loaded);
1690                                                 addedConnectionMap.put(element, loaded);
1691                                             }
1692
1693                                             firstTime = false;
1694
1695                                         }
1696
1697                                         Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1698
1699                                         // Logic for disposing listener
1700                                         if (!previousContent.connectionSet.contains(data)) {
1701                                             if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1702                                                 System.out.println("CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
1703                                             disposeListener();
1704                                             return;
1705                                         }
1706
1707                                         if (addedElementMap.containsKey(data)) {
1708                                             // This element was just loaded, in
1709                                             // which case its hints need to
1710                                             // uploaded to the real mapped
1711                                             // element immediately.
1712                                             IElement mappedElement = getMappedElement(data);
1713                                             if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1714                                                 System.out.println("LOADED ADDED CONNECTION, currently mapped connection: " + mappedElement);
1715                                             if (mappedElement != null && (mappedElement instanceof Element)) {
1716                                                 if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
1717                                                     System.out.println("  mapped hints: " + mappedElement.getHints());
1718                                                     System.out.println("  loaded hints: " + loaded.getHints());
1719                                                 }
1720                                                 updateMappedElement((Element) mappedElement, loaded);
1721                                             }
1722                                         } else {
1723                                             // This element was already loaded.
1724                                             // Just schedule an update some time
1725                                             // in the future.
1726                                             if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1727                                                 System.out.println("PREVIOUSLY LOADED CONNECTION UPDATED, scheduling update into the future");
1728                                             offerGraphUpdate( connectionUpdater(element, loaded) );
1729                                         }
1730                                     }
1731                                 };
1732
1733                                 graph.syncRequest(new ConnectionRequest(canvas, diagram, element, errorHandler, loadListener), new AsyncProcedure<IElement>() {
1734                                     @Override
1735                                     public void execute(AsyncReadGraph graph, final IElement e) {
1736
1737                                         // Read connection type
1738                                         graph.forSingleType(element, br.DIA.Connection, new Procedure<Resource>() {
1739                                             @Override
1740                                             public void exception(Throwable t) {
1741                                                 error(t);
1742                                             }
1743
1744                                             @Override
1745                                             public void execute(Resource connectionType) {
1746                                                 synchronized (GraphToDiagramUpdater.this) {
1747                                                     //System.out.println("new connection entity " + e);
1748                                                     ConnectionEntityImpl entity = new ConnectionEntityImpl(element, connectionType, e);
1749                                                     e.setHint(ElementHints.KEY_CONNECTION_ENTITY, entity);
1750                                                     addedConnectionEntities.put(element, entity);
1751                                                 }
1752                                             }
1753                                         });
1754
1755                                     }
1756
1757                                     @Override
1758                                     public void exception(AsyncReadGraph graph, Throwable throwable) {
1759                                         error(throwable);
1760                                     }
1761                                 });
1762                             } else if (content.nodeSet.contains(element)) {
1763
1764                                 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1765
1766                                     boolean firstTime = true;
1767
1768                                     @Override
1769                                     public String toString() {
1770                                         return "Node load listener for " + element;
1771                                     }
1772                                     @Override
1773                                     public void execute(IElement loaded) {
1774                                         // Invoked when the element has been loaded.
1775                                         if (DebugPolicy.DEBUG_NODE_LISTENER)
1776                                             System.out.println("NODE LoadListener for " + loaded);
1777
1778                                         if (loaded == null) {
1779                                             disposeListener();
1780                                             return;
1781                                         }
1782
1783                                         if (firstTime) {
1784
1785                                             // This is invoked before the element is actually loaded.
1786                                             //System.out.println("NodeRequestProcedure " + e);
1787                                             if (DebugPolicy.DEBUG_NODE_LOAD)
1788                                                 System.out.println("MAPPING ADDED NODE: " + element + " -> " + loaded);
1789                                             mapElement(element, loaded);
1790                                             synchronized (GraphToDiagramUpdater.this) {
1791                                                 addedElements.add(loaded);
1792                                                 addedElementMap.put(element, loaded);
1793                                             }
1794
1795                                             firstTime = false;
1796
1797                                         }
1798
1799                                         Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1800
1801                                         // Logic for disposing listener
1802                                         if (!previousContent.nodeSet.contains(data)) {
1803                                             if (DebugPolicy.DEBUG_NODE_LISTENER)
1804                                                 System.out.println("NODE LoadListener, node not in current content: " + data + ". Disposing.");
1805                                             disposeListener();
1806                                             return;
1807                                         }
1808
1809                                         if (addedElementMap.containsKey(data)) {
1810                                             // This element was just loaded, in
1811                                             // which case its hints need to
1812                                             // uploaded to the real mapped
1813                                             // element immediately.
1814                                             IElement mappedElement = getMappedElement(data);
1815                                             if (DebugPolicy.DEBUG_NODE_LISTENER)
1816                                                 System.out.println("LOADED ADDED ELEMENT, currently mapped element: " + mappedElement);
1817                                             if (mappedElement != null && (mappedElement instanceof Element)) {
1818                                                 if (DebugPolicy.DEBUG_NODE_LISTENER) {
1819                                                     System.out.println("  mapped hints: " + mappedElement.getHints());
1820                                                     System.out.println("  loaded hints: " + loaded.getHints());
1821                                                 }
1822                                                 updateMappedElement((Element) mappedElement, loaded);
1823                                             }
1824                                         } else {
1825                                             // This element was already loaded.
1826                                             // Just schedule an update some time
1827                                             // in the future.
1828                                             if (DebugPolicy.DEBUG_NODE_LISTENER)
1829                                                 System.out.println("PREVIOUSLY LOADED NODE UPDATED, scheduling update into the future");
1830                                             offerGraphUpdate( nodeUpdater(element, loaded) );
1831                                         }
1832                                     }
1833                                 };
1834
1835                                 //System.out.println("NODE REQUEST: " + element);
1836                                 graph.syncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
1837                                     @Override
1838                                     public void execute(AsyncReadGraph graph, IElement e) {
1839                                     }
1840
1841                                     @Override
1842                                     public void exception(AsyncReadGraph graph, Throwable throwable) {
1843                                         error(throwable);
1844                                     }
1845                                 });
1846
1847                             } else {
1848 //                                warning("Diagram elements must be either elements or connections, "
1849 //                                        + NameUtils.getSafeName(g, element) + " is neither",
1850 //                                        new AssumptionException(""));
1851                             }
1852                         }
1853                         break;
1854                     }
1855                     case REMOVED: {
1856                         IElement e = getMappedElement(element);
1857                         if (DebugPolicy.DEBUG_NODE_LOAD)
1858                             graph.syncRequest(new ReadRequest() {
1859                                 @Override
1860                                 public void run(ReadGraph graph) throws DatabaseException {
1861                                     System.out.println("    EXTERNALLY REMOVED ELEMENT: "
1862                                             + NameUtils.getSafeName(graph, element) + " ("
1863                                             + element.getResourceId() + ")");
1864                                 }
1865                             });
1866                         if (e != null) {
1867                             removedElements.add(e);
1868                         }
1869                         break;
1870                     }
1871                     default:
1872                 }
1873             }
1874         }
1875
1876         void gatherChangedConnectionParts(Map<?, Change> changes) {
1877             for (Map.Entry<?, Change> entry : changes.entrySet()) {
1878                 Object part = entry.getKey();
1879                 Change change = entry.getValue();
1880
1881                 switch (change) {
1882                     case ADDED: {
1883                         synchronized (GraphToDiagramUpdater.this) {
1884                             Resource connection = content.partToConnection.get(part);
1885                             assert connection != null;
1886
1887                             IElement ce = getMappedElement(connection);
1888                             if (ce == null)
1889                                 ce = addedElementMap.get(connection);
1890
1891                             if (ce != null)
1892                                 markConnectionChanged(ce);
1893                             break;
1894                         }
1895                     }
1896                     case REMOVED: {
1897                         if (lastContent == null)
1898                             break;
1899                         Resource connection = lastContent.partToConnection.get(part);
1900                         if (connection != null && content.connectionSet.contains(connection)) {
1901                             markConnectionChanged(connection);
1902                         }
1903                         break;
1904                     }
1905                     default:
1906                 }
1907             }
1908         }
1909
1910         void markConnectionChanged(Resource connection) {
1911 //            System.out.println("markConnectionChanged");
1912             ConnectionEntityImpl ce = getMappedConnection(connection);
1913             if (ce != null) {
1914                 markConnectionChanged(ce);
1915                 return;
1916             }
1917             error("WARNING: marking connection entity " + connection
1918                     + " changed, but the connection was not previously mapped",
1919                     new Exception("created exception to get a stack trace"));
1920         }
1921
1922         void markConnectionChanged(IElement connection) {
1923             ConnectionEntityImpl entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
1924             if (entity != null)
1925                 markConnectionChanged(entity);
1926         }
1927
1928         void markConnectionChanged(ConnectionEntityImpl ce) {
1929             if (!changedConnectionEntities.containsKey(ce)) {
1930                 changedConnectionEntities.put(ce, new ConnectionData(ce));
1931             }
1932         }
1933
1934         void processConnections() {
1935             // Find added/removed connection segments/branch points
1936             // in order to find all changed connection entities.
1937             gatherChangedConnectionParts(changes.connectionSegments);
1938             gatherChangedConnectionParts(changes.branchPoints);
1939
1940             // Find removed connection entities
1941             for (Map.Entry<Resource, Change> entry : changes.connections.entrySet()) {
1942                 Resource ce = entry.getKey();
1943                 Change change = entry.getValue();
1944
1945                 switch (change) {
1946                     case REMOVED: {
1947                         removedConnectionEntities.add(ce);
1948                     }
1949                     default:
1950                 }
1951             }
1952
1953             // Generate update data of changed connection entities.
1954             // This ConnectionData will be applied in the canvas thread
1955             // diagram updater.
1956             for (ConnectionData cd : changedConnectionEntities.values()) {
1957                 for (Object part : content.connectionToParts.getValuesUnsafe(cd.impl.connection)) {
1958                     if (part instanceof Resource) {
1959                         cd.branchPoints.add((Resource) part);
1960                     } else if (part instanceof EdgeResource) {
1961                         cd.segments.add((EdgeResource) part);
1962                     }
1963                 }
1964             }
1965         }
1966
1967         void processRouteGraphConnections(ReadGraph graph) throws DatabaseException {
1968             for (Map.Entry<Resource, Change> entry : changes.routeGraphConnections.entrySet()) {
1969                 final Resource connection = entry.getKey();
1970
1971                 Change change = entry.getValue();
1972                 switch (change) {
1973                     case ADDED: {
1974                         IElement mappedElement = getMappedElement(connection);
1975                         if (mappedElement != null)
1976                             continue;
1977
1978                         Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1979
1980                             boolean firstTime = true;
1981
1982                             @Override
1983                             public String toString() {
1984                                 return "processRouteGraphConnections " + connection;
1985                             }
1986                             @Override
1987                             public void execute(IElement loaded) {
1988                                 // Invoked when the element has been loaded.
1989                                 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1990                                     System.out.println("ROUTE GRAPH CONNECTION LoadListener for " + loaded);
1991
1992                                 if (loaded == null) {
1993                                     disposeListener();
1994                                     return;
1995                                 }
1996
1997                                 if(firstTime) {
1998                                     if (DebugPolicy.DEBUG_NODE_LOAD)
1999                                         System.out.println("MAPPING ADDED ROUTE GRAPH CONNECTION: " + connection + " -> " + loaded);
2000                                     mapElement(connection, loaded);
2001                                     synchronized (GraphToDiagramUpdater.this) {
2002                                         addedElements.add(loaded);
2003                                         addedElementMap.put(connection, loaded);
2004                                         addedRouteGraphConnectionMap.put(connection, loaded);
2005                                     }
2006                                     firstTime = false;
2007                                 }
2008
2009                                 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2010
2011                                 // Logic for disposing listener
2012                                 if (!previousContent.routeGraphConnectionSet.contains(data)) {
2013                                     if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2014                                         System.out.println("ROUTE GRAPH CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
2015                                     disposeListener();
2016                                     return;
2017                                 }
2018
2019                                 if (addedElementMap.containsKey(data)) {
2020                                     // This element was just loaded, in
2021                                     // which case its hints need to
2022                                     // uploaded to the real mapped
2023                                     // element immediately.
2024                                     IElement mappedElement = getMappedElement(data);
2025                                     if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2026                                         System.out.println("LOADED ADDED ROUTE GRAPH CONNECTION, currently mapped connection: " + mappedElement);
2027                                     if (mappedElement instanceof Element) {
2028                                         if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
2029                                             System.out.println("  mapped hints: " + mappedElement.getHints());
2030                                             System.out.println("  loaded hints: " + loaded.getHints());
2031                                         }
2032                                         updateMappedElement((Element) mappedElement, loaded);
2033                                     }
2034                                 } else {
2035                                     // This element was already loaded.
2036                                     // Just schedule an update some time
2037                                     // in the future.
2038                                     if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2039                                         System.out.println("PREVIOUSLY LOADED ROUTE GRAPH CONNECTION UPDATED, scheduling update into the future: " + connection);
2040
2041                                     Set<Object> dirtyNodes = new THashSet<Object>(4);
2042                                     IElement mappedElement = getMappedElement(connection);
2043                                     ConnectionEntity ce = mappedElement.getHint(ElementHints.KEY_CONNECTION_ENTITY);
2044                                     if (ce != null) {
2045                                         for (Connection conn : ce.getTerminalConnections(null)) {
2046                                             Object o = conn.node.getHint(ElementHints.KEY_OBJECT);
2047                                             if (o != null) {
2048                                                 dirtyNodes.add(o);
2049                                                 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2050                                                     System.out.println("Marked connectivity dirty for node: " + conn.node);
2051                                             }
2052                                         }
2053                                     }
2054
2055                                     offerGraphUpdate( routeGraphConnectionUpdater(connection, loaded, dirtyNodes) );
2056                                 }
2057                             }
2058                         };
2059
2060                         graph.syncRequest(new ConnectionRequest(canvas, diagram, connection, errorHandler, loadListener), new Procedure<IElement>() {
2061                             @Override
2062                             public void execute(final IElement e) {
2063                             }
2064                             @Override
2065                             public void exception(Throwable throwable) {
2066                                 error(throwable);
2067                             }
2068                         });
2069                         break;
2070                     }
2071                     case REMOVED: {
2072                         IElement e = getMappedElement(connection);
2073                         if (e != null)
2074                             removedRouteGraphConnections.add(e);
2075                         break;
2076                     }
2077                     default:
2078                 }
2079             }
2080         }
2081
2082         ConnectionEntityImpl getConnectionEntity(Object connectionPart) {
2083             Resource connection = content.partToConnection.get(connectionPart);
2084             assert connection != null;
2085             ConnectionEntityImpl ce = addedConnectionEntities.get(connection);
2086             if (ce != null)
2087                 return ce;
2088             return assertMappedConnection(connection);
2089         }
2090
2091         void processBranchPoints(ReadGraph graph) throws DatabaseException {
2092             for (Map.Entry<Resource, Change> entry : changes.branchPoints.entrySet()) {
2093
2094                 final Resource element = entry.getKey();
2095                 Change change = entry.getValue();
2096
2097                 switch (change) {
2098                     case ADDED: {
2099                         IElement mappedElement = getMappedElement(element);
2100                         if (mappedElement == null) {
2101                             if (DebugPolicy.DEBUG_NODE_LOAD)
2102                                 graph.syncRequest(new ReadRequest() {
2103                                     @Override
2104                                     public void run(ReadGraph graph) throws DatabaseException {
2105                                         System.out.println("    EXTERNALLY ADDED BRANCH POINT: "
2106                                                 + NameUtils.getSafeName(graph, element) + " ("
2107                                                 + element.getResourceId() + ")");
2108                                     }
2109                                 });
2110
2111                             Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
2112
2113                                 boolean firstTime = true;
2114
2115                                 @Override
2116                                 public String toString() {
2117                                     return "processBranchPoints for " + element;
2118                                 }
2119                                 @Override
2120                                 public void execute(IElement loaded) {
2121                                     // Invoked when the element has been loaded.
2122                                     if (DebugPolicy.DEBUG_NODE_LISTENER)
2123                                         System.out.println("BRANCH POINT LoadListener for " + loaded);
2124
2125                                     if (loaded == null) {
2126                                         disposeListener();
2127                                         return;
2128                                     }
2129
2130                                     if (firstTime) {
2131
2132                                         mapElement(element, loaded);
2133                                         synchronized (GraphToDiagramUpdater.this) {
2134                                             addedBranchPoints.add(loaded);
2135                                             addedElementMap.put(element, loaded);
2136                                             ConnectionEntityImpl ce = getConnectionEntity(element);
2137                                             loaded.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
2138                                             loaded.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
2139                                         }
2140
2141                                         firstTime = false;
2142
2143                                     }
2144
2145                                     Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2146                                     if (addedElementMap.containsKey(data)) {
2147                                         // This element was just loaded, in
2148                                         // which case its hints need to
2149                                         // uploaded to the real mapped
2150                                         // element immediately.
2151                                         IElement mappedElement = getMappedElement(data);
2152                                         if (DebugPolicy.DEBUG_NODE_LISTENER)
2153                                             System.out.println("LOADED ADDED BRANCH POINT, currently mapped element: " + mappedElement);
2154                                         if (mappedElement != null && (mappedElement instanceof Element)) {
2155                                             if (DebugPolicy.DEBUG_NODE_LISTENER) {
2156                                                 System.out.println("  mapped hints: " + mappedElement.getHints());
2157                                                 System.out.println("  loaded hints: " + loaded.getHints());
2158                                             }
2159                                             updateMappedElement((Element) mappedElement, loaded);
2160                                         }
2161                                     } else {
2162                                         // This element was already loaded.
2163                                         // Just schedule an update some time
2164                                         // in the future.
2165                                         if (DebugPolicy.DEBUG_NODE_LISTENER)
2166                                             System.out.println("PREVIOUSLY LOADED BRANCH POINT UPDATED, scheduling update into the future");
2167                                         offerGraphUpdate( nodeUpdater(element, loaded) );
2168                                     }
2169                                 }
2170                             };
2171
2172                             graph.syncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
2173                                 @Override
2174                                 public void execute(AsyncReadGraph graph, IElement e) {
2175                                 }
2176
2177                                 @Override
2178                                 public void exception(AsyncReadGraph graph, Throwable throwable) {
2179                                     error(throwable);
2180                                 }
2181                             });
2182                         }
2183                         break;
2184                     }
2185                     case REMOVED: {
2186                         IElement e = getMappedElement(element);
2187                         if (DebugPolicy.DEBUG_NODE_LOAD)
2188                             graph.syncRequest(new ReadRequest() {
2189                                 @Override
2190                                 public void run(ReadGraph graph) throws DatabaseException {
2191                                     System.out.println("    EXTERNALLY REMOVED BRANCH POINT: "
2192                                             + NameUtils.getSafeName(graph, element) + " ("
2193                                             + element.getResourceId() + ")");
2194                                 }
2195                             });
2196                         if (e != null) {
2197                             removedBranchPoints.add(e);
2198                         }
2199                         break;
2200                     }
2201                     default:
2202                 }
2203             }
2204         }
2205
2206         void processConnectionSegments(ReadGraph graph) throws DatabaseException {
2207             ConnectionSegmentAdapter adapter = connectionSegmentAdapter;
2208
2209             for (Map.Entry<EdgeResource, Change> entry : changes.connectionSegments.entrySet()) {
2210                 final EdgeResource seg = entry.getKey();
2211                 Change change = entry.getValue();
2212
2213                 switch (change) {
2214                     case ADDED: {
2215                         IElement mappedElement = getMappedElement(seg);
2216                         if (mappedElement == null) {
2217                             if (DebugPolicy.DEBUG_EDGE_LOAD)
2218                                 graph.syncRequest(new ReadRequest() {
2219                                     @Override
2220                                     public void run(ReadGraph graph) throws DatabaseException {
2221                                         System.out.println("    EXTERNALLY ADDED CONNECTION SEGMENT: " + seg.toString()
2222                                                 + " - " + seg.toString(graph));
2223                                     }
2224                                 });
2225
2226                             graph.syncRequest(new EdgeRequest(canvas, errorHandler, canvasListenerSupport, diagram, adapter, seg), new AsyncProcedure<IElement>() {
2227                                 @Override
2228                                 public void execute(AsyncReadGraph graph, IElement e) {
2229                                     if (DebugPolicy.DEBUG_EDGE_LOAD)
2230                                         System.out.println("ADDED EDGE LOADED: " + e);
2231                                     if (e != null) {
2232                                         mapElement(seg, e);
2233                                         synchronized (GraphToDiagramUpdater.this) {
2234                                             addedConnectionSegments.add(e);
2235                                             addedElementMap.put(seg, e);
2236                                             ConnectionEntityImpl ce = getConnectionEntity(seg);
2237                                             e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
2238                                             e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
2239                                         }
2240                                     }
2241                                 }
2242
2243                                 @Override
2244                                 public void exception(AsyncReadGraph graph, Throwable throwable) {
2245                                     error(throwable);
2246                                 }
2247                             });
2248                         }
2249                         break;
2250                     }
2251                     case REMOVED: {
2252                         final IElement e = getMappedElement(seg);
2253                         if (DebugPolicy.DEBUG_EDGE_LOAD)
2254                             graph.syncRequest(new ReadRequest() {
2255                                 @Override
2256                                 public void run(ReadGraph graph) throws DatabaseException {
2257                                     System.out.println("    EXTERNALLY REMOVED CONNECTION SEGMENT: " + seg.toString() + " - "
2258                                             + seg.toString(graph) + " -> " + e);
2259                                 }
2260                             });
2261                         if (e != null) {
2262                             removedConnectionSegments.add(e);
2263                         }
2264                         break;
2265                     }
2266                     default:
2267                 }
2268             }
2269         }
2270
2271         void executeDeferredLoaders(ReadGraph graph) throws DatabaseException {
2272             // The rest of the diagram loading passes
2273             Deque<IElement> q1 = new ArrayDeque<IElement>();
2274             Deque<IElement> q2 = new ArrayDeque<IElement>();
2275             collectElementLoaders(q1, addedElements);
2276             while (!q1.isEmpty()) {
2277                 //System.out.println("DEFFERED LOADERS: " + q1);
2278                 for (IElement e : q1) {
2279                     ElementLoader loader = e.removeHint(DiagramModelHints.KEY_ELEMENT_LOADER);
2280                     //System.out.println("EXECUTING DEFFERED LOADER: " + loader);
2281                     loader.load(graph, diagram, e);
2282                 }
2283
2284                 collectElementLoaders(q2, q1);
2285                 Deque<IElement> qt = q1;
2286                 q1 = q2;
2287                 q2 = qt;
2288                 q2.clear();
2289             }
2290         }
2291
2292         private void collectElementLoaders(Queue<IElement> queue, Collection<IElement> cs) {
2293             for (IElement e : cs) {
2294                 ElementLoader loader = e.getHint(DiagramModelHints.KEY_ELEMENT_LOADER);
2295                 if (loader != null)
2296                     queue.add(e);
2297             }
2298         }
2299
2300         public void process(ReadGraph graph) throws DatabaseException {
2301             // No changes? Do nothing.
2302             if (changes.isEmpty())
2303                 return;
2304
2305             // NOTE: This order is important.
2306             Object task = Timing.BEGIN("processNodesConnections");
2307             //System.out.println("---- PROCESS NODES & CONNECTIONS BEGIN");
2308             if (!changes.elements.isEmpty()) {
2309                 graph.syncRequest(new ReadRequest() {
2310                     @Override
2311                     public void run(ReadGraph graph) throws DatabaseException {
2312                         processNodes(graph);
2313                     }
2314                     @Override
2315                     public String toString() {
2316                         return "processNodes";
2317                     }
2318                 });
2319             }
2320             //System.out.println("---- PROCESS NODES & CONNECTIONS END");
2321
2322             processConnections();
2323
2324             //System.out.println("---- PROCESS BRANCH POINTS BEGIN");
2325             if (!changes.branchPoints.isEmpty()) {
2326                 graph.syncRequest(new ReadRequest() {
2327                     @Override
2328                     public void run(ReadGraph graph) throws DatabaseException {
2329                         processBranchPoints(graph);
2330                     }
2331                     @Override
2332                     public String toString() {
2333                         return "processBranchPoints";
2334                     }
2335                 });
2336             }
2337             //System.out.println("---- PROCESS BRANCH POINTS END");
2338
2339             Timing.END(task);
2340             task = Timing.BEGIN("processConnectionSegments");
2341
2342             //System.out.println("---- PROCESS CONNECTION SEGMENTS BEGIN");
2343             if (!changes.connectionSegments.isEmpty()) {
2344                 graph.syncRequest(new ReadRequest() {
2345                     @Override
2346                     public void run(ReadGraph graph) throws DatabaseException {
2347                         processConnectionSegments(graph);
2348                     }
2349                     @Override
2350                     public String toString() {
2351                         return "processConnectionSegments";
2352                     }
2353                 });
2354             }
2355             //System.out.println("---- PROCESS CONNECTION SEGMENTS END");
2356
2357             Timing.END(task);
2358
2359             task = Timing.BEGIN("processRouteGraphConnections");
2360             if (!changes.routeGraphConnections.isEmpty()) {
2361                 graph.syncRequest(new ReadRequest() {
2362                     @Override
2363                     public void run(ReadGraph graph) throws DatabaseException {
2364                         processRouteGraphConnections(graph);
2365                     }
2366                     @Override
2367                     public String toString() {
2368                         return "processRouteGraphConnections";
2369                     }
2370                 });
2371             }
2372             Timing.END(task);
2373
2374             //System.out.println("---- AFTER LOADING");
2375             //for (IElement e : addedElements)
2376             //    System.out.println("    ADDED ELEMENT: " + e);
2377             //for (IElement e : addedBranchPoints)
2378             //    System.out.println("    ADDED BRANCH POINTS: " + e);
2379
2380             task = Timing.BEGIN("executeDeferredLoaders");
2381             executeDeferredLoaders(graph);
2382             Timing.END(task);
2383         }
2384
2385         public boolean isEmpty() {
2386             return addedElements.isEmpty() && removedElements.isEmpty()
2387             && addedConnectionSegments.isEmpty() && removedConnectionSegments.isEmpty()
2388             && addedBranchPoints.isEmpty() && removedBranchPoints.isEmpty()
2389             && addedConnectionEntities.isEmpty() && removedConnectionEntities.isEmpty()
2390             && addedRouteGraphConnectionMap.isEmpty() && removedRouteGraphConnections.isEmpty()
2391             && !changes.elementOrderChanged;
2392         }
2393
2394         class DefaultConnectionSegmentAdapter implements ConnectionSegmentAdapter {
2395
2396             @Override
2397             public void getClass(AsyncReadGraph graph, EdgeResource edge, ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, IDiagram diagram, final AsyncProcedure<ElementClass> procedure) {
2398                 if (info.connectionType != null) {
2399                     NodeClassRequest request = new NodeClassRequest(canvas, diagram, info.connectionType, true);
2400                     graph.asyncRequest(request, new CacheListener<ElementClass>(listenerSupport));
2401                     graph.asyncRequest(request, procedure);
2402                 } else {
2403                     procedure.execute(graph, null);
2404                 }
2405             }
2406
2407             @Override
2408             public void load(AsyncReadGraph graph, final EdgeResource edge, final ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, final IDiagram diagram, final IElement element) {
2409                 graph.asyncRequest(new Read<IElement>() {
2410                     @Override
2411                     public IElement perform(ReadGraph graph) throws DatabaseException {
2412                         //ITask task = ThreadLogger.getInstance().begin("LoadSegment");
2413                         syncLoad(graph, edge, info, diagram, element);
2414                         //task.finish();
2415                         return element;
2416                     }
2417                     @Override
2418                     public String toString() {
2419                         return "defaultConnectionSegmentAdapter";
2420                     }
2421                 }, new DisposableListener<IElement>(listenerSupport) {
2422                     
2423                     @Override
2424                     public String toString() {
2425                         return "DefaultConnectionSegmentAdapter listener for " + edge;
2426                     }
2427                     
2428                     @Override
2429                     public void execute(IElement loaded) {
2430                         // Invoked when the element has been loaded.
2431                         if (DebugPolicy.DEBUG_EDGE_LISTENER)
2432                             System.out.println("EDGE LoadListener for " + loaded);
2433
2434                         if (loaded == null) {
2435                             disposeListener();
2436                             return;
2437                         }
2438
2439                         Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2440                         if (addedElementMap.containsKey(data)) {
2441                             // This element was just loaded, in
2442                             // which case its hints need to
2443                             // uploaded to the real mapped
2444                             // element immediately.
2445                             IElement mappedElement = getMappedElement(data);
2446                             if (DebugPolicy.DEBUG_EDGE_LISTENER)
2447                                 System.out.println("LOADED ADDED EDGE, currently mapped element: " + mappedElement);
2448                             if (mappedElement != null && (mappedElement instanceof Element)) {
2449                                 if (DebugPolicy.DEBUG_EDGE_LISTENER) {
2450                                     System.out.println("  mapped hints: " + mappedElement.getHints());
2451                                     System.out.println("  loaded hints: " + loaded.getHints());
2452                                 }
2453                                 updateMappedElement((Element) mappedElement, loaded);
2454                             }
2455                         } else {
2456                             // This element was already loaded.
2457                             // Just schedule an update some time
2458                             // in the future.
2459                             if (DebugPolicy.DEBUG_EDGE_LISTENER)
2460                                 System.out.println("PREVIOUSLY LOADED EDGE UPDATED, scheduling update into the future");
2461                             offerGraphUpdate( edgeUpdater(element, loaded) );
2462                         }
2463                     }
2464                 });
2465             }
2466
2467             void syncLoad(ReadGraph graph, EdgeResource connectionSegment, ConnectionInfo info, IDiagram diagram, IElement element) throws DatabaseException {
2468                 // Check that at least some data exists before continuing further.
2469                 if (!graph.hasStatement(connectionSegment.first()) && !graph.hasStatement(connectionSegment.second())) {
2470                     return;
2471                 }
2472
2473                 // Validate that both ends of the segment are
2474                 // part of the same connection before loading.
2475                 // This will happen for connections that are
2476                 // modified through splitting and joining of
2477                 // connection segments.
2478                 Resource c = ConnectionUtil.tryGetConnection(graph, connectionSegment);
2479                 if (c == null) {
2480                     // Ok, this segment is somehow invalid. Just don't load it.
2481                     if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2482                         System.out.println("Skipping edge " + connectionSegment + ". Both segment ends are not part of the same connection.");
2483                     return;
2484                 }
2485
2486                 if (!info.isValid()) {
2487                     // This edge must be somehow invalid, don't proceed with loading.
2488                     if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2489                         warning("Cannot load edge " + connectionSegment + ". ConnectionInfo " + info + " is invalid.", new DebugException("execution trace"));
2490                     return;
2491                 }
2492
2493                 Element edge = (Element) element;
2494                 edge.setHint(ElementHints.KEY_OBJECT, connectionSegment);
2495
2496                 // connectionSegment resources may currently be in a different
2497                 // order than ConnectionInfo.firstEnd/secondEnd. Therefore the
2498                 // segment ends must be resolved here.
2499                 ConnectionSegmentEnd firstEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.first());
2500                 ConnectionSegmentEnd secondEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.second());
2501                 if (firstEnd == null || secondEnd == null) {
2502                     if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2503                         warning("End attachments for edge " + connectionSegment + " are unresolved: (" + firstEnd + "," + secondEnd + ")", new DebugException("execution trace"));
2504                     return;
2505                 }
2506
2507                 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2508                     System.out.println("CONNECTION INFO: " + connectionSegment + " - " + info);
2509                 DesignatedTerminal firstTerminal = DiagramGraphUtil.findDesignatedTerminal(
2510                         graph, diagram, connectionSegment.first(), firstEnd);
2511                 DesignatedTerminal secondTerminal = DiagramGraphUtil.findDesignatedTerminal(
2512                         graph, diagram, connectionSegment.second(), secondEnd);
2513
2514                 // Edges must be connected at both ends in order for edge loading to succeed.
2515                 String err = validateConnectivity(graph, connectionSegment, firstTerminal, secondTerminal);
2516                 if (err != null) {
2517                     // Stop loading edge if the connectivity cannot be completely resolved.
2518                     if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2519                         warning(err, null);
2520                     return;
2521                 }
2522
2523                 graph.syncRequest(new AsyncReadRequest() {
2524                     @Override
2525                     public void run(AsyncReadGraph graph) {
2526                         // NOTICE: Layer information is loaded from the connection entity resource
2527                         // that is shared by all segments of the same connection.
2528                         ElementFactoryUtil.loadLayersForElement(graph, layerManager, diagram, edge, info.connection,
2529                                 new AsyncProcedureAdapter<IElement>() {
2530                             @Override
2531                             public void exception(AsyncReadGraph graph, Throwable t) {
2532                                 error("failed to load layers for connection segment", t);
2533                             }
2534                         });
2535                     }
2536                 });
2537
2538                 edge.setHintWithoutNotification(KEY_CONNECTION_BEGIN_PLACEHOLDER, new PlaceholderConnection(
2539                         EdgeEnd.Begin,
2540                         firstTerminal.element.getHint(ElementHints.KEY_OBJECT),
2541                         firstTerminal.terminal));
2542                 edge.setHintWithoutNotification(KEY_CONNECTION_END_PLACEHOLDER, new PlaceholderConnection(
2543                         EdgeEnd.End,
2544                         secondTerminal.element.getHint(ElementHints.KEY_OBJECT),
2545                         secondTerminal.terminal));
2546
2547                 IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);
2548                 if (modelingRules != null) {
2549                     ConnectionVisualsLoader loader = diagram.getHint(DiagramModelHints.KEY_CONNECTION_VISUALS_LOADER);
2550                     if (loader != null)
2551                         loader.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
2552                     else
2553                         DiagramGraphUtil.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
2554                 }
2555             }
2556
2557             private String validateConnectivity(ReadGraph graph, EdgeResource edge,
2558                     DesignatedTerminal firstTerminal,
2559                     DesignatedTerminal secondTerminal)
2560             throws DatabaseException {
2561                 boolean firstLoose = firstTerminal == null;
2562                 boolean secondLoose = secondTerminal == null;
2563                 boolean stray = firstLoose && secondLoose;
2564                 if (firstTerminal == null || secondTerminal == null) {
2565                     StringBuilder sb = new StringBuilder();
2566                     sb.append("encountered ");
2567                     sb.append(stray ? "stray" : "loose");
2568                     sb.append(" connection segment, ");
2569                     if (firstLoose)
2570                         sb.append("first ");
2571                     if (stray)
2572                         sb.append("and ");
2573                     if (secondLoose)
2574                         sb.append("second ");
2575                     sb.append("end disconnected: ");
2576                     sb.append(edge.toString(graph));
2577                     sb.append(" - ");
2578                     sb.append(edge.toString());
2579                     return sb.toString();
2580                 }
2581                 return null;
2582             }
2583
2584         }
2585
2586         ConnectionSegmentAdapter connectionSegmentAdapter = new DefaultConnectionSegmentAdapter();
2587
2588     }
2589
2590     private static final Double DIAGRAM_UPDATE_DIAGRAM_PRIORITY = 1d;
2591     private static final Double DIAGRAM_UPDATE_NODE_PRIORITY = 2d;
2592     private static final Double DIAGRAM_UPDATE_CONNECTION_PRIORITY = 3d;
2593     private static final Double DIAGRAM_UPDATE_EDGE_PRIORITY = 4d;
2594
2595     interface DiagramUpdater extends Runnable {
2596         Double getPriority();
2597
2598         Comparator<DiagramUpdater> DIAGRAM_UPDATER_COMPARATOR = new Comparator<DiagramUpdater>() {
2599             @Override
2600             public int compare(DiagramUpdater o1, DiagramUpdater o2) {
2601                 return o1.getPriority().compareTo(o2.getPriority());
2602             }
2603         };
2604     }
2605
2606     interface GraphUpdateReactor {
2607         DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException;
2608     }
2609
2610     static abstract class AbstractDiagramUpdater implements DiagramUpdater, GraphUpdateReactor {
2611         protected final Double priority;
2612         protected final String runnerName;
2613
2614         public AbstractDiagramUpdater(Double priority, String runnerName) {
2615             if (priority == null)
2616                 throw new NullPointerException("null priority");
2617             if (runnerName == null)
2618                 throw new NullPointerException("null runner name");
2619             this.priority = priority;
2620             this.runnerName = runnerName;
2621         }
2622
2623         @Override
2624         public Double getPriority() {
2625             return priority;
2626         }
2627
2628         @Override
2629         public AbstractDiagramUpdater graphUpdate(ReadGraph graph) {
2630             return this;
2631         }
2632
2633         @Override
2634         public void run() {
2635             Object task = Timing.BEGIN(runnerName);
2636             forDiagram();
2637             Timing.END(task);
2638         }
2639
2640         protected void forDiagram() {
2641         }
2642
2643         @Override
2644         public String toString() {
2645             return runnerName + "@" + System.identityHashCode(this) + " [" + priority + "]";
2646         }
2647     }
2648
2649     /**
2650      * @param content the new contents of the diagram, must not be
2651      *        <code>null</code>.
2652      */
2653     private GraphUpdateReactor diagramGraphUpdater(final DiagramContents content) {
2654         if (content == null)
2655             throw new NullPointerException("null diagram content");
2656
2657         return new GraphUpdateReactor() {
2658             @Override
2659             public String toString() {
2660                 return "DiagramGraphUpdater@" + System.identityHashCode(this);
2661             }
2662
2663             @Override
2664             public DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException {
2665                 // Never do anything here if the canvas has already been disposed.
2666                 if (!GraphToDiagramSynchronizer.this.isAlive())
2667                     return null;
2668
2669                 // We must be prepared for the following changes in the diagram graph
2670                 // model:
2671                 // - the diagram has been completely removed
2672                 // - elements have been added
2673                 // - elements have been removed
2674                 //
2675                 // Element-specific changes are handled by the element query listeners:
2676                 // - elements have been modified
2677                 // - element position has changed
2678                 // - element class (e.g. image) has changed
2679
2680                 diagramUpdateLock.lock();
2681                 try {
2682                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2683                         System.out.println("In diagramGraphUpdater:");
2684
2685                     // Find out what has changed since the last query.
2686                     Object task = Timing.BEGIN("diagramContentDifference");
2687                     DiagramContents lastContent = previousContent;
2688                     DiagramContentChanges changes = content.differenceFrom(previousContent);
2689                     previousContent = content;
2690                     Timing.END(task);
2691                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2692                         System.out.println("  changes: " + changes);
2693
2694                     // Bail out if there are no changes to react to.
2695                     if (changes.isEmpty())
2696                         return null;
2697
2698                     // Load everything that needs to be loaded from the graph,
2699                     // but don't update the UI model in this thread yet.
2700                     task = Timing.BEGIN("updater.process");
2701                     GraphToDiagramUpdater updater = new GraphToDiagramUpdater(lastContent, content, changes);
2702                     GraphToDiagramSynchronizer.this.currentUpdater = updater;
2703                     try {
2704                         updater.process(graph);
2705                     } finally {
2706                         GraphToDiagramSynchronizer.this.currentUpdater = null;
2707                     }
2708                     Timing.END(task);
2709
2710                     if (updater.isEmpty())
2711                         return null;
2712
2713                     // Return an updater that will update the UI run-time model.
2714                     return diagramUpdater(updater);
2715                 } finally {
2716                     diagramUpdateLock.unlock();
2717                 }
2718             }
2719         };
2720     }
2721
2722     DiagramUpdater diagramUpdater(final GraphToDiagramUpdater updater) {
2723         return new AbstractDiagramUpdater(DIAGRAM_UPDATE_DIAGRAM_PRIORITY, "updateDiagram") {
2724             @Override
2725             protected void forDiagram() {
2726                 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2727                     System.out.println("running diagram updater: " + this);
2728
2729                 // DiagramUtils.testDiagram(diagram);
2730                 Set<IElement> dirty = new HashSet<IElement>();
2731
2732                 Object task2 = Timing.BEGIN("Preprocess connection changes");
2733                 Map<ConnectionEntity, ConnectionChildren> connectionChangeData = new HashMap<ConnectionEntity, ConnectionChildren>(updater.changedConnectionEntities.size());
2734                 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2735                     connectionChangeData.put(cd.impl, cd.impl.getConnectionChildren());
2736                 }
2737                 Timing.END(task2);
2738
2739                 task2 = Timing.BEGIN("removeRouteGraphConnections");
2740                 for (IElement removedRouteGraphConnection : updater.removedRouteGraphConnections) {
2741                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2742                         System.out.println("removing route graph connection: " + removedRouteGraphConnection);
2743
2744                     ConnectionEntity ce = removedRouteGraphConnection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
2745                     if (ce == null)
2746                         continue;
2747                     Object connectionData = ElementUtils.getObject(removedRouteGraphConnection);
2748                     tempConnections.clear();
2749                     for (Connection conn : ce.getTerminalConnections(tempConnections)) {
2750                         ((Element) conn.node).removeHintWithoutNotification(new TerminalKeyOf(conn.terminal, connectionData, Connection.class));
2751                         // To be sure the view will be up-to-date, mark the node
2752                         // connected to the removed connection dirty.
2753                         dirty.add(conn.node);
2754                     }
2755                 }
2756                 Timing.END(task2);
2757
2758                 task2 = Timing.BEGIN("removeBranchPoints");
2759                 for (IElement removed : updater.removedBranchPoints) {
2760                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2761                         System.out.println("removing branch point: " + removed);
2762
2763                     unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2764                     removeNodeTopologyHints((Element) removed);
2765
2766                     IElement connection = ElementUtils.getParent(removed);
2767                     if (connection != null) {
2768                         dirty.add(connection);
2769                     }
2770                 }
2771                 Timing.END(task2);
2772
2773                 task2 = Timing.BEGIN("removeConnectionSegments");
2774                 for (IElement removed : updater.removedConnectionSegments) {
2775                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2776                         System.out.println("removing segment: " + removed);
2777
2778                     unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2779                     removeEdgeTopologyHints((Element) removed);
2780
2781                     IElement connection = ElementUtils.getParent(removed);
2782                     if (connection != null) {
2783                         dirty.add(connection);
2784                     }
2785                 }
2786                 Timing.END(task2);
2787
2788                 task2 = Timing.BEGIN("removeElements");
2789                 for (IElement removed : updater.removedElements) {
2790                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2791                         System.out.println("removing element: " + removed);
2792
2793                     removed.setHint(KEY_REMOVE_RELATIONSHIPS, Boolean.TRUE);
2794                     if (diagram.containsElement(removed)) {
2795                         diagram.removeElement(removed);
2796                     }
2797                     unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2798                     removeNodeTopologyHints((Element) removed);
2799
2800                     // No use marking removed elements dirty.
2801                     dirty.remove(removed);
2802                 }
2803                 Timing.END(task2);
2804
2805                 // TODO: get rid of this
2806                 task2 = Timing.BEGIN("removeConnectionEntities");
2807                 for (Resource ce : updater.removedConnectionEntities) {
2808                     unmapConnection(ce);
2809                 }
2810                 Timing.END(task2);
2811
2812                 task2 = Timing.BEGIN("setConnectionData");
2813                 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2814                     cd.impl.setData(cd.segments, cd.branchPoints);
2815                 }
2816                 Timing.END(task2);
2817
2818                 // TODO: get rid of this
2819                 task2 = Timing.BEGIN("addConnectionEntities");
2820                 for (Map.Entry<Resource, ConnectionEntityImpl> entry : updater.addedConnectionEntities
2821                         .entrySet()) {
2822                     mapConnection(entry.getKey(), entry.getValue());
2823                 }
2824                 Timing.END(task2);
2825
2826                 task2 = Timing.BEGIN("addBranchPoints");
2827                 for (IElement added : updater.addedBranchPoints) {
2828                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2829                         System.out.println("adding branch point: " + added);
2830
2831                     mapElement(ElementUtils.getObject(added), added);
2832
2833                     IElement connection = ElementUtils.getParent(added);
2834                     if (connection != null) {
2835                         dirty.add(connection);
2836                     }
2837                 }
2838                 Timing.END(task2);
2839
2840                 // Add new elements at end of diagram, element order will be synchronized later.
2841                 task2 = Timing.BEGIN("addElements");
2842                 for(Resource r : updater.content.elements) {
2843                     IElement added = updater.addedElementMap.get(r);
2844                     if(added != null) {
2845                         if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2846                             System.out.println("adding element: " + added);
2847
2848                         //Object task3 = BEGIN("mapElement " + added);
2849                         Object task3 = Timing.BEGIN("mapElement");
2850                         mapElement(added.getHint(ElementHints.KEY_OBJECT), added);
2851                         Timing.END(task3);
2852
2853                         //task3 = BEGIN("addElement " + added);
2854                         task3 = Timing.BEGIN("addElement");
2855                         //System.out.println("diagram.addElement: " + added + " - " + diagram);
2856                         diagram.addElement(added);
2857                         dirty.add(added);
2858                         Timing.END(task3);
2859                     }
2860                 }
2861                 Timing.END(task2);
2862
2863                 // We've ensured that all nodes must have been and
2864                 // mapped before reaching this.
2865                 task2 = Timing.BEGIN("addConnectionSegments");
2866                 for (IElement added : updater.addedConnectionSegments) {
2867                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2868                         System.out.println("adding segment: " + added);
2869
2870                     PlaceholderConnection cb = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_BEGIN_PLACEHOLDER);
2871                     PlaceholderConnection ce = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_END_PLACEHOLDER);
2872                     if (cb == null || ce == null) {
2873                         if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2874                             warning("ignoring connection segment " + added + ", connectivity was not resolved (begin=" + cb + ", end=" + ce +")", null);
2875                         continue;
2876                     }
2877
2878                     mapElement(ElementUtils.getObject(added), added);
2879
2880                     IElement beginNode = assertMappedElement(cb.node);
2881                     IElement endNode = assertMappedElement(ce.node);
2882
2883                     if (cb != null)
2884                         connect(added, cb.end, beginNode, cb.terminal);
2885                     if (ce != null)
2886                         connect(added, ce.end, endNode, ce.terminal);
2887
2888                     IElement connection = ElementUtils.getParent(added);
2889                     if (connection != null) {
2890                         dirty.add(connection);
2891                     }
2892                 }
2893                 Timing.END(task2);
2894
2895                 // We've ensured that all nodes must have been and
2896                 // mapped before reaching this.
2897
2898                 task2 = Timing.BEGIN("handle dirty RouteGraph connections");
2899                 for (IElement addedRouteGraphConnection : updater.addedRouteGraphConnectionMap.values()) {
2900                     updateDirtyRouteGraphConnection(addedRouteGraphConnection, dirty);
2901                 }
2902                 Timing.END(task2);
2903
2904                 // Prevent memory leaks
2905                 tempConnections.clear();
2906
2907                 // Make sure that the diagram element order matches that of the database.
2908                 final TObjectIntHashMap<IElement> orderMap = new TObjectIntHashMap<IElement>(2 * updater.content.elements.size());
2909                 int i = 1;
2910                 for (Resource r : updater.content.elements) {
2911                     IElement e = getMappedElement(r);
2912                     if (e != null)
2913                         orderMap.put(e, i);
2914                     ++i;
2915                 }
2916                 diagram.sort(new Comparator<IElement>() {
2917                     @Override
2918                     public int compare(IElement e1, IElement e2) {
2919                         int o1 = orderMap.get(e1);
2920                         int o2 = orderMap.get(e2);
2921                         return o1 - o2;
2922                     }
2923                 });
2924
2925                 // TODO: consider removing this. The whole thing should work without it and
2926                 // this "fix" will only be hiding the real problems.
2927                 task2 = Timing.BEGIN("fixChangedConnections");
2928                 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2929                     cd.impl.fix();
2930                 }
2931                 Timing.END(task2);
2932
2933                 task2 = Timing.BEGIN("validateAndFix");
2934                 DiagramUtils.validateAndFix(diagram, dirty);
2935                 Timing.END(task2);
2936
2937                 // This will fire connection entity change listeners
2938                 task2 = Timing.BEGIN("Postprocess connection changes");
2939                 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2940                     ConnectionChildren oldChildren = connectionChangeData.get(cd.impl);
2941                     if (oldChildren != null) {
2942                         ConnectionChildren currentChildren = cd.impl.getConnectionChildren();
2943                         cd.impl.fireListener(oldChildren, currentChildren);
2944                     }
2945                 }
2946                 Timing.END(task2);
2947
2948                 task2 = Timing.BEGIN("setDirty");
2949                 for (IElement e : dirty) {
2950                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2951                         System.out.println("MARKING ELEMENT DIRTY: " + e);
2952                     e.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
2953                 }
2954                 Timing.END(task2);
2955
2956                 // Signal about possible changes in the z-order of diagram elements.
2957                 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2958                     System.out.println("MARKING DIAGRAM DIRTY: " + diagram);
2959                 diagram.setHint(Hints.KEY_DIRTY, Hints.VALUE_Z_ORDER_CHANGED);
2960
2961                 // Mark the updater as "processed".
2962                 updater.clear();
2963
2964                 // Inform listeners that the diagram has been updated.
2965                 diagram.setHint(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, Boolean.TRUE);
2966             }
2967         };
2968     }
2969
2970     /**
2971      * @param connection
2972      * @param dirtySet
2973      */
2974     private void updateDirtyRouteGraphConnection(IElement connection, Set<IElement> dirtySet) {
2975         if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2976             System.out.println("updating dirty route graph connection: " + connection);
2977
2978         ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
2979         if (ce == null)
2980             return;
2981
2982         tempConnections.clear();
2983         Object connectionData = ElementUtils.getObject(connection);
2984         for (Connection conn : ce.getTerminalConnections(tempConnections)) {
2985             ((Element) conn.node).setHintWithoutNotification(
2986                     new TerminalKeyOf(conn.terminal, connectionData, Connection.class),
2987                     conn);
2988             if (dirtySet != null)
2989                 dirtySet.add(conn.node);
2990         }
2991
2992         // Prevent memory leaks.
2993         tempConnections.clear();
2994     }
2995
2996     abstract class ElementUpdater extends AbstractDiagramUpdater {
2997         private final IElement newElement;
2998
2999         public ElementUpdater(Double priority, String runnerName, IElement newElement) {
3000             super(priority, runnerName);
3001             if (newElement == null)
3002                 throw new NullPointerException("null element");
3003             this.newElement = newElement;
3004         }
3005
3006         @Override
3007         public String toString() {
3008             return super.toString() + "[" + newElement + "]";
3009         }
3010
3011         @Override
3012         public void run() {
3013 //            System.out.println("ElementUpdateRunner new=" + newElement);
3014             Object elementResource = newElement.getHint(ElementHints.KEY_OBJECT);
3015 //            System.out.println("ElementUpdateRunner res=" + elementResource);
3016             final Element mappedElement = (Element) getMappedElement(elementResource);
3017 //            System.out.println("ElementUpdateRunner mapped=" + mappedElement);
3018             if (mappedElement == null) {
3019                 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE) {
3020                     System.out.println("SKIP DIAGRAM UPDATE " + this  + " for element resource " + elementResource + ", no mapped element (newElement=" + newElement + ")");
3021                 }
3022                 // Indicates the element has been removed from the graph.
3023                 return;
3024             }
3025
3026             Object task = Timing.BEGIN(runnerName);
3027             forMappedElement(mappedElement);
3028             Timing.END(task);
3029         }
3030
3031         protected abstract void forMappedElement(Element mappedElement);
3032     }
3033
3034     ElementUpdater nodeUpdater(final Resource resource, final IElement newElement) {
3035
3036         return new ElementUpdater(DIAGRAM_UPDATE_NODE_PRIORITY, "updateNode", newElement) {
3037
3038             Collection<Terminal> getTerminals(IElement e) {
3039                 Collection<Terminal> ts = Collections.emptyList();
3040                 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
3041                 if (tt != null) {
3042                     ts = new ArrayList<Terminal>();
3043                     tt.getTerminals(newElement, ts);
3044                 }
3045                 return ts;
3046             }
3047
3048             @Override
3049             protected void forMappedElement(final Element mappedElement) {
3050                 if (DebugPolicy.DEBUG_NODE_UPDATE)
3051                     System.out.println("running node updater: " + this + " - new element: " + newElement);
3052
3053                 // Newly loaded node elements NEVER contain topology-related
3054                 // hints, i.e. TerminalKeyOf hints. Instead all connections are
3055                 // actually set into element hints when connection edges are
3056                 // loaded.
3057
3058                 Collection<Terminal> oldTerminals = getTerminals(mappedElement);
3059                 Collection<Terminal> newTerminals = getTerminals(newElement);
3060                 if (!oldTerminals.equals(newTerminals)) {
3061                     // Okay, there are differences in the terminals. Need to fix
3062                     // the TerminalKeyOf hint values to use the new terminal
3063                     // instances when correspondences exist.
3064
3065                     // If there is no correspondence for an old terminal, we
3066                     // are simply forced to remove the hints related to this
3067                     // connection.
3068
3069                     Map<Terminal, Terminal> newTerminalMap = new HashMap<Terminal, Terminal>(newTerminals.size());
3070                     for (Terminal t : newTerminals) {
3071                         newTerminalMap.put(t, t);
3072                     }
3073
3074                     for (Map.Entry<TerminalKeyOf, Object> entry : mappedElement.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3075                         TerminalKeyOf key = entry.getKey();
3076                         Connection c = (Connection) entry.getValue();
3077                         if (c.node == mappedElement) {
3078                             Terminal newTerminal = newTerminalMap.get(c.terminal);
3079                             if (newTerminal != null) {
3080                                 c = new Connection(c.edge, c.end, c.node, newTerminal);
3081                                 ((Element) c.edge).setHintWithoutNotification(EndKeyOf.get(c.end), c);
3082                             } else {
3083                                 mappedElement.removeHintWithoutNotification(key);
3084                             }
3085                         }
3086                     }
3087                 }
3088
3089                 updateMappedElement(mappedElement, newElement);
3090                 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3091             }
3092         };
3093     }
3094
3095     ElementUpdater connectionUpdater(final Object data, final IElement newElement) {
3096         return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateConnection", newElement) {
3097             @Override
3098             public void forMappedElement(Element mappedElement) {
3099                 if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
3100                     System.out.println("running connection updater: " + this + " - new element: " + newElement
3101                             + " with data " + data);
3102
3103                 // This is kept up-to-date by GDS, make sure not to overwrite it
3104                 // from the mapped element.
3105                 newElement.removeHint(ElementHints.KEY_CONNECTION_ENTITY);
3106
3107                 updateMappedElement(mappedElement, newElement);
3108                 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3109             }
3110         };
3111     }
3112
3113     ElementUpdater edgeUpdater(final Object data, final IElement newElement) {
3114         return new ElementUpdater(DIAGRAM_UPDATE_EDGE_PRIORITY, "updateEdge", newElement) {
3115             @Override
3116             public void forMappedElement(Element mappedElement) {
3117                 if (DebugPolicy.DEBUG_EDGE_UPDATE)
3118                     System.out.println("running edge updater: " + this + " - new element: " + newElement
3119                             + " with data " + data);
3120
3121                 updateMappedElement(mappedElement, newElement);
3122                 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3123             }
3124         };
3125     }
3126
3127     ElementUpdater routeGraphConnectionUpdater(final Object data, final IElement newElement, final Set<Object> dirtyNodes) {
3128         return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateRouteGraphConnection", newElement) {
3129             @Override
3130             public void forMappedElement(Element mappedElement) {
3131                 if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
3132                     System.out.println("running route graph connection updater: " + this + " - new element: " + newElement
3133                             + " with data " + data);
3134
3135                 // Remove all TerminalKeyOf hints from nodes that were
3136                 // previously connected to the connection (mappedElement)
3137                 // before updating mappedElement with new topology information.
3138
3139                 for (Object dirtyNodeObject : dirtyNodes) {
3140                     Element dirtyNode = (Element) getMappedElement(dirtyNodeObject);
3141                     if (dirtyNode == null)
3142                         continue;
3143                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
3144                         System.out.println("preparing node with dirty connectivity: " + dirtyNode);
3145
3146                     for (Map.Entry<TerminalKeyOf, Object> entry : dirtyNode.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3147                         Connection conn = (Connection) entry.getValue();
3148                         Object connectionNode = conn.edge.getHint(ElementHints.KEY_OBJECT);
3149                         if (data.equals(connectionNode)) {
3150                             dirtyNode.removeHintWithoutNotification(entry.getKey());
3151                         }
3152                     }
3153                 }
3154
3155                 // Update connection information, including topology
3156                 updateMappedElement(mappedElement, newElement);
3157
3158                 // Reinstall TerminalKeyOf hints into nodes that are now connected
3159                 // to mappedElement to keep diagram run-time model properly in sync
3160                 // with the database.
3161                 updateDirtyRouteGraphConnection(mappedElement, null);
3162
3163                 // TODO: should mark dirty nodes' scene graph dirty ?
3164
3165                 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3166             }
3167         };
3168     }
3169
3170     /**
3171      * Copies hints from <code>newElement</code> to <code>mappedElement</code>
3172      * asserting some validity conditions at the same time.
3173      * 
3174      * @param mappedElement
3175      * @param newElement
3176      */
3177     static void updateMappedElement(Element mappedElement, IElement newElement) {
3178         if (mappedElement == newElement)
3179             // Can't update anything if the two elements are the same.
3180             return;
3181
3182         ElementClass oldClass = mappedElement.getElementClass();
3183         ElementClass newClass = newElement.getElementClass();
3184
3185         Object mappedData = mappedElement.getHint(ElementHints.KEY_OBJECT);
3186         Object newData = newElement.getHint(ElementHints.KEY_OBJECT);
3187
3188         assert mappedData != null;
3189         assert newData != null;
3190         assert mappedData.equals(newData);
3191
3192         if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3193             System.out.println("Updating mapped element, setting hints\n  from: " + newElement + "\n  into: " + mappedElement);
3194         }
3195
3196         // TODO: consider if this equals check is a waste of time or does it pay
3197         // off due to having to reinitialize per-class caches for the new
3198         // ElementClass that are constructed on the fly?
3199         if (!newClass.equals(oldClass)) {
3200             if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3201                 System.out.println("  old element class: " + oldClass);
3202                 System.out.println("  new element class: " + newClass);
3203             }
3204             mappedElement.setElementClass(newClass);
3205         }
3206
3207         // Tuukka@2010-02-19: replaced with notifications for making
3208         // the graph synchronizer more transparent to the client.
3209
3210         // Hint notifications will not work when this is used.
3211         //mappedElement.setHintsWithoutNotification(newElement.getHints());
3212
3213         Map<DiscardableKey, Object> discardableHints = mappedElement.getHintsOfClass(DiscardableKey.class);
3214
3215         // Set all hints from newElement to mappedElement.
3216         // Leave any hints in mappedElement but not in newElement as is.
3217         Map<Key, Object> hints = newElement.getHints();
3218         Map<Key, Object> newHints = new HashMap<Key, Object>();
3219         for (Map.Entry<Key, Object> entry : hints.entrySet()) {
3220             Key key = entry.getKey();
3221             Object newValue = entry.getValue();
3222             Object oldValue = mappedElement.getHint(key);
3223             if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE_DETAIL) {
3224                 System.out.println("  hint " + key + " compare values: " + oldValue + "  ->  " + newValue);
3225             }
3226             if (!newValue.equals(oldValue)) {
3227                 newHints.put(key, newValue);
3228                 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3229                     System.out.format("    %-42s : %64s -> %-64s\n", key, oldValue, newValue);
3230                 }
3231             } else {
3232                 // If the hint value has not changed but the hint still exists
3233                 // we don't need to discard it even if it is considered
3234                 // discardable.
3235                 discardableHints.remove(key);
3236             }
3237         }
3238
3239         // Set all hints at once and send notifications after setting the values.
3240         if (!discardableHints.isEmpty()) {
3241             if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE)
3242                 System.out.println("Discarding " + discardableHints.size() + " discardable hints:\n  " + discardableHints);
3243             mappedElement.removeHints(discardableHints.keySet());
3244         }
3245         if (!newHints.isEmpty()) {
3246             if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3247                 System.out.println("Updating mapped element, setting new hints:\n\t"
3248                         + EString.implode(newHints.entrySet(), "\n\t") + "\nto replace old hints\n\t"
3249                         + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
3250             }
3251             mappedElement.setHints(newHints);
3252         }
3253         if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3254             System.out.println("All hints after update:\n\t"
3255                     + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
3256         }
3257     }
3258
3259     class TransactionListener extends SessionEventListenerAdapter {
3260         long startTime;
3261         @Override
3262         public void writeTransactionStarted() {
3263             startTime = System.nanoTime();
3264             if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
3265                 System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionStarted");
3266             inWriteTransaction.set(true);
3267         }
3268         @Override
3269         public void writeTransactionFinished() {
3270             long endTime = System.nanoTime();
3271             if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
3272                 System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionFinished: " + (endTime - startTime)*1e-6 + " ms");
3273             inWriteTransaction.set(false);
3274             scheduleGraphUpdates();
3275         }
3276     };
3277
3278     Object                   graphUpdateLock             = new Object();
3279     TransactionListener      sessionListener             = null;
3280     AtomicBoolean            inWriteTransaction          = new AtomicBoolean(false);
3281     AtomicBoolean            graphUpdateRequestScheduled = new AtomicBoolean(false);
3282     List<GraphUpdateReactor> queuedGraphUpdates          = new ArrayList<GraphUpdateReactor>();
3283
3284     private void offerGraphUpdate(GraphUpdateReactor update) {
3285         if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3286             System.out.println("offerGraphUpdate: " + update);
3287         boolean inWrite = inWriteTransaction.get();
3288         synchronized (graphUpdateLock) {
3289             if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3290                 System.out.println("queueing graph update: " + update);
3291             queuedGraphUpdates.add(update);
3292         }
3293         if (!inWrite) {
3294             if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3295                 System.out.println("scheduling queued graph update immediately: " + update);
3296             scheduleGraphUpdates();
3297         }
3298     }
3299
3300     private Collection<GraphUpdateReactor> scrubGraphUpdates() {
3301         synchronized (graphUpdateLock) {
3302             if (queuedGraphUpdates.isEmpty())
3303                 return Collections.emptyList();
3304             final List<GraphUpdateReactor> updates = queuedGraphUpdates;
3305             queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();
3306             return updates;
3307         }
3308     }
3309
3310     private void scheduleGraphUpdates() {
3311         synchronized (graphUpdateLock) {
3312             if (queuedGraphUpdates.isEmpty())
3313                 return;
3314             if (!graphUpdateRequestScheduled.compareAndSet(false, true))
3315                 return;
3316         }
3317
3318         if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3319             System.out.println("scheduling " + queuedGraphUpdates.size() + " queued graph updates with ");
3320
3321         session.asyncRequest(new ReadRequest() {
3322             @Override
3323             public void run(final ReadGraph graph) throws DatabaseException {
3324                 Collection<GraphUpdateReactor> updates;
3325                 synchronized (graphUpdateLock) {
3326                     graphUpdateRequestScheduled.set(false);
3327                     updates = scrubGraphUpdates();
3328                 }
3329
3330                 if (!GraphToDiagramSynchronizer.this.isAlive())
3331                     return;
3332
3333                 processGraphUpdates(graph, updates);
3334             }
3335         }, new ProcedureAdapter<Object>() {
3336             @Override
3337             public void exception(Throwable t) {
3338                 error(t);
3339             }
3340         });
3341     }
3342
3343     private void processGraphUpdates(ReadGraph graph, final Collection<GraphUpdateReactor> graphUpdates)
3344     throws DatabaseException {
3345         final List<DiagramUpdater> diagramUpdates = new ArrayList<DiagramUpdater>(graphUpdates.size());
3346
3347         // Run GraphUpdaters and gather DiagramUpdaters.
3348         if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3349             System.out.println("Running GRAPH updates: " + graphUpdates);
3350         for (GraphUpdateReactor graphUpdate : graphUpdates) {
3351             DiagramUpdater diagramUpdate = graphUpdate.graphUpdate(graph);
3352             if (diagramUpdate != null) {
3353                 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3354                     System.out.println(graphUpdate + " => " + diagramUpdate);
3355                 diagramUpdates.add(diagramUpdate);
3356             }
3357         }
3358
3359         if (diagramUpdates.isEmpty())
3360             return;
3361
3362         if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3363             System.out.println("Diagram updates: " + diagramUpdates);
3364         Collections.sort(diagramUpdates, DiagramUpdater.DIAGRAM_UPDATER_COMPARATOR);
3365         if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3366             System.out.println("Sorted diagram updates: " + diagramUpdates);
3367
3368         ThreadUtils.asyncExec(canvas.getThreadAccess(), new StateRunnable() {
3369             @Override
3370             public void run() {
3371                 if (GraphToDiagramSynchronizer.this.isAlive() && getState() != State.DISPOSED)
3372                     safeRunInState(State.UPDATING_DIAGRAM, this);
3373             }
3374
3375             @Override
3376             public void execute() throws InvocationTargetException {
3377                 // Block out diagram write transactions.
3378                 DiagramUtils.inDiagramTransaction(diagram, TransactionType.READ, new Runnable() {
3379                     @Override
3380                     public void run() {
3381                         if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3382                             System.out.println("Running DIAGRAM updates: " + diagramUpdates);
3383                         for (DiagramUpdater update : diagramUpdates) {
3384                             if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3385                                 System.out.println("Running DIAGRAM update: " + update);
3386                             update.run();
3387                         }
3388                     }
3389                 });
3390
3391                 setCanvasDirty();
3392             }
3393         });
3394     }
3395
3396     private void attachSessionListener(Session session) {
3397         SessionEventSupport support = session.peekService(SessionEventSupport.class);
3398         if (support != null) {
3399             sessionListener = new TransactionListener();
3400             support.addListener(sessionListener);
3401         }
3402     }
3403
3404     private void detachSessionListener() {
3405         if (sessionListener != null) {
3406             session.getService(SessionEventSupport.class).removeListener(sessionListener);
3407             sessionListener = null;
3408         }
3409     }
3410
3411
3412     // ------------------------------------------------------------------------
3413     // GRAPH TO DIAGRAM SYNCHRONIZATION LOGIC END
3414     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3415
3416     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3417     // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
3418     // This does not try to synchronize anything back into the graph.
3419     // ------------------------------------------------------------------------
3420
3421     IHintListener elementHintValidator = new HintListenerAdapter() {
3422         @Override
3423         public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
3424             if (!(sender instanceof Element))
3425                 throw new IllegalStateException("invalid sender: " + sender);
3426             Element e = (Element) sender;
3427             if (newValue != null) {
3428                 if (key instanceof TerminalKeyOf) {
3429                     Connection c = (Connection) newValue;
3430                     if (e != c.node)
3431                         throw new IllegalStateException("TerminalKeyOf hint of node " + e + " refers to a different node " + c.node + ". Should be the same.");
3432                     Object edgeObject = ElementUtils.getObject(c.edge);
3433                     if (!(edgeObject instanceof EdgeResource))
3434                         throw new IllegalStateException("EndKeyOf hint of edge " + c.edge + " refers contains an invalid object: " + edgeObject);
3435                 } else if (key instanceof EndKeyOf) {
3436                     Connection c = (Connection) newValue;
3437                     if (e != c.edge)
3438                         throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers to a different edge " + c.edge + ". Should be the same.");
3439                     Object edgeObject = ElementUtils.getObject(c.edge);
3440                     if (!(edgeObject instanceof EdgeResource))
3441                         throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers contains an invalid object: " + edgeObject);
3442                 }
3443             }
3444         }
3445     };
3446
3447     class DiagramListener implements CompositionListener, CompositionVetoListener {
3448         @Override
3449         public boolean beforeElementAdded(IDiagram d, IElement e) {
3450             // Make sure that MutatedElements NEVER get added to the diagram.
3451             if (d == diagram) {
3452                 if (!(e instanceof Element)) {
3453                     // THIS IS NOT GOOD!
3454                     error("Attempting to add another implementation of IElement besides Element (=" + e.getElementClass().getClass().getName() + ") to the synchronized diagram which means that there is a bug somewhere! See stack trace to find out who is doing this!", new Exception("stacktrace"));
3455                     System.err.println("Attempting to add another implementation of IElement besides Element (=" + e.getElementClass().getClass().getName() + ") to the synchronized diagram which means that there is a bug somewhere! See Error Log.");
3456                     return false;
3457                 }
3458
3459                 // Perform sanity checks that might veto the element addition.
3460                 boolean pass = true;
3461
3462                 // Check that all elements added to the diagram are adaptable to Resource
3463                 ElementClass ec = e.getElementClass();
3464                 Resource resource = ElementUtils.adapt(ec, Resource.class);
3465                 if (resource == null) {
3466                     pass = false;
3467                     LOGGER.error("", new Exception("Attempted to add an element to the diagram that is not adaptable to Resource: " + e + ", class: " + ec));
3468                 }
3469
3470                 // Sanity check connection hints
3471                 for (Map.Entry<TerminalKeyOf, Object> entry : e.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3472                     Connection c = (Connection) entry.getValue();
3473                     Object edgeObject = ElementUtils.getObject(c.edge);
3474                     if (e != c.node) {
3475                         System.err.println("Invalid node in TerminalKeyOf hint: " + entry.getKey() + "=" + entry.getValue());
3476                         System.err.println("\tconnection.edge=" + c.edge);
3477                         System.err.println("\tconnection.node=" + c.node);
3478                         System.err.println("\tconnection.end=" + c.end);
3479                         System.err.println("\telement=" + e);
3480                         System.err.println("\telement class=" + e.getElementClass());
3481                         pass = false;
3482                     }
3483                     if (!(edgeObject instanceof EdgeResource)) {
3484                         System.err.println("Invalid object in TerminalKeyOf hint edge: " + entry.getKey() + "=" + entry.getValue());
3485                         System.err.println("\tconnection.edge=" + c.edge);
3486                         System.err.println("\tconnection.node=" + c.node);
3487                         System.err.println("\tconnection.end=" + c.end);
3488                         System.err.println("\telement=" + e);
3489                         System.err.println("\telement class=" + e.getElementClass());
3490                         pass = false;
3491                     }
3492                 }
3493
3494                 return pass;
3495             }
3496             return true;
3497         }
3498
3499         @Override
3500         public boolean beforeElementRemoved(IDiagram d, IElement e) {
3501             // Never veto diagram changes.
3502             return true;
3503         }
3504
3505         @Override
3506         public void onElementAdded(IDiagram d, IElement e) {
3507             if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
3508                 System.out.println("[" + d + "] element added: " + e);
3509
3510             if (USE_ELEMENT_VALIDATING_LISTENERS)
3511                 e.addHintListener(elementHintValidator);
3512         }
3513
3514         @Override
3515         public void onElementRemoved(IDiagram d, IElement e) {
3516             if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
3517                 System.out.println("[" + d + "] element removed: " + e);
3518
3519             if (USE_ELEMENT_VALIDATING_LISTENERS)
3520                 e.removeHintListener(elementHintValidator);
3521
3522             if (e.containsHint(KEY_REMOVE_RELATIONSHIPS))
3523                 relationshipHandler.denyAll(diagram, e);
3524         }
3525     }
3526
3527     DiagramListener diagramListener = new DiagramListener();
3528
3529     static void removeNodeTopologyHints(Element node) {
3530         Set<TerminalKeyOf> terminalKeys = node.getHintsOfClass(TerminalKeyOf.class).keySet();
3531         if (!terminalKeys.isEmpty()) {
3532             removeNodeTopologyHints(node, terminalKeys);
3533         }
3534     }
3535
3536     static void removeNodeTopologyHints(Element node, Collection<TerminalKeyOf> terminalKeys) {
3537         for (TerminalKeyOf key : terminalKeys) {
3538             Connection c = node.removeHintWithoutNotification(key);
3539             if (c != null) {
3540                 removeEdgeTopologyHints((Element) c.edge);
3541             }
3542         }
3543     }
3544
3545     static void removeEdgeTopologyHints(Element edge) {
3546         Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
3547         for (EndKeyOf key : EndKeyOf.KEYS) {
3548             Connection c = edge.removeHintWithoutNotification(key);
3549             if (c != null) {
3550                 ((Element) c.node).removeHintWithoutNotification(new TerminalKeyOf(c.terminal, edgeData, Connection.class));
3551             }
3552         }
3553     }
3554
3555     // ------------------------------------------------------------------------
3556     // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
3557     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3558
3559     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3560     // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC BEGIN
3561     // ------------------------------------------------------------------------
3562
3563     void adaptDiagramClass(AsyncReadGraph graph, Resource diagram, final AsyncProcedure<DiagramClass> procedure) {
3564         graph.forAdapted(diagram, DiagramClass.class, new AsyncProcedure<DiagramClass>() {
3565             @Override
3566             public void exception(AsyncReadGraph graph, Throwable throwable) {
3567                 procedure.exception(graph, throwable);
3568             }
3569
3570             @Override
3571             public void execute(AsyncReadGraph graph, DiagramClass dc) {
3572                 // To move TopologyImpl out of here, we need a separate
3573                 // DiagramClassFactory that takes a canvas context as an argument.
3574                 // DataElementMapImpl, ElementFactoryImpl and diagramLifeCycle can
3575                 // safely stay here.
3576                 procedure.execute(graph, dc.newClassWith(
3577                         // This handler takes care of the topology of the diagram model.
3578                         // It sets and fixes element hints related to describing the
3579                         // connectivity of elements.
3580                         diagramTopology,
3581                         // TODO: not quite sure whether this can prove itself useful or not.
3582                         elementFactory,
3583                         // This map provides a bidirectional mapping between
3584                         // IElement and back-end objects.
3585                         dataElementMap,
3586                         // This handler provides a facility to adapt an element class
3587                         // to work properly with a diagram synchronized using this
3588                         // GraphToDiagramSynchronizer.
3589                         substituteElementClass,
3590                         // These handlers provide a way to create simple identified
3591                         // uni- and bidirectional relationships between any diagram
3592                         // objects/elements.
3593                         relationshipHandler));
3594             }
3595         });
3596     }
3597
3598     static Connection connect(IElement edge, EdgeEnd end, IElement element, Terminal terminal) {
3599         Connection c = new Connection(edge, end, element, terminal);
3600
3601         Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
3602         if (DebugPolicy.DEBUG_CONNECTION) {
3603             System.out.println("[connect](edge=" + edge + ", edgeData=" + edgeData + ", end=" + end + ", element="
3604                     + element + ", terminal=" + terminal + ")");
3605         }
3606
3607         TerminalKeyOf key = new TerminalKeyOf(terminal, edgeData, Connection.class);
3608         element.setHint(key, c);
3609
3610         EndKeyOf key2 = EndKeyOf.get(end);
3611         edge.setHint(key2, c);
3612
3613         return c;
3614     }
3615
3616     static class ElementFactoryImpl implements ElementFactory {
3617         @Override
3618         public IElement spawnNew(ElementClass clazz) {
3619             IElement e = Element.spawnNew(clazz);
3620             return e;
3621         }
3622     }
3623
3624     ElementFactoryImpl elementFactory = new ElementFactoryImpl();
3625
3626     public static final Object FIRST_TIME = new Object() {
3627         @Override
3628         public String toString() {
3629             return "FIRST_TIME";
3630         }
3631     };
3632
3633
3634     /**
3635      * A base for all listeners of graph requests performed internally by
3636      * GraphToDiagramSynchronizer.
3637      *
3638      * @param <T> type of stored data element
3639      * @param <Result> query result type
3640      */
3641     abstract class BaseListener<T, Result> implements AsyncListener<Result> {
3642
3643         protected final T    data;
3644
3645         private Object       oldResult = FIRST_TIME;
3646
3647         protected boolean    disposed  = false;
3648
3649         final ICanvasContext canvas;
3650
3651         public BaseListener(T data) {
3652             this.canvas = GraphToDiagramSynchronizer.this.canvas;
3653             this.data = data;
3654         }
3655
3656         @Override
3657         public void exception(AsyncReadGraph graph, Throwable throwable) {
3658             // Exceptions are always expected to mean that the listener should
3659             // be considered disposed once a query fails.
3660             disposed = true;
3661         }
3662
3663         abstract void execute(AsyncReadGraph graph, Object oldResult, Object newResult);
3664
3665         @Override
3666         public void execute(AsyncReadGraph graph, Result result) {
3667             if (DebugPolicy.DEBUG_LISTENER_BASE)
3668                 System.out.println("BaseListener: " + result);
3669
3670             if (disposed) {
3671                 if (DebugPolicy.DEBUG_LISTENER_BASE)
3672                     System.out.println("BaseListener: execute invoked although listener is disposed!");
3673                 return;
3674             }
3675
3676             // A null result will permanently mark this listener disposed!
3677             if (result == null) {
3678                 disposed = true;
3679                 if (DebugPolicy.DEBUG_LISTENER_BASE)
3680                     System.out.println(this + " null result, listener marked disposed");
3681             }
3682
3683             if (oldResult == FIRST_TIME) {
3684                 oldResult = result;
3685                 if (DebugPolicy.DEBUG_LISTENER_BASE)
3686                     System.out.println(this + " first result computed: " + result);
3687             } else {
3688                 if (DebugPolicy.DEBUG_LISTENER_BASE)
3689                     System.out.println(this + " result changed from '" + oldResult + "' to '" + result + "'");
3690                 try {
3691                     execute(graph, oldResult, result);
3692                 } finally {
3693                     oldResult = result;
3694                 }
3695             }
3696         }
3697
3698         @Override
3699         public boolean isDisposed() {
3700             if (disposed)
3701                 return true;
3702
3703             boolean alive = isAlive();
3704             //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", isAlive=" + alive);
3705             if (!alive)
3706                 return true;
3707             // If a mapping no longer exists for this element, dispose of this
3708             // listener.
3709             //IElement e = getMappedElement(resource);
3710             //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", element=" + e);
3711             //return e == null;
3712             return false;
3713         }
3714
3715 //        @Override
3716 //        protected void finalize() throws Throwable {
3717 //            System.out.println("finalize listener: " + this);
3718 //            super.finalize();
3719 //        }
3720     }
3721
3722     class DiagramClassRequest extends BaseRequest2<Resource, DiagramClass> {
3723         public DiagramClassRequest(Resource resource) {
3724             super(GraphToDiagramSynchronizer.this.canvas, resource);
3725         }
3726
3727         @Override
3728         public void perform(AsyncReadGraph graph, AsyncProcedure<DiagramClass> procedure) {
3729             adaptDiagramClass(graph, data, procedure);
3730         }
3731     }
3732
3733     public class DiagramContentListener extends BaseListener<Resource, DiagramContents> {
3734
3735         public DiagramContentListener(Resource resource) {
3736             super(resource);
3737         }
3738
3739         @Override
3740         public void execute(final AsyncReadGraph graph, Object oldResult, Object newResult) {
3741             final DiagramContents newContent = (newResult == null) ? new DiagramContents()
3742             : (DiagramContents) newResult;
3743
3744             // diagramGraphUpdater is called synchronously during
3745             // loading. The first result will not get updated through
3746             // this listener but through loadDiagram.
3747
3748             if (DebugPolicy.DISABLE_DIAGRAM_UPDATES) {
3749                 System.out.println("Skipped diagram content update: " + newResult);
3750                 return;
3751             }
3752
3753             if (DebugPolicy.DEBUG_DIAGRAM_LISTENER)
3754                 System.out.println("diagram contents changed: " + oldResult + " => " + newResult);
3755
3756             offerGraphUpdate( diagramGraphUpdater(newContent) );
3757         }
3758
3759         @Override
3760         public boolean isDisposed() {
3761             return !isAlive();
3762         }
3763
3764         @Override
3765         public void exception(AsyncReadGraph graph, Throwable t) {
3766             super.exception(graph, t);
3767             error("DiagramContentRequest failed", t);
3768         }
3769     }
3770
3771     // ------------------------------------------------------------------------
3772     // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC END
3773     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3774
3775     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3776     // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER BEGIN
3777     // ------------------------------------------------------------------------
3778
3779     static class TopologyImpl implements Topology {
3780
3781         @Override
3782         public Connection getConnection(IElement edge, EdgeEnd end) {
3783             Key key = EndKeyOf.get(end);
3784             Connection c = edge.getHint(key);
3785             if (c == null)
3786                 return null;
3787             return c;
3788         }
3789
3790         @Override
3791         public void getConnections(IElement node, Terminal terminal, Collection<Connection> connections) {
3792 //            IDiagram d = ElementUtils.getDiagram(node);
3793             for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3794                 // First check that the terminal matches.
3795                 TerminalKeyOf key = entry.getKey();
3796                 if (!key.getTerminal().equals(terminal))
3797                     continue;
3798
3799                 Connection c = (Connection) entry.getValue();
3800                 if (c != null) {
3801                     connections.add(c);
3802                 }
3803             }
3804         }
3805
3806         @Override
3807         public void connect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
3808             if (node != null && terminal != null)
3809                 GraphToDiagramSynchronizer.connect(edge, end, node, terminal);
3810
3811             if (DebugPolicy.DEBUG_CONNECTION) {
3812                 if (end == EdgeEnd.Begin)
3813                     System.out.println("Connection started from: " + edge + ", " + end + ", " + node + ", " + terminal);
3814                 else
3815                     System.out.println("Creating connection to: " + edge + ", " + end + ", " + node + ", " + terminal);
3816             }
3817         }
3818
3819         @Override
3820         public void disconnect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
3821             EndKeyOf edgeKey = EndKeyOf.get(end);
3822             Connection c = edge.getHint(edgeKey);
3823             if (c == null)
3824                 throw new UnsupportedOperationException("cannot disconnect, no Connection in edge " + edge);
3825
3826             for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3827                 Connection cc = (Connection) entry.getValue();
3828                 if (c == cc) {
3829                     node.removeHint(entry.getKey());
3830                     edge.removeHint(edgeKey);
3831                     return;
3832                 }
3833             }
3834
3835             throw new UnsupportedOperationException("cannot disconnect, no connection between found between edge "
3836                     + edge + " and node " + node);
3837         }
3838     }
3839
3840     Topology            diagramTopology     = new TopologyImpl();
3841
3842     // ------------------------------------------------------------------------
3843     // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER END
3844     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3845
3846     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3847     // DIAGRAM OBJECT RELATIONSHIP HANDLER BEGIN
3848     // ------------------------------------------------------------------------
3849
3850     RelationshipHandler relationshipHandler = new RelationshipHandler() {
3851
3852         AssociativeMap map = new AssociativeMap(Associativity.of(true, false, false));
3853
3854         Object getPossibleObjectOrElement(Object o) {
3855             if (o instanceof IElement) {
3856                 IElement e = (IElement) o;
3857                 Object oo = e.getHint(ElementHints.KEY_OBJECT);
3858                 return oo != null ? oo : e;
3859             }
3860             return o;
3861         }
3862
3863         IElement getElement(Object o) {
3864             if (o instanceof IElement)
3865                 return (IElement) o;
3866             return getMappedElement(o);
3867         }
3868
3869         @Override
3870         public void claim(IDiagram diagram, Object subject,
3871                 Relationship predicate, Object object) {
3872             Object sd = getPossibleObjectOrElement(subject);
3873             Object od = getPossibleObjectOrElement(object);
3874
3875             Collection<Tuple> ts = null;
3876             Relationship inverse = predicate.getInverse();
3877             if (inverse != null)
3878                 ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od, inverse, sd));
3879             else
3880                 ts = Collections.singletonList(new Tuple(sd, predicate, od));
3881
3882             synchronized (this) {
3883                 map.add(ts);
3884             }
3885
3886             if (DebugPolicy.DEBUG_RELATIONSHIP) {
3887                 new Exception().printStackTrace();
3888                 System.out.println("Claimed relationships:");
3889                 for (Tuple t : ts)
3890                     System.out.println("\t" + t);
3891             }
3892         }
3893
3894         private void doDeny(IDiagram diagram, Object subject,
3895                 Relationship predicate, Object object) {
3896             Object sd = getPossibleObjectOrElement(subject);
3897             Object od = getPossibleObjectOrElement(object);
3898             if (sd == subject || od == object) {
3899                 System.out
3900                 .println("WARNING: denying relationship '"
3901                         + predicate
3902                         + "' between diagram element(s), not back-end object(s): "
3903                         + sd + " -> " + od);
3904             }
3905
3906             Collection<Tuple> ts = null;
3907             Relationship inverse = predicate.getInverse();
3908             if (inverse != null)
3909                 ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od,
3910                         inverse, sd));
3911             else
3912                 ts = Collections.singleton(new Tuple(sd, predicate, od));
3913
3914             synchronized (this) {
3915                 map.remove(ts);
3916             }
3917
3918             if (DebugPolicy.DEBUG_RELATIONSHIP) {
3919                 new Exception().printStackTrace();
3920                 System.out.println("Denied relationships:");
3921                 for (Tuple t : ts)
3922                     System.out.println("\t" + t);
3923             }
3924         }
3925
3926         @Override
3927         public void deny(IDiagram diagram, Object subject,
3928                 Relationship predicate, Object object) {
3929             synchronized (this) {
3930                 doDeny(diagram, subject, predicate, object);
3931             }
3932         }
3933
3934         @Override
3935         public void deny(IDiagram diagram, Relation relation) {
3936             synchronized (this) {
3937                 doDeny(diagram, relation.getSubject(), relation
3938                         .getRelationship(), relation.getObject());
3939             }
3940         }
3941
3942         @Override
3943         public void denyAll(IDiagram diagram, Object element) {
3944             synchronized (this) {
3945                 for (Relation relation : getRelations(diagram, element, null)) {
3946                     doDeny(diagram, relation.getSubject(), relation
3947                             .getRelationship(), relation.getObject());
3948                 }
3949             }
3950         }
3951
3952         @Override
3953         public Collection<Relation> getRelations(IDiagram diagram,
3954                 Object element, Collection<Relation> result) {
3955             if (DebugPolicy.DEBUG_GET_RELATIONSHIP)
3956                 System.out.println("getRelations(" + element + ")");
3957             Object e = getPossibleObjectOrElement(element);
3958
3959             Collection<Tuple> tuples = null;
3960             synchronized (this) {
3961                 tuples = map.get(new Tuple(e, null, null), null);
3962             }
3963
3964             if (DebugPolicy.DEBUG_GET_RELATIONSHIP) {
3965                 System.out.println("Result size: " + tuples.size());
3966                 for (Tuple t : tuples)
3967                     System.out.println("\t" + t);
3968             }
3969
3970             if (tuples.isEmpty())
3971                 return Collections.emptyList();
3972             if (result == null)
3973                 result = new ArrayList<Relation>(tuples.size());
3974             for (Tuple t : tuples) {
3975                 Object obj = t.getField(2);
3976                 IElement el = getElement(obj);
3977                 Relationship r = (Relationship) t.getField(1);
3978                 result.add(new Relation(element, r, el != null ? el : obj));
3979             }
3980             return result;
3981         }
3982
3983     };
3984
3985     // ------------------------------------------------------------------------
3986     // DIAGRAM ELEMENT RELATIONSHIP HANDLER END
3987     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3988
3989 }