]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/GraphToDiagramSynchronizer.java
Working towards multiple readers.
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / adapter / GraphToDiagramSynchronizer.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.adapter;
13
14 import gnu.trove.map.hash.TObjectIntHashMap;
15 import gnu.trove.set.hash.THashSet;
16
17 import java.awt.geom.AffineTransform;
18 import java.lang.reflect.InvocationTargetException;
19 import java.util.ArrayDeque;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.Comparator;
25 import java.util.Deque;
26 import java.util.EnumSet;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Queue;
32 import java.util.Set;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.concurrent.ConcurrentMap;
35 import java.util.concurrent.atomic.AtomicBoolean;
36 import java.util.concurrent.locks.Condition;
37 import java.util.concurrent.locks.ReentrantLock;
38
39 import org.eclipse.core.runtime.IProgressMonitor;
40 import org.eclipse.core.runtime.SubMonitor;
41 import org.simantics.db.AsyncReadGraph;
42 import org.simantics.db.ReadGraph;
43 import org.simantics.db.RequestProcessor;
44 import org.simantics.db.Resource;
45 import org.simantics.db.Session;
46 import org.simantics.db.common.ResourceArray;
47 import org.simantics.db.common.exception.DebugException;
48 import org.simantics.db.common.procedure.adapter.AsyncProcedureAdapter;
49 import org.simantics.db.common.procedure.adapter.CacheListener;
50 import org.simantics.db.common.procedure.adapter.ListenerSupport;
51 import org.simantics.db.common.procedure.adapter.ProcedureAdapter;
52 import org.simantics.db.common.request.AsyncReadRequest;
53 import org.simantics.db.common.request.ReadRequest;
54 import org.simantics.db.common.request.UniqueAsyncRead;
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).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);
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                                                 
1345                                                 @Override
1346                                                 public void run(AsyncReadGraph graph) {
1347                                     profileObserver.listen(graph, GraphToDiagramSynchronizer.this);
1348                                                 }
1349                                         });
1350
1351                     return d;
1352
1353                 }
1354
1355                 this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
1356                 return this.diagram;
1357
1358             } finally {
1359                 idle();
1360             }
1361         } catch (InterruptedException e) {
1362             throw new RuntimeException(e);
1363         } catch (IllegalStateException e) {
1364             // If the synchronizer was disposed ahead of time, it was done
1365             // for a reason, such as the user having closed the owner editor.
1366             if (!isAlive())
1367                 throw new CancelTransactionException(e);
1368             throw new RuntimeException(e);
1369         } finally {
1370             Timing.END(loadTask);
1371         }
1372     }
1373
1374     static class CanvasNotification implements Runnable {
1375
1376         final private ICanvasContext canvas;
1377
1378         public CanvasNotification(ICanvasContext canvas) {
1379             this.canvas = canvas;
1380         }
1381
1382         public void run() {
1383             canvas.getContentContext().setDirty();
1384         }
1385
1386     }
1387
1388     ArrayList<IModification>        pendingModifications = new ArrayList<IModification>();
1389     MapSet<IElement, IModification> modificationIndex    = new MapSet.Hash<IElement, IModification>();
1390
1391     void addModification(IElement element, IModification modification) {
1392         pendingModifications.add(modification);
1393         if (element != null)
1394             modificationIndex.add(element, modification);
1395
1396     }
1397     class DefaultDiagramMutator implements DiagramMutator {
1398
1399         Map<IElement, Resource> creation = new HashMap<IElement, Resource>();
1400
1401         IDiagram d;
1402         Resource diagram;
1403
1404         IModifiableSynchronizationContext synchronizationContext;
1405
1406         public DefaultDiagramMutator(IDiagram d, Resource diagram, IModifiableSynchronizationContext synchronizationContext) {
1407             this.d = d;
1408             this.diagram = diagram;
1409             this.synchronizationContext = synchronizationContext;
1410
1411             if (synchronizationContext.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER) == null)
1412                 throw new IllegalArgumentException("SynchronizationHints.ELEMENT_CLASS_PROVIDER not available");
1413         }
1414
1415         void assertNotDisposed() {
1416             if (!isAlive())
1417                 throw new IllegalStateException(getClass().getSimpleName() + " is disposed");
1418         }
1419
1420         @Override
1421         public IElement newElement(ElementClass clazz) {
1422             assertNotDisposed();
1423             ElementFactory ef = d.getDiagramClass().getAtMostOneItemOfClass(ElementFactory.class);
1424             IElement element = null;
1425             if (ef != null)
1426                 element = ef.spawnNew(clazz);
1427             else
1428                 element = Element.spawnNew(clazz);
1429
1430             element.setHint(ElementHints.KEY_OBJECT, new TransientElementObject());
1431
1432             addModification(element, new AddElement(synchronizationContext, d, element));
1433
1434             return element;
1435         }
1436
1437         @Override
1438         public void commit() {
1439             assertNotDisposed();
1440             if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
1441                 System.out.println("DiagramMutator is about to commit changes:");
1442                 for (IModification mod : pendingModifications)
1443                     System.out.println("\t- " + mod);
1444             }
1445
1446             Collections.sort(pendingModifications);
1447
1448             if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
1449                 if (pendingModifications.size() > 1) {
1450                     System.out.println("* changes were re-ordered to:");
1451                     for (IModification mod : pendingModifications)
1452                         System.out.println("\t" + mod);
1453                 }
1454             }
1455
1456             Timing.safeTimed(errorHandler, "QUEUE AND WAIT FOR MODIFICATIONS TO FINISH", new GTask() {
1457                 @Override
1458                 public void run() throws DatabaseException {
1459                     // Performs a separate write request and query result update
1460                     // for each modification
1461 //                    for (IModification mod : pendingModifications) {
1462 //                        try {
1463 //                            modificationQueue.sync(mod);
1464 //                        } catch (InterruptedException e) {
1465 //                            error("Pending diagram modification " + mod
1466 //                                    + " was interrupted. See exception for details.", e);
1467 //                        }
1468 //                    }
1469
1470                     // NOTE: this is still under testing, the author is not
1471                     // truly certain that it should work in all cases ATM.
1472
1473                     // Performs all modifications with in a single write request
1474                     for (IModification mod : pendingModifications) {
1475                         modificationQueue.offer(mod, null);
1476                     }
1477                     try {
1478                         // Perform the modifications in a single request.
1479                         modificationQueue.finish();
1480                     } catch (InterruptedException e) {
1481                         errorHandler.error("Diagram modification finishing was interrupted. See exception for details.", e);
1482                     }
1483                 }
1484             });
1485             pendingModifications.clear();
1486             modificationIndex.clear();
1487             creation.clear();
1488             if (DebugPolicy.DEBUG_MUTATOR_COMMIT)
1489                 System.out.println("DiagramMutator has committed");
1490         }
1491
1492         @Override
1493         public void clear() {
1494             assertNotDisposed();
1495             pendingModifications.clear();
1496             modificationIndex.clear();
1497             creation.clear();
1498             if (DebugPolicy.DEBUG_MUTATOR)
1499                 System.out.println("DiagramMutator has been cleared");
1500         }
1501
1502         @Override
1503         public void modifyTransform(IElement element) {
1504             assertNotDisposed();
1505             Resource resource = backendObject(element);
1506             AffineTransform tr = element.getHint(ElementHints.KEY_TRANSFORM);
1507             if (resource != null && tr != null) {
1508                 addModification(element, new TransformElement(resource, tr));
1509             }
1510         }
1511
1512         @Override
1513         public void synchronizeHintsToBackend(IElement element) {
1514             assertNotDisposed();
1515             IHintSynchronizer synchronizer = element.getHint(SynchronizationHints.HINT_SYNCHRONIZER);
1516             if (synchronizer != null) {
1517                 CollectingModificationQueue queue = new CollectingModificationQueue();
1518                 synchronizer.synchronize(synchronizationContext, element);
1519                 addModification(element, new CompositeModification(ModificationAdapter.LOW_PRIORITY, queue.getQueue()));
1520             }
1521         }
1522
1523         @Override
1524         public void synchronizeElementOrder() {
1525             assertNotDisposed();
1526             List<IElement> snapshot = d.getSnapshot();
1527             addModification(null, new ElementReorder(d, snapshot));
1528         }
1529
1530         @Override
1531         public void register(IElement element, Object object) {
1532             creation.put(element, (Resource) object);
1533         }
1534
1535         @SuppressWarnings("unchecked")
1536         @Override
1537         public <T> T backendObject(IElement element) {
1538             Object object = ElementUtils.getObject(element);
1539             if (object instanceof Resource)
1540                 return (T) object;
1541             else
1542                 return (T) creation.get(element);
1543         }
1544
1545     }
1546
1547     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1548     // GRAPH TO DIAGRAM SYCHRONIZATION LOGIC BEGIN
1549     // ------------------------------------------------------------------------
1550
1551     static class ConnectionData {
1552         ConnectionEntityImpl impl;
1553         List<Resource>       branchPoints = new ArrayList<Resource>();
1554         List<EdgeResource>   segments     = new ArrayList<EdgeResource>();
1555
1556         ConnectionData(ConnectionEntityImpl ce) {
1557             this.impl = ce;
1558         }
1559
1560         void addBranchPoint(Resource bp) {
1561             branchPoints.add(bp);
1562         }
1563
1564         void addSegment(EdgeResource seg) {
1565             segments.add(seg);
1566         }
1567     }
1568
1569     class GraphToDiagramUpdater {
1570         DiagramContents                                 lastContent;
1571         DiagramContents                                 content;
1572         DiagramContentChanges                           changes;
1573
1574         final List<IElement>                            addedElements;
1575         final List<IElement>                            removedElements;
1576
1577         final List<IElement>                            addedConnectionSegments;
1578         final List<IElement>                            removedConnectionSegments;
1579
1580         final List<IElement>                            addedBranchPoints;
1581         final List<IElement>                            removedBranchPoints;
1582
1583         final Map<Object, IElement>                     addedElementMap;
1584         final Map<Resource, IElement>                   addedConnectionMap;
1585         final Map<Resource, ConnectionEntityImpl>       addedConnectionEntities;
1586         final List<Resource>                            removedConnectionEntities;
1587         final Map<ConnectionEntityImpl, ConnectionData> changedConnectionEntities;
1588
1589         final Map<Resource, IElement>                   addedRouteGraphConnectionMap;
1590         final List<IElement>                            removedRouteGraphConnections;
1591
1592
1593         GraphToDiagramUpdater(DiagramContents lastContent, DiagramContents content, DiagramContentChanges changes) {
1594             this.lastContent = lastContent;
1595             this.content = content;
1596             this.changes = changes;
1597
1598             this.addedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
1599             this.removedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
1600             this.addedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
1601             this.removedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
1602             this.addedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
1603             this.removedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
1604             this.addedElementMap = new HashMap<Object, IElement>();
1605             this.addedConnectionMap = new HashMap<Resource, IElement>();
1606             this.addedConnectionEntities = new HashMap<Resource, ConnectionEntityImpl>();
1607             this.removedConnectionEntities = new ArrayList<Resource>(changes.connections.size());
1608             this.changedConnectionEntities = new HashMap<ConnectionEntityImpl, ConnectionData>();
1609             this.addedRouteGraphConnectionMap = new HashMap<Resource, IElement>();
1610             this.removedRouteGraphConnections = new ArrayList<IElement>(changes.routeGraphConnections.size());
1611         }
1612
1613         public void clear() {
1614                 // Prevent DiagramContents leakage through DisposableListeners.
1615                 lastContent = null;
1616                 content = null;
1617                 changes = null;
1618                 
1619             this.addedElements.clear();
1620             this.removedElements.clear();
1621             this.addedConnectionSegments.clear();
1622             this.removedConnectionSegments.clear();
1623             this.addedBranchPoints.clear();
1624             this.removedBranchPoints.clear();
1625             this.addedElementMap.clear();
1626             this.addedConnectionMap.clear();
1627             this.addedConnectionEntities.clear();
1628             this.removedConnectionEntities.clear();
1629             this.changedConnectionEntities.clear();
1630             this.addedRouteGraphConnectionMap.clear();
1631             this.removedRouteGraphConnections.clear();
1632         }
1633
1634         void processNodes(ReadGraph graph) throws DatabaseException {
1635
1636             for (Map.Entry<Resource, Change> entry : changes.elements.entrySet()) {
1637
1638                 final Resource element = entry.getKey();
1639                 Change change = entry.getValue();
1640
1641                 switch (change) {
1642                     case ADDED: {
1643                         IElement mappedElement = getMappedElement(element);
1644                         if (mappedElement == null) {
1645                             if (DebugPolicy.DEBUG_NODE_LOAD)
1646                                 graph.syncRequest(new ReadRequest() {
1647                                     @Override
1648                                     public void run(ReadGraph graph) throws DatabaseException {
1649                                         System.out.println("    EXTERNALLY ADDED ELEMENT: "
1650                                                 + NameUtils.getSafeName(graph, element) + " ("
1651                                                 + element.getResourceId() + ")");
1652                                     }
1653                                 });
1654
1655                             if (content.connectionSet.contains(element)) {
1656
1657                                 // TODO: Connection loading has no listening, changes :Connection will not be noticed by this code!
1658                                 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1659                                         @Override
1660                                         public String toString() {
1661                                                 return "Connection load listener for " + element;
1662                                         }
1663                                     @Override
1664                                     public void execute(IElement loaded) {
1665                                         // Invoked when the element has been loaded.
1666                                         if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1667                                             System.out.println("CONNECTION LoadListener for " + loaded);
1668
1669                                         if (loaded == null) {
1670                                             disposeListener();
1671                                             return;
1672                                         }
1673
1674                                         Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1675
1676                                         // Logic for disposing listener
1677                                         if (!previousContent.connectionSet.contains(data)) {
1678                                             if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1679                                                 System.out.println("CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
1680                                             disposeListener();
1681                                             return;
1682                                         }
1683
1684                                         if (addedElementMap.containsKey(data)) {
1685                                             // This element was just loaded, in
1686                                             // which case its hints need to
1687                                             // uploaded to the real mapped
1688                                             // element immediately.
1689                                             IElement mappedElement = getMappedElement(data);
1690                                             if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1691                                                 System.out.println("LOADED ADDED CONNECTION, currently mapped connection: " + mappedElement);
1692                                             if (mappedElement != null && (mappedElement instanceof Element)) {
1693                                                 if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
1694                                                     System.out.println("  mapped hints: " + mappedElement.getHints());
1695                                                     System.out.println("  loaded hints: " + loaded.getHints());
1696                                                 }
1697                                                 updateMappedElement((Element) mappedElement, loaded);
1698                                             }
1699                                         } else {
1700                                             // This element was already loaded.
1701                                             // Just schedule an update some time
1702                                             // in the future.
1703                                             if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1704                                                 System.out.println("PREVIOUSLY LOADED CONNECTION UPDATED, scheduling update into the future");
1705                                             offerGraphUpdate( connectionUpdater(element, loaded) );
1706                                         }
1707                                     }
1708                                 };
1709
1710                                 graph.syncRequest(new ConnectionRequest(canvas, diagram, element, errorHandler, loadListener), new AsyncProcedure<IElement>() {
1711                                     @Override
1712                                     public void execute(AsyncReadGraph graph, final IElement e) {
1713                                         if (e == null)
1714                                             return;
1715
1716                                         //System.out.println("ConnectionRequestProcedure " + e);
1717                                         mapElement(element, e);
1718                                         synchronized (GraphToDiagramUpdater.this) {
1719                                             addedElements.add(e);
1720                                             addedElementMap.put(element, e);
1721                                             addedConnectionMap.put(element, e);
1722                                         }
1723
1724                                         // Read connection type
1725                                         graph.forSingleType(element, br.DIA.Connection, new Procedure<Resource>() {
1726                                             @Override
1727                                             public void exception(Throwable t) {
1728                                                 error(t);
1729                                             }
1730
1731                                             @Override
1732                                             public void execute(Resource connectionType) {
1733                                                 synchronized (GraphToDiagramUpdater.this) {
1734                                                     //System.out.println("new connection entity " + e);
1735                                                     ConnectionEntityImpl entity = new ConnectionEntityImpl(element, connectionType, e);
1736                                                     e.setHint(ElementHints.KEY_CONNECTION_ENTITY, entity);
1737                                                     addedConnectionEntities.put(element, entity);
1738                                                 }
1739                                             }
1740                                         });
1741                                     }
1742
1743                                     @Override
1744                                     public void exception(AsyncReadGraph graph, Throwable throwable) {
1745                                         error(throwable);
1746                                     }
1747                                 });
1748                             } else if (content.nodeSet.contains(element)) {
1749
1750                                 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1751                                         @Override
1752                                         public String toString() {
1753                                                 return "Node load listener for " + element;
1754                                         }
1755                                     @Override
1756                                     public void execute(IElement loaded) {
1757                                         // Invoked when the element has been loaded.
1758                                         if (DebugPolicy.DEBUG_NODE_LISTENER)
1759                                             System.out.println("NODE LoadListener for " + loaded);
1760
1761                                         if (loaded == null) {
1762                                             disposeListener();
1763                                             return;
1764                                         }
1765
1766                                         Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1767
1768                                         // Logic for disposing listener
1769                                         if (!previousContent.nodeSet.contains(data)) {
1770                                             if (DebugPolicy.DEBUG_NODE_LISTENER)
1771                                                 System.out.println("NODE LoadListener, node not in current content: " + data + ". Disposing.");
1772                                             disposeListener();
1773                                             return;
1774                                         }
1775
1776                                         if (addedElementMap.containsKey(data)) {
1777                                             // This element was just loaded, in
1778                                             // which case its hints need to
1779                                             // uploaded to the real mapped
1780                                             // element immediately.
1781                                             IElement mappedElement = getMappedElement(data);
1782                                             if (DebugPolicy.DEBUG_NODE_LISTENER)
1783                                                 System.out.println("LOADED ADDED ELEMENT, currently mapped element: " + mappedElement);
1784                                             if (mappedElement != null && (mappedElement instanceof Element)) {
1785                                                 if (DebugPolicy.DEBUG_NODE_LISTENER) {
1786                                                     System.out.println("  mapped hints: " + mappedElement.getHints());
1787                                                     System.out.println("  loaded hints: " + loaded.getHints());
1788                                                 }
1789                                                 updateMappedElement((Element) mappedElement, loaded);
1790                                             }
1791                                         } else {
1792                                             // This element was already loaded.
1793                                             // Just schedule an update some time
1794                                             // in the future.
1795                                             if (DebugPolicy.DEBUG_NODE_LISTENER)
1796                                                 System.out.println("PREVIOUSLY LOADED NODE UPDATED, scheduling update into the future");
1797                                             offerGraphUpdate( nodeUpdater(element, loaded) );
1798                                         }
1799                                     }
1800                                 };
1801
1802                                 //System.out.println("NODE REQUEST: " + element);
1803                                 graph.syncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
1804                                     @Override
1805                                     public void execute(AsyncReadGraph graph, IElement e) {
1806                                         if (e == null)
1807                                             return;
1808
1809                                         // This is invoked before the element is actually loaded.
1810                                         //System.out.println("NodeRequestProcedure " + e);
1811                                         if (DebugPolicy.DEBUG_NODE_LOAD)
1812                                             System.out.println("MAPPING ADDED NODE: " + element + " -> " + e);
1813                                         mapElement(element, e);
1814                                         synchronized (GraphToDiagramUpdater.this) {
1815                                             addedElements.add(e);
1816                                             addedElementMap.put(element, e);
1817                                         }
1818                                     }
1819
1820                                     @Override
1821                                     public void exception(AsyncReadGraph graph, Throwable throwable) {
1822                                         error(throwable);
1823                                     }
1824                                 });
1825
1826                             } else {
1827 //                                warning("Diagram elements must be either elements or connections, "
1828 //                                        + NameUtils.getSafeName(g, element) + " is neither",
1829 //                                        new AssumptionException(""));
1830                             }
1831                         }
1832                         break;
1833                     }
1834                     case REMOVED: {
1835                         IElement e = getMappedElement(element);
1836                         if (DebugPolicy.DEBUG_NODE_LOAD)
1837                             graph.syncRequest(new ReadRequest() {
1838                                 @Override
1839                                 public void run(ReadGraph graph) throws DatabaseException {
1840                                     System.out.println("    EXTERNALLY REMOVED ELEMENT: "
1841                                             + NameUtils.getSafeName(graph, element) + " ("
1842                                             + element.getResourceId() + ")");
1843                                 }
1844                             });
1845                         if (e != null) {
1846                             removedElements.add(e);
1847                         }
1848                         break;
1849                     }
1850                 }
1851             }
1852         }
1853
1854         void gatherChangedConnectionParts(Map<?, Change> changes) {
1855             for (Map.Entry<?, Change> entry : changes.entrySet()) {
1856                 Object part = entry.getKey();
1857                 Change change = entry.getValue();
1858
1859                 switch (change) {
1860                     case ADDED: {
1861                         synchronized (GraphToDiagramUpdater.this) {
1862                             Resource connection = content.partToConnection.get(part);
1863                             assert connection != null;
1864
1865                             IElement ce = getMappedElement(connection);
1866                             if (ce == null)
1867                                 ce = addedElementMap.get(connection);
1868
1869                             if (ce != null)
1870                                 markConnectionChanged(ce);
1871                             break;
1872                         }
1873                     }
1874                     case REMOVED: {
1875                         if (lastContent == null)
1876                             break;
1877                         Resource connection = lastContent.partToConnection.get(part);
1878                         if (connection != null && content.connectionSet.contains(connection)) {
1879                             markConnectionChanged(connection);
1880                         }
1881                         break;
1882                     }
1883                 }
1884             }
1885         }
1886
1887         void markConnectionChanged(Resource connection) {
1888 //            System.out.println("markConnectionChanged");
1889             ConnectionEntityImpl ce = getMappedConnection(connection);
1890             if (ce != null) {
1891                 markConnectionChanged(ce);
1892                 return;
1893             }
1894             error("WARNING: marking connection entity " + connection
1895                     + " changed, but the connection was not previously mapped",
1896                     new Exception("created exception to get a stack trace"));
1897         }
1898
1899         void markConnectionChanged(IElement connection) {
1900             ConnectionEntityImpl entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
1901             if (entity != null)
1902                 markConnectionChanged(entity);
1903         }
1904
1905         void markConnectionChanged(ConnectionEntityImpl ce) {
1906             if (!changedConnectionEntities.containsKey(ce)) {
1907                 changedConnectionEntities.put(ce, new ConnectionData(ce));
1908             }
1909         }
1910
1911         void processConnections() {
1912             // Find added/removed connection segments/branch points
1913             // in order to find all changed connection entities.
1914             gatherChangedConnectionParts(changes.connectionSegments);
1915             gatherChangedConnectionParts(changes.branchPoints);
1916
1917             // Find removed connection entities
1918             for (Map.Entry<Resource, Change> entry : changes.connections.entrySet()) {
1919                 Resource ce = entry.getKey();
1920                 Change change = entry.getValue();
1921
1922                 switch (change) {
1923                     case REMOVED: {
1924                         removedConnectionEntities.add(ce);
1925                     }
1926                 }
1927             }
1928
1929             // Generate update data of changed connection entities.
1930             // This ConnectionData will be applied in the canvas thread
1931             // diagram updater.
1932             for (ConnectionData cd : changedConnectionEntities.values()) {
1933                 for (Object part : content.connectionToParts.getValuesUnsafe(cd.impl.connection)) {
1934                     if (part instanceof Resource) {
1935                         cd.branchPoints.add((Resource) part);
1936                     } else if (part instanceof EdgeResource) {
1937                         cd.segments.add((EdgeResource) part);
1938                     }
1939                 }
1940             }
1941         }
1942
1943         void processRouteGraphConnections(ReadGraph graph) throws DatabaseException {
1944             for (Map.Entry<Resource, Change> entry : changes.routeGraphConnections.entrySet()) {
1945                 final Resource connection = entry.getKey();
1946
1947                 Change change = entry.getValue();
1948                 switch (change) {
1949                     case ADDED: {
1950                         IElement mappedElement = getMappedElement(connection);
1951                         if (mappedElement != null)
1952                             continue;
1953
1954                         Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1955                                 @Override
1956                                 public String toString() {
1957                                         return "processRouteGraphConnections " + connection;
1958                                 }
1959                             @Override
1960                             public void execute(IElement loaded) {
1961                                 // Invoked when the element has been loaded.
1962                                 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1963                                     System.out.println("ROUTE GRAPH CONNECTION LoadListener for " + loaded);
1964
1965                                 if (loaded == null) {
1966                                     disposeListener();
1967                                     return;
1968                                 }
1969
1970                                 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1971
1972                                 // Logic for disposing listener
1973                                 if (!previousContent.routeGraphConnectionSet.contains(data)) {
1974                                     if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1975                                         System.out.println("ROUTE GRAPH CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
1976                                     disposeListener();
1977                                     return;
1978                                 }
1979
1980                                 if (addedElementMap.containsKey(data)) {
1981                                     // This element was just loaded, in
1982                                     // which case its hints need to
1983                                     // uploaded to the real mapped
1984                                     // element immediately.
1985                                     IElement mappedElement = getMappedElement(data);
1986                                     if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1987                                         System.out.println("LOADED ADDED ROUTE GRAPH CONNECTION, currently mapped connection: " + mappedElement);
1988                                     if (mappedElement instanceof Element) {
1989                                         if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
1990                                             System.out.println("  mapped hints: " + mappedElement.getHints());
1991                                             System.out.println("  loaded hints: " + loaded.getHints());
1992                                         }
1993                                         updateMappedElement((Element) mappedElement, loaded);
1994                                     }
1995                                 } else {
1996                                     // This element was already loaded.
1997                                     // Just schedule an update some time
1998                                     // in the future.
1999                                     if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2000                                         System.out.println("PREVIOUSLY LOADED ROUTE GRAPH CONNECTION UPDATED, scheduling update into the future: " + connection);
2001
2002                                     Set<Object> dirtyNodes = new THashSet<Object>(4);
2003                                     IElement mappedElement = getMappedElement(connection);
2004                                     ConnectionEntity ce = mappedElement.getHint(ElementHints.KEY_CONNECTION_ENTITY);
2005                                     if (ce != null) {
2006                                         for (Connection conn : ce.getTerminalConnections(null)) {
2007                                             Object o = conn.node.getHint(ElementHints.KEY_OBJECT);
2008                                             if (o != null) {
2009                                                 dirtyNodes.add(o);
2010                                                 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2011                                                     System.out.println("Marked connectivity dirty for node: " + conn.node);
2012                                             }
2013                                         }
2014                                     }
2015
2016                                     offerGraphUpdate( routeGraphConnectionUpdater(connection, loaded, dirtyNodes) );
2017                                 }
2018                             }
2019                         };
2020
2021                         graph.syncRequest(new ConnectionRequest(canvas, diagram, connection, errorHandler, loadListener), new Procedure<IElement>() {
2022                             @Override
2023                             public void execute(final IElement e) {
2024                                 if (e == null)
2025                                     return;
2026
2027                                 //System.out.println("ConnectionRequestProcedure " + e);
2028                                 if (DebugPolicy.DEBUG_NODE_LOAD)
2029                                     System.out.println("MAPPING ADDED ROUTE GRAPH CONNECTION: " + connection + " -> " + e);
2030                                 mapElement(connection, e);
2031                                 synchronized (GraphToDiagramUpdater.this) {
2032                                     addedElements.add(e);
2033                                     addedElementMap.put(connection, e);
2034                                     addedRouteGraphConnectionMap.put(connection, e);
2035                                 }
2036                             }
2037                             @Override
2038                             public void exception(Throwable throwable) {
2039                                 error(throwable);
2040                             }
2041                         });
2042                         break;
2043                     }
2044                     case REMOVED: {
2045                         IElement e = getMappedElement(connection);
2046                         if (e != null)
2047                             removedRouteGraphConnections.add(e);
2048                         break;
2049                     }
2050                 }
2051             }
2052         }
2053
2054         ConnectionEntityImpl getConnectionEntity(Object connectionPart) {
2055             Resource connection = content.partToConnection.get(connectionPart);
2056             assert connection != null;
2057             ConnectionEntityImpl ce = addedConnectionEntities.get(connection);
2058             if (ce != null)
2059                 return ce;
2060             return assertMappedConnection(connection);
2061         }
2062
2063         void processBranchPoints(ReadGraph graph) throws DatabaseException {
2064             for (Map.Entry<Resource, Change> entry : changes.branchPoints.entrySet()) {
2065
2066                 final Resource element = entry.getKey();
2067                 Change change = entry.getValue();
2068
2069                 switch (change) {
2070                     case ADDED: {
2071                         IElement mappedElement = getMappedElement(element);
2072                         if (mappedElement == null) {
2073                             if (DebugPolicy.DEBUG_NODE_LOAD)
2074                                 graph.syncRequest(new ReadRequest() {
2075                                     @Override
2076                                     public void run(ReadGraph graph) throws DatabaseException {
2077                                         System.out.println("    EXTERNALLY ADDED BRANCH POINT: "
2078                                                 + NameUtils.getSafeName(graph, element) + " ("
2079                                                 + element.getResourceId() + ")");
2080                                     }
2081                                 });
2082
2083                             Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
2084                                 @Override
2085                                 public String toString() {
2086                                         return "processBranchPoints for " + element;
2087                                 }
2088                                 @Override
2089                                 public void execute(IElement loaded) {
2090                                     // Invoked when the element has been loaded.
2091                                     if (DebugPolicy.DEBUG_NODE_LISTENER)
2092                                         System.out.println("BRANCH POINT LoadListener for " + loaded);
2093
2094                                     if (loaded == null) {
2095                                         disposeListener();
2096                                         return;
2097                                     }
2098
2099                                     Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2100                                     if (addedElementMap.containsKey(data)) {
2101                                         // This element was just loaded, in
2102                                         // which case its hints need to
2103                                         // uploaded to the real mapped
2104                                         // element immediately.
2105                                         IElement mappedElement = getMappedElement(data);
2106                                         if (DebugPolicy.DEBUG_NODE_LISTENER)
2107                                             System.out.println("LOADED ADDED BRANCH POINT, currently mapped element: " + mappedElement);
2108                                         if (mappedElement != null && (mappedElement instanceof Element)) {
2109                                             if (DebugPolicy.DEBUG_NODE_LISTENER) {
2110                                                 System.out.println("  mapped hints: " + mappedElement.getHints());
2111                                                 System.out.println("  loaded hints: " + loaded.getHints());
2112                                             }
2113                                             updateMappedElement((Element) mappedElement, loaded);
2114                                         }
2115                                     } else {
2116                                         // This element was already loaded.
2117                                         // Just schedule an update some time
2118                                         // in the future.
2119                                         if (DebugPolicy.DEBUG_NODE_LISTENER)
2120                                             System.out.println("PREVIOUSLY LOADED BRANCH POINT UPDATED, scheduling update into the future");
2121                                         offerGraphUpdate( nodeUpdater(element, loaded) );
2122                                     }
2123                                 }
2124                             };
2125
2126                             graph.syncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
2127                                 @Override
2128                                 public void execute(AsyncReadGraph graph, IElement e) {
2129                                     if (e != null) {
2130                                         mapElement(element, e);
2131                                         synchronized (GraphToDiagramUpdater.this) {
2132                                             addedBranchPoints.add(e);
2133                                             addedElementMap.put(element, e);
2134                                             ConnectionEntityImpl ce = getConnectionEntity(element);
2135                                             e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
2136                                             e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
2137                                         }
2138                                     }
2139                                 }
2140
2141                                 @Override
2142                                 public void exception(AsyncReadGraph graph, Throwable throwable) {
2143                                     error(throwable);
2144                                 }
2145                             });
2146                         }
2147                         break;
2148                     }
2149                     case REMOVED: {
2150                         IElement e = getMappedElement(element);
2151                         if (DebugPolicy.DEBUG_NODE_LOAD)
2152                             graph.syncRequest(new ReadRequest() {
2153                                 @Override
2154                                 public void run(ReadGraph graph) throws DatabaseException {
2155                                     System.out.println("    EXTERNALLY REMOVED BRANCH POINT: "
2156                                             + NameUtils.getSafeName(graph, element) + " ("
2157                                             + element.getResourceId() + ")");
2158                                 }
2159                             });
2160                         if (e != null) {
2161                             removedBranchPoints.add(e);
2162                         }
2163                         break;
2164                     }
2165                 }
2166             }
2167         }
2168
2169         void processConnectionSegments(ReadGraph graph) throws DatabaseException {
2170             ConnectionSegmentAdapter adapter = connectionSegmentAdapter;
2171
2172             for (Map.Entry<EdgeResource, Change> entry : changes.connectionSegments.entrySet()) {
2173                 final EdgeResource seg = entry.getKey();
2174                 Change change = entry.getValue();
2175
2176                 switch (change) {
2177                     case ADDED: {
2178                         IElement mappedElement = getMappedElement(seg);
2179                         if (mappedElement == null) {
2180                             if (DebugPolicy.DEBUG_EDGE_LOAD)
2181                                 graph.syncRequest(new ReadRequest() {
2182                                     @Override
2183                                     public void run(ReadGraph graph) throws DatabaseException {
2184                                         System.out.println("    EXTERNALLY ADDED CONNECTION SEGMENT: " + seg.toString()
2185                                                 + " - " + seg.toString(graph));
2186                                     }
2187                                 });
2188
2189                             graph.syncRequest(new EdgeRequest(canvas, errorHandler, canvasListenerSupport, diagram, adapter, seg), new AsyncProcedure<IElement>() {
2190                                 @Override
2191                                 public void execute(AsyncReadGraph graph, IElement e) {
2192                                     if (DebugPolicy.DEBUG_EDGE_LOAD)
2193                                         System.out.println("ADDED EDGE LOADED: " + e);
2194                                     if (e != null) {
2195                                         mapElement(seg, e);
2196                                         synchronized (GraphToDiagramUpdater.this) {
2197                                             addedConnectionSegments.add(e);
2198                                             addedElementMap.put(seg, e);
2199                                             ConnectionEntityImpl ce = getConnectionEntity(seg);
2200                                             e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
2201                                             e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
2202                                         }
2203                                     }
2204                                 }
2205
2206                                 @Override
2207                                 public void exception(AsyncReadGraph graph, Throwable throwable) {
2208                                     error(throwable);
2209                                 }
2210                             });
2211                         }
2212                         break;
2213                     }
2214                     case REMOVED: {
2215                         final IElement e = getMappedElement(seg);
2216                         if (DebugPolicy.DEBUG_EDGE_LOAD)
2217                             graph.syncRequest(new ReadRequest() {
2218                                 @Override
2219                                 public void run(ReadGraph graph) throws DatabaseException {
2220                                     System.out.println("    EXTERNALLY REMOVED CONNECTION SEGMENT: " + seg.toString() + " - "
2221                                             + seg.toString(graph) + " -> " + e);
2222                                 }
2223                             });
2224                         if (e != null) {
2225                             removedConnectionSegments.add(e);
2226                         }
2227                         break;
2228                     }
2229                 }
2230             }
2231         }
2232
2233         void executeDeferredLoaders(ReadGraph graph) throws DatabaseException {
2234             // The rest of the diagram loading passes
2235             Deque<IElement> q1 = new ArrayDeque<IElement>();
2236             Deque<IElement> q2 = new ArrayDeque<IElement>();
2237             collectElementLoaders(q1, addedElements);
2238             while (!q1.isEmpty()) {
2239                 //System.out.println("DEFFERED LOADERS: " + q1);
2240                 for (IElement e : q1) {
2241                     ElementLoader loader = e.removeHint(DiagramModelHints.KEY_ELEMENT_LOADER);
2242                     //System.out.println("EXECUTING DEFFERED LOADER: " + loader);
2243                     loader.load(graph, diagram, e);
2244                 }
2245
2246                 collectElementLoaders(q2, q1);
2247                 Deque<IElement> qt = q1;
2248                 q1 = q2;
2249                 q2 = qt;
2250                 q2.clear();
2251             }
2252         }
2253
2254         private void collectElementLoaders(Queue<IElement> queue, Collection<IElement> cs) {
2255             for (IElement e : cs) {
2256                 ElementLoader loader = e.getHint(DiagramModelHints.KEY_ELEMENT_LOADER);
2257                 if (loader != null)
2258                     queue.add(e);
2259             }
2260         }
2261
2262         public void process(ReadGraph graph) throws DatabaseException {
2263             // No changes? Do nothing.
2264             if (changes.isEmpty())
2265                 return;
2266
2267             // NOTE: This order is important.
2268             Object task = Timing.BEGIN("processNodesConnections");
2269             //System.out.println("---- PROCESS NODES & CONNECTIONS BEGIN");
2270             if (!changes.elements.isEmpty()) {
2271                 graph.syncRequest(new ReadRequest() {
2272                     @Override
2273                     public void run(ReadGraph graph) throws DatabaseException {
2274                         processNodes(graph);
2275                     }
2276                     @Override
2277                     public String toString() {
2278                         return "processNodes";
2279                     }
2280                 });
2281             }
2282             //System.out.println("---- PROCESS NODES & CONNECTIONS END");
2283
2284             processConnections();
2285
2286             //System.out.println("---- PROCESS BRANCH POINTS BEGIN");
2287             if (!changes.branchPoints.isEmpty()) {
2288                 graph.syncRequest(new ReadRequest() {
2289                     @Override
2290                     public void run(ReadGraph graph) throws DatabaseException {
2291                         processBranchPoints(graph);
2292                     }
2293                     @Override
2294                     public String toString() {
2295                         return "processBranchPoints";
2296                     }
2297                 });
2298             }
2299             //System.out.println("---- PROCESS BRANCH POINTS END");
2300
2301             Timing.END(task);
2302             task = Timing.BEGIN("processConnectionSegments");
2303
2304             //System.out.println("---- PROCESS CONNECTION SEGMENTS BEGIN");
2305             if (!changes.connectionSegments.isEmpty()) {
2306                 graph.syncRequest(new ReadRequest() {
2307                     @Override
2308                     public void run(ReadGraph graph) throws DatabaseException {
2309                         processConnectionSegments(graph);
2310                     }
2311                     @Override
2312                     public String toString() {
2313                         return "processConnectionSegments";
2314                     }
2315                 });
2316             }
2317             //System.out.println("---- PROCESS CONNECTION SEGMENTS END");
2318
2319             Timing.END(task);
2320
2321             task = Timing.BEGIN("processRouteGraphConnections");
2322             if (!changes.routeGraphConnections.isEmpty()) {
2323                 graph.syncRequest(new ReadRequest() {
2324                     @Override
2325                     public void run(ReadGraph graph) throws DatabaseException {
2326                         processRouteGraphConnections(graph);
2327                     }
2328                     @Override
2329                     public String toString() {
2330                         return "processRouteGraphConnections";
2331                     }
2332                 });
2333             }
2334             Timing.END(task);
2335
2336             //System.out.println("---- AFTER LOADING");
2337             //for (IElement e : addedElements)
2338             //    System.out.println("    ADDED ELEMENT: " + e);
2339             //for (IElement e : addedBranchPoints)
2340             //    System.out.println("    ADDED BRANCH POINTS: " + e);
2341
2342             task = Timing.BEGIN("executeDeferredLoaders");
2343             executeDeferredLoaders(graph);
2344             Timing.END(task);
2345         }
2346
2347         public boolean isEmpty() {
2348             return addedElements.isEmpty() && removedElements.isEmpty()
2349             && addedConnectionSegments.isEmpty() && removedConnectionSegments.isEmpty()
2350             && addedBranchPoints.isEmpty() && removedBranchPoints.isEmpty()
2351             && addedConnectionEntities.isEmpty() && removedConnectionEntities.isEmpty()
2352             && addedRouteGraphConnectionMap.isEmpty() && removedRouteGraphConnections.isEmpty()
2353             && !changes.elementOrderChanged;
2354         }
2355
2356         class DefaultConnectionSegmentAdapter implements ConnectionSegmentAdapter {
2357
2358             @Override
2359             public void getClass(AsyncReadGraph graph, EdgeResource edge, ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, IDiagram diagram, final AsyncProcedure<ElementClass> procedure) {
2360                 if (info.connectionType != null) {
2361                     NodeClassRequest request = new NodeClassRequest(canvas, diagram, info.connectionType, true);
2362                     graph.asyncRequest(request, new CacheListener<ElementClass>(listenerSupport));
2363                     graph.asyncRequest(request, procedure);
2364                 } else {
2365                     procedure.execute(graph, null);
2366                 }
2367             }
2368
2369             @Override
2370             public void load(AsyncReadGraph graph, final EdgeResource edge, final ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, final IDiagram diagram, final IElement element) {
2371                 graph.asyncRequest(new Read<IElement>() {
2372                     @Override
2373                     public IElement perform(ReadGraph graph) throws DatabaseException {
2374                         //ITask task = ThreadLogger.getInstance().begin("LoadSegment");
2375                         syncLoad(graph, edge, info, diagram, element);
2376                         //task.finish();
2377                         return element;
2378                     }
2379                     @Override
2380                     public String toString() {
2381                         return "defaultConnectionSegmentAdapter";
2382                     }
2383                 }, new DisposableListener<IElement>(listenerSupport) {
2384                         
2385                         @Override
2386                         public String toString() {
2387                                 return "DefaultConnectionSegmentAdapter listener for " + edge;
2388                         }
2389                         
2390                     @Override
2391                     public void execute(IElement loaded) {
2392                         // Invoked when the element has been loaded.
2393                         if (DebugPolicy.DEBUG_EDGE_LISTENER)
2394                             System.out.println("EDGE LoadListener for " + loaded);
2395
2396                         if (loaded == null) {
2397                             disposeListener();
2398                             return;
2399                         }
2400
2401                         Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2402                         if (addedElementMap.containsKey(data)) {
2403                             // This element was just loaded, in
2404                             // which case its hints need to
2405                             // uploaded to the real mapped
2406                             // element immediately.
2407                             IElement mappedElement = getMappedElement(data);
2408                             if (DebugPolicy.DEBUG_EDGE_LISTENER)
2409                                 System.out.println("LOADED ADDED EDGE, currently mapped element: " + mappedElement);
2410                             if (mappedElement != null && (mappedElement instanceof Element)) {
2411                                 if (DebugPolicy.DEBUG_EDGE_LISTENER) {
2412                                     System.out.println("  mapped hints: " + mappedElement.getHints());
2413                                     System.out.println("  loaded hints: " + loaded.getHints());
2414                                 }
2415                                 updateMappedElement((Element) mappedElement, loaded);
2416                             }
2417                         } else {
2418                             // This element was already loaded.
2419                             // Just schedule an update some time
2420                             // in the future.
2421                             if (DebugPolicy.DEBUG_EDGE_LISTENER)
2422                                 System.out.println("PREVIOUSLY LOADED EDGE UPDATED, scheduling update into the future");
2423                             offerGraphUpdate( edgeUpdater(element, loaded) );
2424                         }
2425                     }
2426                 });
2427             }
2428
2429             void syncLoad(ReadGraph graph, EdgeResource connectionSegment, ConnectionInfo info, IDiagram diagram, IElement element) throws DatabaseException {
2430                 // Check that at least some data exists before continuing further.
2431                 if (!graph.hasStatement(connectionSegment.first()) && !graph.hasStatement(connectionSegment.second())) {
2432                     return;
2433                 }
2434
2435                 // Validate that both ends of the segment are
2436                 // part of the same connection before loading.
2437                 // This will happen for connections that are
2438                 // modified through splitting and joining of
2439                 // connection segments.
2440                 Resource c = ConnectionUtil.tryGetConnection(graph, connectionSegment);
2441                 if (c == null) {
2442                     // Ok, this segment is somehow invalid. Just don't load it.
2443                     if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2444                         System.out.println("Skipping edge " + connectionSegment + ". Both segment ends are not part of the same connection.");
2445                     return;
2446                 }
2447
2448                 if (!info.isValid()) {
2449                     // This edge must be somehow invalid, don't proceed with loading.
2450                     if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2451                         warning("Cannot load edge " + connectionSegment + ". ConnectionInfo " + info + " is invalid.", new DebugException("execution trace"));
2452                     return;
2453                 }
2454
2455                 Element edge = (Element) element;
2456                 edge.setHint(ElementHints.KEY_OBJECT, connectionSegment);
2457
2458                 // connectionSegment resources may currently be in a different
2459                 // order than ConnectionInfo.firstEnd/secondEnd. Therefore the
2460                 // segment ends must be resolved here.
2461                 ConnectionSegmentEnd firstEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.first());
2462                 ConnectionSegmentEnd secondEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.second());
2463                 if (firstEnd == null || secondEnd == null) {
2464                     if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2465                         warning("End attachments for edge " + connectionSegment + " are unresolved: (" + firstEnd + "," + secondEnd + ")", new DebugException("execution trace"));
2466                     return;
2467                 }
2468
2469                 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2470                     System.out.println("CONNECTION INFO: " + connectionSegment + " - " + info);
2471                 DesignatedTerminal firstTerminal = DiagramGraphUtil.findDesignatedTerminal(
2472                         graph, diagram, connectionSegment.first(), firstEnd);
2473                 DesignatedTerminal secondTerminal = DiagramGraphUtil.findDesignatedTerminal(
2474                         graph, diagram, connectionSegment.second(), secondEnd);
2475
2476                 // Edges must be connected at both ends in order for edge loading to succeed.
2477                 String err = validateConnectivity(graph, connectionSegment, firstTerminal, secondTerminal);
2478                 if (err != null) {
2479                     // Stop loading edge if the connectivity cannot be completely resolved.
2480                     if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2481                         warning(err, null);
2482                     return;
2483                 }
2484
2485                 graph.syncRequest(new AsyncReadRequest() {
2486                                         @Override
2487                                         public void run(AsyncReadGraph graph) {
2488                                 // NOTICE: Layer information is loaded from the connection entity resource
2489                                 // that is shared by all segments of the same connection.
2490                                 ElementFactoryUtil.loadLayersForElement(graph, layerManager, diagram, edge, info.connection,
2491                                         new AsyncProcedureAdapter<IElement>() {
2492                                     @Override
2493                                     public void exception(AsyncReadGraph graph, Throwable t) {
2494                                         error("failed to load layers for connection segment", t);
2495                                     }
2496                                 });
2497                                         }
2498                                 });
2499
2500                 edge.setHintWithoutNotification(KEY_CONNECTION_BEGIN_PLACEHOLDER, new PlaceholderConnection(
2501                         EdgeEnd.Begin,
2502                         firstTerminal.element.getHint(ElementHints.KEY_OBJECT),
2503                         firstTerminal.terminal));
2504                 edge.setHintWithoutNotification(KEY_CONNECTION_END_PLACEHOLDER, new PlaceholderConnection(
2505                         EdgeEnd.End,
2506                         secondTerminal.element.getHint(ElementHints.KEY_OBJECT),
2507                         secondTerminal.terminal));
2508
2509                 IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);
2510                 if (modelingRules != null) {
2511                     ConnectionVisualsLoader loader = diagram.getHint(DiagramModelHints.KEY_CONNECTION_VISUALS_LOADER);
2512                     if (loader != null)
2513                         loader.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
2514                     else
2515                         DiagramGraphUtil.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
2516                 }
2517             }
2518
2519             private String validateConnectivity(ReadGraph graph, EdgeResource edge,
2520                     DesignatedTerminal firstTerminal,
2521                     DesignatedTerminal secondTerminal)
2522             throws DatabaseException {
2523                 boolean firstLoose = firstTerminal == null;
2524                 boolean secondLoose = secondTerminal == null;
2525                 boolean stray = firstLoose && secondLoose;
2526                 if (firstTerminal == null || secondTerminal == null) {
2527                     StringBuilder sb = new StringBuilder();
2528                     sb.append("encountered ");
2529                     sb.append(stray ? "stray" : "loose");
2530                     sb.append(" connection segment, ");
2531                     if (firstLoose)
2532                         sb.append("first ");
2533                     if (stray)
2534                         sb.append("and ");
2535                     if (secondLoose)
2536                         sb.append("second ");
2537                     sb.append("end disconnected: ");
2538                     sb.append(edge.toString(graph));
2539                     sb.append(" - ");
2540                     sb.append(edge.toString());
2541                     return sb.toString();
2542                 }
2543                 return null;
2544             }
2545
2546         }
2547
2548         ConnectionSegmentAdapter connectionSegmentAdapter = new DefaultConnectionSegmentAdapter();
2549
2550     }
2551
2552     private static final Double DIAGRAM_UPDATE_DIAGRAM_PRIORITY = 1d;
2553     private static final Double DIAGRAM_UPDATE_NODE_PRIORITY = 2d;
2554     private static final Double DIAGRAM_UPDATE_CONNECTION_PRIORITY = 3d;
2555     private static final Double DIAGRAM_UPDATE_EDGE_PRIORITY = 4d;
2556
2557     interface DiagramUpdater extends Runnable {
2558         Double getPriority();
2559
2560         Comparator<DiagramUpdater> DIAGRAM_UPDATER_COMPARATOR = new Comparator<DiagramUpdater>() {
2561             @Override
2562             public int compare(DiagramUpdater o1, DiagramUpdater o2) {
2563                 return o1.getPriority().compareTo(o2.getPriority());
2564             }
2565         };
2566     }
2567
2568     interface GraphUpdateReactor {
2569         DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException;
2570     }
2571
2572     static abstract class AbstractDiagramUpdater implements DiagramUpdater, GraphUpdateReactor {
2573         protected final Double priority;
2574         protected final String runnerName;
2575
2576         public AbstractDiagramUpdater(Double priority, String runnerName) {
2577             if (priority == null)
2578                 throw new NullPointerException("null priority");
2579             if (runnerName == null)
2580                 throw new NullPointerException("null runner name");
2581             this.priority = priority;
2582             this.runnerName = runnerName;
2583         }
2584
2585         @Override
2586         public Double getPriority() {
2587             return priority;
2588         }
2589
2590         @Override
2591         public AbstractDiagramUpdater graphUpdate(ReadGraph graph) {
2592             return this;
2593         }
2594
2595         @Override
2596         public void run() {
2597             Object task = Timing.BEGIN(runnerName);
2598             forDiagram();
2599             Timing.END(task);
2600         }
2601
2602         protected void forDiagram() {
2603         }
2604
2605         @Override
2606         public String toString() {
2607             return runnerName + "@" + System.identityHashCode(this) + " [" + priority + "]";
2608         }
2609     }
2610
2611     /**
2612      * @param content the new contents of the diagram, must not be
2613      *        <code>null</code>.
2614      */
2615     private GraphUpdateReactor diagramGraphUpdater(final DiagramContents content) {
2616         if (content == null)
2617             throw new NullPointerException("null diagram content");
2618
2619         return new GraphUpdateReactor() {
2620             @Override
2621             public String toString() {
2622                 return "DiagramGraphUpdater@" + System.identityHashCode(this);
2623             }
2624
2625             @Override
2626             public DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException {
2627                 // Never do anything here if the canvas has already been disposed.
2628                 if (!GraphToDiagramSynchronizer.this.isAlive())
2629                     return null;
2630
2631                 // We must be prepared for the following changes in the diagram graph
2632                 // model:
2633                 // - the diagram has been completely removed
2634                 // - elements have been added
2635                 // - elements have been removed
2636                 //
2637                 // Element-specific changes are handled by the element query listeners:
2638                 // - elements have been modified
2639                 // - element position has changed
2640                 // - element class (e.g. image) has changed
2641
2642                 diagramUpdateLock.lock();
2643                 try {
2644                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2645                         System.out.println("In diagramGraphUpdater:");
2646
2647                     // Find out what has changed since the last query.
2648                     Object task = Timing.BEGIN("diagramContentDifference");
2649                     DiagramContents lastContent = previousContent;
2650                     DiagramContentChanges changes = content.differenceFrom(previousContent);
2651                     previousContent = content;
2652                     Timing.END(task);
2653                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2654                         System.out.println("  changes: " + changes);
2655
2656                     // Bail out if there are no changes to react to.
2657                     if (changes.isEmpty())
2658                         return null;
2659
2660                     // Load everything that needs to be loaded from the graph,
2661                     // but don't update the UI model in this thread yet.
2662                     task = Timing.BEGIN("updater.process");
2663                     GraphToDiagramUpdater updater = new GraphToDiagramUpdater(lastContent, content, changes);
2664                     GraphToDiagramSynchronizer.this.currentUpdater = updater;
2665                     try {
2666                         updater.process(graph);
2667                     } finally {
2668                         GraphToDiagramSynchronizer.this.currentUpdater = null;
2669                     }
2670                     Timing.END(task);
2671
2672                     if (updater.isEmpty())
2673                         return null;
2674
2675                     // Return an updater that will update the UI run-time model.
2676                     return diagramUpdater(updater);
2677                 } finally {
2678                     diagramUpdateLock.unlock();
2679                 }
2680             }
2681         };
2682     }
2683
2684     DiagramUpdater diagramUpdater(final GraphToDiagramUpdater updater) {
2685         return new AbstractDiagramUpdater(DIAGRAM_UPDATE_DIAGRAM_PRIORITY, "updateDiagram") {
2686             @Override
2687             protected void forDiagram() {
2688                 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2689                     System.out.println("running diagram updater: " + this);
2690
2691                 // DiagramUtils.testDiagram(diagram);
2692                 Set<IElement> dirty = new HashSet<IElement>();
2693
2694                 Object task2 = Timing.BEGIN("Preprocess connection changes");
2695                 Map<ConnectionEntity, ConnectionChildren> connectionChangeData = new HashMap<ConnectionEntity, ConnectionChildren>(updater.changedConnectionEntities.size());
2696                 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2697                     connectionChangeData.put(cd.impl, cd.impl.getConnectionChildren());
2698                 }
2699                 Timing.END(task2);
2700
2701                 task2 = Timing.BEGIN("removeRouteGraphConnections");
2702                 for (IElement removedRouteGraphConnection : updater.removedRouteGraphConnections) {
2703                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2704                         System.out.println("removing route graph connection: " + removedRouteGraphConnection);
2705
2706                     ConnectionEntity ce = removedRouteGraphConnection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
2707                     if (ce == null)
2708                         continue;
2709                     Object connectionData = ElementUtils.getObject(removedRouteGraphConnection);
2710                     tempConnections.clear();
2711                     for (Connection conn : ce.getTerminalConnections(tempConnections)) {
2712                         ((Element) conn.node).removeHintWithoutNotification(new TerminalKeyOf(conn.terminal, connectionData, Connection.class));
2713                         // To be sure the view will be up-to-date, mark the node
2714                         // connected to the removed connection dirty.
2715                         dirty.add(conn.node);
2716                     }
2717                 }
2718                 Timing.END(task2);
2719
2720                 task2 = Timing.BEGIN("removeBranchPoints");
2721                 for (IElement removed : updater.removedBranchPoints) {
2722                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2723                         System.out.println("removing branch point: " + removed);
2724
2725                     unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2726                     removeNodeTopologyHints((Element) removed);
2727
2728                     IElement connection = ElementUtils.getParent(removed);
2729                     if (connection != null) {
2730                         dirty.add(connection);
2731                     }
2732                 }
2733                 Timing.END(task2);
2734
2735                 task2 = Timing.BEGIN("removeConnectionSegments");
2736                 for (IElement removed : updater.removedConnectionSegments) {
2737                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2738                         System.out.println("removing segment: " + removed);
2739
2740                     unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2741                     removeEdgeTopologyHints((Element) removed);
2742
2743                     IElement connection = ElementUtils.getParent(removed);
2744                     if (connection != null) {
2745                         dirty.add(connection);
2746                     }
2747                 }
2748                 Timing.END(task2);
2749
2750                 task2 = Timing.BEGIN("removeElements");
2751                 for (IElement removed : updater.removedElements) {
2752                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2753                         System.out.println("removing element: " + removed);
2754
2755                     removed.setHint(KEY_REMOVE_RELATIONSHIPS, Boolean.TRUE);
2756                     if (diagram.containsElement(removed)) {
2757                         diagram.removeElement(removed);
2758                     }
2759                     unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2760                     removeNodeTopologyHints((Element) removed);
2761
2762                     // No use marking removed elements dirty.
2763                     dirty.remove(removed);
2764                 }
2765                 Timing.END(task2);
2766
2767                 // TODO: get rid of this
2768                 task2 = Timing.BEGIN("removeConnectionEntities");
2769                 for (Resource ce : updater.removedConnectionEntities) {
2770                     unmapConnection(ce);
2771                 }
2772                 Timing.END(task2);
2773
2774                 task2 = Timing.BEGIN("setConnectionData");
2775                 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2776                     cd.impl.setData(cd.segments, cd.branchPoints);
2777                 }
2778                 Timing.END(task2);
2779
2780                 // TODO: get rid of this
2781                 task2 = Timing.BEGIN("addConnectionEntities");
2782                 for (Map.Entry<Resource, ConnectionEntityImpl> entry : updater.addedConnectionEntities
2783                         .entrySet()) {
2784                     mapConnection(entry.getKey(), entry.getValue());
2785                 }
2786                 Timing.END(task2);
2787
2788                 task2 = Timing.BEGIN("addBranchPoints");
2789                 for (IElement added : updater.addedBranchPoints) {
2790                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2791                         System.out.println("adding branch point: " + added);
2792
2793                     mapElement(ElementUtils.getObject(added), added);
2794
2795                     IElement connection = ElementUtils.getParent(added);
2796                     if (connection != null) {
2797                         dirty.add(connection);
2798                     }
2799                 }
2800                 Timing.END(task2);
2801
2802                 // Add new elements at end of diagram, element order will be synchronized later.
2803                 task2 = Timing.BEGIN("addElements");
2804                 for(Resource r : updater.content.elements) {
2805                     IElement added = updater.addedElementMap.get(r);
2806                     if(added != null) {
2807                         if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2808                             System.out.println("adding element: " + added);
2809
2810                         //Object task3 = BEGIN("mapElement " + added);
2811                         Object task3 = Timing.BEGIN("mapElement");
2812                         mapElement(added.getHint(ElementHints.KEY_OBJECT), added);
2813                         Timing.END(task3);
2814
2815                         //task3 = BEGIN("addElement " + added);
2816                         task3 = Timing.BEGIN("addElement");
2817                         //System.out.println("diagram.addElement: " + added + " - " + diagram);
2818                         diagram.addElement(added);
2819                         dirty.add(added);
2820                         Timing.END(task3);
2821                     }
2822                 }
2823                 Timing.END(task2);
2824
2825                 // We've ensured that all nodes must have been and
2826                 // mapped before reaching this.
2827                 task2 = Timing.BEGIN("addConnectionSegments");
2828                 for (IElement added : updater.addedConnectionSegments) {
2829                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2830                         System.out.println("adding segment: " + added);
2831
2832                     PlaceholderConnection cb = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_BEGIN_PLACEHOLDER);
2833                     PlaceholderConnection ce = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_END_PLACEHOLDER);
2834                     if (cb == null || ce == null) {
2835                         if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2836                             warning("ignoring connection segment " + added + ", connectivity was not resolved (begin=" + cb + ", end=" + ce +")", null);
2837                         continue;
2838                     }
2839
2840                     mapElement(ElementUtils.getObject(added), added);
2841
2842                     IElement beginNode = assertMappedElement(cb.node);
2843                     IElement endNode = assertMappedElement(ce.node);
2844
2845                     if (cb != null)
2846                         connect(added, cb.end, beginNode, cb.terminal);
2847                     if (ce != null)
2848                         connect(added, ce.end, endNode, ce.terminal);
2849
2850                     IElement connection = ElementUtils.getParent(added);
2851                     if (connection != null) {
2852                         dirty.add(connection);
2853                     }
2854                 }
2855                 Timing.END(task2);
2856
2857                 // We've ensured that all nodes must have been and
2858                 // mapped before reaching this.
2859
2860                 task2 = Timing.BEGIN("handle dirty RouteGraph connections");
2861                 for (IElement addedRouteGraphConnection : updater.addedRouteGraphConnectionMap.values()) {
2862                     updateDirtyRouteGraphConnection(addedRouteGraphConnection, dirty);
2863                 }
2864                 Timing.END(task2);
2865
2866                 // Prevent memory leaks
2867                 tempConnections.clear();
2868
2869                 // Make sure that the diagram element order matches that of the database.
2870                 final TObjectIntHashMap<IElement> orderMap = new TObjectIntHashMap<IElement>(2 * updater.content.elements.size());
2871                 int i = 1;
2872                 for (Resource r : updater.content.elements) {
2873                     IElement e = getMappedElement(r);
2874                     if (e != null)
2875                         orderMap.put(e, i);
2876                     ++i;
2877                 }
2878                 diagram.sort(new Comparator<IElement>() {
2879                     @Override
2880                     public int compare(IElement e1, IElement e2) {
2881                         int o1 = orderMap.get(e1);
2882                         int o2 = orderMap.get(e2);
2883                         return o1 - o2;
2884                     }
2885                 });
2886
2887                 // TODO: consider removing this. The whole thing should work without it and
2888                 // this "fix" will only be hiding the real problems.
2889                 task2 = Timing.BEGIN("fixChangedConnections");
2890                 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2891                     cd.impl.fix();
2892                 }
2893                 Timing.END(task2);
2894
2895                 task2 = Timing.BEGIN("validateAndFix");
2896                 DiagramUtils.validateAndFix(diagram, dirty);
2897                 Timing.END(task2);
2898
2899                 // This will fire connection entity change listeners
2900                 task2 = Timing.BEGIN("Postprocess connection changes");
2901                 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2902                     ConnectionChildren oldChildren = connectionChangeData.get(cd.impl);
2903                     if (oldChildren != null) {
2904                         ConnectionChildren currentChildren = cd.impl.getConnectionChildren();
2905                         cd.impl.fireListener(oldChildren, currentChildren);
2906                     }
2907                 }
2908                 Timing.END(task2);
2909
2910                 task2 = Timing.BEGIN("setDirty");
2911                 for (IElement e : dirty) {
2912                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2913                         System.out.println("MARKING ELEMENT DIRTY: " + e);
2914                     e.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
2915                 }
2916                 Timing.END(task2);
2917
2918                 // Signal about possible changes in the z-order of diagram elements.
2919                 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2920                     System.out.println("MARKING DIAGRAM DIRTY: " + diagram);
2921                 diagram.setHint(Hints.KEY_DIRTY, Hints.VALUE_Z_ORDER_CHANGED);
2922
2923                 // Mark the updater as "processed".
2924                 updater.clear();
2925
2926                 // Inform listeners that the diagram has been updated.
2927                 diagram.setHint(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, Boolean.TRUE);
2928             }
2929         };
2930     }
2931
2932     /**
2933      * @param connection
2934      * @param dirtySet
2935      */
2936     private void updateDirtyRouteGraphConnection(IElement connection, Set<IElement> dirtySet) {
2937         if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2938             System.out.println("updating dirty route graph connection: " + connection);
2939
2940         ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
2941         if (ce == null)
2942             return;
2943
2944         tempConnections.clear();
2945         Object connectionData = ElementUtils.getObject(connection);
2946         for (Connection conn : ce.getTerminalConnections(tempConnections)) {
2947             ((Element) conn.node).setHintWithoutNotification(
2948                     new TerminalKeyOf(conn.terminal, connectionData, Connection.class),
2949                     conn);
2950             if (dirtySet != null)
2951                 dirtySet.add(conn.node);
2952         }
2953
2954         // Prevent memory leaks.
2955         tempConnections.clear();
2956     }
2957
2958     abstract class ElementUpdater extends AbstractDiagramUpdater {
2959         private final IElement newElement;
2960
2961         public ElementUpdater(Double priority, String runnerName, IElement newElement) {
2962             super(priority, runnerName);
2963             if (newElement == null)
2964                 throw new NullPointerException("null element");
2965             this.newElement = newElement;
2966         }
2967
2968         @Override
2969         public String toString() {
2970             return super.toString() + "[" + newElement + "]";
2971         }
2972
2973         @Override
2974         public void run() {
2975 //            System.out.println("ElementUpdateRunner new=" + newElement);
2976             Object elementResource = newElement.getHint(ElementHints.KEY_OBJECT);
2977 //            System.out.println("ElementUpdateRunner res=" + elementResource);
2978             final Element mappedElement = (Element) getMappedElement(elementResource);
2979 //            System.out.println("ElementUpdateRunner mapped=" + mappedElement);
2980             if (mappedElement == null) {
2981                 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE) {
2982                     System.out.println("SKIP DIAGRAM UPDATE " + this  + " for element resource " + elementResource + ", no mapped element (newElement=" + newElement + ")");
2983                 }
2984                 // Indicates the element has been removed from the graph.
2985                 return;
2986             }
2987
2988             Object task = Timing.BEGIN(runnerName);
2989             forMappedElement(mappedElement);
2990             Timing.END(task);
2991         }
2992
2993         protected abstract void forMappedElement(Element mappedElement);
2994     }
2995
2996     ElementUpdater nodeUpdater(final Resource resource, final IElement newElement) {
2997
2998         return new ElementUpdater(DIAGRAM_UPDATE_NODE_PRIORITY, "updateNode", newElement) {
2999
3000             Collection<Terminal> getTerminals(IElement e) {
3001                 Collection<Terminal> ts = Collections.emptyList();
3002                 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
3003                 if (tt != null) {
3004                     ts = new ArrayList<Terminal>();
3005                     tt.getTerminals(newElement, ts);
3006                 }
3007                 return ts;
3008             }
3009
3010             @Override
3011             protected void forMappedElement(final Element mappedElement) {
3012                 if (DebugPolicy.DEBUG_NODE_UPDATE)
3013                     System.out.println("running node updater: " + this + " - new element: " + newElement);
3014
3015                 // Newly loaded node elements NEVER contain topology-related
3016                 // hints, i.e. TerminalKeyOf hints. Instead all connections are
3017                 // actually set into element hints when connection edges are
3018                 // loaded.
3019
3020                 Collection<Terminal> oldTerminals = getTerminals(mappedElement);
3021                 Collection<Terminal> newTerminals = getTerminals(newElement);
3022                 if (!oldTerminals.equals(newTerminals)) {
3023                     // Okay, there are differences in the terminals. Need to fix
3024                     // the TerminalKeyOf hint values to use the new terminal
3025                     // instances when correspondences exist.
3026
3027                     // If there is no correspondence for an old terminal, we
3028                     // are simply forced to remove the hints related to this
3029                     // connection.
3030
3031                     Map<Terminal, Terminal> newTerminalMap = new HashMap<Terminal, Terminal>(newTerminals.size());
3032                     for (Terminal t : newTerminals) {
3033                         newTerminalMap.put(t, t);
3034                     }
3035
3036                     for (Map.Entry<TerminalKeyOf, Object> entry : mappedElement.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3037                         TerminalKeyOf key = entry.getKey();
3038                         Connection c = (Connection) entry.getValue();
3039                         if (c.node == mappedElement) {
3040                             Terminal newTerminal = newTerminalMap.get(c.terminal);
3041                             if (newTerminal != null) {
3042                                 c = new Connection(c.edge, c.end, c.node, newTerminal);
3043                                 ((Element) c.edge).setHintWithoutNotification(EndKeyOf.get(c.end), c);
3044                             } else {
3045                                 mappedElement.removeHintWithoutNotification(key);
3046                             }
3047                         }
3048                     }
3049                 }
3050
3051                 updateMappedElement(mappedElement, newElement);
3052                 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3053             }
3054         };
3055     }
3056
3057     ElementUpdater connectionUpdater(final Object data, final IElement newElement) {
3058         return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateConnection", newElement) {
3059             @Override
3060             public void forMappedElement(Element mappedElement) {
3061                 if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
3062                     System.out.println("running connection updater: " + this + " - new element: " + newElement
3063                             + " with data " + data);
3064
3065                 // This is kept up-to-date by GDS, make sure not to overwrite it
3066                 // from the mapped element.
3067                 newElement.removeHint(ElementHints.KEY_CONNECTION_ENTITY);
3068
3069                 updateMappedElement(mappedElement, newElement);
3070                 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3071             }
3072         };
3073     }
3074
3075     ElementUpdater edgeUpdater(final Object data, final IElement newElement) {
3076         return new ElementUpdater(DIAGRAM_UPDATE_EDGE_PRIORITY, "updateEdge", newElement) {
3077             @Override
3078             public void forMappedElement(Element mappedElement) {
3079                 if (DebugPolicy.DEBUG_EDGE_UPDATE)
3080                     System.out.println("running edge updater: " + this + " - new element: " + newElement
3081                             + " with data " + data);
3082
3083                 updateMappedElement(mappedElement, newElement);
3084                 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3085             }
3086         };
3087     }
3088
3089     ElementUpdater routeGraphConnectionUpdater(final Object data, final IElement newElement, final Set<Object> dirtyNodes) {
3090         return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateRouteGraphConnection", newElement) {
3091             @Override
3092             public void forMappedElement(Element mappedElement) {
3093                 if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
3094                     System.out.println("running route graph connection updater: " + this + " - new element: " + newElement
3095                             + " with data " + data);
3096
3097                 // Remove all TerminalKeyOf hints from nodes that were
3098                 // previously connected to the connection (mappedElement)
3099                 // before updating mappedElement with new topology information.
3100
3101                 for (Object dirtyNodeObject : dirtyNodes) {
3102                     Element dirtyNode = (Element) getMappedElement(dirtyNodeObject);
3103                     if (dirtyNode == null)
3104                         continue;
3105                     if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
3106                         System.out.println("preparing node with dirty connectivity: " + dirtyNode);
3107
3108                     for (Map.Entry<TerminalKeyOf, Object> entry : dirtyNode.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3109                         Connection conn = (Connection) entry.getValue();
3110                         Object connectionNode = conn.edge.getHint(ElementHints.KEY_OBJECT);
3111                         if (data.equals(connectionNode)) {
3112                             dirtyNode.removeHintWithoutNotification(entry.getKey());
3113                         }
3114                     }
3115                 }
3116
3117                 // Update connection information, including topology
3118                 updateMappedElement(mappedElement, newElement);
3119
3120                 // Reinstall TerminalKeyOf hints into nodes that are now connected
3121                 // to mappedElement to keep diagram run-time model properly in sync
3122                 // with the database.
3123                 updateDirtyRouteGraphConnection(mappedElement, null);
3124
3125                 // TODO: should mark dirty nodes' scene graph dirty ?
3126
3127                 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3128             }
3129         };
3130     }
3131
3132     /**
3133      * Copies hints from <code>newElement</code> to <code>mappedElement</code>
3134      * asserting some validity conditions at the same time.
3135      * 
3136      * @param mappedElement
3137      * @param newElement
3138      */
3139     static void updateMappedElement(Element mappedElement, IElement newElement) {
3140         if (mappedElement == newElement)
3141             // Can't update anything if the two elements are the same.
3142             return;
3143
3144         ElementClass oldClass = mappedElement.getElementClass();
3145         ElementClass newClass = newElement.getElementClass();
3146
3147         Object mappedData = mappedElement.getHint(ElementHints.KEY_OBJECT);
3148         Object newData = newElement.getHint(ElementHints.KEY_OBJECT);
3149
3150         assert mappedData != null;
3151         assert newData != null;
3152         assert mappedData.equals(newData);
3153
3154         if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3155             System.out.println("Updating mapped element, setting hints\n  from: " + newElement + "\n  into: " + mappedElement);
3156         }
3157
3158         // TODO: consider if this equals check is a waste of time or does it pay
3159         // off due to having to reinitialize per-class caches for the new
3160         // ElementClass that are constructed on the fly?
3161         if (!newClass.equals(oldClass)) {
3162             if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3163                 System.out.println("  old element class: " + oldClass);
3164                 System.out.println("  new element class: " + newClass);
3165             }
3166             mappedElement.setElementClass(newClass);
3167         }
3168
3169         // Tuukka@2010-02-19: replaced with notifications for making
3170         // the graph synchronizer more transparent to the client.
3171
3172         // Hint notifications will not work when this is used.
3173         //mappedElement.setHintsWithoutNotification(newElement.getHints());
3174
3175         Map<DiscardableKey, Object> discardableHints = mappedElement.getHintsOfClass(DiscardableKey.class);
3176
3177         // Set all hints from newElement to mappedElement.
3178         // Leave any hints in mappedElement but not in newElement as is.
3179         Map<Key, Object> hints = newElement.getHints();
3180         Map<Key, Object> newHints = new HashMap<Key, Object>();
3181         for (Map.Entry<Key, Object> entry : hints.entrySet()) {
3182             Key key = entry.getKey();
3183             Object newValue = entry.getValue();
3184             Object oldValue = mappedElement.getHint(key);
3185             if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE_DETAIL) {
3186                 System.out.println("  hint " + key + " compare values: " + oldValue + "  ->  " + newValue);
3187             }
3188             if (!newValue.equals(oldValue)) {
3189                 newHints.put(key, newValue);
3190                 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3191                     System.out.format("    %-42s : %64s -> %-64s\n", key, oldValue, newValue);
3192                 }
3193             } else {
3194                 // If the hint value has not changed but the hint still exists
3195                 // we don't need to discard it even if it is considered
3196                 // discardable.
3197                 discardableHints.remove(key);
3198             }
3199         }
3200
3201         // Set all hints at once and send notifications after setting the values.
3202         if (!discardableHints.isEmpty()) {
3203             if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE)
3204                 System.out.println("Discarding " + discardableHints.size() + " discardable hints:\n  " + discardableHints);
3205             mappedElement.removeHints(discardableHints.keySet());
3206         }
3207         if (!newHints.isEmpty()) {
3208             if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3209                 System.out.println("Updating mapped element, setting new hints:\n\t"
3210                         + EString.implode(newHints.entrySet(), "\n\t") + "\nto replace old hints\n\t"
3211                         + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
3212             }
3213             mappedElement.setHints(newHints);
3214         }
3215         if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3216             System.out.println("All hints after update:\n\t"
3217                     + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
3218         }
3219     }
3220
3221     class TransactionListener extends SessionEventListenerAdapter {
3222         long startTime;
3223         @Override
3224         public void writeTransactionStarted() {
3225             startTime = System.nanoTime();
3226             if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
3227                 System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionStarted");
3228             inWriteTransaction.set(true);
3229         }
3230         @Override
3231         public void writeTransactionFinished() {
3232             long endTime = System.nanoTime();
3233             if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
3234                 System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionFinished: " + (endTime - startTime)*1e-6 + " ms");
3235             inWriteTransaction.set(false);
3236             scheduleGraphUpdates();
3237         }
3238     };
3239
3240     Object                   graphUpdateLock             = new Object();
3241     TransactionListener      sessionListener             = null;
3242     AtomicBoolean            inWriteTransaction          = new AtomicBoolean(false);
3243     AtomicBoolean            graphUpdateRequestScheduled = new AtomicBoolean(false);
3244     List<GraphUpdateReactor> queuedGraphUpdates          = new ArrayList<GraphUpdateReactor>();
3245
3246     private void offerGraphUpdate(GraphUpdateReactor update) {
3247         if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3248             System.out.println("offerGraphUpdate: " + update);
3249         boolean inWrite = inWriteTransaction.get();
3250         synchronized (graphUpdateLock) {
3251             if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3252                 System.out.println("queueing graph update: " + update);
3253             queuedGraphUpdates.add(update);
3254         }
3255         if (!inWrite) {
3256             if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3257                 System.out.println("scheduling queued graph update immediately: " + update);
3258             scheduleGraphUpdates();
3259         }
3260     }
3261
3262     private Collection<GraphUpdateReactor> scrubGraphUpdates() {
3263         synchronized (graphUpdateLock) {
3264             if (queuedGraphUpdates.isEmpty())
3265                 return Collections.emptyList();
3266             final List<GraphUpdateReactor> updates = queuedGraphUpdates;
3267             queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();
3268             return updates;
3269         }
3270     }
3271
3272     private void scheduleGraphUpdates() {
3273         synchronized (graphUpdateLock) {
3274             if (queuedGraphUpdates.isEmpty())
3275                 return;
3276             if (!graphUpdateRequestScheduled.compareAndSet(false, true))
3277                 return;
3278         }
3279
3280         if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3281             System.out.println("scheduling " + queuedGraphUpdates.size() + " queued graph updates with ");
3282
3283         session.asyncRequest(new ReadRequest() {
3284             @Override
3285             public void run(final ReadGraph graph) throws DatabaseException {
3286                 Collection<GraphUpdateReactor> updates;
3287                 synchronized (graphUpdateLock) {
3288                     graphUpdateRequestScheduled.set(false);
3289                     updates = scrubGraphUpdates();
3290                 }
3291
3292                 if (!GraphToDiagramSynchronizer.this.isAlive())
3293                     return;
3294
3295                 processGraphUpdates(graph, updates);
3296             }
3297         }, new ProcedureAdapter<Object>() {
3298             @Override
3299             public void exception(Throwable t) {
3300                 error(t);
3301             }
3302         });
3303     }
3304
3305     private void processGraphUpdates(ReadGraph graph, final Collection<GraphUpdateReactor> graphUpdates)
3306     throws DatabaseException {
3307         final List<DiagramUpdater> diagramUpdates = new ArrayList<DiagramUpdater>(graphUpdates.size());
3308
3309         // Run GraphUpdaters and gather DiagramUpdaters.
3310         if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3311             System.out.println("Running GRAPH updates: " + graphUpdates);
3312         for (GraphUpdateReactor graphUpdate : graphUpdates) {
3313             DiagramUpdater diagramUpdate = graphUpdate.graphUpdate(graph);
3314             if (diagramUpdate != null) {
3315                 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3316                     System.out.println(graphUpdate + " => " + diagramUpdate);
3317                 diagramUpdates.add(diagramUpdate);
3318             }
3319         }
3320
3321         if (diagramUpdates.isEmpty())
3322             return;
3323
3324         if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3325             System.out.println("Diagram updates: " + diagramUpdates);
3326         Collections.sort(diagramUpdates, DiagramUpdater.DIAGRAM_UPDATER_COMPARATOR);
3327         if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3328             System.out.println("Sorted diagram updates: " + diagramUpdates);
3329
3330         ThreadUtils.asyncExec(canvas.getThreadAccess(), new StateRunnable() {
3331             @Override
3332             public void run() {
3333                 if (GraphToDiagramSynchronizer.this.isAlive() && getState() != State.DISPOSED)
3334                     safeRunInState(State.UPDATING_DIAGRAM, this);
3335             }
3336
3337             @Override
3338             public void execute() throws InvocationTargetException {
3339                 // Block out diagram write transactions.
3340                 DiagramUtils.inDiagramTransaction(diagram, TransactionType.READ, new Runnable() {
3341                     @Override
3342                     public void run() {
3343                         if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3344                             System.out.println("Running DIAGRAM updates: " + diagramUpdates);
3345                         for (DiagramUpdater update : diagramUpdates) {
3346                             if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3347                                 System.out.println("Running DIAGRAM update: " + update);
3348                             update.run();
3349                         }
3350                     }
3351                 });
3352
3353                 setCanvasDirty();
3354             }
3355         });
3356     }
3357
3358     private void attachSessionListener(Session session) {
3359         SessionEventSupport support = session.peekService(SessionEventSupport.class);
3360         if (support != null) {
3361             sessionListener = new TransactionListener();
3362             support.addListener(sessionListener);
3363         }
3364     }
3365
3366     private void detachSessionListener() {
3367         if (sessionListener != null) {
3368             session.getService(SessionEventSupport.class).removeListener(sessionListener);
3369             sessionListener = null;
3370         }
3371     }
3372
3373
3374     // ------------------------------------------------------------------------
3375     // GRAPH TO DIAGRAM SYNCHRONIZATION LOGIC END
3376     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3377
3378     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3379     // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
3380     // This does not try to synchronize anything back into the graph.
3381     // ------------------------------------------------------------------------
3382
3383     IHintListener elementHintValidator = new HintListenerAdapter() {
3384         @Override
3385         public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
3386             if (!(sender instanceof Element))
3387                 throw new IllegalStateException("invalid sender: " + sender);
3388             Element e = (Element) sender;
3389             if (newValue != null) {
3390                 if (key instanceof TerminalKeyOf) {
3391                     Connection c = (Connection) newValue;
3392                     if (e != c.node)
3393                         throw new IllegalStateException("TerminalKeyOf hint of node " + e + " refers to a different node " + c.node + ". Should be the same.");
3394                     Object edgeObject = ElementUtils.getObject(c.edge);
3395                     if (!(edgeObject instanceof EdgeResource))
3396                         throw new IllegalStateException("EndKeyOf hint of edge " + c.edge + " refers contains an invalid object: " + edgeObject);
3397                 } else if (key instanceof EndKeyOf) {
3398                     Connection c = (Connection) newValue;
3399                     if (e != c.edge)
3400                         throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers to a different edge " + c.edge + ". Should be the same.");
3401                     Object edgeObject = ElementUtils.getObject(c.edge);
3402                     if (!(edgeObject instanceof EdgeResource))
3403                         throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers contains an invalid object: " + edgeObject);
3404                 }
3405             }
3406         }
3407     };
3408
3409     class DiagramListener implements CompositionListener, CompositionVetoListener {
3410         @Override
3411         public boolean beforeElementAdded(IDiagram d, IElement e) {
3412             // Make sure that MutatedElements NEVER get added to the diagram.
3413             if (d == diagram) {
3414                 if (!(e instanceof Element)) {
3415                     // THIS IS NOT GOOD!
3416                     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"));
3417                     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.");
3418                     return false;
3419                 }
3420
3421                 // Perform sanity checks that might veto the element addition.
3422                 boolean pass = true;
3423
3424                 // Check that all elements added to the diagram are adaptable to Resource
3425                 ElementClass ec = e.getElementClass();
3426                 Resource resource = ElementUtils.adapt(ec, Resource.class);
3427                 if (resource == null) {
3428                     pass = false;
3429                     new Exception("Attempted to add an element to the diagram that is not adaptable to Resource: " + e + ", class: " + ec).printStackTrace();
3430                 }
3431
3432                 // Sanity check connection hints
3433                 for (Map.Entry<TerminalKeyOf, Object> entry : e.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3434                     Connection c = (Connection) entry.getValue();
3435                     Object edgeObject = ElementUtils.getObject(c.edge);
3436                     if (e != c.node) {
3437                         System.err.println("Invalid node in TerminalKeyOf hint: " + entry.getKey() + "=" + entry.getValue());
3438                         System.err.println("\tconnection.edge=" + c.edge);
3439                         System.err.println("\tconnection.node=" + c.node);
3440                         System.err.println("\tconnection.end=" + c.end);
3441                         System.err.println("\telement=" + e);
3442                         System.err.println("\telement class=" + e.getElementClass());
3443                         pass = false;
3444                     }
3445                     if (!(edgeObject instanceof EdgeResource)) {
3446                         System.err.println("Invalid object in TerminalKeyOf hint edge: " + entry.getKey() + "=" + entry.getValue());
3447                         System.err.println("\tconnection.edge=" + c.edge);
3448                         System.err.println("\tconnection.node=" + c.node);
3449                         System.err.println("\tconnection.end=" + c.end);
3450                         System.err.println("\telement=" + e);
3451                         System.err.println("\telement class=" + e.getElementClass());
3452                         pass = false;
3453                     }
3454                 }
3455
3456                 return pass;
3457             }
3458             return true;
3459         }
3460
3461         @Override
3462         public boolean beforeElementRemoved(IDiagram d, IElement e) {
3463             // Never veto diagram changes.
3464             return true;
3465         }
3466
3467         @Override
3468         public void onElementAdded(IDiagram d, IElement e) {
3469             if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
3470                 System.out.println("[" + d + "] element added: " + e);
3471
3472             if (USE_ELEMENT_VALIDATING_LISTENERS)
3473                 e.addHintListener(elementHintValidator);
3474         }
3475
3476         @Override
3477         public void onElementRemoved(IDiagram d, IElement e) {
3478             if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
3479                 System.out.println("[" + d + "] element removed: " + e);
3480
3481             if (USE_ELEMENT_VALIDATING_LISTENERS)
3482                 e.removeHintListener(elementHintValidator);
3483
3484             if (e.containsHint(KEY_REMOVE_RELATIONSHIPS))
3485                 relationshipHandler.denyAll(diagram, e);
3486         }
3487     }
3488
3489     DiagramListener diagramListener = new DiagramListener();
3490
3491     static void removeNodeTopologyHints(Element node) {
3492         Set<TerminalKeyOf> terminalKeys = node.getHintsOfClass(TerminalKeyOf.class).keySet();
3493         if (!terminalKeys.isEmpty()) {
3494             removeNodeTopologyHints(node, terminalKeys);
3495         }
3496     }
3497
3498     static void removeNodeTopologyHints(Element node, Collection<TerminalKeyOf> terminalKeys) {
3499         for (TerminalKeyOf key : terminalKeys) {
3500             Connection c = node.removeHintWithoutNotification(key);
3501             if (c != null) {
3502                 removeEdgeTopologyHints((Element) c.edge);
3503             }
3504         }
3505     }
3506
3507     static void removeEdgeTopologyHints(Element edge) {
3508         Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
3509         for (EndKeyOf key : EndKeyOf.KEYS) {
3510             Connection c = edge.removeHintWithoutNotification(key);
3511             if (c != null) {
3512                 ((Element) c.node).removeHintWithoutNotification(new TerminalKeyOf(c.terminal, edgeData, Connection.class));
3513             }
3514         }
3515     }
3516
3517     // ------------------------------------------------------------------------
3518     // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
3519     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3520
3521     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3522     // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC BEGIN
3523     // ------------------------------------------------------------------------
3524
3525     void adaptDiagramClass(AsyncReadGraph graph, Resource diagram, final AsyncProcedure<DiagramClass> procedure) {
3526         graph.forAdapted(diagram, DiagramClass.class, new AsyncProcedure<DiagramClass>() {
3527             @Override
3528             public void exception(AsyncReadGraph graph, Throwable throwable) {
3529                 procedure.exception(graph, throwable);
3530             }
3531
3532             @Override
3533             public void execute(AsyncReadGraph graph, DiagramClass dc) {
3534                 // To move TopologyImpl out of here, we need a separate
3535                 // DiagramClassFactory that takes a canvas context as an argument.
3536                 // DataElementMapImpl, ElementFactoryImpl and diagramLifeCycle can
3537                 // safely stay here.
3538                 procedure.execute(graph, dc.newClassWith(
3539                         // This handler takes care of the topology of the diagram model.
3540                         // It sets and fixes element hints related to describing the
3541                         // connectivity of elements.
3542                         diagramTopology,
3543                         // TODO: not quite sure whether this can prove itself useful or not.
3544                         elementFactory,
3545                         // This map provides a bidirectional mapping between
3546                         // IElement and back-end objects.
3547                         dataElementMap,
3548                         // This handler provides a facility to adapt an element class
3549                         // to work properly with a diagram synchronized using this
3550                         // GraphToDiagramSynchronizer.
3551                         substituteElementClass,
3552                         // These handlers provide a way to create simple identified
3553                         // uni- and bidirectional relationships between any diagram
3554                         // objects/elements.
3555                         relationshipHandler));
3556             }
3557         });
3558     }
3559
3560     static Connection connect(IElement edge, EdgeEnd end, IElement element, Terminal terminal) {
3561         Connection c = new Connection(edge, end, element, terminal);
3562
3563         Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
3564         if (DebugPolicy.DEBUG_CONNECTION) {
3565             System.out.println("[connect](edge=" + edge + ", edgeData=" + edgeData + ", end=" + end + ", element="
3566                     + element + ", terminal=" + terminal + ")");
3567         }
3568
3569         TerminalKeyOf key = new TerminalKeyOf(terminal, edgeData, Connection.class);
3570         element.setHint(key, c);
3571
3572         EndKeyOf key2 = EndKeyOf.get(end);
3573         edge.setHint(key2, c);
3574
3575         return c;
3576     }
3577
3578     static class ElementFactoryImpl implements ElementFactory {
3579         @Override
3580         public IElement spawnNew(ElementClass clazz) {
3581             IElement e = Element.spawnNew(clazz);
3582             return e;
3583         }
3584     }
3585
3586     ElementFactoryImpl elementFactory = new ElementFactoryImpl();
3587
3588     public static final Object FIRST_TIME = new Object() {
3589         @Override
3590         public String toString() {
3591             return "FIRST_TIME";
3592         }
3593     };
3594
3595
3596     /**
3597      * A base for all listeners of graph requests performed internally by
3598      * GraphToDiagramSynchronizer.
3599      *
3600      * @param <T> type of stored data element
3601      * @param <Result> query result type
3602      */
3603     abstract class BaseListener<T, Result> implements AsyncListener<Result> {
3604
3605         protected final T    data;
3606
3607         private Object       oldResult = FIRST_TIME;
3608
3609         protected boolean    disposed  = false;
3610
3611         final ICanvasContext canvas;
3612
3613         public BaseListener(T data) {
3614             this.canvas = GraphToDiagramSynchronizer.this.canvas;
3615             this.data = data;
3616         }
3617
3618         @Override
3619         public void exception(AsyncReadGraph graph, Throwable throwable) {
3620             // Exceptions are always expected to mean that the listener should
3621             // be considered disposed once a query fails.
3622             disposed = true;
3623         }
3624
3625         abstract void execute(AsyncReadGraph graph, Object oldResult, Object newResult);
3626
3627         @Override
3628         public void execute(AsyncReadGraph graph, Result result) {
3629             if (DebugPolicy.DEBUG_LISTENER_BASE)
3630                 System.out.println("BaseListener: " + result);
3631
3632             if (disposed) {
3633                 if (DebugPolicy.DEBUG_LISTENER_BASE)
3634                     System.out.println("BaseListener: execute invoked although listener is disposed!");
3635                 return;
3636             }
3637
3638             // A null result will permanently mark this listener disposed!
3639             if (result == null) {
3640                 disposed = true;
3641                 if (DebugPolicy.DEBUG_LISTENER_BASE)
3642                     System.out.println(this + " null result, listener marked disposed");
3643             }
3644
3645             if (oldResult == FIRST_TIME) {
3646                 oldResult = result;
3647                 if (DebugPolicy.DEBUG_LISTENER_BASE)
3648                     System.out.println(this + " first result computed: " + result);
3649             } else {
3650                 if (DebugPolicy.DEBUG_LISTENER_BASE)
3651                     System.out.println(this + " result changed from '" + oldResult + "' to '" + result + "'");
3652                 try {
3653                     execute(graph, oldResult, result);
3654                 } finally {
3655                     oldResult = result;
3656                 }
3657             }
3658         }
3659
3660         @Override
3661         public boolean isDisposed() {
3662             if (disposed)
3663                 return true;
3664
3665             boolean alive = isAlive();
3666             //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", isAlive=" + alive);
3667             if (!alive)
3668                 return true;
3669             // If a mapping no longer exists for this element, dispose of this
3670             // listener.
3671             //IElement e = getMappedElement(resource);
3672             //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", element=" + e);
3673             //return e == null;
3674             return false;
3675         }
3676
3677 //        @Override
3678 //        protected void finalize() throws Throwable {
3679 //            System.out.println("finalize listener: " + this);
3680 //            super.finalize();
3681 //        }
3682     }
3683
3684     class DiagramClassRequest extends BaseRequest2<Resource, DiagramClass> {
3685         public DiagramClassRequest(Resource resource) {
3686             super(GraphToDiagramSynchronizer.this.canvas, resource);
3687         }
3688
3689         @Override
3690         public void perform(AsyncReadGraph graph, AsyncProcedure<DiagramClass> procedure) {
3691             adaptDiagramClass(graph, data, procedure);
3692         }
3693     }
3694
3695     public class DiagramContentListener extends BaseListener<Resource, DiagramContents> {
3696
3697         public DiagramContentListener(Resource resource) {
3698             super(resource);
3699         }
3700
3701         @Override
3702         public void execute(final AsyncReadGraph graph, Object oldResult, Object newResult) {
3703             final DiagramContents newContent = (newResult == null) ? new DiagramContents()
3704             : (DiagramContents) newResult;
3705
3706             // diagramGraphUpdater is called synchronously during
3707             // loading. The first result will not get updated through
3708             // this listener but through loadDiagram.
3709
3710             if (DebugPolicy.DISABLE_DIAGRAM_UPDATES) {
3711                 System.out.println("Skipped diagram content update: " + newResult);
3712                 return;
3713             }
3714
3715             if (DebugPolicy.DEBUG_DIAGRAM_LISTENER)
3716                 System.out.println("diagram contents changed: " + oldResult + " => " + newResult);
3717
3718             offerGraphUpdate( diagramGraphUpdater(newContent) );
3719         }
3720
3721         @Override
3722         public boolean isDisposed() {
3723             return !isAlive();
3724         }
3725
3726         @Override
3727         public void exception(AsyncReadGraph graph, Throwable t) {
3728             super.exception(graph, t);
3729             error("DiagramContentRequest failed", t);
3730         }
3731     }
3732
3733     // ------------------------------------------------------------------------
3734     // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC END
3735     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3736
3737     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3738     // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER BEGIN
3739     // ------------------------------------------------------------------------
3740
3741     static class TopologyImpl implements Topology {
3742
3743         @Override
3744         public Connection getConnection(IElement edge, EdgeEnd end) {
3745             Key key = EndKeyOf.get(end);
3746             Connection c = edge.getHint(key);
3747             if (c == null)
3748                 return null;
3749             return c;
3750         }
3751
3752         @Override
3753         public void getConnections(IElement node, Terminal terminal, Collection<Connection> connections) {
3754 //            IDiagram d = ElementUtils.getDiagram(node);
3755             for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3756                 // First check that the terminal matches.
3757                 TerminalKeyOf key = entry.getKey();
3758                 if (!key.getTerminal().equals(terminal))
3759                     continue;
3760
3761                 Connection c = (Connection) entry.getValue();
3762                 if (c != null) {
3763                     connections.add(c);
3764                 }
3765             }
3766         }
3767
3768         @Override
3769         public void connect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
3770             if (node != null && terminal != null)
3771                 GraphToDiagramSynchronizer.connect(edge, end, node, terminal);
3772
3773             if (DebugPolicy.DEBUG_CONNECTION) {
3774                 if (end == EdgeEnd.Begin)
3775                     System.out.println("Connection started from: " + edge + ", " + end + ", " + node + ", " + terminal);
3776                 else
3777                     System.out.println("Creating connection to: " + edge + ", " + end + ", " + node + ", " + terminal);
3778             }
3779         }
3780
3781         @Override
3782         public void disconnect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
3783             EndKeyOf edgeKey = EndKeyOf.get(end);
3784             Connection c = edge.getHint(edgeKey);
3785             if (c == null)
3786                 throw new UnsupportedOperationException("cannot disconnect, no Connection in edge " + edge);
3787
3788             for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3789                 Connection cc = (Connection) entry.getValue();
3790                 if (c == cc) {
3791                     node.removeHint(entry.getKey());
3792                     edge.removeHint(edgeKey);
3793                     return;
3794                 }
3795             }
3796
3797             throw new UnsupportedOperationException("cannot disconnect, no connection between found between edge "
3798                     + edge + " and node " + node);
3799         }
3800     }
3801
3802     Topology            diagramTopology     = new TopologyImpl();
3803
3804     // ------------------------------------------------------------------------
3805     // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER END
3806     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3807
3808     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3809     // DIAGRAM OBJECT RELATIONSHIP HANDLER BEGIN
3810     // ------------------------------------------------------------------------
3811
3812     RelationshipHandler relationshipHandler = new RelationshipHandler() {
3813
3814         AssociativeMap map = new AssociativeMap(Associativity.of(true, false, false));
3815
3816         Object getPossibleObjectOrElement(Object o) {
3817             if (o instanceof IElement) {
3818                 IElement e = (IElement) o;
3819                 Object oo = e.getHint(ElementHints.KEY_OBJECT);
3820                 return oo != null ? oo : e;
3821             }
3822             return o;
3823         }
3824
3825         IElement getElement(Object o) {
3826             if (o instanceof IElement)
3827                 return (IElement) o;
3828             return getMappedElement(o);
3829         }
3830
3831         @Override
3832         public void claim(IDiagram diagram, Object subject,
3833                 Relationship predicate, Object object) {
3834             Object sd = getPossibleObjectOrElement(subject);
3835             Object od = getPossibleObjectOrElement(object);
3836
3837             Collection<Tuple> ts = null;
3838             Relationship inverse = predicate.getInverse();
3839             if (inverse != null)
3840                 ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od, inverse, sd));
3841             else
3842                 ts = Collections.singletonList(new Tuple(sd, predicate, od));
3843
3844             synchronized (this) {
3845                 map.add(ts);
3846             }
3847
3848             if (DebugPolicy.DEBUG_RELATIONSHIP) {
3849                 new Exception().printStackTrace();
3850                 System.out.println("Claimed relationships:");
3851                 for (Tuple t : ts)
3852                     System.out.println("\t" + t);
3853             }
3854         }
3855
3856         private void doDeny(IDiagram diagram, Object subject,
3857                 Relationship predicate, Object object) {
3858             Object sd = getPossibleObjectOrElement(subject);
3859             Object od = getPossibleObjectOrElement(object);
3860             if (sd == subject || od == object) {
3861                 System.out
3862                 .println("WARNING: denying relationship '"
3863                         + predicate
3864                         + "' between diagram element(s), not back-end object(s): "
3865                         + sd + " -> " + od);
3866             }
3867
3868             Collection<Tuple> ts = null;
3869             Relationship inverse = predicate.getInverse();
3870             if (inverse != null)
3871                 ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od,
3872                         inverse, sd));
3873             else
3874                 ts = Collections.singleton(new Tuple(sd, predicate, od));
3875
3876             synchronized (this) {
3877                 map.remove(ts);
3878             }
3879
3880             if (DebugPolicy.DEBUG_RELATIONSHIP) {
3881                 new Exception().printStackTrace();
3882                 System.out.println("Denied relationships:");
3883                 for (Tuple t : ts)
3884                     System.out.println("\t" + t);
3885             }
3886         }
3887
3888         @Override
3889         public void deny(IDiagram diagram, Object subject,
3890                 Relationship predicate, Object object) {
3891             synchronized (this) {
3892                 doDeny(diagram, subject, predicate, object);
3893             }
3894         }
3895
3896         @Override
3897         public void deny(IDiagram diagram, Relation relation) {
3898             synchronized (this) {
3899                 doDeny(diagram, relation.getSubject(), relation
3900                         .getRelationship(), relation.getObject());
3901             }
3902         }
3903
3904         @Override
3905         public void denyAll(IDiagram diagram, Object element) {
3906             synchronized (this) {
3907                 for (Relation relation : getRelations(diagram, element, null)) {
3908                     doDeny(diagram, relation.getSubject(), relation
3909                             .getRelationship(), relation.getObject());
3910                 }
3911             }
3912         }
3913
3914         @Override
3915         public Collection<Relation> getRelations(IDiagram diagram,
3916                 Object element, Collection<Relation> result) {
3917             if (DebugPolicy.DEBUG_GET_RELATIONSHIP)
3918                 System.out.println("getRelations(" + element + ")");
3919             Object e = getPossibleObjectOrElement(element);
3920
3921             Collection<Tuple> tuples = null;
3922             synchronized (this) {
3923                 tuples = map.get(new Tuple(e, null, null), null);
3924             }
3925
3926             if (DebugPolicy.DEBUG_GET_RELATIONSHIP) {
3927                 System.out.println("Result size: " + tuples.size());
3928                 for (Tuple t : tuples)
3929                     System.out.println("\t" + t);
3930             }
3931
3932             if (tuples.isEmpty())
3933                 return Collections.emptyList();
3934             if (result == null)
3935                 result = new ArrayList<Relation>(tuples.size());
3936             for (Tuple t : tuples) {
3937                 Object obj = t.getField(2);
3938                 IElement el = getElement(obj);
3939                 Relationship r = (Relationship) t.getField(1);
3940                 result.add(new Relation(element, r, el != null ? el : obj));
3941             }
3942             return result;
3943         }
3944
3945     };
3946
3947     // ------------------------------------------------------------------------
3948     // DIAGRAM ELEMENT RELATIONSHIP HANDLER END
3949     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3950
3951 }