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