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