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