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