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