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