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