e2c1521ee45b21c6e63dc166535d645b60e0354e
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / adapter / GraphToDiagramSynchronizer.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2018 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.adapter;
13
14 import 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.procedure.adapter.TransientCacheAsyncListener;
53 import org.simantics.db.common.request.AsyncReadRequest;
54 import org.simantics.db.common.request.ReadRequest;
55 import org.simantics.db.common.session.SessionEventListenerAdapter;
56 import org.simantics.db.common.utils.NameUtils;
57 import org.simantics.db.exception.CancelTransactionException;
58 import org.simantics.db.exception.DatabaseException;
59 import org.simantics.db.exception.NoSingleResultException;
60 import org.simantics.db.exception.ServiceException;
61 import org.simantics.db.procedure.AsyncListener;
62 import org.simantics.db.procedure.AsyncProcedure;
63 import org.simantics.db.procedure.Listener;
64 import org.simantics.db.procedure.Procedure;
65 import org.simantics.db.request.Read;
66 import org.simantics.db.service.SessionEventSupport;
67 import org.simantics.diagram.connection.ConnectionSegmentEnd;
68 import org.simantics.diagram.content.Change;
69 import org.simantics.diagram.content.ConnectionUtil;
70 import org.simantics.diagram.content.DesignatedTerminal;
71 import org.simantics.diagram.content.DiagramContentChanges;
72 import org.simantics.diagram.content.DiagramContents;
73 import org.simantics.diagram.content.EdgeResource;
74 import org.simantics.diagram.content.ResourceTerminal;
75 import org.simantics.diagram.internal.DebugPolicy;
76 import org.simantics.diagram.internal.timing.GTask;
77 import org.simantics.diagram.internal.timing.Timing;
78 import org.simantics.diagram.profile.ProfileKeys;
79 import org.simantics.diagram.synchronization.CollectingModificationQueue;
80 import org.simantics.diagram.synchronization.CompositeModification;
81 import org.simantics.diagram.synchronization.CopyAdvisor;
82 import org.simantics.diagram.synchronization.ErrorHandler;
83 import org.simantics.diagram.synchronization.IHintSynchronizer;
84 import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;
85 import org.simantics.diagram.synchronization.IModification;
86 import org.simantics.diagram.synchronization.LogErrorHandler;
87 import org.simantics.diagram.synchronization.ModificationAdapter;
88 import org.simantics.diagram.synchronization.SynchronizationHints;
89 import org.simantics.diagram.synchronization.graph.AddElement;
90 import org.simantics.diagram.synchronization.graph.BasicResources;
91 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
92 import org.simantics.diagram.synchronization.graph.ElementLoader;
93 import org.simantics.diagram.synchronization.graph.ElementReorder;
94 import org.simantics.diagram.synchronization.graph.ElementWriter;
95 import org.simantics.diagram.synchronization.graph.GraphSynchronizationContext;
96 import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;
97 import org.simantics.diagram.synchronization.graph.ModificationQueue;
98 import org.simantics.diagram.synchronization.graph.TagChange;
99 import org.simantics.diagram.synchronization.graph.TransformElement;
100 import org.simantics.diagram.synchronization.graph.layer.GraphLayer;
101 import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;
102 import org.simantics.diagram.ui.DiagramModelHints;
103 import org.simantics.g2d.canvas.Hints;
104 import org.simantics.g2d.canvas.ICanvasContext;
105 import org.simantics.g2d.connection.ConnectionEntity;
106 import org.simantics.g2d.connection.EndKeyOf;
107 import org.simantics.g2d.connection.TerminalKeyOf;
108 import org.simantics.g2d.diagram.DiagramClass;
109 import org.simantics.g2d.diagram.DiagramHints;
110 import org.simantics.g2d.diagram.DiagramMutator;
111 import org.simantics.g2d.diagram.DiagramUtils;
112 import org.simantics.g2d.diagram.IDiagram;
113 import org.simantics.g2d.diagram.IDiagram.CompositionListener;
114 import org.simantics.g2d.diagram.IDiagram.CompositionVetoListener;
115 import org.simantics.g2d.diagram.handler.DataElementMap;
116 import org.simantics.g2d.diagram.handler.ElementFactory;
117 import org.simantics.g2d.diagram.handler.Relationship;
118 import org.simantics.g2d.diagram.handler.RelationshipHandler;
119 import org.simantics.g2d.diagram.handler.SubstituteElementClass;
120 import org.simantics.g2d.diagram.handler.Topology;
121 import org.simantics.g2d.diagram.handler.Topology.Connection;
122 import org.simantics.g2d.diagram.handler.Topology.Terminal;
123 import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType;
124 import org.simantics.g2d.diagram.impl.Diagram;
125 import org.simantics.g2d.diagram.participant.ElementPainter;
126 import org.simantics.g2d.element.ElementClass;
127 import org.simantics.g2d.element.ElementHints;
128 import org.simantics.g2d.element.ElementHints.DiscardableKey;
129 import org.simantics.g2d.element.ElementUtils;
130 import org.simantics.g2d.element.IElement;
131 import org.simantics.g2d.element.IElementClassProvider;
132 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
133 import org.simantics.g2d.element.handler.ElementHandler;
134 import org.simantics.g2d.element.handler.ElementLayerListener;
135 import org.simantics.g2d.element.handler.TerminalTopology;
136 import org.simantics.g2d.element.impl.Element;
137 import org.simantics.g2d.layers.ILayer;
138 import org.simantics.g2d.layers.ILayersEditor;
139 import org.simantics.g2d.routing.RouterFactory;
140 import org.simantics.scenegraph.INode;
141 import org.simantics.scenegraph.profile.DataNodeConstants;
142 import org.simantics.scenegraph.profile.DataNodeMap;
143 import org.simantics.scenegraph.profile.common.ProfileObserver;
144 import org.simantics.structural2.modelingRules.IModelingRules;
145 import org.simantics.utils.datastructures.ArrayMap;
146 import org.simantics.utils.datastructures.MapSet;
147 import org.simantics.utils.datastructures.Pair;
148 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
149 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
150 import org.simantics.utils.datastructures.hints.IHintContext.Key;
151 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
152 import org.simantics.utils.datastructures.hints.IHintListener;
153 import org.simantics.utils.datastructures.hints.IHintObservable;
154 import org.simantics.utils.datastructures.map.AssociativeMap;
155 import org.simantics.utils.datastructures.map.Associativity;
156 import org.simantics.utils.datastructures.map.Tuple;
157 import org.simantics.utils.strings.EString;
158 import org.simantics.utils.threads.ThreadUtils;
159 import org.simantics.utils.threads.logger.ITask;
160 import org.simantics.utils.threads.logger.ThreadLogger;
161
162 /**
163  * This class loads a diagram contained in the graph database into the runtime
164  * diagram model and synchronizes changes in the graph into the run-time
165  * diagram. Any modifications to the graph model will be reflected to the
166  * run-time diagram model. Hence the name GraphToDiagramSynchronizer.
167  * 
168  * <p>
169  * This class does not in itself support modification of the graph diagram
170  * model. This manipulation is meant to be performed through a
171  * {@link DiagramMutator} implementation that is installed into the diagram
172  * using the {@link DiagramHints#KEY_MUTATOR} hint key.
173  * 
174  * This implementations is built to only support diagrams defined in the graph
175  * model as indicated in <a
176  * href="https://www.simantics.org/wiki/index.php/Org.simantics.diagram" >this
177  * </a> document.
178  * 
179  * <p>
180  * The synchronizer in itself is an {@link IDiagramLoader} which means that it
181  * can be used for loading a diagram from the graph. In order for the
182  * synchronizer to keep tracking the graph diagram model for changes the diagram
183  * must be loaded with it. The tracking is implemented using graph database
184  * queries. If you just want to load the diagram but detach it from the
185  * synchronizer's tracking mechanisms, all you need to do is to dispose the
186  * synchronizer after loading the diagram.
187  * 
188  * <p>
189  * This class guarantees that a single diagram element (IElement) representing a
190  * single back-end object ({@link ElementHints#KEY_OBJECT}) will stay the same
191  * object for the same back-end object.
192  * 
193  * <p>
194  * TODO: Currently it just happens that {@link GraphToDiagramSynchronizer}
195  * contains {@link DefaultDiagramMutator} which depends on some internal details
196  * of {@link GraphToDiagramSynchronizer} but it should be moved out of here by
197  * introducing new interfaces.
198  * 
199  * <h2>Basic usage example</h2>
200  * <p>
201  * This example shows how to initialize {@link GraphToDiagramSynchronizer} for a
202  * specified {@link ICanvasContext} and load a diagram from a specified diagram
203  * resource in the graph.
204  * 
205  * <pre>
206  * IDiagram loadDiagram(final ICanvasContext canvasContext, RequestProcessor processor, Resource diagramResource,
207  *         ResourceArray structuralPath) throws DatabaseException {
208  *     GraphToDiagramSynchronizer synchronizer = processor.syncRequest(new Read&lt;GraphToDiagramSynchronizer&gt;() {
209  *         public GraphToDiagramSynchronizer perform(ReadGraph graph) throws DatabaseException {
210  *             return new GraphToDiagramSynchronizer(graph, canvasContext, createElementClassProvider(graph));
211  *         }
212  *     });
213  *     IDiagram d = requestProcessor
214  *             .syncRequest(new DiagramLoadQuery(diagramResource, structuralPath, synchronizer, null));
215  *     return d;
216  * }
217  * 
218  * protected IElementClassProvider createElementClassProvider(ReadGraph graph) {
219  *     DiagramResource dr = DiagramResource.getInstance(graph);
220  *     return ElementClassProviders.mappedProvider(ElementClasses.CONNECTION, DefaultConnectionClassFactory.CLASS
221  *             .newClassWith(new ResourceAdapterImpl(dr.Connection)), ElementClasses.FLAG, FlagClassFactory
222  *             .createFlagClass(dr.Flag));
223  * }
224  * </pre>
225  * 
226  * <p>
227  * TODO: make GraphToDiagramSynchronizer a canvas participant to make it more
228  * uniform with the rest of the canvas system. This does not mean that G2DS must
229  * be attached to an ICanvasContext in order to be used, rather that it can be
230  * attached to one.
231  * 
232  * <p>
233  * TODO: test that detaching the synchronizer via {@link #dispose()} actually
234  * works.
235  * <p>
236  * TODO: remove {@link DefaultDiagramMutator} and all {@link DiagramMutator}
237  * stuff altogether
238  * 
239  * <p>
240  * TODO: diagram connection loading has no listener
241  * 
242  * @author Tuukka Lehtonen
243  * 
244  * @see GraphElementClassFactory
245  * @see GraphElementFactory
246  * @see ElementLoader
247  * @see ElementWriter
248  * @see IHintSynchronizer
249  * @see CopyAdvisor
250  */
251 public class GraphToDiagramSynchronizer extends AbstractDisposable implements IDiagramLoader, IModifiableSynchronizationContext {
252
253     /**
254      * Controls whether the class adds hint listeners to each diagram element
255      * that try to perform basic sanity checks on changes happening in element
256      * hints. Having this will immediately inform you of bugs that corrupt the
257      * diagram model within the element hints in some way.
258      */
259     private static final boolean USE_ELEMENT_VALIDATING_LISTENERS = false;
260
261     /**
262      * These keys are used to hang on to Connection instances of edges that will
263      * be later installed as EndKeyOf/TerminalKeyOf hints into the loaded
264      * element during the "graph to diagram update transaction".
265      */
266     private static final Key     KEY_CONNECTION_BEGIN_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_BEGIN_PLACEHOLDER");
267     private static final Key     KEY_CONNECTION_END_PLACEHOLDER   = new KeyOf(PlaceholderConnection.class, "CONNECTION_END_PLACEHOLDER");
268
269     /**
270      * Stored into an edge node during connection edge requests using the
271      * KEY_CONNECTION_BEGIN_PLACEHOLDER and KEY_CONNECTION_END_PLACEHOLDER keys.
272      */
273     static class PlaceholderConnection {
274         public final EdgeEnd end;
275         public final Object node;
276         public final Terminal terminal;
277         public PlaceholderConnection(EdgeEnd end, Object node, Terminal terminal) {
278             this.end = end;
279             this.node = node;
280             this.terminal = terminal;
281         }
282     }
283
284     /**
285      * Indicates to the diagram CompositionListener of this synchronizer that is
286      * should deny all relationships for the element this hint is attached to.
287      */
288     private static final Key        KEY_REMOVE_RELATIONSHIPS = new KeyOf(Boolean.class, "REMOVE_RELATIONSHIPS");
289
290     static ErrorHandler             errorHandler             = LogErrorHandler.INSTANCE;
291
292     /**
293      * The canvas context which is being synchronized with the graph. Received
294      * during construction.
295      * 
296      * <p>
297      * Is not nulled during disposal of this class since internal listener's
298      * life-cycles depend on canvas.isDisposed.
299      */
300     ICanvasContext                  canvas;
301
302     /**
303      * The session used by this synchronizer. Received during construction.
304      */
305     Session                         session;
306
307     /**
308      * Locked while updating diagram contents from the graph.
309      */
310     ReentrantLock                   diagramUpdateLock        = new ReentrantLock();
311
312     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
313     // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING BEGIN
314     // ------------------------------------------------------------------------
315
316     /**
317      * Holds a GraphToDiagramUpdater instance while a diagram content update is
318      * currently in progress.
319      *
320      * <p>
321      * Basically this is a hack solution to the problem of properly finding
322      * newly added Resource<->IElement mappings while loading the diagram
323      * contents. See {@link DataElementMapImpl} for why this is necessary.
324      */
325     GraphToDiagramUpdater                   currentUpdater                   = null;
326
327     /**
328      * A map from data objects to elements. Elements should already contain the
329      * data objects as {@link ElementHints#KEY_OBJECT} hints.
330      */
331     ConcurrentMap<Object, IElement>         dataElement                      = new ConcurrentHashMap<Object, IElement>();
332
333     /**
334      * Temporary structure for single-threaded use in #{@link DiagramUpdater}.
335      */
336     Collection<Connection>                  tempConnections = new ArrayList<Connection>();
337
338     /**
339      * A dummy class of which an instance will be given to each new edge element
340      * to make {@link TerminalKeyOf} keys unique for each edge.
341      */
342     static class TransientElementObject {
343         @Override
344         public String toString() {
345             return "MUTATOR GENERATED (hash=" + System.identityHashCode(this) + ")";
346         }
347     }
348
349     private static class ConnectionChildren {
350         public Set<IElement> branchPoints;
351         public Set<IElement> segments;
352
353         public ConnectionChildren(Set<IElement> branchPoints, Set<IElement> segments) {
354             this.branchPoints = branchPoints;
355             this.segments = segments;
356         }
357     }
358
359     ListenerSupport canvasListenerSupport = new ListenerSupport() {
360         @Override
361         public void exception(Throwable t) {
362             error(t);
363         }
364
365         @Override
366         public boolean isDisposed() {
367             return !isAlive() || canvas.isDisposed();
368         }
369     };
370
371     /**
372      * @see ElementHints#KEY_CONNECTION_ENTITY
373      */
374     class ConnectionEntityImpl implements ConnectionEntity {
375
376         /**
377          * The connection instance resource in the graph backend.
378          *
379          * May be <code>null</code> if the connection has not been synchronized
380          * yet.
381          */
382         Resource                 connection;
383
384         /**
385          * The connection type resource in the graph backend.
386          *
387          * May be <code>null</code> if the connection has not been synchronized
388          * yet.
389          */
390         Resource                 connectionType;
391
392         /**
393          * The connection entity element which is a part of the diagram.
394          */
395         IElement                 connectionElement;
396
397         /**
398          * List of backend-synchronized branch points that are part of this
399          * connection.
400          */
401         Collection<Resource>     branchPoints        = Collections.emptyList();
402
403         /**
404          * List of backend-synchronized edges that are part of this connection.
405          */
406         Collection<EdgeResource> segments            = Collections.emptyList();
407
408         Set<Object>              removedBranchPoints = new HashSet<Object>(4);
409
410         Set<Object>              removedSegments     = new HashSet<Object>(4);
411
412         /**
413          * List of non-backend-synchronized branch point element that are part
414          * of this connection.
415          */
416         List<IElement>           branchPointElements = new ArrayList<IElement>(1);
417
418         /**
419          * List of non-backend-synchronized edge element that are part of this
420          * connection.
421          */
422         List<IElement>           segmentElements     = new ArrayList<IElement>(2);
423
424         ConnectionListener       listener;
425
426         ConnectionEntityImpl(Resource connection, Resource connectionType, IElement connectionElement) {
427             this.connection = connection;
428             this.connectionType = connectionType;
429             this.connectionElement = connectionElement;
430         }
431
432         ConnectionEntityImpl(Resource connectionType, IElement connectionElement) {
433             this.connectionType = connectionType;
434             this.connectionElement = connectionElement;
435         }
436
437         ConnectionEntityImpl(ReadGraph graph, Resource connection, IElement connectionElement)
438         throws NoSingleResultException, ServiceException {
439             this.connection = connection;
440             this.connectionType = graph.getSingleType(connection, br.DIA.Connection);
441             this.connectionElement = connectionElement;
442         }
443
444         @Override
445         public IElement getConnection() {
446             return connectionElement;
447         }
448
449         public Object getConnectionObject() {
450             return connection;
451         }
452
453         public IElement getConnectionElement() {
454             if (connectionElement == null)
455                 return getMappedConnectionElement();
456             return connectionElement;
457         }
458
459         private IElement getMappedConnectionElement() {
460             IElement ce = null;
461             if (connection != null)
462                 ce = getMappedElement(connection);
463             return ce == null ? connectionElement : ce;
464         }
465
466         void fix() {
467             Collection<IElement> segments = getSegments(null);
468
469             // Remove all TerminalKeyOf hints from branch points that do not
470             // match
471             ArrayList<TerminalKeyOf> pruned = null;
472             for (IElement bp : getBranchPoints(null)) {
473                 if (pruned == null)
474                     pruned = new ArrayList<TerminalKeyOf>(4);
475                 pruned.clear();
476                 for (Map.Entry<TerminalKeyOf, Object> entry : bp.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
477                     // First check that the terminal matches.
478                     Connection c = (Connection) entry.getValue();
479                     if (!segments.contains(c.edge))
480                         pruned.add(entry.getKey());
481                 }
482                 removeNodeTopologyHints((Element) bp, pruned);
483             }
484         }
485
486         public ConnectionChildren getConnectionChildren() {
487             Set<IElement> bps = Collections.emptySet();
488             Set<IElement> segs = Collections.emptySet();
489             if (!branchPoints.isEmpty()) {
490                 bps = new HashSet<IElement>(branchPoints.size());
491                 for (Resource bp : branchPoints) {
492                     IElement e = getMappedElement(bp);
493                     if (e != null)
494                         bps.add(e);
495                 }
496             }
497             if (!segments.isEmpty()) {
498                 segs = new HashSet<IElement>(segments.size());
499                 for (EdgeResource seg : segments) {
500                     IElement e = getMappedElement(seg);
501                     if (e != null)
502                         segs.add(e);
503                 }
504             }
505             return new ConnectionChildren(bps, segs);
506         }
507
508         public void setData(Collection<EdgeResource> segments, Collection<Resource> branchPoints) {
509             // System.out.println("setData " + segments.size());
510             this.branchPoints = branchPoints;
511             this.segments = segments;
512
513             // Reset the added/removed state of segments and branchpoints.
514             this.removedBranchPoints = new HashSet<Object>(4);
515             this.removedSegments = new HashSet<Object>(4);
516             this.branchPointElements = new ArrayList<IElement>(4);
517             this.segmentElements = new ArrayList<IElement>(4);
518         }
519
520         public void fireListener(ConnectionChildren old, ConnectionChildren current) {
521             if (listener != null) {
522                 List<IElement> removed = new ArrayList<IElement>();
523                 List<IElement> added = new ArrayList<IElement>();
524
525                 for (IElement oldBp : old.branchPoints)
526                     if (!current.branchPoints.contains(oldBp))
527                         removed.add(oldBp);
528                 for (IElement oldSeg : old.segments)
529                     if (!current.segments.contains(oldSeg))
530                         removed.add(oldSeg);
531
532                 for (IElement bp : current.branchPoints)
533                     if (!old.branchPoints.contains(bp))
534                         added.add(bp);
535                 for (IElement seg : current.segments)
536                     if (!old.segments.contains(seg))
537                         added.add(seg);
538
539                 if (!removed.isEmpty() || !added.isEmpty()) {
540                     listener.connectionChanged(new ConnectionEvent(this.connectionElement, removed, added));
541                 }
542             }
543         }
544
545         @Override
546         public Collection<IElement> getBranchPoints(Collection<IElement> result) {
547             if (result == null)
548                 result = new ArrayList<IElement>(branchPoints.size());
549             for (Resource bp : branchPoints) {
550                 if (!removedBranchPoints.contains(bp)) {
551                     IElement e = getMappedElement(bp);
552                     if (e != null)
553                         result.add(e);
554                 }
555             }
556             result.addAll(branchPointElements);
557             return result;
558         }
559
560         @Override
561         public Collection<IElement> getSegments(Collection<IElement> result) {
562             if (result == null)
563                 result = new ArrayList<IElement>(segments.size());
564             for (EdgeResource seg : segments) {
565                 if (!removedSegments.contains(seg)) {
566                     IElement e = getMappedElement(seg);
567                     if (e != null)
568                         result.add(e);
569                 }
570             }
571             result.addAll(segmentElements);
572             return result;
573         }
574
575         @Override
576         public Collection<Connection> getTerminalConnections(Collection<Connection> result) {
577             if (result == null)
578                 result = new ArrayList<Connection>(segments.size() * 2);
579             Set<org.simantics.utils.datastructures.Pair<IElement, Terminal>> processed = new HashSet<org.simantics.utils.datastructures.Pair<IElement, Terminal>>();
580             for (EdgeResource seg : segments) {
581                 IElement edge = getMappedElement(seg);
582                 if (edge != null) {
583                     for (EndKeyOf key : EndKeyOf.KEYS) {
584                         Connection c = edge.getHint(key);
585                         if (c != null && (c.terminal instanceof ResourceTerminal) && processed.add(Pair.make(c.node, c.terminal)))
586                             result.add(c);
587                     }
588                 }
589             }
590             return result;
591         }
592
593         @Override
594         public void setListener(ConnectionListener listener) {
595             this.listener = listener;
596         }
597
598         @Override
599         public String toString() {
600             return getClass().getSimpleName() + "[resource=" + connection + ", branch points=" + branchPoints
601             + ", segments=" + segments + ", connectionElement=" + connectionElement
602             + ", branch point elements=" + branchPointElements + ", segment elements=" + segmentElements
603             + ", removed branch points=" + removedBranchPoints + ", removed segments=" + removedSegments + "]";
604         }
605
606     }
607
608     /**
609      * A map from connection data objects to connection entities. The connection
610      * part elements should already contain the data objects as
611      * {@link ElementHints#KEY_OBJECT} hints.
612      */
613     ConcurrentMap<Object, ConnectionEntityImpl> dataConnection = new ConcurrentHashMap<Object, ConnectionEntityImpl>();
614
615     /**
616      * @param data
617      * @param element
618      */
619     void mapElement(final Object data, final IElement element) {
620         if (!(element instanceof Element)) {
621             throw new IllegalArgumentException("mapElement: expected instance of Element, got " + element + " with data " + data);
622         }
623         assert data != null;
624         assert element != null;
625         if (DebugPolicy.DEBUG_MAPPING)
626             new Exception(Thread.currentThread() + " MAPPING: " + data + " -> " + element).printStackTrace();
627         dataElement.put(data, element);
628     }
629
630     /**
631      * @param data
632      * @return
633      */
634     IElement getMappedElement(final Object data) {
635         assert (data != null);
636         IElement element = dataElement.get(data);
637         return element;
638     }
639
640     IElement getMappedElementByElementObject(IElement e) {
641         if (e == null)
642             return null;
643         Object o = e.getHint(ElementHints.KEY_OBJECT);
644         if (o == null)
645             return null;
646         return getMappedElement(o);
647     }
648
649     /**
650      * @param data
651      * @return
652      */
653     IElement assertMappedElement(final Object data) {
654         IElement element = dataElement.get(data);
655         assert element != null;
656         return element;
657     }
658
659     /**
660      * @param data
661      * @return
662      */
663     IElement unmapElement(final Object data) {
664         IElement element = dataElement.remove(data);
665         if (DebugPolicy.DEBUG_MAPPING)
666             new Exception(Thread.currentThread() + " UN-MAPPED: " + data + " -> " + element).printStackTrace();
667         return element;
668     }
669
670     /**
671      * @param data
672      * @param element
673      */
674     void mapConnection(final Object data, final ConnectionEntityImpl connection) {
675         assert data != null;
676         assert connection != null;
677         if (DebugPolicy.DEBUG_MAPPING)
678             System.out.println(Thread.currentThread() + " MAPPING CONNECTION: " + data + " -> " + connection);
679         dataConnection.put(data, connection);
680     }
681
682     /**
683      * @param data
684      * @return
685      */
686     ConnectionEntityImpl getMappedConnection(final Object data) {
687         ConnectionEntityImpl connection = dataConnection.get(data);
688         return connection;
689     }
690
691     /**
692      * @param data
693      * @return
694      */
695     ConnectionEntityImpl assertMappedConnection(final Object data) {
696         ConnectionEntityImpl connection = getMappedConnection(data);
697         assert connection != null;
698         return connection;
699     }
700
701     /**
702      * @param data
703      * @return
704      */
705     ConnectionEntityImpl unmapConnection(final Object data) {
706         ConnectionEntityImpl connection = dataConnection.remove(data);
707         if (DebugPolicy.DEBUG_MAPPING)
708             System.out.println(Thread.currentThread() + " UN-MAPPED CONNECTION: " + data + " -> " + connection);
709         return connection;
710     }
711
712     class DataElementMapImpl implements DataElementMap {
713         @Override
714         public Object getData(IDiagram d, IElement element) {
715             if (d == null)
716                 throw new NullPointerException("null diagram");
717             if (element == null)
718                 throw new NullPointerException("null element");
719
720             assert ElementUtils.getDiagram(element) == d;
721             return element.getHint(ElementHints.KEY_OBJECT);
722         }
723
724         @Override
725         public IElement getElement(IDiagram d, Object data) {
726             if (d == null)
727                 throw new NullPointerException("null diagram");
728             if (data == null)
729                 throw new NullPointerException("null data");
730
731             GraphToDiagramUpdater updater = currentUpdater;
732             if (updater != null) {
733                 // This HACK is for allowing GraphElementFactory implementations
734                 // to find the IElements they are related to.
735                 IElement e = updater.addedElementMap.get(data);
736                 if (e != null)
737                     return e;
738             }
739
740             IElement e = getMappedElement(data);
741             if (e != null)
742                 return e;
743             return null;
744         }
745     }
746
747     class SubstituteElementClassImpl implements SubstituteElementClass {
748         @Override
749         public ElementClass substitute(IDiagram d, ElementClass ec) {
750             if (d != diagram)
751                 throw new IllegalArgumentException("specified diagram does not have this SubstituteElementClass handler");
752
753             // If the element class is our own, there's no point in creating
754             // a copy of it.
755             if (ec.contains(elementLayerListener))
756                 return ec;
757
758             List<ElementHandler> all = ec.getAll();
759             List<ElementHandler> result = new ArrayList<ElementHandler>(all.size());
760             for (ElementHandler eh : all) {
761                 if (eh instanceof ElementLayerListenerImpl)
762                     result.add(elementLayerListener);
763                 else
764                     result.add(eh);
765             }
766             return ElementClass.compile(result, false).setId(ec.getId());
767         }
768     }
769
770     final DataElementMapImpl         dataElementMap         = new DataElementMapImpl();
771
772     final SubstituteElementClassImpl substituteElementClass = new SubstituteElementClassImpl();
773
774     // ------------------------------------------------------------------------
775     // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING END
776     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
777
778     void warning(String message, Exception e) {
779         errorHandler.warning(message, e);
780     }
781
782     void warning(Exception e) {
783         errorHandler.warning(e.getMessage(), e);
784     }
785
786     void error(String message, Throwable e) {
787         errorHandler.error(message, e);
788     }
789
790     void error(Throwable e) {
791         errorHandler.error(e.getMessage(), e);
792     }
793
794     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
795     // GRAPH MODIFICATION QUEUE BEGIN
796     // ------------------------------------------------------------------------
797
798     ModificationQueue                 modificationQueue;
799     IModifiableSynchronizationContext synchronizationContext;
800
801     @Override
802     public <T> T set(Key key, Object value) {
803         if (synchronizationContext == null)
804             return null;
805         return synchronizationContext.set(key, value);
806     }
807
808     @Override
809     public <T> T get(Key key) {
810         if (synchronizationContext == null)
811             return null;
812         return synchronizationContext.get(key);
813     }
814
815     // ------------------------------------------------------------------------
816     // GRAPH MODIFICATION QUEUE END
817     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
818
819     /**
820      * The previously loaded version of the diagram content. This is needed to
821      * calculate the difference between new and old content on each
822      * {@link #diagramGraphUpdater(DiagramContents)} invocation.
823      */
824     DiagramContents       previousContent;
825
826     /**
827      * The diagram instance that this synchronizer is synchronizing with the
828      * graph.
829      */
830     IDiagram              diagram;
831
832     /**
833      * An observer for diagram profile entries. Has a life-cycle that must be
834      * bound to the life-cycle of this GraphToDiagramSynchronizer instance.
835      * Disposed if synchronizer is detached in {@link #doDispose()} or finally
836      * when the canvas is disposed.
837      */
838     ProfileObserver       profileObserver;
839
840     IElementClassProvider elementClassProvider;
841
842     BasicResources        br;
843
844     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
845     // Internal state machine handling BEGIN
846     // ------------------------------------------------------------------------
847
848     /**
849      * An indicator for the current state of this synchronizer. This is a simple
850      * state machine with the following possible state transitions:
851      *
852      * <ul>
853      * <li>INITIAL -> LOADING, DISPOSED</li>
854      * <li>LOADING -> IDLE</li>
855      * <li>IDLE -> UPDATING_DIAGRAM, DISPOSED</li>
856      * <li>UPDATING_DIAGRAM -> IDLE</li>
857      * </ul>
858      * 
859      * Start states: INITIAL
860      * End states: DISPOSED
861      */
862     static enum State {
863         /**
864          * The initial state of the synchronizer.
865          */
866         INITIAL,
867         /**
868          * The synchronizer is performing load-time initialization. During this
869          * time no canvas refreshes should be forced.
870          */
871         LOADING,
872         /**
873          * The synchronizer is performing updates to the diagram model. This
874          * process goes on in the canvas context thread.
875          */
876         UPDATING_DIAGRAM,
877         /**
878          * The synchronizer is doing nothing.
879          */
880         IDLE,
881         /**
882          * The synchronized diagram is being disposed, which means that this
883          * synchronizer should not accept any further actions.
884          */
885         DISPOSED,
886     }
887
888     public static final EnumSet<State> FROM_INITIAL          = EnumSet.of(State.LOADING, State.DISPOSED);
889     public static final EnumSet<State> FROM_LOADING          = EnumSet.of(State.IDLE);
890     public static final EnumSet<State> FROM_UPDATING_DIAGRAM = EnumSet.of(State.IDLE);
891     public static final EnumSet<State> FROM_IDLE             = EnumSet.of(State.UPDATING_DIAGRAM, State.DISPOSED);
892     public static final EnumSet<State> NO_STATES             = EnumSet.noneOf(State.class);
893
894     private EnumSet<State> validTargetStates(State start) {
895         switch (start) {
896             case INITIAL: return FROM_INITIAL;
897             case LOADING: return FROM_LOADING;
898             case UPDATING_DIAGRAM: return FROM_UPDATING_DIAGRAM;
899             case IDLE: return FROM_IDLE;
900             case DISPOSED: return NO_STATES;
901         }
902         throw new IllegalArgumentException("unrecognized state " + start);
903     }
904
905     private String validateStateChange(State start, State end) {
906         EnumSet<State> validTargets = validTargetStates(start);
907         if (!validTargets.contains(end))
908             return "Cannot transition from " + start + " state to " + end + ".";
909         return null;
910     }
911
912     /**
913      * The current state of the synchronizer. At start it is
914      * {@link State#INITIAL} and after loading it is {@link State#IDLE}.
915      */
916     State                              synchronizerState     = State.INITIAL;
917
918     /**
919      * A condition variable used to synchronize synchronizer state changes.
920      */
921     ReentrantLock                      stateLock             = new ReentrantLock();
922
923     /**
924      * A condition that is signaled when the synchronizer state changes to IDLE.
925      */
926     Condition                          idleCondition         = stateLock.newCondition();
927
928     State getState() {
929         return synchronizerState;
930     }
931
932     /**
933      * Activates the desired state after making sure that the synchronizer has
934      * been IDLE in between its current state and this invocation.
935      *
936      * @param newState the new state to activate
937      * @throws InterruptedException if waiting for IDLE state gets interrupted
938      * @throws IllegalStateException if the requested transition from the
939      *         current state to the desired state would be illegal.
940      */
941     void activateState(State newState, boolean waitForIdle) throws InterruptedException {
942         stateLock.lock();
943         try {
944             // Wait until the state of the synchronizer IDLEs if necessary.
945             if (waitForIdle && synchronizerState != State.IDLE) {
946                 String error = validateStateChange(synchronizerState, State.IDLE);
947                 if (error != null)
948                     throw new IllegalStateException(error);
949
950                 while (synchronizerState != State.IDLE) {
951                     if (DebugPolicy.DEBUG_STATE)
952                         System.out.println(Thread.currentThread() + " waiting for IDLE state, current="
953                                 + synchronizerState);
954                     idleCondition.await();
955                 }
956             }
957
958             String error = validateStateChange(synchronizerState, newState);
959             if (error != null)
960                 throw new IllegalStateException(error);
961
962             if (DebugPolicy.DEBUG_STATE)
963                 System.out.println(Thread.currentThread() + " activated state " + newState);
964             this.synchronizerState = newState;
965
966             if (newState == State.IDLE)
967                 idleCondition.signalAll();
968         } finally {
969             stateLock.unlock();
970         }
971     }
972
973     void idle() throws IllegalStateException, InterruptedException {
974         activateState(State.IDLE, false);
975     }
976
977     static interface StateRunnable extends Runnable {
978         void execute() throws InvocationTargetException;
979
980         public abstract class Stub implements StateRunnable {
981             @Override
982             public void run() {
983             }
984
985             @Override
986             public final void execute() throws InvocationTargetException {
987                 try {
988                     perform();
989                 } catch (Exception e) {
990                     throw new InvocationTargetException(e);
991                 } catch (LinkageError e) {
992                     throw new InvocationTargetException(e);
993                 }
994             }
995
996             protected abstract void perform() throws Exception;
997         }
998     }
999
1000     protected void runInState(State state, StateRunnable runnable) throws InvocationTargetException {
1001         try {
1002             activateState(state, true);
1003             try {
1004                 runnable.execute();
1005             } finally {
1006                 idle();
1007             }
1008         } catch (IllegalStateException e) {
1009             throw new InvocationTargetException(e);
1010         } catch (InterruptedException e) {
1011             throw new InvocationTargetException(e);
1012         }
1013     }
1014
1015     protected void safeRunInState(State state, StateRunnable runnable) {
1016         try {
1017             runInState(state, runnable);
1018         } catch (InvocationTargetException e) {
1019             error("Failed to run runnable " + runnable + " in state " + state + ". See exception for details.", e
1020                     .getCause());
1021         }
1022     }
1023
1024     // ------------------------------------------------------------------------
1025     // Internal state machine handling END
1026     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1027
1028     /**
1029      * @param processor
1030      * @param canvas
1031      * @param elementClassProvider
1032      * @throws DatabaseException
1033      */
1034     public GraphToDiagramSynchronizer(RequestProcessor processor, ICanvasContext canvas, IElementClassProvider elementClassProvider) throws DatabaseException {
1035         if (processor == null)
1036             throw new IllegalArgumentException("null processor");
1037         if (canvas == null)
1038             throw new IllegalArgumentException("null canvas");
1039         if (elementClassProvider == null)
1040             throw new IllegalArgumentException("null element class provider");
1041
1042         this.session = processor.getSession();
1043         this.canvas = canvas;
1044         this.modificationQueue = new ModificationQueue(session, errorHandler);
1045
1046         processor.syncRequest(new ReadRequest() {
1047             @Override
1048             public void run(ReadGraph graph) throws DatabaseException {
1049                 initializeResources(graph);
1050             }
1051         });
1052
1053         this.elementClassProvider = elementClassProvider;
1054         synchronizationContext.set(SynchronizationHints.ELEMENT_CLASS_PROVIDER, elementClassProvider);
1055
1056         attachSessionListener(processor.getSession());
1057     }
1058
1059     /**
1060      * @return
1061      */
1062     public IElementClassProvider getElementClassProvider() {
1063         return elementClassProvider;
1064     }
1065
1066     public Session getSession() {
1067         return session;
1068     }
1069
1070     public ICanvasContext getCanvasContext() {
1071         return canvas;
1072     }
1073
1074     public IDiagram getDiagram() {
1075         return diagram;
1076     }
1077
1078     void setCanvasDirty() {
1079         ICanvasContext c = canvas;
1080         if (synchronizerState != State.LOADING && c != null && !c.isDisposed()) {
1081             // TODO: Consider adding an invocation limiter here, to prevent
1082             // calling setDirty too often if enough time hasn't passed yet since
1083             // the last invocation.
1084             c.getContentContext().setDirty();
1085         }
1086     }
1087
1088     /**
1089      * @param elementType
1090      * @return
1091      * @throws DatabaseException if ElementClass cannot be retrieved
1092      */
1093     public ElementClass getNodeClass(Resource elementType) throws DatabaseException {
1094         return getNodeClass(session, elementType);
1095     }
1096
1097     public ElementClass getNodeClass(RequestProcessor processor, Resource elementType) throws DatabaseException {
1098         ElementClass ec = processor.syncRequest(new NodeClassRequest(canvas, diagram, elementType, true));
1099         return ec;
1100     }
1101
1102     @Override
1103     protected void doDispose() {
1104         try {
1105             try {
1106                 stateLock.lock();
1107                 boolean isInitial = getState() == State.INITIAL;
1108                 activateState(State.DISPOSED, !isInitial);
1109             } finally {
1110                 stateLock.unlock();
1111             }
1112         } catch (InterruptedException e) {
1113             // Shouldn't happen.
1114             e.printStackTrace();
1115         } finally {
1116             detachSessionListener();
1117
1118             if (profileObserver != null) {
1119                 profileObserver.dispose();
1120                 profileObserver = null;
1121             }
1122
1123             if (diagram != null) {
1124                 diagram.removeCompositionListener(diagramListener);
1125                 diagram.removeCompositionVetoListener(diagramListener);
1126             }
1127
1128             // TODO: we should probably leave the dataElement map as is since DataElementMap needs it even after the synchronizer has been disposed.
1129             // Currently the diagram's DataElementMap will be broken after disposal.
1130 //            dataElement.clear();
1131 //            dataConnection.clear();
1132
1133             if (layerManager != null) {
1134                 layerManager.dispose();
1135             }
1136
1137             // Let GC work.
1138             modificationQueue.dispose();
1139         }
1140     }
1141
1142     void initializeResources(ReadGraph graph) {
1143         this.br = new BasicResources(graph);
1144
1145         // Initialize synchronization context
1146         synchronizationContext = new GraphSynchronizationContext(graph, modificationQueue);
1147         synchronizationContext.set(SynchronizationHints.ERROR_HANDLER, errorHandler);
1148     }
1149
1150     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1151     // LAYERS BEGIN
1152     // ------------------------------------------------------------------------
1153
1154     GraphLayerManager layerManager;
1155
1156     /**
1157      * A common handler for all elements that is used to listen to changes in
1158      * element visibility and focusability on diagram layers.
1159      */
1160     class ElementLayerListenerImpl implements ElementLayerListener {
1161         private static final long serialVersionUID = -3410052116598828129L;
1162
1163         @Override
1164         public void visibilityChanged(IElement e, ILayer layer, boolean visible) {
1165             if (!isAlive())
1166                 return;
1167             if (DebugPolicy.DEBUG_LAYERS)
1168                 System.out.println("visibility changed: " + e + ", " + layer + ", " + visible);
1169             GraphLayer gl = layerManager.getGraphLayer(layer.getName());
1170             if (gl != null) {
1171                 changeTag(e, gl.getVisible(), visible);
1172             }
1173         }
1174
1175         @Override
1176         public void focusabilityChanged(IElement e, ILayer layer, boolean focusable) {
1177             if (!isAlive())
1178                 return;
1179             if (DebugPolicy.DEBUG_LAYERS)
1180                 System.out.println("focusability changed: " + e + ", " + layer + ", " + focusable);
1181             GraphLayer gl = layerManager.getGraphLayer(layer.getName());
1182             if (gl != null) {
1183                 changeTag(e, gl.getFocusable(), focusable);
1184             }
1185         }
1186
1187         void changeTag(IElement e, Resource tag, boolean set) {
1188             Object object = e.getHint(ElementHints.KEY_OBJECT);
1189             Resource tagged = null;
1190             if (object instanceof Resource) {
1191                 tagged = (Resource) object;
1192             } else if (object instanceof EdgeResource) {
1193                 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
1194                 if (ce instanceof ConnectionEntityImpl) {
1195                     tagged = ((ConnectionEntityImpl) ce).connection;
1196                 }
1197             }
1198             if (tagged == null)
1199                 return;
1200
1201             modificationQueue.async(new TagChange(tagged, tag, set), null);
1202         }
1203     };
1204
1205     ElementLayerListenerImpl elementLayerListener = new ElementLayerListenerImpl();
1206
1207     // ------------------------------------------------------------------------
1208     // LAYERS END
1209     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1210
1211     @Override
1212     public IDiagram loadDiagram(IProgressMonitor progressMonitor, ReadGraph g, final String modelURI, final Resource diagram, final Resource runtime, final ResourceArray structuralPath,
1213             IHintObservable initialHints) throws DatabaseException {
1214         if (DebugPolicy.DEBUG_LOAD)
1215             System.out.println(Thread.currentThread() + " loadDiagram: " + NameUtils.getSafeName(g, diagram));
1216
1217         SubMonitor monitor = SubMonitor.convert(progressMonitor, "Load Diagram", 100);
1218
1219         Object loadTask = Timing.BEGIN("GDS.loadDiagram");
1220         try {
1221             try {
1222                 activateState(State.LOADING, false);
1223             } catch (IllegalStateException e) {
1224                 // Disposed already before loading even began.
1225                 this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
1226                 return this.diagram;
1227             }
1228             try {
1229                 // Query for diagram class
1230                 Resource diagramClassResource = g.getPossibleType(diagram, br.DIA.Composite);
1231                 if (diagramClassResource != null) {
1232                     // Spawn new diagram
1233                     Object task = Timing.BEGIN("GDS.DiagramClassRequest");
1234                     final DiagramClass diagramClass = g.syncRequest(new DiagramClassRequest(diagram));
1235                     Timing.END(task);
1236                     final IDiagram d = Diagram.spawnNew(diagramClass);
1237                     {
1238                         d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, diagram);
1239                         if (runtime != null)
1240                             d.setHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE, runtime);
1241                         if (modelURI != null)
1242                             d.setHint(DiagramModelHints.KEY_DIAGRAM_MODEL_URI, modelURI);
1243                         d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE_ARRAY, structuralPath);
1244
1245                         // Set dumb default routing when DiagramClass does not
1246                         // predefine the default connection routing for the diagram.
1247                         if (!d.containsHint(DiagramHints.ROUTE_ALGORITHM))
1248                             d.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, false));
1249
1250                         d.setHint(SynchronizationHints.CONTEXT, this);
1251
1252                         // Initialize hints with hints from initialHints if given
1253                         if (initialHints != null) {
1254                             d.setHints(initialHints.getHints());
1255                         }
1256                     }
1257
1258                     // ITask task2 = ThreadLogger.getInstance().begin("loadLayers");
1259                     monitor.subTask("Layers");
1260                     {
1261                         this.layerManager = new GraphLayerManager(g, modificationQueue, diagram);
1262                         synchronizationContext.set(GraphSynchronizationHints.GRAPH_LAYER_MANAGER, this.layerManager);
1263                         ILayersEditor layers = layerManager.loadLayers(d, g, diagram);
1264                         // task2.finish();
1265
1266                         d.setHint(DiagramHints.KEY_LAYERS, layers);
1267                         d.setHint(DiagramHints.KEY_LAYERS_EDITOR, layers);
1268
1269                         d.addCompositionVetoListener(diagramListener);
1270                         d.addCompositionListener(diagramListener);
1271
1272                         this.diagram = d;
1273
1274                         d.setHint(DiagramHints.KEY_MUTATOR, new DefaultDiagramMutator(d, diagram, synchronizationContext));
1275
1276                         // Add default layer if no layers exist.
1277                         // NOTE: this must be done after this.diagram has been set
1278                         // as it will trigger a graph modification which needs the
1279                         // diagram resource.
1280                         // ITask task3 = ThreadLogger.getInstance().begin("addDefaultLayer");
1281 //                        if (layers.getLayers().isEmpty()) {
1282 //                            if (DebugPolicy.DEBUG_LAYERS)
1283 //                                System.out.println("No layers, creating default layer '"
1284 //                                        + DiagramConstants.DEFAULT_LAYER_NAME + "'");
1285 //                            SimpleLayer defaultLayer = new SimpleLayer(DiagramConstants.DEFAULT_LAYER_NAME);
1286 //                            layers.addLayer(defaultLayer);
1287 //                            layers.activate(defaultLayer);
1288 //                        }
1289 //                        // task3.finish();
1290                     }
1291                     monitor.worked(10);
1292
1293                     monitor.subTask("Contents");
1294                     // Discover the plain resources that form the content of the
1295                     // diagram through a separate query. This allows us to
1296                     // separately
1297                     // track changes to the diagram structure itself, not the
1298                     // substructures contained by the structure elements.
1299                     ITask task4 = ThreadLogger.getInstance().begin("DiagramContentRequest1");
1300                     DiagramContentRequest query = new DiagramContentRequest(canvas, diagram, errorHandler);
1301                     g.syncRequest(query, new DiagramContentListener(diagram));
1302                     task4.finish();
1303                     // ITask task5 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
1304                     ITask task42 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
1305                     DiagramContents contents = g.syncRequest(query, TransientCacheAsyncListener.instance());
1306                     //System.err.println("contents: " + contents);
1307                     task42.finish();
1308                     // task5.finish();
1309                     monitor.worked(10);
1310
1311                     monitor.subTask("Graphical elements");
1312                     {
1313                         Object applyDiagramContents = Timing.BEGIN("GDS.applyDiagramContents");
1314                         ITask task6 = ThreadLogger.getInstance().begin("applyDiagramContents");
1315                         processGraphUpdates(g, Collections.singleton(diagramGraphUpdater(contents)));
1316                         task6.finish();
1317                         Timing.END(applyDiagramContents);
1318                     }
1319                     monitor.worked(80);
1320
1321                     DataNodeMap dn = new DataNodeMap() {
1322                         @Override
1323                         public INode getNode(Object data) {
1324                             if (DataNodeConstants.CANVAS_ROOT == data)
1325                                 return canvas.getCanvasNode();
1326                             if (DataNodeConstants.DIAGRAM_ELEMENT_PARENT == data) {
1327                                 ElementPainter ep = canvas.getAtMostOneItemOfClass(ElementPainter.class);
1328                                 return ep != null ? ep.getDiagramElementParentNode() : null;
1329                             }
1330
1331                             DataElementMap emap = GraphToDiagramSynchronizer.this.diagram.getDiagramClass().getSingleItem(DataElementMap.class);
1332                             IElement element = emap.getElement(GraphToDiagramSynchronizer.this.diagram, data);
1333                             if(element == null) return null;
1334                             return element.getHint(ElementHints.KEY_SG_NODE);
1335                         }
1336                     };
1337
1338                     profileObserver = new ProfileObserver(g.getSession(), runtime,
1339                             canvas.getThreadAccess(), canvas, canvas.getSceneGraph(), diagram, 
1340                             ArrayMap.keys(ProfileKeys.DIAGRAM, ProfileKeys.CANVAS, ProfileKeys.NODE_MAP).values(GraphToDiagramSynchronizer.this.diagram, canvas, dn),
1341                             new CanvasNotification(canvas));
1342
1343                     g.getSession().asyncRequest(new AsyncReadRequest() {
1344                         @Override
1345                         public void run(AsyncReadGraph graph) {
1346                             profileObserver.listen(graph, GraphToDiagramSynchronizer.this);
1347                         }
1348                     });
1349
1350                     return d;
1351
1352                 }
1353
1354                 this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
1355                 return this.diagram;
1356
1357             } finally {
1358                 idle();
1359             }
1360         } catch (InterruptedException e) {
1361             throw new RuntimeException(e);
1362         } catch (IllegalStateException e) {
1363             // If the synchronizer was disposed ahead of time, it was done
1364             // for a reason, such as the user having closed the owner editor.
1365             if (!isAlive())
1366                 throw new CancelTransactionException(e);
1367             throw new RuntimeException(e);
1368         } finally {
1369             Timing.END(loadTask);
1370         }
1371     }
1372
1373     static class CanvasNotification implements Runnable {
1374
1375         final private ICanvasContext canvas;
1376
1377         public CanvasNotification(ICanvasContext canvas) {
1378             this.canvas = canvas;
1379         }
1380
1381         public void run() {
1382             canvas.getContentContext().setDirty();
1383         }
1384
1385     }
1386
1387     ArrayList<IModification>        pendingModifications = new ArrayList<IModification>();
1388     MapSet<IElement, IModification> modificationIndex    = new MapSet.Hash<IElement, IModification>();
1389
1390     void addModification(IElement element, IModification modification) {
1391         pendingModifications.add(modification);
1392         if (element != null)
1393             modificationIndex.add(element, modification);
1394
1395     }
1396     class DefaultDiagramMutator implements DiagramMutator {
1397
1398         Map<IElement, Resource> creation = new HashMap<IElement, Resource>();
1399
1400         IDiagram d;
1401         Resource diagram;
1402
1403         IModifiableSynchronizationContext synchronizationContext;
1404
1405         public DefaultDiagramMutator(IDiagram d, Resource diagram, IModifiableSynchronizationContext synchronizationContext) {
1406             this.d = d;
1407             this.diagram = diagram;
1408             this.synchronizationContext = synchronizationContext;
1409
1410             if (synchronizationContext.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER) == null)
1411                 throw new IllegalArgumentException("SynchronizationHints.ELEMENT_CLASS_PROVIDER not available");
1412         }
1413
1414         void assertNotDisposed() {
1415             if (!isAlive())
1416                 throw new IllegalStateException(getClass().getSimpleName() + " is disposed");
1417         }
1418
1419         @Override
1420         public IElement newElement(ElementClass clazz) {
1421             assertNotDisposed();
1422             ElementFactory ef = d.getDiagramClass().getAtMostOneItemOfClass(ElementFactory.class);
1423             IElement element = null;
1424             if (ef != null)
1425                 element = ef.spawnNew(clazz);
1426             else
1427                 element = Element.spawnNew(clazz);
1428
1429             element.setHint(ElementHints.KEY_OBJECT, new TransientElementObject());
1430
1431             addModification(element, new AddElement(synchronizationContext, d, element));
1432
1433             return element;
1434         }
1435
1436         @Override
1437         public void commit() {
1438             assertNotDisposed();
1439             if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
1440                 System.out.println("DiagramMutator is about to commit changes:");
1441                 for (IModification mod : pendingModifications)
1442                     System.out.println("\t- " + mod);
1443             }
1444
1445             Collections.sort(pendingModifications);
1446
1447             if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
1448                 if (pendingModifications.size() > 1) {
1449                     System.out.println("* changes were re-ordered to:");
1450                     for (IModification mod : pendingModifications)
1451                         System.out.println("\t" + mod);
1452                 }
1453             }
1454
1455             Timing.safeTimed(errorHandler, "QUEUE AND WAIT FOR MODIFICATIONS TO FINISH", new GTask() {
1456                 @Override
1457                 public void run() throws DatabaseException {
1458                     // Performs a separate write request and query result update
1459                     // for each modification
1460 //                    for (IModification mod : pendingModifications) {
1461 //                        try {
1462 //                            modificationQueue.sync(mod);
1463 //                        } catch (InterruptedException e) {
1464 //                            error("Pending diagram modification " + mod
1465 //                                    + " was interrupted. See exception for details.", e);
1466 //                        }
1467 //                    }
1468
1469                     // NOTE: this is still under testing, the author is not
1470                     // truly certain that it should work in all cases ATM.
1471
1472                     // Performs all modifications with in a single write request
1473                     for (IModification mod : pendingModifications) {
1474                         modificationQueue.offer(mod, null);
1475                     }
1476                     try {
1477                         // Perform the modifications in a single request.
1478                         modificationQueue.finish();
1479                     } catch (InterruptedException e) {
1480                         errorHandler.error("Diagram modification finishing was interrupted. See exception for details.", e);
1481                     }
1482                 }
1483             });
1484             pendingModifications.clear();
1485             modificationIndex.clear();
1486             creation.clear();
1487             if (DebugPolicy.DEBUG_MUTATOR_COMMIT)
1488                 System.out.println("DiagramMutator has committed");
1489         }
1490
1491         @Override
1492         public void clear() {
1493             assertNotDisposed();
1494             pendingModifications.clear();
1495             modificationIndex.clear();
1496             creation.clear();
1497             if (DebugPolicy.DEBUG_MUTATOR)
1498                 System.out.println("DiagramMutator has been cleared");
1499         }
1500
1501         @Override
1502         public void modifyTransform(IElement element) {
1503             assertNotDisposed();
1504             Resource resource = backendObject(element);
1505             AffineTransform tr = element.getHint(ElementHints.KEY_TRANSFORM);
1506             if (resource != null && tr != null) {
1507                 addModification(element, new TransformElement(resource, tr));
1508             }
1509         }
1510
1511         @Override
1512         public void synchronizeHintsToBackend(IElement element) {
1513             assertNotDisposed();
1514             IHintSynchronizer synchronizer = element.getHint(SynchronizationHints.HINT_SYNCHRONIZER);
1515             if (synchronizer != null) {
1516                 CollectingModificationQueue queue = new CollectingModificationQueue();
1517                 synchronizer.synchronize(synchronizationContext, element);
1518                 addModification(element, new CompositeModification(ModificationAdapter.LOW_PRIORITY, queue.getQueue()));
1519             }
1520         }
1521
1522         @Override
1523         public void synchronizeElementOrder() {
1524             assertNotDisposed();
1525             List<IElement> snapshot = d.getSnapshot();
1526             addModification(null, new ElementReorder(d, snapshot));
1527         }
1528
1529         @Override
1530         public void register(IElement element, Object object) {
1531             creation.put(element, (Resource) object);
1532         }
1533
1534         @SuppressWarnings("unchecked")
1535         @Override
1536         public <T> T backendObject(IElement element) {
1537             Object object = ElementUtils.getObject(element);
1538             if (object instanceof Resource)
1539                 return (T) object;
1540             else
1541                 return (T) creation.get(element);
1542         }
1543
1544     }
1545
1546     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1547     // GRAPH TO DIAGRAM SYCHRONIZATION LOGIC BEGIN
1548     // ------------------------------------------------------------------------
1549
1550     static class ConnectionData {
1551         ConnectionEntityImpl impl;
1552         List<Resource>       branchPoints = new ArrayList<Resource>();
1553         List<EdgeResource>   segments     = new ArrayList<EdgeResource>();
1554
1555         ConnectionData(ConnectionEntityImpl ce) {
1556             this.impl = ce;
1557         }
1558
1559         void addBranchPoint(Resource bp) {
1560             branchPoints.add(bp);
1561         }
1562
1563         void addSegment(EdgeResource seg) {
1564             segments.add(seg);
1565         }
1566     }
1567
1568     class GraphToDiagramUpdater {
1569         DiagramContents                                 lastContent;
1570         DiagramContents                                 content;
1571         DiagramContentChanges                           changes;
1572
1573         final List<IElement>                            addedElements;
1574         final List<IElement>                            removedElements;
1575
1576         final List<IElement>                            addedConnectionSegments;
1577         final List<IElement>                            removedConnectionSegments;
1578
1579         final List<IElement>                            addedBranchPoints;
1580         final List<IElement>                            removedBranchPoints;
1581
1582         final Map<Object, IElement>                     addedElementMap;
1583         final Map<Resource, IElement>                   addedConnectionMap;
1584         final Map<Resource, ConnectionEntityImpl>       addedConnectionEntities;
1585         final List<Resource>                            removedConnectionEntities;
1586         final Map<ConnectionEntityImpl, ConnectionData> changedConnectionEntities;
1587
1588         final Map<Resource, IElement>                   addedRouteGraphConnectionMap;
1589         final List<IElement>                            removedRouteGraphConnections;
1590
1591
1592         GraphToDiagramUpdater(DiagramContents lastContent, DiagramContents content, DiagramContentChanges changes) {
1593             this.lastContent = lastContent;
1594             this.content = content;
1595             this.changes = changes;
1596
1597             this.addedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
1598             this.removedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
1599             this.addedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
1600             this.removedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
1601             this.addedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
1602             this.removedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
1603             this.addedElementMap = new HashMap<Object, IElement>();
1604             this.addedConnectionMap = new HashMap<Resource, IElement>();
1605             this.addedConnectionEntities = new HashMap<Resource, ConnectionEntityImpl>();
1606             this.removedConnectionEntities = new ArrayList<Resource>(changes.connections.size());
1607             this.changedConnectionEntities = new HashMap<ConnectionEntityImpl, ConnectionData>();
1608             this.addedRouteGraphConnectionMap = new HashMap<Resource, IElement>();
1609             this.removedRouteGraphConnections = new ArrayList<IElement>(changes.routeGraphConnections.size());
1610         }
1611
1612         public void clear() {
1613             // Prevent DiagramContents leakage through DisposableListeners.
1614             lastContent = null;
1615             content = null;
1616             changes = null;
1617
1618             this.addedElements.clear();
1619             this.removedElements.clear();
1620             this.addedConnectionSegments.clear();
1621             this.removedConnectionSegments.clear();
1622             this.addedBranchPoints.clear();
1623             this.removedBranchPoints.clear();
1624             this.addedElementMap.clear();
1625             this.addedConnectionMap.clear();
1626             this.addedConnectionEntities.clear();
1627             this.removedConnectionEntities.clear();
1628             this.changedConnectionEntities.clear();
1629             this.addedRouteGraphConnectionMap.clear();
1630             this.removedRouteGraphConnections.clear();
1631         }
1632
1633         void processNodes(ReadGraph graph) throws DatabaseException {
1634
1635             for (Map.Entry<Resource, Change> entry : changes.elements.entrySet()) {
1636
1637                 final Resource element = entry.getKey();
1638                 Change change = entry.getValue();
1639
1640                 switch (change) {
1641                     case ADDED: {
1642                         IElement mappedElement = getMappedElement(element);
1643                         if (mappedElement == null) {
1644                             if (DebugPolicy.DEBUG_NODE_LOAD)
1645                                 graph.syncRequest(new ReadRequest() {
1646                                     @Override
1647                                     public void run(ReadGraph graph) throws DatabaseException {
1648                                         System.out.println("    EXTERNALLY ADDED ELEMENT: "
1649                                                 + NameUtils.getSafeName(graph, element) + " ("
1650                                                 + element.getResourceId() + ")");
1651                                     }
1652                                 });
1653
1654                             if (content.connectionSet.contains(element)) {
1655
1656                                 // TODO: Connection loading has no listening, changes :Connection will not be noticed by this code!
1657                                 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1658
1659                                     boolean firstTime = true;
1660
1661                                     @Override
1662                                     public String toString() {
1663                                         return "Connection load listener for " + element;
1664                                     }
1665                                     @Override
1666                                     public void execute(IElement loaded) {
1667                                         // Invoked when the element has been loaded.
1668                                         if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1669                                             System.out.println("CONNECTION LoadListener for " + loaded);
1670
1671                                         if (loaded == null) {
1672                                             disposeListener();
1673                                             return;
1674                                         }
1675
1676                                         if (firstTime) {
1677
1678                                             mapElement(element, loaded);
1679                                             synchronized (GraphToDiagramUpdater.this) {
1680                                                 addedElements.add(loaded);
1681                                                 addedElementMap.put(element, loaded);
1682                                                 addedConnectionMap.put(element, loaded);
1683                                             }
1684
1685                                             firstTime = false;
1686
1687                                         }
1688
1689                                         Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1690
1691                                         // Logic for disposing listener
1692                                         if (!previousContent.connectionSet.contains(data)) {
1693                                             if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1694                                                 System.out.println("CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
1695                                             disposeListener();
1696                                             return;
1697                                         }
1698
1699                                         if (addedElementMap.containsKey(data)) {
1700                                             // This element was just loaded, in
1701                                             // which case its hints need to
1702                                             // uploaded to the real mapped
1703                                             // element immediately.
1704                                             IElement mappedElement = getMappedElement(data);
1705                                             if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1706                                                 System.out.println("LOADED ADDED CONNECTION, currently mapped connection: " + mappedElement);
1707                                             if (mappedElement != null && (mappedElement instanceof Element)) {
1708                                                 if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
1709                                                     System.out.println("  mapped hints: " + mappedElement.getHints());
1710                                                     System.out.println("  loaded hints: " + loaded.getHints());
1711                                                 }
1712                                                 updateMappedElement((Element) mappedElement, loaded);
1713                                             }
1714                                         } else {
1715                                             // This element was already loaded.
1716                                             // Just schedule an update some time
1717                                             // in the future.
1718                                             if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1719                                                 System.out.println("PREVIOUSLY LOADED CONNECTION UPDATED, scheduling update into the future");
1720                                             offerGraphUpdate( connectionUpdater(element, loaded) );
1721                                         }
1722                                     }
1723                                 };
1724
1725                                 graph.syncRequest(new ConnectionRequest(canvas, diagram, element, errorHandler, loadListener), new AsyncProcedure<IElement>() {
1726                                     @Override
1727                                     public void execute(AsyncReadGraph graph, final IElement e) {
1728
1729                                         // Read connection type
1730                                         graph.forSingleType(element, br.DIA.Connection, new Procedure<Resource>() {
1731                                             @Override
1732                                             public void exception(Throwable t) {
1733                                                 error(t);
1734                                             }
1735
1736                                             @Override
1737                                             public void execute(Resource connectionType) {
1738                                                 synchronized (GraphToDiagramUpdater.this) {
1739                                                     //System.out.println("new connection entity " + e);
1740                                                     ConnectionEntityImpl entity = new ConnectionEntityImpl(element, connectionType, e);
1741                                                     e.setHint(ElementHints.KEY_CONNECTION_ENTITY, entity);
1742                                                     addedConnectionEntities.put(element, entity);
1743                                                 }
1744                                             }
1745                                         });
1746
1747                                     }
1748
1749                                     @Override
1750                                     public void exception(AsyncReadGraph graph, Throwable throwable) {
1751                                         error(throwable);
1752                                     }
1753                                 });
1754                             } else if (content.nodeSet.contains(element)) {
1755
1756                                 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1757
1758                                     boolean firstTime = true;
1759
1760                                     @Override
1761                                     public String toString() {
1762                                         return "Node load listener for " + element;
1763                                     }
1764                                     @Override
1765                                     public void execute(IElement loaded) {
1766                                         // Invoked when the element has been loaded.
1767                                         if (DebugPolicy.DEBUG_NODE_LISTENER)
1768                                             System.out.println("NODE LoadListener for " + loaded);
1769
1770                                         if (loaded == null) {
1771                                             disposeListener();
1772                                             return;
1773                                         }
1774
1775                                         if (firstTime) {
1776
1777                                             // This is invoked before the element is actually loaded.
1778                                             //System.out.println("NodeRequestProcedure " + e);
1779                                             if (DebugPolicy.DEBUG_NODE_LOAD)
1780                                                 System.out.println("MAPPING ADDED NODE: " + element + " -> " + loaded);
1781                                             mapElement(element, loaded);
1782                                             synchronized (GraphToDiagramUpdater.this) {
1783                                                 addedElements.add(loaded);
1784                                                 addedElementMap.put(element, loaded);
1785                                             }
1786
1787                                             firstTime = false;
1788
1789                                         }
1790
1791                                         Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1792
1793                                         // Logic for disposing listener
1794                                         if (!previousContent.nodeSet.contains(data)) {
1795                                             if (DebugPolicy.DEBUG_NODE_LISTENER)
1796                                                 System.out.println("NODE LoadListener, node not in current content: " + data + ". Disposing.");
1797                                             disposeListener();
1798                                             return;
1799                                         }
1800
1801                                         if (addedElementMap.containsKey(data)) {
1802                                             // This element was just loaded, in
1803                                             // which case its hints need to
1804                                             // uploaded to the real mapped
1805                                             // element immediately.
1806                                             IElement mappedElement = getMappedElement(data);
1807                                             if (DebugPolicy.DEBUG_NODE_LISTENER)
1808                                                 System.out.println("LOADED ADDED ELEMENT, currently mapped element: " + mappedElement);
1809                                             if (mappedElement != null && (mappedElement instanceof Element)) {
1810                                                 if (DebugPolicy.DEBUG_NODE_LISTENER) {
1811                                                     System.out.println("  mapped hints: " + mappedElement.getHints());
1812                                                     System.out.println("  loaded hints: " + loaded.getHints());
1813                                                 }
1814                                                 updateMappedElement((Element) mappedElement, loaded);
1815                                             }
1816                                         } else {
1817                                             // This element was already loaded.
1818                                             // Just schedule an update some time
1819                                             // in the future.
1820                                             if (DebugPolicy.DEBUG_NODE_LISTENER)
1821                                                 System.out.println("PREVIOUSLY LOADED NODE UPDATED, scheduling update into the future");
1822                                             offerGraphUpdate( nodeUpdater(element, loaded) );
1823                                         }
1824                                     }
1825                                 };
1826
1827                                 //System.out.println("NODE REQUEST: " + element);
1828                                 graph.syncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
1829                                     @Override
1830                                     public void execute(AsyncReadGraph graph, IElement e) {
1831                                     }
1832
1833                                     @Override
1834                                     public void exception(AsyncReadGraph graph, Throwable throwable) {
1835                                         error(throwable);
1836                                     }
1837                                 });
1838
1839                             } else {
1840 //                                warning("Diagram elements must be either elements or connections, "
1841 //                                        + NameUtils.getSafeName(g, element) + " is neither",
1842 //                                        new AssumptionException(""));
1843                             }
1844                         }
1845                         break;
1846                     }
1847                     case REMOVED: {
1848                         IElement e = getMappedElement(element);
1849                         if (DebugPolicy.DEBUG_NODE_LOAD)
1850                             graph.syncRequest(new ReadRequest() {
1851                                 @Override
1852                                 public void run(ReadGraph graph) throws DatabaseException {
1853                                     System.out.println("    EXTERNALLY REMOVED ELEMENT: "
1854                                             + NameUtils.getSafeName(graph, element) + " ("
1855                                             + element.getResourceId() + ")");
1856                                 }
1857                             });
1858                         if (e != null) {
1859                             removedElements.add(e);
1860                         }
1861                         break;
1862                     }
1863                     default:
1864                 }
1865             }
1866         }
1867
1868         void gatherChangedConnectionParts(Map<?, Change> changes) {
1869             for (Map.Entry<?, Change> entry : changes.entrySet()) {
1870                 Object part = entry.getKey();
1871                 Change change = entry.getValue();
1872
1873                 switch (change) {
1874                     case ADDED: {
1875                         synchronized (GraphToDiagramUpdater.this) {
1876                             Resource connection = content.partToConnection.get(part);
1877                             assert connection != null;
1878
1879                             IElement ce = getMappedElement(connection);
1880                             if (ce == null)
1881                                 ce = addedElementMap.get(connection);
1882
1883                             if (ce != null)
1884                                 markConnectionChanged(ce);
1885                             break;
1886                         }
1887                     }
1888                     case REMOVED: {
1889                         if (lastContent == null)
1890                             break;
1891                         Resource connection = lastContent.partToConnection.get(part);
1892                         if (connection != null && content.connectionSet.contains(connection)) {
1893                             markConnectionChanged(connection);
1894                         }
1895                         break;
1896                     }
1897                     default:
1898                 }
1899             }
1900         }
1901
1902         void markConnectionChanged(Resource connection) {
1903 //            System.out.println("markConnectionChanged");
1904             ConnectionEntityImpl ce = getMappedConnection(connection);
1905             if (ce != null) {
1906                 markConnectionChanged(ce);
1907                 return;
1908             }
1909             error("WARNING: marking connection entity " + connection
1910                     + " changed, but the connection was not previously mapped",
1911                     new Exception("created exception to get a stack trace"));
1912         }
1913
1914         void markConnectionChanged(IElement connection) {
1915             ConnectionEntityImpl entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
1916             if (entity != null)
1917                 markConnectionChanged(entity);
1918         }
1919
1920         void markConnectionChanged(ConnectionEntityImpl ce) {
1921             if (!changedConnectionEntities.containsKey(ce)) {
1922                 changedConnectionEntities.put(ce, new ConnectionData(ce));
1923             }
1924         }
1925
1926         void processConnections() {
1927             // Find added/removed connection segments/branch points
1928             // in order to find all changed connection entities.
1929             gatherChangedConnectionParts(changes.connectionSegments);
1930             gatherChangedConnectionParts(changes.branchPoints);
1931
1932             // Find removed connection entities
1933             for (Map.Entry<Resource, Change> entry : changes.connections.entrySet()) {
1934                 Resource ce = entry.getKey();
1935                 Change change = entry.getValue();
1936
1937                 switch (change) {
1938                     case REMOVED: {
1939                         removedConnectionEntities.add(ce);
1940                     }
1941                     default:
1942                 }
1943             }
1944
1945             // Generate update data of changed connection entities.
1946             // This ConnectionData will be applied in the canvas thread
1947             // diagram updater.
1948             for (ConnectionData cd : changedConnectionEntities.values()) {
1949                 for (Object part : content.connectionToParts.getValuesUnsafe(cd.impl.connection)) {
1950                     if (part instanceof Resource) {
1951                         cd.branchPoints.add((Resource) part);
1952                     } else if (part instanceof EdgeResource) {
1953                         cd.segments.add((EdgeResource) part);
1954                     }
1955                 }
1956             }
1957         }
1958
1959         void processRouteGraphConnections(ReadGraph graph) throws DatabaseException {
1960             for (Map.Entry<Resource, Change> entry : changes.routeGraphConnections.entrySet()) {
1961                 final Resource connection = entry.getKey();
1962
1963                 Change change = entry.getValue();
1964                 switch (change) {
1965                     case ADDED: {
1966                         IElement mappedElement = getMappedElement(connection);
1967                         if (mappedElement != null)
1968                             continue;
1969
1970                         Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1971
1972                             boolean firstTime = true;
1973
1974                             @Override
1975                             public String toString() {
1976                                 return "processRouteGraphConnections " + connection;
1977                             }
1978                             @Override
1979                             public void execute(IElement loaded) {
1980                                 // Invoked when the element has been loaded.
1981                                 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1982                                     System.out.println("ROUTE GRAPH CONNECTION LoadListener for " + loaded);
1983
1984                                 if (loaded == null) {
1985                                     disposeListener();
1986                                     return;
1987                                 }
1988
1989                                 if(firstTime) {
1990                                     if (DebugPolicy.DEBUG_NODE_LOAD)
1991                                         System.out.println("MAPPING ADDED ROUTE GRAPH CONNECTION: " + connection + " -> " + loaded);
1992                                     mapElement(connection, loaded);
1993                                     synchronized (GraphToDiagramUpdater.this) {
1994                                         addedElements.add(loaded);
1995                                         addedElementMap.put(connection, loaded);
1996                                         addedRouteGraphConnectionMap.put(connection, loaded);
1997                                     }
1998                                     firstTime = false;
1999                                 }
2000
2001                                 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2002
2003                                 // Logic for disposing listener
2004                                 if (!previousContent.routeGraphConnectionSet.contains(data)) {
2005                                     if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2006                                         System.out.println("ROUTE GRAPH CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
2007                                     disposeListener();
2008                                     return;
2009                                 }
2010
2011                                 if (addedElementMap.containsKey(data)) {
2012                                     // This element was just loaded, in
2013                                     // which case its hints need to
2014                                     // uploaded to the real mapped
2015                                     // element immediately.
2016                                     IElement mappedElement = getMappedElement(data);
2017                                     if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2018                                         System.out.println("LOADED ADDED ROUTE GRAPH CONNECTION, currently mapped connection: " + mappedElement);
2019                                     if (mappedElement instanceof Element) {
2020                                         if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
2021                                             System.out.println("  mapped hints: " + mappedElement.getHints());
2022                                             System.out.println("  loaded hints: " + loaded.getHints());
2023                                         }
2024                                         updateMappedElement((Element) mappedElement, loaded);
2025                                     }
2026                                 } else {
2027                                     // This element was already loaded.
2028                                     // Just schedule an update some time
2029                                     // in the future.
2030                                     if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2031                                         System.out.println("PREVIOUSLY LOADED ROUTE GRAPH CONNECTION UPDATED, scheduling update into the future: " + connection);
2032
2033                                     Set<Object> dirtyNodes = new THashSet<Object>(4);
2034                                     IElement mappedElement = getMappedElement(connection);
2035                                     ConnectionEntity ce = mappedElement.getHint(ElementHints.KEY_CONNECTION_ENTITY);
2036                                     if (ce != null) {
2037                                         for (Connection conn : ce.getTerminalConnections(null)) {
2038                                             Object o = conn.node.getHint(ElementHints.KEY_OBJECT);
2039                                             if (o != null) {
2040                                                 dirtyNodes.add(o);
2041                                                 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2042                                                     System.out.println("Marked connectivity dirty for node: " + conn.node);
2043                                             }
2044                                         }
2045                                     }
2046
2047                                     offerGraphUpdate( routeGraphConnectionUpdater(connection, loaded, dirtyNodes) );
2048                                 }
2049                             }
2050                         };
2051
2052                         graph.syncRequest(new ConnectionRequest(canvas, diagram, connection, errorHandler, loadListener), new Procedure<IElement>() {
2053                             @Override
2054                             public void execute(final IElement e) {
2055                             }
2056                             @Override
2057                             public void exception(Throwable throwable) {
2058                                 error(throwable);
2059                             }
2060                         });
2061                         break;
2062                     }
2063                     case REMOVED: {
2064                         IElement e = getMappedElement(connection);
2065                         if (e != null)
2066                             removedRouteGraphConnections.add(e);
2067                         break;
2068                     }
2069                     default:
2070                 }
2071             }
2072         }
2073
2074         ConnectionEntityImpl getConnectionEntity(Object connectionPart) {
2075             Resource connection = content.partToConnection.get(connectionPart);
2076             assert connection != null;
2077             ConnectionEntityImpl ce = addedConnectionEntities.get(connection);
2078             if (ce != null)
2079                 return ce;
2080             return assertMappedConnection(connection);
2081         }
2082
2083         void processBranchPoints(ReadGraph graph) throws DatabaseException {
2084             for (Map.Entry<Resource, Change> entry : changes.branchPoints.entrySet()) {
2085
2086                 final Resource element = entry.getKey();
2087                 Change change = entry.getValue();
2088
2089                 switch (change) {
2090                     case ADDED: {
2091                         IElement mappedElement = getMappedElement(element);
2092                         if (mappedElement == null) {
2093                             if (DebugPolicy.DEBUG_NODE_LOAD)
2094                                 graph.syncRequest(new ReadRequest() {
2095                                     @Override
2096                                     public void run(ReadGraph graph) throws DatabaseException {
2097                                         System.out.println("    EXTERNALLY ADDED BRANCH POINT: "
2098                                                 + NameUtils.getSafeName(graph, element) + " ("
2099                                                 + element.getResourceId() + ")");
2100                                     }
2101                                 });
2102
2103                             Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
2104
2105                                 boolean firstTime = true;
2106
2107                                 @Override
2108                                 public String toString() {
2109                                     return "processBranchPoints for " + element;
2110                                 }
2111                                 @Override
2112                                 public void execute(IElement loaded) {
2113                                     // Invoked when the element has been loaded.
2114                                     if (DebugPolicy.DEBUG_NODE_LISTENER)
2115                                         System.out.println("BRANCH POINT LoadListener for " + loaded);
2116
2117                                     if (loaded == null) {
2118                                         disposeListener();
2119                                         return;
2120                                     }
2121
2122                                     if (firstTime) {
2123
2124                                         mapElement(element, loaded);
2125                                         synchronized (GraphToDiagramUpdater.this) {
2126                                             addedBranchPoints.add(loaded);
2127                                             addedElementMap.put(element, loaded);
2128                                             ConnectionEntityImpl ce = getConnectionEntity(element);
2129                                             loaded.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
2130                                             loaded.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
2131                                         }
2132
2133                                         firstTime = false;
2134
2135                                     }
2136
2137                                     Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2138                                     if (addedElementMap.containsKey(data)) {
2139                                         // This element was just loaded, in
2140                                         // which case its hints need to
2141                                         // uploaded to the real mapped
2142                                         // element immediately.
2143                                         IElement mappedElement = getMappedElement(data);
2144                                         if (DebugPolicy.DEBUG_NODE_LISTENER)
2145                                             System.out.println("LOADED ADDED BRANCH POINT, currently mapped element: " + mappedElement);
2146                                         if (mappedElement != null && (mappedElement instanceof Element)) {
2147                                             if (DebugPolicy.DEBUG_NODE_LISTENER) {
2148                                                 System.out.println("  mapped hints: " + mappedElement.getHints());
2149                                                 System.out.println("  loaded hints: " + loaded.getHints());
2150                                             }
2151                                             updateMappedElement((Element) mappedElement, loaded);
2152                                         }
2153                                     } else {
2154                                         // This element was already loaded.
2155                                         // Just schedule an update some time
2156                                         // in the future.
2157                                         if (DebugPolicy.DEBUG_NODE_LISTENER)
2158                                             System.out.println("PREVIOUSLY LOADED BRANCH POINT UPDATED, scheduling update into the future");
2159                                         offerGraphUpdate( nodeUpdater(element, loaded) );
2160                                     }
2161                                 }
2162                             };
2163
2164                             graph.syncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
2165                                 @Override
2166                                 public void execute(AsyncReadGraph graph, IElement e) {
2167                                 }
2168
2169                                 @Override
2170                                 public void exception(AsyncReadGraph graph, Throwable throwable) {
2171                                     error(throwable);
2172                                 }
2173                             });
2174                         }
2175                         break;
2176                     }
2177                     case REMOVED: {
2178                         IElement e = getMappedElement(element);
2179                         if (DebugPolicy.DEBUG_NODE_LOAD)
2180                             graph.syncRequest(new ReadRequest() {
2181                                 @Override
2182                                 public void run(ReadGraph graph) throws DatabaseException {
2183                                     System.out.println("    EXTERNALLY REMOVED BRANCH POINT: "
2184                                             + NameUtils.getSafeName(graph, element) + " ("
2185                                             + element.getResourceId() + ")");
2186                                 }
2187                             });
2188                         if (e != null) {
2189                             removedBranchPoints.add(e);
2190                         }
2191                         break;
2192                     }
2193                     default:
2194                 }
2195             }
2196         }
2197
2198         void processConnectionSegments(ReadGraph graph) throws DatabaseException {
2199             ConnectionSegmentAdapter adapter = connectionSegmentAdapter;
2200
2201             for (Map.Entry<EdgeResource, Change> entry : changes.connectionSegments.entrySet()) {
2202                 final EdgeResource seg = entry.getKey();
2203                 Change change = entry.getValue();
2204
2205                 switch (change) {
2206                     case ADDED: {
2207                         IElement mappedElement = getMappedElement(seg);
2208                         if (mappedElement == null) {
2209                             if (DebugPolicy.DEBUG_EDGE_LOAD)
2210                                 graph.syncRequest(new ReadRequest() {
2211                                     @Override
2212                                     public void run(ReadGraph graph) throws DatabaseException {
2213                                         System.out.println("    EXTERNALLY ADDED CONNECTION SEGMENT: " + seg.toString()
2214                                                 + " - " + seg.toString(graph));
2215                                     }
2216                                 });
2217
2218                             graph.syncRequest(new EdgeRequest(canvas, errorHandler, canvasListenerSupport, diagram, adapter, seg), new AsyncProcedure<IElement>() {
2219                                 @Override
2220                                 public void execute(AsyncReadGraph graph, IElement e) {
2221                                     if (DebugPolicy.DEBUG_EDGE_LOAD)
2222                                         System.out.println("ADDED EDGE LOADED: " + e);
2223                                     if (e != null) {
2224                                         mapElement(seg, e);
2225                                         synchronized (GraphToDiagramUpdater.this) {
2226                                             addedConnectionSegments.add(e);
2227                                             addedElementMap.put(seg, e);
2228                                             ConnectionEntityImpl ce = getConnectionEntity(seg);
2229                                             e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
2230                                             e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
2231                                         }
2232                                     }
2233                                 }
2234
2235                                 @Override
2236                                 public void exception(AsyncReadGraph graph, Throwable throwable) {
2237                                     error(throwable);
2238                                 }
2239                             });
2240                         }
2241                         break;
2242                     }
2243                     case REMOVED: {
2244                         final IElement e = getMappedElement(seg);
2245                         if (DebugPolicy.DEBUG_EDGE_LOAD)
2246                             graph.syncRequest(new ReadRequest() {
2247                                 @Override
2248                                 public void run(ReadGraph graph) throws DatabaseException {
2249                                     System.out.println("    EXTERNALLY REMOVED CONNECTION SEGMENT: " + seg.toString() + " - "
2250                                             + seg.toString(graph) + " -> " + e);
2251                                 }
2252                             });
2253                         if (e != null) {
2254                             removedConnectionSegments.add(e);
2255                         }
2256                         break;
2257                     }
2258                     default:
2259                 }
2260             }
2261         }
2262
2263         void executeDeferredLoaders(ReadGraph graph) throws DatabaseException {
2264             // The rest of the diagram loading passes
2265             Deque<IElement> q1 = new ArrayDeque<IElement>();
2266             Deque<IElement> q2 = new ArrayDeque<IElement>();
2267             collectElementLoaders(q1, addedElements);
2268             while (!q1.isEmpty()) {
2269                 //System.out.println("DEFFERED LOADERS: " + q1);
2270                 for (IElement e : q1) {
2271                     ElementLoader loader = e.removeHint(DiagramModelHints.KEY_ELEMENT_LOADER);
2272                     //System.out.println("EXECUTING DEFFERED LOADER: " + loader);
2273                     loader.load(graph, diagram, e);
2274                 }
2275
2276                 collectElementLoaders(q2, q1);
2277                 Deque<IElement> qt = q1;
2278                 q1 = q2;
2279                 q2 = qt;
2280                 q2.clear();
2281             }
2282         }
2283
2284         private void collectElementLoaders(Queue<IElement> queue, Collection<IElement> cs) {
2285             for (IElement e : cs) {
2286                 ElementLoader loader = e.getHint(DiagramModelHints.KEY_ELEMENT_LOADER);
2287                 if (loader != null)
2288                     queue.add(e);
2289             }
2290         }
2291
2292         public void process(ReadGraph graph) throws DatabaseException {
2293             // No changes? Do nothing.
2294             if (changes.isEmpty())
2295                 return;
2296
2297             // NOTE: This order is important.
2298             Object task = Timing.BEGIN("processNodesConnections");
2299             //System.out.println("---- PROCESS NODES & CONNECTIONS BEGIN");
2300             if (!changes.elements.isEmpty()) {
2301                 graph.syncRequest(new ReadRequest() {
2302                     @Override
2303                     public void run(ReadGraph graph) throws DatabaseException {
2304                         processNodes(graph);
2305                     }
2306                     @Override
2307                     public String toString() {
2308                         return "processNodes";
2309                     }
2310                 });
2311             }
2312             //System.out.println("---- PROCESS NODES & CONNECTIONS END");
2313
2314             processConnections();
2315
2316             //System.out.println("---- PROCESS BRANCH POINTS BEGIN");
2317             if (!changes.branchPoints.isEmpty()) {
2318                 graph.syncRequest(new ReadRequest() {
2319                     @Override
2320                     public void run(ReadGraph graph) throws DatabaseException {
2321                         processBranchPoints(graph);
2322                     }
2323                     @Override
2324                     public String toString() {
2325                         return "processBranchPoints";
2326                     }
2327                 });
2328             }
2329             //System.out.println("---- PROCESS BRANCH POINTS END");
2330
2331             Timing.END(task);
2332             task = Timing.BEGIN("processConnectionSegments");
2333
2334             //System.out.println("---- PROCESS CONNECTION SEGMENTS BEGIN");
2335             if (!changes.connectionSegments.isEmpty()) {
2336                 graph.syncRequest(new ReadRequest() {
2337                     @Override
2338                     public void run(ReadGraph graph) throws DatabaseException {
2339                         processConnectionSegments(graph);
2340                     }
2341                     @Override
2342                     public String toString() {
2343                         return "processConnectionSegments";
2344                     }
2345                 });
2346             }
2347             //System.out.println("---- PROCESS CONNECTION SEGMENTS END");
2348
2349             Timing.END(task);
2350
2351             task = Timing.BEGIN("processRouteGraphConnections");
2352             if (!changes.routeGraphConnections.isEmpty()) {
2353                 graph.syncRequest(new ReadRequest() {
2354                     @Override
2355                     public void run(ReadGraph graph) throws DatabaseException {
2356                         processRouteGraphConnections(graph);
2357                     }
2358                     @Override
2359                     public String toString() {
2360                         return "processRouteGraphConnections";
2361                     }
2362                 });
2363             }
2364             Timing.END(task);
2365
2366             //System.out.println("---- AFTER LOADING");
2367             //for (IElement e : addedElements)
2368             //    System.out.println("    ADDED ELEMENT: " + e);
2369             //for (IElement e : addedBranchPoints)
2370             //    System.out.println("    ADDED BRANCH POINTS: " + e);
2371
2372             task = Timing.BEGIN("executeDeferredLoaders");
2373             executeDeferredLoaders(graph);
2374             Timing.END(task);
2375         }
2376
2377         public boolean isEmpty() {
2378             return addedElements.isEmpty() && removedElements.isEmpty()
2379             && addedConnectionSegments.isEmpty() && removedConnectionSegments.isEmpty()
2380             && addedBranchPoints.isEmpty() && removedBranchPoints.isEmpty()
2381             && addedConnectionEntities.isEmpty() && removedConnectionEntities.isEmpty()
2382             && addedRouteGraphConnectionMap.isEmpty() && removedRouteGraphConnections.isEmpty()
2383             && !changes.elementOrderChanged;
2384         }
2385
2386         class DefaultConnectionSegmentAdapter implements ConnectionSegmentAdapter {
2387
2388             @Override
2389             public void getClass(AsyncReadGraph graph, EdgeResource edge, ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, IDiagram diagram, final AsyncProcedure<ElementClass> procedure) {
2390                 if (info.connectionType != null) {
2391                     NodeClassRequest request = new NodeClassRequest(canvas, diagram, info.connectionType, true);
2392                     graph.asyncRequest(request, new CacheListener<ElementClass>(listenerSupport));
2393                     graph.asyncRequest(request, procedure);
2394                 } else {
2395                     procedure.execute(graph, null);
2396                 }
2397             }
2398
2399             @Override
2400             public void load(AsyncReadGraph graph, final EdgeResource edge, final ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, final IDiagram diagram, final IElement element) {
2401                 graph.asyncRequest(new Read<IElement>() {
2402                     @Override
2403                     public IElement perform(ReadGraph graph) throws DatabaseException {
2404                         //ITask task = ThreadLogger.getInstance().begin("LoadSegment");
2405                         syncLoad(graph, edge, info, diagram, element);
2406                         //task.finish();
2407                         return element;
2408                     }
2409                     @Override
2410                     public String toString() {
2411                         return "defaultConnectionSegmentAdapter";
2412                     }
2413                 }, new DisposableListener<IElement>(listenerSupport) {
2414                     
2415                     @Override
2416                     public String toString() {
2417                         return "DefaultConnectionSegmentAdapter listener for " + edge;
2418                     }
2419                     
2420                     @Override
2421                     public void execute(IElement loaded) {
2422                         // Invoked when the element has been loaded.
2423                         if (DebugPolicy.DEBUG_EDGE_LISTENER)
2424                             System.out.println("EDGE LoadListener for " + loaded);
2425
2426                         if (loaded == null) {
2427                             disposeListener();
2428                             return;
2429                         }
2430
2431                         Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2432                         if (addedElementMap.containsKey(data)) {
2433                             // This element was just loaded, in
2434                             // which case its hints need to
2435                             // uploaded to the real mapped
2436                             // element immediately.
2437                             IElement mappedElement = getMappedElement(data);
2438                             if (DebugPolicy.DEBUG_EDGE_LISTENER)
2439                                 System.out.println("LOADED ADDED EDGE, currently mapped element: " + mappedElement);
2440                             if (mappedElement != null && (mappedElement instanceof Element)) {
2441                                 if (DebugPolicy.DEBUG_EDGE_LISTENER) {
2442                                     System.out.println("  mapped hints: " + mappedElement.getHints());
2443                                     System.out.println("  loaded hints: " + loaded.getHints());
2444                                 }
2445                                 updateMappedElement((Element) mappedElement, loaded);
2446                             }
2447                         } else {
2448                             // This element was already loaded.
2449                             // Just schedule an update some time
2450                             // in the future.
2451                             if (DebugPolicy.DEBUG_EDGE_LISTENER)
2452                                 System.out.println("PREVIOUSLY LOADED EDGE UPDATED, scheduling update into the future");
2453                             offerGraphUpdate( edgeUpdater(element, loaded) );
2454                         }
2455                     }
2456                 });
2457             }
2458
2459             void syncLoad(ReadGraph graph, EdgeResource connectionSegment, ConnectionInfo info, IDiagram diagram, IElement element) throws DatabaseException {
2460                 // Check that at least some data exists before continuing further.
2461                 if (!graph.hasStatement(connectionSegment.first()) && !graph.hasStatement(connectionSegment.second())) {
2462                     return;
2463                 }
2464
2465                 // Validate that both ends of the segment are
2466                 // part of the same connection before loading.
2467                 // This will happen for connections that are
2468                 // modified through splitting and joining of
2469                 // connection segments.
2470                 Resource c = ConnectionUtil.tryGetConnection(graph, connectionSegment);
2471                 if (c == null) {
2472                     // Ok, this segment is somehow invalid. Just don't load it.
2473                     if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2474                         System.out.println("Skipping edge " + connectionSegment + ". Both segment ends are not part of the same connection.");
2475                     return;
2476                 }
2477
2478                 if (!info.isValid()) {
2479                     // This edge must be somehow invalid, don't proceed with loading.
2480                     if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2481                         warning("Cannot load edge " + connectionSegment + ". ConnectionInfo " + info + " is invalid.", new DebugException("execution trace"));
2482                     return;
2483                 }
2484
2485                 Element edge = (Element) element;
2486                 edge.setHint(ElementHints.KEY_OBJECT, connectionSegment);
2487
2488                 // connectionSegment resources may currently be in a different
2489                 // order than ConnectionInfo.firstEnd/secondEnd. Therefore the
2490                 // segment ends must be resolved here.
2491                 ConnectionSegmentEnd firstEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.first());
2492                 ConnectionSegmentEnd secondEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.second());
2493                 if (firstEnd == null || secondEnd == null) {
2494                     if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2495                         warning("End attachments for edge " + connectionSegment + " are unresolved: (" + firstEnd + "," + secondEnd + ")", new DebugException("execution trace"));
2496                     return;
2497                 }
2498
2499                 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2500                     System.out.println("CONNECTION INFO: " + connectionSegment + " - " + info);
2501                 DesignatedTerminal firstTerminal = DiagramGraphUtil.findDesignatedTerminal(
2502                         graph, diagram, connectionSegment.first(), firstEnd);
2503                 DesignatedTerminal secondTerminal = DiagramGraphUtil.findDesignatedTerminal(
2504                         graph, diagram, connectionSegment.second(), secondEnd);
2505
2506                 // Edges must be connected at both ends in order for edge loading to succeed.
2507                 String err = validateConnectivity(graph, connectionSegment, firstTerminal, secondTerminal);
2508                 if (err != null) {
2509                     // Stop loading edge if the connectivity cannot be completely resolved.
2510                     if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2511                         warning(err, null);
2512                     return;
2513                 }
2514
2515                 graph.syncRequest(new AsyncReadRequest() {
2516                     @Override
2517                     public void run(AsyncReadGraph graph) {
2518                         // NOTICE: Layer information is loaded from the connection entity resource
2519                         // that is shared by all segments of the same connection.
2520                         ElementFactoryUtil.loadLayersForElement(graph, layerManager, diagram, edge, info.connection,
2521                                 new AsyncProcedureAdapter<IElement>() {
2522                             @Override
2523                             public void exception(AsyncReadGraph graph, Throwable t) {
2524                                 error("failed to load layers for connection segment", t);
2525                             }
2526                         });
2527                     }
2528                 });
2529
2530                 edge.setHintWithoutNotification(KEY_CONNECTION_BEGIN_PLACEHOLDER, new PlaceholderConnection(
2531                         EdgeEnd.Begin,
2532                         firstTerminal.element.getHint(ElementHints.KEY_OBJECT),
2533                         firstTerminal.terminal));
2534                 edge.setHintWithoutNotification(KEY_CONNECTION_END_PLACEHOLDER, new PlaceholderConnection(
2535                         EdgeEnd.End,
2536                         secondTerminal.element.getHint(ElementHints.KEY_OBJECT),
2537                         secondTerminal.terminal));
2538
2539                 IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);
2540                 if (modelingRules != null) {
2541                     ConnectionVisualsLoader loader = diagram.getHint(DiagramModelHints.KEY_CONNECTION_VISUALS_LOADER);
2542                     if (loader != null)
2543                         loader.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
2544                     else
2545                         DiagramGraphUtil.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
2546                 }
2547             }
2548
2549             private String validateConnectivity(ReadGraph graph, EdgeResource edge,
2550                     DesignatedTerminal firstTerminal,
2551                     DesignatedTerminal secondTerminal)
2552             throws DatabaseException {
2553                 boolean firstLoose = firstTerminal == null;
2554                 boolean secondLoose = secondTerminal == null;
2555                 boolean stray = firstLoose && secondLoose;
2556                 if (firstTerminal == null || secondTerminal == null) {
2557                     StringBuilder sb = new StringBuilder();
2558                     sb.append("encountered ");
2559                     sb.append(stray ? "stray" : "loose");
2560                     sb.append(" connection segment, ");
2561                     if (firstLoose)
2562                         sb.append("first ");
2563                     if (stray)
2564                         sb.append("and ");
2565                     if (secondLoose)
2566                         sb.append("second ");
2567                     sb.append("end disconnected: ");
2568                     sb.append(edge.toString(graph));
2569                     sb.append(" - ");
2570                     sb.append(edge.toString());
2571                     return sb.toString();
2572                 }
2573                 return null;
2574             }
2575
2576         }
2577
2578         ConnectionSegmentAdapter connectionSegmentAdapter = new DefaultConnectionSegmentAdapter();
2579
2580     }
2581
2582     private static final Double DIAGRAM_UPDATE_DIAGRAM_PRIORITY = 1d;
2583     private static final Double DIAGRAM_UPDATE_NODE_PRIORITY = 2d;
2584     private static final Double DIAGRAM_UPDATE_CONNECTION_PRIORITY = 3d;
2585     private static final Double DIAGRAM_UPDATE_EDGE_PRIORITY = 4d;
2586
2587     interface DiagramUpdater extends Runnable {
2588         Double getPriority();
2589
2590         Comparator<DiagramUpdater> DIAGRAM_UPDATER_COMPARATOR = new Comparator<DiagramUpdater>() {
2591             @Override
2592             public int compare(DiagramUpdater o1, DiagramUpdater o2) {
2593                 return o1.getPriority().compareTo(o2.getPriority());
2594             }
2595         };
2596     }
2597
2598     interface GraphUpdateReactor {
2599         DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException;
2600     }
2601
2602     static abstract class AbstractDiagramUpdater implements DiagramUpdater, GraphUpdateReactor {
2603         protected final Double priority;
2604         protected final String runnerName;
2605
2606         public AbstractDiagramUpdater(Double priority, String runnerName) {
2607             if (priority == null)
2608                 throw new NullPointerException("null priority");
2609             if (runnerName == null)
2610                 throw new NullPointerException("null runner name");
2611             this.priority = priority;
2612             this.runnerName = runnerName;
2613         }
2614
2615         @Override
2616         public Double getPriority() {
2617             return priority;
2618         }
2619
2620         @Override
2621         public AbstractDiagramUpdater graphUpdate(ReadGraph graph) {
2622             return this;
2623         }
2624
2625         @Override
2626         public void run() {
2627             Object task = Timing.BEGIN(runnerName);
2628             forDiagram();
2629             Timing.END(task);
2630         }
2631
2632         protected void forDiagram() {
2633         }
2634
2635         @Override
2636         public String toString() {
2637             return runnerName + "@" + System.identityHashCode(this) + " [" + priority + "]";
2638         }
2639     }
2640
2641     /**
2642      * @param content the new contents of the diagram, must not be
2643      *        <code>null</code>.
2644      */
2645     private GraphUpdateReactor diagramGraphUpdater(final DiagramContents content) {
2646         if (content == null)
2647             throw new NullPointerException("null diagram content");
2648
2649         return new GraphUpdateReactor() {
2650             @Override
2651             public String toString() {
2652                 return "DiagramGraphUpdater@" + System.identityHashCode(this);
2653             }
2654
2655             @Override
2656             public DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException {
2657                 // Never do anything here if the canvas has already been disposed.
2658                 if (!GraphToDiagramSynchronizer.this.isAlive())
2659                     return null;
2660
2661                 // We must be prepared for the following changes in the diagram graph
2662                 // model:
2663                 // - the diagram has been completely removed
2664                 // - elements have been added
2665                 // - elements have been removed
2666                 //
2667                 // Element-specific changes are handled by the element query listeners:
2668                 // - elements have been modified
2669                 // - element position has changed
2670                 // - element class (e.g. image) has changed
2671
2672                 diagramUpdateLock.lock();
2673                 try {
2674                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2675                         System.out.println("In diagramGraphUpdater:");
2676
2677                     // Find out what has changed since the last query.
2678                     Object task = Timing.BEGIN("diagramContentDifference");
2679                     DiagramContents lastContent = previousContent;
2680                     DiagramContentChanges changes = content.differenceFrom(previousContent);
2681                     previousContent = content;
2682                     Timing.END(task);
2683                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2684                         System.out.println("  changes: " + changes);
2685
2686                     // Bail out if there are no changes to react to.
2687                     if (changes.isEmpty())
2688                         return null;
2689
2690                     // Load everything that needs to be loaded from the graph,
2691                     // but don't update the UI model in this thread yet.
2692                     task = Timing.BEGIN("updater.process");
2693                     GraphToDiagramUpdater updater = new GraphToDiagramUpdater(lastContent, content, changes);
2694                     GraphToDiagramSynchronizer.this.currentUpdater = updater;
2695                     try {
2696                         updater.process(graph);
2697                     } finally {
2698                         GraphToDiagramSynchronizer.this.currentUpdater = null;
2699                     }
2700                     Timing.END(task);
2701
2702                     if (updater.isEmpty())
2703                         return null;
2704
2705                     // Return an updater that will update the UI run-time model.
2706