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