1 /*******************************************************************************
2 * Copyright (c) 2007, 2018 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.diagram.adapter;
14 import java.awt.geom.AffineTransform;
15 import java.lang.reflect.InvocationTargetException;
16 import java.util.ArrayDeque;
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.Comparator;
22 import java.util.Deque;
23 import java.util.EnumSet;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
28 import java.util.Queue;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.ConcurrentMap;
32 import java.util.concurrent.atomic.AtomicBoolean;
33 import java.util.concurrent.locks.Condition;
34 import java.util.concurrent.locks.ReentrantLock;
36 import org.eclipse.core.runtime.IProgressMonitor;
37 import org.eclipse.core.runtime.SubMonitor;
38 import org.simantics.db.AsyncReadGraph;
39 import org.simantics.db.ReadGraph;
40 import org.simantics.db.RequestProcessor;
41 import org.simantics.db.Resource;
42 import org.simantics.db.Session;
43 import org.simantics.db.common.ResourceArray;
44 import org.simantics.db.common.exception.DebugException;
45 import org.simantics.db.common.procedure.adapter.AsyncProcedureAdapter;
46 import org.simantics.db.common.procedure.adapter.CacheListener;
47 import org.simantics.db.common.procedure.adapter.ListenerSupport;
48 import org.simantics.db.common.procedure.adapter.ProcedureAdapter;
49 import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;
50 import org.simantics.db.common.request.AsyncReadRequest;
51 import org.simantics.db.common.request.ReadRequest;
52 import org.simantics.db.common.session.SessionEventListenerAdapter;
53 import org.simantics.db.common.utils.NameUtils;
54 import org.simantics.db.exception.CancelTransactionException;
55 import org.simantics.db.exception.DatabaseException;
56 import org.simantics.db.exception.NoSingleResultException;
57 import org.simantics.db.exception.ServiceException;
58 import org.simantics.db.procedure.AsyncListener;
59 import org.simantics.db.procedure.AsyncProcedure;
60 import org.simantics.db.procedure.Listener;
61 import org.simantics.db.procedure.Procedure;
62 import org.simantics.db.request.Read;
63 import org.simantics.db.service.SessionEventSupport;
64 import org.simantics.diagram.connection.ConnectionSegmentEnd;
65 import org.simantics.diagram.content.Change;
66 import org.simantics.diagram.content.ConnectionUtil;
67 import org.simantics.diagram.content.DesignatedTerminal;
68 import org.simantics.diagram.content.DiagramContentChanges;
69 import org.simantics.diagram.content.DiagramContents;
70 import org.simantics.diagram.content.EdgeResource;
71 import org.simantics.diagram.content.ResourceTerminal;
72 import org.simantics.diagram.internal.DebugPolicy;
73 import org.simantics.diagram.internal.timing.GTask;
74 import org.simantics.diagram.internal.timing.Timing;
75 import org.simantics.diagram.profile.ProfileKeys;
76 import org.simantics.diagram.synchronization.CollectingModificationQueue;
77 import org.simantics.diagram.synchronization.CompositeModification;
78 import org.simantics.diagram.synchronization.CopyAdvisor;
79 import org.simantics.diagram.synchronization.ErrorHandler;
80 import org.simantics.diagram.synchronization.IHintSynchronizer;
81 import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;
82 import org.simantics.diagram.synchronization.IModification;
83 import org.simantics.diagram.synchronization.LogErrorHandler;
84 import org.simantics.diagram.synchronization.ModificationAdapter;
85 import org.simantics.diagram.synchronization.SynchronizationHints;
86 import org.simantics.diagram.synchronization.graph.AddElement;
87 import org.simantics.diagram.synchronization.graph.BasicResources;
88 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
89 import org.simantics.diagram.synchronization.graph.ElementLoader;
90 import org.simantics.diagram.synchronization.graph.ElementReorder;
91 import org.simantics.diagram.synchronization.graph.ElementWriter;
92 import org.simantics.diagram.synchronization.graph.GraphSynchronizationContext;
93 import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;
94 import org.simantics.diagram.synchronization.graph.ModificationQueue;
95 import org.simantics.diagram.synchronization.graph.TagChange;
96 import org.simantics.diagram.synchronization.graph.TransformElement;
97 import org.simantics.diagram.synchronization.graph.layer.GraphLayer;
98 import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;
99 import org.simantics.diagram.ui.DiagramModelHints;
100 import org.simantics.g2d.canvas.Hints;
101 import org.simantics.g2d.canvas.ICanvasContext;
102 import org.simantics.g2d.connection.ConnectionEntity;
103 import org.simantics.g2d.connection.EndKeyOf;
104 import org.simantics.g2d.connection.TerminalKeyOf;
105 import org.simantics.g2d.diagram.DiagramClass;
106 import org.simantics.g2d.diagram.DiagramHints;
107 import org.simantics.g2d.diagram.DiagramMutator;
108 import org.simantics.g2d.diagram.DiagramUtils;
109 import org.simantics.g2d.diagram.IDiagram;
110 import org.simantics.g2d.diagram.IDiagram.CompositionListener;
111 import org.simantics.g2d.diagram.IDiagram.CompositionVetoListener;
112 import org.simantics.g2d.diagram.handler.DataElementMap;
113 import org.simantics.g2d.diagram.handler.ElementFactory;
114 import org.simantics.g2d.diagram.handler.Relationship;
115 import org.simantics.g2d.diagram.handler.RelationshipHandler;
116 import org.simantics.g2d.diagram.handler.SubstituteElementClass;
117 import org.simantics.g2d.diagram.handler.Topology;
118 import org.simantics.g2d.diagram.handler.Topology.Connection;
119 import org.simantics.g2d.diagram.handler.Topology.Terminal;
120 import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType;
121 import org.simantics.g2d.diagram.impl.Diagram;
122 import org.simantics.g2d.diagram.participant.ElementPainter;
123 import org.simantics.g2d.element.ElementClass;
124 import org.simantics.g2d.element.ElementHints;
125 import org.simantics.g2d.element.ElementHints.DiscardableKey;
126 import org.simantics.g2d.element.ElementUtils;
127 import org.simantics.g2d.element.IElement;
128 import org.simantics.g2d.element.IElementClassProvider;
129 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
130 import org.simantics.g2d.element.handler.ElementHandler;
131 import org.simantics.g2d.element.handler.ElementLayerListener;
132 import org.simantics.g2d.element.handler.TerminalTopology;
133 import org.simantics.g2d.element.impl.Element;
134 import org.simantics.g2d.layers.ILayer;
135 import org.simantics.g2d.layers.ILayersEditor;
136 import org.simantics.g2d.routing.RouterFactory;
137 import org.simantics.scenegraph.INode;
138 import org.simantics.scenegraph.profile.DataNodeConstants;
139 import org.simantics.scenegraph.profile.DataNodeMap;
140 import org.simantics.scenegraph.profile.common.ProfileObserver;
141 import org.simantics.structural2.modelingRules.IModelingRules;
142 import org.simantics.utils.datastructures.ArrayMap;
143 import org.simantics.utils.datastructures.MapSet;
144 import org.simantics.utils.datastructures.Pair;
145 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
146 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
147 import org.simantics.utils.datastructures.hints.IHintContext.Key;
148 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
149 import org.simantics.utils.datastructures.hints.IHintListener;
150 import org.simantics.utils.datastructures.hints.IHintObservable;
151 import org.simantics.utils.datastructures.map.AssociativeMap;
152 import org.simantics.utils.datastructures.map.Associativity;
153 import org.simantics.utils.datastructures.map.Tuple;
154 import org.simantics.utils.strings.EString;
155 import org.simantics.utils.threads.ThreadUtils;
156 import org.simantics.utils.threads.logger.ITask;
157 import org.simantics.utils.threads.logger.ThreadLogger;
158 import org.slf4j.Logger;
159 import org.slf4j.LoggerFactory;
161 import gnu.trove.map.hash.TObjectIntHashMap;
162 import gnu.trove.set.hash.THashSet;
165 * This class loads a diagram contained in the graph database into the runtime
166 * diagram model and synchronizes changes in the graph into the run-time
167 * diagram. Any modifications to the graph model will be reflected to the
168 * run-time diagram model. Hence the name GraphToDiagramSynchronizer.
171 * This class does not in itself support modification of the graph diagram
172 * model. This manipulation is meant to be performed through a
173 * {@link DiagramMutator} implementation that is installed into the diagram
174 * using the {@link DiagramHints#KEY_MUTATOR} hint key.
176 * This implementations is built to only support diagrams defined in the graph
177 * model as indicated in <a
178 * href="https://www.simantics.org/wiki/index.php/Org.simantics.diagram" >this
182 * The synchronizer in itself is an {@link IDiagramLoader} which means that it
183 * can be used for loading a diagram from the graph. In order for the
184 * synchronizer to keep tracking the graph diagram model for changes the diagram
185 * must be loaded with it. The tracking is implemented using graph database
186 * queries. If you just want to load the diagram but detach it from the
187 * synchronizer's tracking mechanisms, all you need to do is to dispose the
188 * synchronizer after loading the diagram.
191 * This class guarantees that a single diagram element (IElement) representing a
192 * single back-end object ({@link ElementHints#KEY_OBJECT}) will stay the same
193 * object for the same back-end object.
196 * TODO: Currently it just happens that {@link GraphToDiagramSynchronizer}
197 * contains {@link DefaultDiagramMutator} which depends on some internal details
198 * of {@link GraphToDiagramSynchronizer} but it should be moved out of here by
199 * introducing new interfaces.
201 * <h2>Basic usage example</h2>
203 * This example shows how to initialize {@link GraphToDiagramSynchronizer} for a
204 * specified {@link ICanvasContext} and load a diagram from a specified diagram
205 * resource in the graph.
208 * IDiagram loadDiagram(final ICanvasContext canvasContext, RequestProcessor processor, Resource diagramResource,
209 * ResourceArray structuralPath) throws DatabaseException {
210 * GraphToDiagramSynchronizer synchronizer = processor.syncRequest(new Read<GraphToDiagramSynchronizer>() {
211 * public GraphToDiagramSynchronizer perform(ReadGraph graph) throws DatabaseException {
212 * return new GraphToDiagramSynchronizer(graph, canvasContext, createElementClassProvider(graph));
215 * IDiagram d = requestProcessor
216 * .syncRequest(new DiagramLoadQuery(diagramResource, structuralPath, synchronizer, null));
220 * protected IElementClassProvider createElementClassProvider(ReadGraph graph) {
221 * DiagramResource dr = DiagramResource.getInstance(graph);
222 * return ElementClassProviders.mappedProvider(ElementClasses.CONNECTION, DefaultConnectionClassFactory.CLASS
223 * .newClassWith(new ResourceAdapterImpl(dr.Connection)), ElementClasses.FLAG, FlagClassFactory
224 * .createFlagClass(dr.Flag));
229 * TODO: make GraphToDiagramSynchronizer a canvas participant to make it more
230 * uniform with the rest of the canvas system. This does not mean that G2DS must
231 * be attached to an ICanvasContext in order to be used, rather that it can be
235 * TODO: test that detaching the synchronizer via {@link #dispose()} actually
238 * TODO: remove {@link DefaultDiagramMutator} and all {@link DiagramMutator}
242 * TODO: diagram connection loading has no listener
244 * @author Tuukka Lehtonen
246 * @see GraphElementClassFactory
247 * @see GraphElementFactory
250 * @see IHintSynchronizer
253 public class GraphToDiagramSynchronizer extends AbstractDisposable implements IDiagramLoader, IModifiableSynchronizationContext {
255 private static final Logger LOGGER = LoggerFactory.getLogger(GraphToDiagramSynchronizer.class);
258 * Controls whether the class adds hint listeners to each diagram element
259 * that try to perform basic sanity checks on changes happening in element
260 * hints. Having this will immediately inform you of bugs that corrupt the
261 * diagram model within the element hints in some way.
263 private static final boolean USE_ELEMENT_VALIDATING_LISTENERS = false;
266 * These keys are used to hang on to Connection instances of edges that will
267 * be later installed as EndKeyOf/TerminalKeyOf hints into the loaded
268 * element during the "graph to diagram update transaction".
270 private static final Key KEY_CONNECTION_BEGIN_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_BEGIN_PLACEHOLDER");
271 private static final Key KEY_CONNECTION_END_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_END_PLACEHOLDER");
274 * Stored into an edge node during connection edge requests using the
275 * KEY_CONNECTION_BEGIN_PLACEHOLDER and KEY_CONNECTION_END_PLACEHOLDER keys.
277 static class PlaceholderConnection {
278 public final EdgeEnd end;
279 public final Object node;
280 public final Terminal terminal;
281 public PlaceholderConnection(EdgeEnd end, Object node, Terminal terminal) {
284 this.terminal = terminal;
289 * Indicates to the diagram CompositionListener of this synchronizer that is
290 * should deny all relationships for the element this hint is attached to.
292 private static final Key KEY_REMOVE_RELATIONSHIPS = new KeyOf(Boolean.class, "REMOVE_RELATIONSHIPS");
294 static ErrorHandler errorHandler = LogErrorHandler.INSTANCE;
297 * The canvas context which is being synchronized with the graph. Received
298 * during construction.
301 * Is not nulled during disposal of this class since internal listener's
302 * life-cycles depend on canvas.isDisposed.
304 ICanvasContext canvas;
307 * The session used by this synchronizer. Received during construction.
312 * Locked while updating diagram contents from the graph.
314 ReentrantLock diagramUpdateLock = new ReentrantLock();
316 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
317 // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING BEGIN
318 // ------------------------------------------------------------------------
321 * Holds a GraphToDiagramUpdater instance while a diagram content update is
322 * currently in progress.
325 * Basically this is a hack solution to the problem of properly finding
326 * newly added Resource<->IElement mappings while loading the diagram
327 * contents. See {@link DataElementMapImpl} for why this is necessary.
329 GraphToDiagramUpdater currentUpdater = null;
332 * A map from data objects to elements. Elements should already contain the
333 * data objects as {@link ElementHints#KEY_OBJECT} hints.
335 ConcurrentMap<Object, IElement> dataElement = new ConcurrentHashMap<Object, IElement>();
338 * Temporary structure for single-threaded use in #{@link DiagramUpdater}.
340 Collection<Connection> tempConnections = new ArrayList<Connection>();
343 * A dummy class of which an instance will be given to each new edge element
344 * to make {@link TerminalKeyOf} keys unique for each edge.
346 static class TransientElementObject {
348 public String toString() {
349 return "MUTATOR GENERATED (hash=" + System.identityHashCode(this) + ")";
353 private static class ConnectionChildren {
354 public Set<IElement> branchPoints;
355 public Set<IElement> segments;
357 public ConnectionChildren(Set<IElement> branchPoints, Set<IElement> segments) {
358 this.branchPoints = branchPoints;
359 this.segments = segments;
363 ListenerSupport canvasListenerSupport = new ListenerSupport() {
365 public void exception(Throwable t) {
370 public boolean isDisposed() {
371 return !isAlive() || canvas.isDisposed();
376 * @see ElementHints#KEY_CONNECTION_ENTITY
378 class ConnectionEntityImpl implements ConnectionEntity {
381 * The connection instance resource in the graph backend.
383 * May be <code>null</code> if the connection has not been synchronized
389 * The connection type resource in the graph backend.
391 * May be <code>null</code> if the connection has not been synchronized
394 Resource connectionType;
397 * The connection entity element which is a part of the diagram.
399 IElement connectionElement;
402 * List of backend-synchronized branch points that are part of this
405 Collection<Resource> branchPoints = Collections.emptyList();
408 * List of backend-synchronized edges that are part of this connection.
410 Collection<EdgeResource> segments = Collections.emptyList();
412 Set<Object> removedBranchPoints = new HashSet<Object>(4);
414 Set<Object> removedSegments = new HashSet<Object>(4);
417 * List of non-backend-synchronized branch point element that are part
418 * of this connection.
420 List<IElement> branchPointElements = new ArrayList<IElement>(1);
423 * List of non-backend-synchronized edge element that are part of this
426 List<IElement> segmentElements = new ArrayList<IElement>(2);
428 ConnectionListener listener;
430 ConnectionEntityImpl(Resource connection, Resource connectionType, IElement connectionElement) {
431 this.connection = connection;
432 this.connectionType = connectionType;
433 this.connectionElement = connectionElement;
436 ConnectionEntityImpl(Resource connectionType, IElement connectionElement) {
437 this.connectionType = connectionType;
438 this.connectionElement = connectionElement;
441 ConnectionEntityImpl(ReadGraph graph, Resource connection, IElement connectionElement)
442 throws NoSingleResultException, ServiceException {
443 this.connection = connection;
444 this.connectionType = graph.getSingleType(connection, br.DIA.Connection);
445 this.connectionElement = connectionElement;
449 public IElement getConnection() {
450 return connectionElement;
453 public Object getConnectionObject() {
457 public IElement getConnectionElement() {
458 if (connectionElement == null)
459 return getMappedConnectionElement();
460 return connectionElement;
463 private IElement getMappedConnectionElement() {
465 if (connection != null)
466 ce = getMappedElement(connection);
467 return ce == null ? connectionElement : ce;
471 Collection<IElement> segments = getSegments(null);
473 // Remove all TerminalKeyOf hints from branch points that do not
475 ArrayList<TerminalKeyOf> pruned = null;
476 for (IElement bp : getBranchPoints(null)) {
478 pruned = new ArrayList<TerminalKeyOf>(4);
480 for (Map.Entry<TerminalKeyOf, Object> entry : bp.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
481 // First check that the terminal matches.
482 Connection c = (Connection) entry.getValue();
483 if (!segments.contains(c.edge))
484 pruned.add(entry.getKey());
486 removeNodeTopologyHints((Element) bp, pruned);
490 public ConnectionChildren getConnectionChildren() {
491 Set<IElement> bps = Collections.emptySet();
492 Set<IElement> segs = Collections.emptySet();
493 if (!branchPoints.isEmpty()) {
494 bps = new HashSet<IElement>(branchPoints.size());
495 for (Resource bp : branchPoints) {
496 IElement e = getMappedElement(bp);
501 if (!segments.isEmpty()) {
502 segs = new HashSet<IElement>(segments.size());
503 for (EdgeResource seg : segments) {
504 IElement e = getMappedElement(seg);
509 return new ConnectionChildren(bps, segs);
512 public void setData(Collection<EdgeResource> segments, Collection<Resource> branchPoints) {
513 // System.out.println("setData " + segments.size());
514 this.branchPoints = branchPoints;
515 this.segments = segments;
517 // Reset the added/removed state of segments and branchpoints.
518 this.removedBranchPoints = new HashSet<Object>(4);
519 this.removedSegments = new HashSet<Object>(4);
520 this.branchPointElements = new ArrayList<IElement>(4);
521 this.segmentElements = new ArrayList<IElement>(4);
524 public void fireListener(ConnectionChildren old, ConnectionChildren current) {
525 if (listener != null) {
526 List<IElement> removed = new ArrayList<IElement>();
527 List<IElement> added = new ArrayList<IElement>();
529 for (IElement oldBp : old.branchPoints)
530 if (!current.branchPoints.contains(oldBp))
532 for (IElement oldSeg : old.segments)
533 if (!current.segments.contains(oldSeg))
536 for (IElement bp : current.branchPoints)
537 if (!old.branchPoints.contains(bp))
539 for (IElement seg : current.segments)
540 if (!old.segments.contains(seg))
543 if (!removed.isEmpty() || !added.isEmpty()) {
544 listener.connectionChanged(new ConnectionEvent(this.connectionElement, removed, added));
550 public Collection<IElement> getBranchPoints(Collection<IElement> result) {
552 result = new ArrayList<IElement>(branchPoints.size());
553 for (Resource bp : branchPoints) {
554 if (!removedBranchPoints.contains(bp)) {
555 IElement e = getMappedElement(bp);
560 result.addAll(branchPointElements);
565 public Collection<IElement> getSegments(Collection<IElement> result) {
567 result = new ArrayList<IElement>(segments.size());
568 for (EdgeResource seg : segments) {
569 if (!removedSegments.contains(seg)) {
570 IElement e = getMappedElement(seg);
575 result.addAll(segmentElements);
580 public Collection<Connection> getTerminalConnections(Collection<Connection> result) {
582 result = new ArrayList<Connection>(segments.size() * 2);
583 Set<org.simantics.utils.datastructures.Pair<IElement, Terminal>> processed = new HashSet<org.simantics.utils.datastructures.Pair<IElement, Terminal>>();
584 for (EdgeResource seg : segments) {
585 IElement edge = getMappedElement(seg);
587 for (EndKeyOf key : EndKeyOf.KEYS) {
588 Connection c = edge.getHint(key);
589 if (c != null && (c.terminal instanceof ResourceTerminal) && processed.add(Pair.make(c.node, c.terminal)))
598 public void setListener(ConnectionListener listener) {
599 this.listener = listener;
603 public String toString() {
604 return getClass().getSimpleName() + "[resource=" + connection + ", branch points=" + branchPoints
605 + ", segments=" + segments + ", connectionElement=" + connectionElement
606 + ", branch point elements=" + branchPointElements + ", segment elements=" + segmentElements
607 + ", removed branch points=" + removedBranchPoints + ", removed segments=" + removedSegments + "]";
613 * A map from connection data objects to connection entities. The connection
614 * part elements should already contain the data objects as
615 * {@link ElementHints#KEY_OBJECT} hints.
617 ConcurrentMap<Object, ConnectionEntityImpl> dataConnection = new ConcurrentHashMap<Object, ConnectionEntityImpl>();
620 void mapElementIfNew(final Object data, final IElement element) {
621 IElement mapped = getMappedElement(data);
623 mapElement(data, element);
624 currentUpdater.addedElements.add(element);
625 currentUpdater.addedElementMap.put(data, element);
634 void mapElement(final Object data, final IElement element) {
635 if (!(element instanceof Element)) {
636 throw new IllegalArgumentException("mapElement: expected instance of Element, got " + element + " with data " + data);
639 assert element != null;
640 if (DebugPolicy.DEBUG_MAPPING)
641 new Exception(Thread.currentThread() + " MAPPING: " + data + " -> " + element).printStackTrace();
642 dataElement.put(data, element);
649 IElement getMappedElement(final Object data) {
650 assert (data != null);
651 IElement element = dataElement.get(data);
655 IElement getMappedElementByElementObject(IElement e) {
658 Object o = e.getHint(ElementHints.KEY_OBJECT);
661 return getMappedElement(o);
668 IElement assertMappedElement(final Object data) {
669 IElement element = dataElement.get(data);
670 assert element != null;
678 IElement unmapElement(final Object data) {
679 IElement element = dataElement.remove(data);
680 if (DebugPolicy.DEBUG_MAPPING)
681 new Exception(Thread.currentThread() + " UN-MAPPED: " + data + " -> " + element).printStackTrace();
689 void mapConnection(final Object data, final ConnectionEntityImpl connection) {
691 assert connection != null;
692 if (DebugPolicy.DEBUG_MAPPING)
693 System.out.println(Thread.currentThread() + " MAPPING CONNECTION: " + data + " -> " + connection);
694 dataConnection.put(data, connection);
701 ConnectionEntityImpl getMappedConnection(final Object data) {
702 ConnectionEntityImpl connection = dataConnection.get(data);
710 ConnectionEntityImpl assertMappedConnection(final Object data) {
711 ConnectionEntityImpl connection = getMappedConnection(data);
712 assert connection != null;
720 ConnectionEntityImpl unmapConnection(final Object data) {
721 ConnectionEntityImpl connection = dataConnection.remove(data);
722 if (DebugPolicy.DEBUG_MAPPING)
723 System.out.println(Thread.currentThread() + " UN-MAPPED CONNECTION: " + data + " -> " + connection);
727 class DataElementMapImpl implements DataElementMap {
729 public Object getData(IDiagram d, IElement element) {
731 throw new NullPointerException("null diagram");
733 throw new NullPointerException("null element");
735 assert ElementUtils.getDiagram(element) == d;
736 return element.getHint(ElementHints.KEY_OBJECT);
740 public IElement getElement(IDiagram d, Object data) {
742 throw new NullPointerException("null diagram");
744 throw new NullPointerException("null data");
746 GraphToDiagramUpdater updater = currentUpdater;
747 if (updater != null) {
748 // This HACK is for allowing GraphElementFactory implementations
749 // to find the IElements they are related to.
750 IElement e = updater.addedElementMap.get(data);
755 IElement e = getMappedElement(data);
762 class SubstituteElementClassImpl implements SubstituteElementClass {
764 public ElementClass substitute(IDiagram d, ElementClass ec) {
766 throw new IllegalArgumentException("specified diagram does not have this SubstituteElementClass handler");
768 // If the element class is our own, there's no point in creating
770 if (ec.contains(elementLayerListener))
773 List<ElementHandler> all = ec.getAll();
774 List<ElementHandler> result = new ArrayList<ElementHandler>(all.size());
775 for (ElementHandler eh : all) {
776 if (eh instanceof ElementLayerListenerImpl)
777 result.add(elementLayerListener);
781 return ElementClass.compile(result, false).setId(ec.getId());
785 final DataElementMapImpl dataElementMap = new DataElementMapImpl();
787 final SubstituteElementClassImpl substituteElementClass = new SubstituteElementClassImpl();
789 // ------------------------------------------------------------------------
790 // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING END
791 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
793 void warning(String message, Exception e) {
794 errorHandler.warning(message, e);
797 void warning(Exception e) {
798 errorHandler.warning(e.getMessage(), e);
801 void error(String message, Throwable e) {
802 errorHandler.error(message, e);
805 void error(Throwable e) {
806 errorHandler.error(e.getMessage(), e);
809 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
810 // GRAPH MODIFICATION QUEUE BEGIN
811 // ------------------------------------------------------------------------
813 ModificationQueue modificationQueue;
814 IModifiableSynchronizationContext synchronizationContext;
817 public <T> T set(Key key, Object value) {
818 if (synchronizationContext == null)
820 return synchronizationContext.set(key, value);
824 public <T> T get(Key key) {
825 if (synchronizationContext == null)
827 return synchronizationContext.get(key);
830 // ------------------------------------------------------------------------
831 // GRAPH MODIFICATION QUEUE END
832 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
835 * The previously loaded version of the diagram content. This is needed to
836 * calculate the difference between new and old content on each
837 * {@link #diagramGraphUpdater(DiagramContents)} invocation.
839 DiagramContents previousContent;
842 * The diagram instance that this synchronizer is synchronizing with the
848 * An observer for diagram profile entries. Has a life-cycle that must be
849 * bound to the life-cycle of this GraphToDiagramSynchronizer instance.
850 * Disposed if synchronizer is detached in {@link #doDispose()} or finally
851 * when the canvas is disposed.
853 ProfileObserver profileObserver;
855 IElementClassProvider elementClassProvider;
859 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
860 // Internal state machine handling BEGIN
861 // ------------------------------------------------------------------------
864 * An indicator for the current state of this synchronizer. This is a simple
865 * state machine with the following possible state transitions:
868 * <li>INITIAL -> LOADING, DISPOSED</li>
869 * <li>LOADING -> IDLE</li>
870 * <li>IDLE -> UPDATING_DIAGRAM, DISPOSED</li>
871 * <li>UPDATING_DIAGRAM -> IDLE</li>
874 * Start states: INITIAL
875 * End states: DISPOSED
879 * The initial state of the synchronizer.
883 * The synchronizer is performing load-time initialization. During this
884 * time no canvas refreshes should be forced.
888 * The synchronizer is performing updates to the diagram model. This
889 * process goes on in the canvas context thread.
893 * The synchronizer is doing nothing.
897 * The synchronized diagram is being disposed, which means that this
898 * synchronizer should not accept any further actions.
903 public static final EnumSet<State> FROM_INITIAL = EnumSet.of(State.LOADING, State.DISPOSED);
904 public static final EnumSet<State> FROM_LOADING = EnumSet.of(State.IDLE);
905 public static final EnumSet<State> FROM_UPDATING_DIAGRAM = EnumSet.of(State.IDLE);
906 public static final EnumSet<State> FROM_IDLE = EnumSet.of(State.UPDATING_DIAGRAM, State.DISPOSED);
907 public static final EnumSet<State> NO_STATES = EnumSet.noneOf(State.class);
909 private EnumSet<State> validTargetStates(State start) {
911 case INITIAL: return FROM_INITIAL;
912 case LOADING: return FROM_LOADING;
913 case UPDATING_DIAGRAM: return FROM_UPDATING_DIAGRAM;
914 case IDLE: return FROM_IDLE;
915 case DISPOSED: return NO_STATES;
917 throw new IllegalArgumentException("unrecognized state " + start);
920 private String validateStateChange(State start, State end) {
921 EnumSet<State> validTargets = validTargetStates(start);
922 if (!validTargets.contains(end))
923 return "Cannot transition from " + start + " state to " + end + ".";
928 * The current state of the synchronizer. At start it is
929 * {@link State#INITIAL} and after loading it is {@link State#IDLE}.
931 State synchronizerState = State.INITIAL;
934 * A condition variable used to synchronize synchronizer state changes.
936 ReentrantLock stateLock = new ReentrantLock();
939 * A condition that is signaled when the synchronizer state changes to IDLE.
941 Condition idleCondition = stateLock.newCondition();
944 return synchronizerState;
948 * Activates the desired state after making sure that the synchronizer has
949 * been IDLE in between its current state and this invocation.
951 * @param newState the new state to activate
952 * @throws InterruptedException if waiting for IDLE state gets interrupted
953 * @throws IllegalStateException if the requested transition from the
954 * current state to the desired state would be illegal.
956 void activateState(State newState, boolean waitForIdle) throws InterruptedException {
959 // Wait until the state of the synchronizer IDLEs if necessary.
960 if (waitForIdle && synchronizerState != State.IDLE) {
961 String error = validateStateChange(synchronizerState, State.IDLE);
963 throw new IllegalStateException(error);
965 while (synchronizerState != State.IDLE) {
966 if (DebugPolicy.DEBUG_STATE)
967 System.out.println(Thread.currentThread() + " waiting for IDLE state, current="
968 + synchronizerState);
969 idleCondition.await();
973 String error = validateStateChange(synchronizerState, newState);
975 throw new IllegalStateException(error);
977 if (DebugPolicy.DEBUG_STATE)
978 System.out.println(Thread.currentThread() + " activated state " + newState);
979 this.synchronizerState = newState;
981 if (newState == State.IDLE)
982 idleCondition.signalAll();
988 void idle() throws IllegalStateException, InterruptedException {
989 activateState(State.IDLE, false);
992 static interface StateRunnable extends Runnable {
993 void execute() throws InvocationTargetException;
995 public abstract class Stub implements StateRunnable {
1001 public final void execute() throws InvocationTargetException {
1004 } catch (Exception e) {
1005 throw new InvocationTargetException(e);
1006 } catch (LinkageError e) {
1007 throw new InvocationTargetException(e);
1011 protected abstract void perform() throws Exception;
1015 protected void runInState(State state, StateRunnable runnable) throws InvocationTargetException {
1017 activateState(state, true);
1023 } catch (IllegalStateException e) {
1024 throw new InvocationTargetException(e);
1025 } catch (InterruptedException e) {
1026 throw new InvocationTargetException(e);
1030 protected void safeRunInState(State state, StateRunnable runnable) {
1032 runInState(state, runnable);
1033 } catch (InvocationTargetException e) {
1034 error("Failed to run runnable " + runnable + " in state " + state + ". See exception for details.", e
1039 // ------------------------------------------------------------------------
1040 // Internal state machine handling END
1041 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1046 * @param elementClassProvider
1047 * @throws DatabaseException
1049 public GraphToDiagramSynchronizer(RequestProcessor processor, ICanvasContext canvas, IElementClassProvider elementClassProvider) throws DatabaseException {
1050 if (processor == null)
1051 throw new IllegalArgumentException("null processor");
1053 throw new IllegalArgumentException("null canvas");
1054 if (elementClassProvider == null)
1055 throw new IllegalArgumentException("null element class provider");
1057 this.session = processor.getSession();
1058 this.canvas = canvas;
1059 this.modificationQueue = new ModificationQueue(session, errorHandler);
1061 processor.syncRequest(new ReadRequest() {
1063 public void run(ReadGraph graph) throws DatabaseException {
1064 initializeResources(graph);
1068 this.elementClassProvider = elementClassProvider;
1069 synchronizationContext.set(SynchronizationHints.ELEMENT_CLASS_PROVIDER, elementClassProvider);
1071 attachSessionListener(processor.getSession());
1077 public IElementClassProvider getElementClassProvider() {
1078 return elementClassProvider;
1081 public Session getSession() {
1085 public ICanvasContext getCanvasContext() {
1089 public IDiagram getDiagram() {
1093 void setCanvasDirty() {
1094 ICanvasContext c = canvas;
1095 if (synchronizerState != State.LOADING && c != null && !c.isDisposed()) {
1096 // TODO: Consider adding an invocation limiter here, to prevent
1097 // calling setDirty too often if enough time hasn't passed yet since
1098 // the last invocation.
1099 c.getContentContext().setDirty();
1104 * @param elementType
1106 * @throws DatabaseException if ElementClass cannot be retrieved
1108 public ElementClass getNodeClass(Resource elementType) throws DatabaseException {
1109 return getNodeClass(session, elementType);
1112 public ElementClass getNodeClass(RequestProcessor processor, Resource elementType) throws DatabaseException {
1113 ElementClass ec = processor.syncRequest(new NodeClassRequest(canvas, diagram, elementType, true));
1118 protected void doDispose() {
1122 boolean isInitial = getState() == State.INITIAL;
1123 activateState(State.DISPOSED, !isInitial);
1127 } catch (InterruptedException e) {
1128 // Shouldn't happen.
1129 LOGGER.error("Dispose interrupted!", e);
1131 detachSessionListener();
1133 if (profileObserver != null) {
1134 profileObserver.dispose();
1135 profileObserver = null;
1138 if (diagram != null) {
1139 diagram.removeCompositionListener(diagramListener);
1140 diagram.removeCompositionVetoListener(diagramListener);
1143 // TODO: we should probably leave the dataElement map as is since DataElementMap needs it even after the synchronizer has been disposed.
1144 // Currently the diagram's DataElementMap will be broken after disposal.
1145 // dataElement.clear();
1146 // dataConnection.clear();
1148 if (layerManager != null) {
1149 layerManager.dispose();
1153 modificationQueue.dispose();
1157 void initializeResources(ReadGraph graph) {
1158 this.br = new BasicResources(graph);
1160 // Initialize synchronization context
1161 synchronizationContext = new GraphSynchronizationContext(graph, modificationQueue);
1162 synchronizationContext.set(SynchronizationHints.ERROR_HANDLER, errorHandler);
1165 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1167 // ------------------------------------------------------------------------
1169 GraphLayerManager layerManager;
1172 * A common handler for all elements that is used to listen to changes in
1173 * element visibility and focusability on diagram layers.
1175 class ElementLayerListenerImpl implements ElementLayerListener {
1176 private static final long serialVersionUID = -3410052116598828129L;
1179 public void visibilityChanged(IElement e, ILayer layer, boolean visible) {
1182 if (DebugPolicy.DEBUG_LAYERS)
1183 System.out.println("visibility changed: " + e + ", " + layer + ", " + visible);
1184 GraphLayer gl = layerManager.getGraphLayer(layer.getName());
1186 changeTag(e, gl.getVisible(), visible);
1191 public void focusabilityChanged(IElement e, ILayer layer, boolean focusable) {
1194 if (DebugPolicy.DEBUG_LAYERS)
1195 System.out.println("focusability changed: " + e + ", " + layer + ", " + focusable);
1196 GraphLayer gl = layerManager.getGraphLayer(layer.getName());
1198 changeTag(e, gl.getFocusable(), focusable);
1202 void changeTag(IElement e, Resource tag, boolean set) {
1203 Object object = e.getHint(ElementHints.KEY_OBJECT);
1204 Resource tagged = null;
1205 if (object instanceof Resource) {
1206 tagged = (Resource) object;
1207 } else if (object instanceof EdgeResource) {
1208 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
1209 if (ce instanceof ConnectionEntityImpl) {
1210 tagged = ((ConnectionEntityImpl) ce).connection;
1216 modificationQueue.async(new TagChange(tagged, tag, set), null);
1220 ElementLayerListenerImpl elementLayerListener = new ElementLayerListenerImpl();
1222 // ------------------------------------------------------------------------
1224 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1227 public IDiagram loadDiagram(IProgressMonitor progressMonitor, ReadGraph g, final String modelURI, final Resource diagram, final Resource runtime, final ResourceArray structuralPath,
1228 IHintObservable initialHints) throws DatabaseException {
1229 if (DebugPolicy.DEBUG_LOAD)
1230 System.out.println(Thread.currentThread() + " loadDiagram: " + NameUtils.getSafeName(g, diagram));
1232 SubMonitor monitor = SubMonitor.convert(progressMonitor, "Load Diagram", 100);
1234 Object loadTask = Timing.BEGIN("GDS.loadDiagram");
1237 activateState(State.LOADING, false);
1238 } catch (IllegalStateException e) {
1239 // Disposed already before loading even began.
1240 this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
1241 return this.diagram;
1244 // Query for diagram class
1245 Resource diagramClassResource = g.getPossibleType(diagram, br.DIA.Composite);
1246 if (diagramClassResource != null) {
1247 // Spawn new diagram
1248 Object task = Timing.BEGIN("GDS.DiagramClassRequest");
1249 final DiagramClass diagramClass = g.syncRequest(new DiagramClassRequest(diagram));
1251 final IDiagram d = Diagram.spawnNew(diagramClass);
1253 d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, diagram);
1254 if (runtime != null)
1255 d.setHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE, runtime);
1256 if (modelURI != null)
1257 d.setHint(DiagramModelHints.KEY_DIAGRAM_MODEL_URI, modelURI);
1258 d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE_ARRAY, structuralPath);
1260 // Set dumb default routing when DiagramClass does not
1261 // predefine the default connection routing for the diagram.
1262 if (!d.containsHint(DiagramHints.ROUTE_ALGORITHM))
1263 d.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, false));
1265 d.setHint(SynchronizationHints.CONTEXT, this);
1267 // Initialize hints with hints from initialHints if given
1268 if (initialHints != null) {
1269 d.setHints(initialHints.getHints());
1273 // ITask task2 = ThreadLogger.getInstance().begin("loadLayers");
1274 monitor.subTask("Layers");
1276 this.layerManager = new GraphLayerManager(g, modificationQueue, diagram);
1277 synchronizationContext.set(GraphSynchronizationHints.GRAPH_LAYER_MANAGER, this.layerManager);
1278 ILayersEditor layers = layerManager.loadLayers(d, g, diagram);
1281 d.setHint(DiagramHints.KEY_LAYERS, layers);
1282 d.setHint(DiagramHints.KEY_LAYERS_EDITOR, layers);
1284 d.addCompositionVetoListener(diagramListener);
1285 d.addCompositionListener(diagramListener);
1289 d.setHint(DiagramHints.KEY_MUTATOR, new DefaultDiagramMutator(d, diagram, synchronizationContext));
1291 // Add default layer if no layers exist.
1292 // NOTE: this must be done after this.diagram has been set
1293 // as it will trigger a graph modification which needs the
1294 // diagram resource.
1295 // ITask task3 = ThreadLogger.getInstance().begin("addDefaultLayer");
1296 // if (layers.getLayers().isEmpty()) {
1297 // if (DebugPolicy.DEBUG_LAYERS)
1298 // System.out.println("No layers, creating default layer '"
1299 // + DiagramConstants.DEFAULT_LAYER_NAME + "'");
1300 // SimpleLayer defaultLayer = new SimpleLayer(DiagramConstants.DEFAULT_LAYER_NAME);
1301 // layers.addLayer(defaultLayer);
1302 // layers.activate(defaultLayer);
1304 // // task3.finish();
1308 monitor.subTask("Contents");
1309 // Discover the plain resources that form the content of the
1310 // diagram through a separate query. This allows us to
1312 // track changes to the diagram structure itself, not the
1313 // substructures contained by the structure elements.
1314 ITask task4 = ThreadLogger.getInstance().begin("DiagramContentRequest1");
1315 DiagramContentRequest query = new DiagramContentRequest(canvas, diagram, errorHandler);
1316 g.syncRequest(query, new DiagramContentListener(diagram));
1318 // ITask task5 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
1319 ITask task42 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
1320 DiagramContents contents = g.syncRequest(query, TransientCacheAsyncListener.instance());
1321 //System.err.println("contents: " + contents);
1326 monitor.subTask("Graphical elements");
1328 Object applyDiagramContents = Timing.BEGIN("GDS.applyDiagramContents");
1329 ITask task6 = ThreadLogger.getInstance().begin("applyDiagramContents");
1330 processGraphUpdates(g, Collections.singleton(diagramGraphUpdater(contents)));
1332 Timing.END(applyDiagramContents);
1336 DataNodeMap dn = new DataNodeMap() {
1338 public INode getNode(Object data) {
1339 if (DataNodeConstants.CANVAS_ROOT == data)
1340 return canvas.getCanvasNode();
1341 if (DataNodeConstants.DIAGRAM_ELEMENT_PARENT == data) {
1342 ElementPainter ep = canvas.getAtMostOneItemOfClass(ElementPainter.class);
1343 return ep != null ? ep.getDiagramElementParentNode() : null;
1346 DataElementMap emap = GraphToDiagramSynchronizer.this.diagram.getDiagramClass().getSingleItem(DataElementMap.class);
1347 IElement element = emap.getElement(GraphToDiagramSynchronizer.this.diagram, data);
1348 if(element == null) return null;
1349 return element.getHint(ElementHints.KEY_SG_NODE);
1353 profileObserver = new ProfileObserver(g.getSession(), runtime,
1354 canvas.getThreadAccess(), canvas, canvas.getSceneGraph(), diagram,
1355 ArrayMap.keys(ProfileKeys.DIAGRAM, ProfileKeys.CANVAS, ProfileKeys.NODE_MAP).values(GraphToDiagramSynchronizer.this.diagram, canvas, dn),
1356 new CanvasNotification(canvas));
1358 g.getSession().asyncRequest(new AsyncReadRequest() {
1360 public void run(AsyncReadGraph graph) {
1361 ProfileObserver po = profileObserver;
1363 po.listen(graph, GraphToDiagramSynchronizer.this);
1365 LOGGER.info("profileObserver has been disposed already!");
1373 this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
1374 return this.diagram;
1379 } catch (InterruptedException e) {
1380 throw new RuntimeException(e);
1381 } catch (IllegalStateException e) {
1382 // If the synchronizer was disposed ahead of time, it was done
1383 // for a reason, such as the user having closed the owner editor.
1385 throw new CancelTransactionException(e);
1386 throw new RuntimeException(e);
1388 Timing.END(loadTask);
1392 static class CanvasNotification implements Runnable {
1394 final private ICanvasContext canvas;
1396 public CanvasNotification(ICanvasContext canvas) {
1397 this.canvas = canvas;
1401 canvas.getContentContext().setDirty();
1406 ArrayList<IModification> pendingModifications = new ArrayList<IModification>();
1407 MapSet<IElement, IModification> modificationIndex = new MapSet.Hash<IElement, IModification>();
1409 void addModification(IElement element, IModification modification) {
1410 pendingModifications.add(modification);
1411 if (element != null)
1412 modificationIndex.add(element, modification);
1415 class DefaultDiagramMutator implements DiagramMutator {
1417 Map<IElement, Resource> creation = new HashMap<IElement, Resource>();
1422 IModifiableSynchronizationContext synchronizationContext;
1424 public DefaultDiagramMutator(IDiagram d, Resource diagram, IModifiableSynchronizationContext synchronizationContext) {
1426 this.diagram = diagram;
1427 this.synchronizationContext = synchronizationContext;
1429 if (synchronizationContext.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER) == null)
1430 throw new IllegalArgumentException("SynchronizationHints.ELEMENT_CLASS_PROVIDER not available");
1433 void assertNotDisposed() {
1435 throw new IllegalStateException(getClass().getSimpleName() + " is disposed");
1439 public IElement newElement(ElementClass clazz) {
1440 assertNotDisposed();
1441 ElementFactory ef = d.getDiagramClass().getAtMostOneItemOfClass(ElementFactory.class);
1442 IElement element = null;
1444 element = ef.spawnNew(clazz);
1446 element = Element.spawnNew(clazz);
1448 element.setHint(ElementHints.KEY_OBJECT, new TransientElementObject());
1450 addModification(element, new AddElement(synchronizationContext, d, element));
1456 public void commit() {
1457 assertNotDisposed();
1458 if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
1459 System.out.println("DiagramMutator is about to commit changes:");
1460 for (IModification mod : pendingModifications)
1461 System.out.println("\t- " + mod);
1464 Collections.sort(pendingModifications);
1466 if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
1467 if (pendingModifications.size() > 1) {
1468 System.out.println("* changes were re-ordered to:");
1469 for (IModification mod : pendingModifications)
1470 System.out.println("\t" + mod);
1474 Timing.safeTimed(errorHandler, "QUEUE AND WAIT FOR MODIFICATIONS TO FINISH", new GTask() {
1476 public void run() throws DatabaseException {
1477 // Performs a separate write request and query result update
1478 // for each modification
1479 // for (IModification mod : pendingModifications) {
1481 // modificationQueue.sync(mod);
1482 // } catch (InterruptedException e) {
1483 // error("Pending diagram modification " + mod
1484 // + " was interrupted. See exception for details.", e);
1488 // NOTE: this is still under testing, the author is not
1489 // truly certain that it should work in all cases ATM.
1491 // Performs all modifications with in a single write request
1492 for (IModification mod : pendingModifications) {
1493 modificationQueue.offer(mod, null);
1496 // Perform the modifications in a single request.
1497 modificationQueue.finish();
1498 } catch (InterruptedException e) {
1499 errorHandler.error("Diagram modification finishing was interrupted. See exception for details.", e);
1503 pendingModifications.clear();
1504 modificationIndex.clear();
1506 if (DebugPolicy.DEBUG_MUTATOR_COMMIT)
1507 System.out.println("DiagramMutator has committed");
1511 public void clear() {
1512 assertNotDisposed();
1513 pendingModifications.clear();
1514 modificationIndex.clear();
1516 if (DebugPolicy.DEBUG_MUTATOR)
1517 System.out.println("DiagramMutator has been cleared");
1521 public void modifyTransform(IElement element) {
1522 assertNotDisposed();
1523 Resource resource = backendObject(element);
1524 AffineTransform tr = element.getHint(ElementHints.KEY_TRANSFORM);
1525 if (resource != null && tr != null) {
1526 addModification(element, new TransformElement(resource, tr));
1531 public void synchronizeHintsToBackend(IElement element) {
1532 assertNotDisposed();
1533 IHintSynchronizer synchronizer = element.getHint(SynchronizationHints.HINT_SYNCHRONIZER);
1534 if (synchronizer != null) {
1535 CollectingModificationQueue queue = new CollectingModificationQueue();
1536 synchronizer.synchronize(synchronizationContext, element);
1537 addModification(element, new CompositeModification(ModificationAdapter.LOW_PRIORITY, queue.getQueue()));
1542 public void synchronizeElementOrder() {
1543 assertNotDisposed();
1544 List<IElement> snapshot = d.getSnapshot();
1545 addModification(null, new ElementReorder(d, snapshot));
1549 public void register(IElement element, Object object) {
1550 creation.put(element, (Resource) object);
1553 @SuppressWarnings("unchecked")
1555 public <T> T backendObject(IElement element) {
1556 Object object = ElementUtils.getObject(element);
1557 if (object instanceof Resource)
1560 return (T) creation.get(element);
1565 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1566 // GRAPH TO DIAGRAM SYCHRONIZATION LOGIC BEGIN
1567 // ------------------------------------------------------------------------
1569 static class ConnectionData {
1570 ConnectionEntityImpl impl;
1571 List<Resource> branchPoints = new ArrayList<Resource>();
1572 List<EdgeResource> segments = new ArrayList<EdgeResource>();
1574 ConnectionData(ConnectionEntityImpl ce) {
1578 void addBranchPoint(Resource bp) {
1579 branchPoints.add(bp);
1582 void addSegment(EdgeResource seg) {
1587 class GraphToDiagramUpdater {
1588 DiagramContents lastContent;
1589 DiagramContents content;
1590 DiagramContentChanges changes;
1592 final List<IElement> addedElements;
1593 final List<IElement> removedElements;
1595 final List<IElement> addedConnectionSegments;
1596 final List<IElement> removedConnectionSegments;
1598 final List<IElement> addedBranchPoints;
1599 final List<IElement> removedBranchPoints;
1601 final Map<Object, IElement> addedElementMap;
1602 final Map<Resource, IElement> addedConnectionMap;
1603 final Map<Resource, ConnectionEntityImpl> addedConnectionEntities;
1604 final List<Resource> removedConnectionEntities;
1605 final Map<ConnectionEntityImpl, ConnectionData> changedConnectionEntities;
1607 final Map<Resource, IElement> addedRouteGraphConnectionMap;
1608 final List<IElement> removedRouteGraphConnections;
1611 GraphToDiagramUpdater(DiagramContents lastContent, DiagramContents content, DiagramContentChanges changes) {
1612 this.lastContent = lastContent;
1613 this.content = content;
1614 this.changes = changes;
1616 this.addedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
1617 this.removedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
1618 this.addedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
1619 this.removedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
1620 this.addedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
1621 this.removedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
1622 this.addedElementMap = new HashMap<Object, IElement>();
1623 this.addedConnectionMap = new HashMap<Resource, IElement>();
1624 this.addedConnectionEntities = new HashMap<Resource, ConnectionEntityImpl>();
1625 this.removedConnectionEntities = new ArrayList<Resource>(changes.connections.size());
1626 this.changedConnectionEntities = new HashMap<ConnectionEntityImpl, ConnectionData>();
1627 this.addedRouteGraphConnectionMap = new HashMap<Resource, IElement>();
1628 this.removedRouteGraphConnections = new ArrayList<IElement>(changes.routeGraphConnections.size());
1631 public void clear() {
1632 // Prevent DiagramContents leakage through DisposableListeners.
1637 this.addedElements.clear();
1638 this.removedElements.clear();
1639 this.addedConnectionSegments.clear();
1640 this.removedConnectionSegments.clear();
1641 this.addedBranchPoints.clear();
1642 this.removedBranchPoints.clear();
1643 this.addedElementMap.clear();
1644 this.addedConnectionMap.clear();
1645 this.addedConnectionEntities.clear();
1646 this.removedConnectionEntities.clear();
1647 this.changedConnectionEntities.clear();
1648 this.addedRouteGraphConnectionMap.clear();
1649 this.removedRouteGraphConnections.clear();
1652 void processNodes(AsyncReadGraph graph) throws DatabaseException {
1654 for (Map.Entry<Resource, Change> entry : changes.elements.entrySet()) {
1656 final Resource element = entry.getKey();
1657 Change change = entry.getValue();
1661 IElement mappedElement = getMappedElement(element);
1662 if (mappedElement == null) {
1663 if (DebugPolicy.DEBUG_NODE_LOAD)
1664 graph.syncRequest(new ReadRequest() {
1666 public void run(ReadGraph graph) throws DatabaseException {
1667 System.out.println(" EXTERNALLY ADDED ELEMENT: "
1668 + NameUtils.getSafeName(graph, element) + " ("
1669 + element.getResourceId() + ")");
1673 if (content.connectionSet.contains(element)) {
1675 // TODO: Connection loading has no listening, changes :Connection will not be noticed by this code!
1676 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1678 boolean firstTime = true;
1681 public String toString() {
1682 return "Connection load listener for " + element;
1685 public void execute(IElement loaded) {
1686 // Invoked when the element has been loaded.
1687 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1688 System.out.println("CONNECTION LoadListener for " + loaded);
1690 if (loaded == null) {
1697 mapElement(element, loaded);
1698 synchronized (GraphToDiagramUpdater.this) {
1699 addedElements.add(loaded);
1700 addedElementMap.put(element, loaded);
1701 addedConnectionMap.put(element, loaded);
1708 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1710 // Logic for disposing listener
1711 if (!previousContent.connectionSet.contains(data)) {
1712 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1713 System.out.println("CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
1718 if (addedElementMap.containsKey(data)) {
1719 // This element was just loaded, in
1720 // which case its hints need to
1721 // uploaded to the real mapped
1722 // element immediately.
1723 IElement mappedElement = getMappedElement(data);
1724 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1725 System.out.println("LOADED ADDED CONNECTION, currently mapped connection: " + mappedElement);
1726 if (mappedElement != null && (mappedElement instanceof Element)) {
1727 if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
1728 System.out.println(" mapped hints: " + mappedElement.getHints());
1729 System.out.println(" loaded hints: " + loaded.getHints());
1731 updateMappedElement((Element) mappedElement, loaded);
1734 // This element was already loaded.
1735 // Just schedule an update some time
1737 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1738 System.out.println("PREVIOUSLY LOADED CONNECTION UPDATED, scheduling update into the future");
1739 offerGraphUpdate( connectionUpdater(element, loaded) );
1744 graph.asyncRequest(new ConnectionRequest(canvas, diagram, element, errorHandler, loadListener), new AsyncProcedure<IElement>() {
1746 public void execute(AsyncReadGraph graph, final IElement e) {
1748 // Read connection type
1749 graph.forSingleType(element, br.DIA.Connection, new Procedure<Resource>() {
1751 public void exception(Throwable t) {
1756 public void execute(Resource connectionType) {
1757 synchronized (GraphToDiagramUpdater.this) {
1758 IElement mapped = getMappedElement(element);
1759 assert(mapped != null);
1761 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1762 System.out.println("CONNECTION ENTITY CREATED " + e + " " + element);
1764 //System.out.println("new connection entity " + e);
1765 ConnectionEntityImpl entity = new ConnectionEntityImpl(element, connectionType, mapped);
1766 mapped.setHint(ElementHints.KEY_CONNECTION_ENTITY, entity);
1767 addedConnectionEntities.put(element, entity);
1775 public void exception(AsyncReadGraph graph, Throwable throwable) {
1779 } else if (content.nodeSet.contains(element)) {
1781 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1783 boolean firstTime = true;
1786 public String toString() {
1787 return "Node load listener for " + element;
1790 public void execute(IElement loaded) {
1791 // Invoked when the element has been loaded.
1792 if (DebugPolicy.DEBUG_NODE_LISTENER)
1793 System.out.println("NODE LoadListener for " + loaded);
1795 if (loaded == null) {
1802 // This is invoked before the element is actually loaded.
1803 //System.out.println("NodeRequestProcedure " + e);
1804 if (DebugPolicy.DEBUG_NODE_LOAD)
1805 System.out.println("MAPPING ADDED NODE: " + element + " -> " + loaded);
1806 mapElement(element, loaded);
1807 synchronized (GraphToDiagramUpdater.this) {
1808 addedElements.add(loaded);
1809 addedElementMap.put(element, loaded);
1816 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1818 // Logic for disposing listener
1819 if (!previousContent.nodeSet.contains(data)) {
1820 if (DebugPolicy.DEBUG_NODE_LISTENER)
1821 System.out.println("NODE LoadListener, node not in current content: " + data + ". Disposing.");
1826 if (addedElementMap.containsKey(data)) {
1827 // This element was just loaded, in
1828 // which case its hints need to
1829 // uploaded to the real mapped
1830 // element immediately.
1831 IElement mappedElement = getMappedElement(data);
1832 if (DebugPolicy.DEBUG_NODE_LISTENER)
1833 System.out.println("LOADED ADDED ELEMENT, currently mapped element: " + mappedElement);
1834 if (mappedElement != null && (mappedElement instanceof Element)) {
1835 if (DebugPolicy.DEBUG_NODE_LISTENER) {
1836 System.out.println(" mapped hints: " + mappedElement.getHints());
1837 System.out.println(" loaded hints: " + loaded.getHints());
1839 updateMappedElement((Element) mappedElement, loaded);
1842 // This element was already loaded.
1843 // Just schedule an update some time
1845 if (DebugPolicy.DEBUG_NODE_LISTENER)
1846 System.out.println("PREVIOUSLY LOADED NODE UPDATED, scheduling update into the future");
1847 offerGraphUpdate( nodeUpdater(element, loaded) );
1852 //System.out.println("NODE REQUEST: " + element);
1853 graph.asyncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
1855 public void execute(AsyncReadGraph graph, IElement e) {
1859 public void exception(AsyncReadGraph graph, Throwable throwable) {
1865 // warning("Diagram elements must be either elements or connections, "
1866 // + NameUtils.getSafeName(g, element) + " is neither",
1867 // new AssumptionException(""));
1873 IElement e = getMappedElement(element);
1874 if (DebugPolicy.DEBUG_NODE_LOAD)
1875 graph.syncRequest(new ReadRequest() {
1877 public void run(ReadGraph graph) throws DatabaseException {
1878 System.out.println(" EXTERNALLY REMOVED ELEMENT: "
1879 + NameUtils.getSafeName(graph, element) + " ("
1880 + element.getResourceId() + ")");
1884 removedElements.add(e);
1893 void gatherChangedConnectionParts(Map<?, Change> changes) {
1894 for (Map.Entry<?, Change> entry : changes.entrySet()) {
1895 Object part = entry.getKey();
1896 Change change = entry.getValue();
1900 synchronized (GraphToDiagramUpdater.this) {
1901 Resource connection = content.partToConnection.get(part);
1902 assert connection != null;
1904 IElement ce = getMappedElement(connection);
1906 ce = addedElementMap.get(connection);
1909 markConnectionChanged(ce);
1914 if (lastContent == null)
1916 Resource connection = lastContent.partToConnection.get(part);
1917 if (connection != null && content.connectionSet.contains(connection)) {
1918 markConnectionChanged(connection);
1927 void markConnectionChanged(Resource connection) {
1928 // System.out.println("markConnectionChanged");
1929 ConnectionEntityImpl ce = getMappedConnection(connection);
1931 markConnectionChanged(ce);
1934 error("WARNING: marking connection entity " + connection
1935 + " changed, but the connection was not previously mapped",
1936 new Exception("created exception to get a stack trace"));
1939 void markConnectionChanged(IElement connection) {
1940 ConnectionEntityImpl entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
1942 markConnectionChanged(entity);
1945 void markConnectionChanged(ConnectionEntityImpl ce) {
1946 if (!changedConnectionEntities.containsKey(ce)) {
1947 changedConnectionEntities.put(ce, new ConnectionData(ce));
1951 void processConnections() {
1952 // Find added/removed connection segments/branch points
1953 // in order to find all changed connection entities.
1954 gatherChangedConnectionParts(changes.connectionSegments);
1955 gatherChangedConnectionParts(changes.branchPoints);
1957 // Find removed connection entities
1958 for (Map.Entry<Resource, Change> entry : changes.connections.entrySet()) {
1959 Resource ce = entry.getKey();
1960 Change change = entry.getValue();
1964 removedConnectionEntities.add(ce);
1970 // Generate update data of changed connection entities.
1971 // This ConnectionData will be applied in the canvas thread
1973 for (ConnectionData cd : changedConnectionEntities.values()) {
1974 for (Object part : content.connectionToParts.getValuesUnsafe(cd.impl.connection)) {
1975 if (part instanceof Resource) {
1976 cd.branchPoints.add((Resource) part);
1977 } else if (part instanceof EdgeResource) {
1978 cd.segments.add((EdgeResource) part);
1984 void processRouteGraphConnections(ReadGraph graph) throws DatabaseException {
1985 for (Map.Entry<Resource, Change> entry : changes.routeGraphConnections.entrySet()) {
1986 final Resource connection = entry.getKey();
1988 Change change = entry.getValue();
1991 IElement mappedElement = getMappedElement(connection);
1992 if (mappedElement != null)
1995 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1997 boolean firstTime = true;
2000 public String toString() {
2001 return "processRouteGraphConnections " + connection;
2004 public void execute(IElement loaded) {
2005 // Invoked when the element has been loaded.
2006 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2007 System.out.println("ROUTE GRAPH CONNECTION LoadListener for " + loaded);
2009 if (loaded == null) {
2015 if (DebugPolicy.DEBUG_NODE_LOAD)
2016 System.out.println("MAPPING ADDED ROUTE GRAPH CONNECTION: " + connection + " -> " + loaded);
2017 mapElement(connection, loaded);
2018 synchronized (GraphToDiagramUpdater.this) {
2019 addedElements.add(loaded);
2020 addedElementMap.put(connection, loaded);
2021 addedRouteGraphConnectionMap.put(connection, loaded);
2026 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2028 // Logic for disposing listener
2029 if (!previousContent.routeGraphConnectionSet.contains(data)) {
2030 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2031 System.out.println("ROUTE GRAPH CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
2036 if (addedElementMap.containsKey(data)) {
2037 // This element was just loaded, in
2038 // which case its hints need to
2039 // uploaded to the real mapped
2040 // element immediately.
2041 IElement mappedElement = getMappedElement(data);
2042 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2043 System.out.println("LOADED ADDED ROUTE GRAPH CONNECTION, currently mapped connection: " + mappedElement);
2044 if (mappedElement instanceof Element) {
2045 if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
2046 System.out.println(" mapped hints: " + mappedElement.getHints());
2047 System.out.println(" loaded hints: " + loaded.getHints());
2049 updateMappedElement((Element) mappedElement, loaded);
2052 // This element was already loaded.
2053 // Just schedule an update some time
2055 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2056 System.out.println("PREVIOUSLY LOADED ROUTE GRAPH CONNECTION UPDATED, scheduling update into the future: " + connection);
2058 Set<Object> dirtyNodes = new THashSet<Object>(4);
2059 IElement mappedElement = getMappedElement(connection);
2060 ConnectionEntity ce = mappedElement.getHint(ElementHints.KEY_CONNECTION_ENTITY);
2062 for (Connection conn : ce.getTerminalConnections(null)) {
2063 Object o = conn.node.getHint(ElementHints.KEY_OBJECT);
2066 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2067 System.out.println("Marked connectivity dirty for node: " + conn.node);
2072 offerGraphUpdate( routeGraphConnectionUpdater(connection, loaded, dirtyNodes) );
2077 graph.syncRequest(new ConnectionRequest(canvas, diagram, connection, errorHandler, loadListener), new Procedure<IElement>() {
2079 public void execute(final IElement e) {
2082 public void exception(Throwable throwable) {
2089 IElement e = getMappedElement(connection);
2091 removedRouteGraphConnections.add(e);
2099 ConnectionEntityImpl getConnectionEntity(Object connectionPart) {
2100 Resource connection = content.partToConnection.get(connectionPart);
2101 assert connection != null;
2102 ConnectionEntityImpl ce = addedConnectionEntities.get(connection);
2105 return assertMappedConnection(connection);
2108 void processBranchPoints(AsyncReadGraph graph) throws DatabaseException {
2109 for (Map.Entry<Resource, Change> entry : changes.branchPoints.entrySet()) {
2111 final Resource element = entry.getKey();
2112 Change change = entry.getValue();
2116 IElement mappedElement = getMappedElement(element);
2117 if (mappedElement == null) {
2118 if (DebugPolicy.DEBUG_NODE_LOAD)
2119 graph.asyncRequest(new ReadRequest() {
2121 public void run(ReadGraph graph) throws DatabaseException {
2122 System.out.println(" EXTERNALLY ADDED BRANCH POINT: "
2123 + NameUtils.getSafeName(graph, element) + " ("
2124 + element.getResourceId() + ")");
2128 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
2130 boolean firstTime = true;
2133 public String toString() {
2134 return "processBranchPoints for " + element;
2137 public void execute(IElement loaded) {
2138 // Invoked when the element has been loaded.
2139 if (DebugPolicy.DEBUG_NODE_LISTENER)
2140 System.out.println("BRANCH POINT LoadListener for " + loaded);
2142 if (loaded == null) {
2149 mapElement(element, loaded);
2150 synchronized (GraphToDiagramUpdater.this) {
2151 addedBranchPoints.add(loaded);
2152 addedElementMap.put(element, loaded);
2153 ConnectionEntityImpl ce = getConnectionEntity(element);
2154 loaded.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
2155 loaded.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
2162 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2163 if (addedElementMap.containsKey(data)) {
2164 // This element was just loaded, in
2165 // which case its hints need to
2166 // uploaded to the real mapped
2167 // element immediately.
2168 IElement mappedElement = getMappedElement(data);
2169 if (DebugPolicy.DEBUG_NODE_LISTENER)
2170 System.out.println("LOADED ADDED BRANCH POINT, currently mapped element: " + mappedElement);
2171 if (mappedElement != null && (mappedElement instanceof Element)) {
2172 if (DebugPolicy.DEBUG_NODE_LISTENER) {
2173 System.out.println(" mapped hints: " + mappedElement.getHints());
2174 System.out.println(" loaded hints: " + loaded.getHints());
2176 updateMappedElement((Element) mappedElement, loaded);
2179 // This element was already loaded.
2180 // Just schedule an update some time
2182 if (DebugPolicy.DEBUG_NODE_LISTENER)
2183 System.out.println("PREVIOUSLY LOADED BRANCH POINT UPDATED, scheduling update into the future");
2184 offerGraphUpdate( nodeUpdater(element, loaded) );
2189 graph.asyncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
2191 public void execute(AsyncReadGraph graph, IElement e) {
2195 public void exception(AsyncReadGraph graph, Throwable throwable) {
2203 IElement e = getMappedElement(element);
2204 if (DebugPolicy.DEBUG_NODE_LOAD)
2205 graph.syncRequest(new ReadRequest() {
2207 public void run(ReadGraph graph) throws DatabaseException {
2208 System.out.println(" EXTERNALLY REMOVED BRANCH POINT: "
2209 + NameUtils.getSafeName(graph, element) + " ("
2210 + element.getResourceId() + ")");
2214 removedBranchPoints.add(e);
2223 void processConnectionSegments(AsyncReadGraph graph) throws DatabaseException {
2224 ConnectionSegmentAdapter adapter = connectionSegmentAdapter;
2226 for (Map.Entry<EdgeResource, Change> entry : changes.connectionSegments.entrySet()) {
2227 final EdgeResource seg = entry.getKey();
2228 Change change = entry.getValue();
2232 IElement mappedElement = getMappedElement(seg);
2233 if (mappedElement == null) {
2234 if (DebugPolicy.DEBUG_EDGE_LOAD)
2235 graph.asyncRequest(new ReadRequest() {
2237 public void run(ReadGraph graph) throws DatabaseException {
2238 System.out.println(" EXTERNALLY ADDED CONNECTION SEGMENT: " + seg.toString()
2239 + " - " + seg.toString(graph));
2243 graph.asyncRequest(new EdgeRequest(GraphToDiagramSynchronizer.this, canvas, errorHandler, canvasListenerSupport, diagram, adapter, seg), new AsyncProcedure<IElement>() {
2245 public void execute(AsyncReadGraph graph, IElement e) {
2246 if (DebugPolicy.DEBUG_EDGE_LOAD)
2247 System.out.println("ADDED EDGE LOADED: " + e + " " + seg);
2250 synchronized (GraphToDiagramUpdater.this) {
2251 addedConnectionSegments.add(e);
2252 addedElementMap.put(seg, e);
2253 ConnectionEntityImpl ce = getConnectionEntity(seg);
2254 e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
2255 e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
2261 public void exception(AsyncReadGraph graph, Throwable throwable) {
2269 final IElement e = getMappedElement(seg);
2270 if (DebugPolicy.DEBUG_EDGE_LOAD)
2271 graph.asyncRequest(new ReadRequest() {
2273 public void run(ReadGraph graph) throws DatabaseException {
2274 System.out.println(" EXTERNALLY REMOVED CONNECTION SEGMENT: " + seg.toString() + " - "
2275 + seg.toString(graph) + " -> " + e);
2279 removedConnectionSegments.add(e);
2288 void executeDeferredLoaders(ReadGraph graph) throws DatabaseException {
2289 // The rest of the diagram loading passes
2290 Deque<IElement> q1 = new ArrayDeque<IElement>();
2291 Deque<IElement> q2 = new ArrayDeque<IElement>();
2292 collectElementLoaders(q1, addedElements);
2293 while (!q1.isEmpty()) {
2294 //System.out.println("DEFFERED LOADERS: " + q1);
2295 for (IElement e : q1) {
2296 ElementLoader loader = e.removeHint(DiagramModelHints.KEY_ELEMENT_LOADER);
2297 //System.out.println("EXECUTING DEFFERED LOADER: " + loader);
2298 loader.load(graph, diagram, e);
2301 collectElementLoaders(q2, q1);
2302 Deque<IElement> qt = q1;
2309 private void collectElementLoaders(Queue<IElement> queue, Collection<IElement> cs) {
2310 for (IElement e : cs) {
2311 ElementLoader loader = e.getHint(DiagramModelHints.KEY_ELEMENT_LOADER);
2317 public void process(ReadGraph graph) throws DatabaseException {
2318 // No changes? Do nothing.
2319 if (changes.isEmpty())
2322 ITask threadLog = ThreadLogger.task("processNodes");
2324 // NOTE: This order is important.
2325 Object task = Timing.BEGIN("processNodesConnections");
2326 //System.out.println("---- PROCESS NODES & CONNECTIONS BEGIN");
2327 if (!changes.elements.isEmpty()) {
2328 graph.syncRequest(new AsyncReadRequest() {
2330 public void run(AsyncReadGraph graph) throws DatabaseException {
2331 processNodes(graph);
2334 public String toString() {
2335 return "processNodes";
2339 //System.out.println("---- PROCESS NODES & CONNECTIONS END");
2343 threadLog = ThreadLogger.task("processConnections");
2345 processConnections();
2349 threadLog = ThreadLogger.task("processBranchPoints");
2351 //System.out.println("---- PROCESS BRANCH POINTS BEGIN");
2352 if (!changes.branchPoints.isEmpty()) {
2353 graph.syncRequest(new AsyncReadRequest() {
2355 public void run(AsyncReadGraph graph) throws DatabaseException {
2356 processBranchPoints(graph);
2359 public String toString() {
2360 return "processBranchPoints";
2364 //System.out.println("---- PROCESS BRANCH POINTS END");
2370 threadLog = ThreadLogger.task("processConnectionSegments");
2372 task = Timing.BEGIN("processConnectionSegments");
2374 //System.out.println("---- PROCESS CONNECTION SEGMENTS BEGIN");
2375 if (!changes.connectionSegments.isEmpty()) {
2376 graph.syncRequest(new AsyncReadRequest() {
2378 public void run(AsyncReadGraph graph) throws DatabaseException {
2379 processConnectionSegments(graph);
2382 public String toString() {
2383 return "processConnectionSegments";
2387 //System.out.println("---- PROCESS CONNECTION SEGMENTS END");
2393 threadLog = ThreadLogger.task("processRouteGraphConnections");
2395 task = Timing.BEGIN("processRouteGraphConnections");
2396 if (!changes.routeGraphConnections.isEmpty()) {
2397 graph.syncRequest(new ReadRequest() {
2399 public void run(ReadGraph graph) throws DatabaseException {
2400 processRouteGraphConnections(graph);
2403 public String toString() {
2404 return "processRouteGraphConnections";
2412 //System.out.println("---- AFTER LOADING");
2413 //for (IElement e : addedElements)
2414 // System.out.println(" ADDED ELEMENT: " + e);
2415 //for (IElement e : addedBranchPoints)
2416 // System.out.println(" ADDED BRANCH POINTS: " + e);
2418 task = Timing.BEGIN("executeDeferredLoaders");
2419 threadLog = ThreadLogger.task("executeDeferredLoaders");
2421 executeDeferredLoaders(graph);
2428 public boolean isEmpty() {
2429 return addedElements.isEmpty() && removedElements.isEmpty()
2430 && addedConnectionSegments.isEmpty() && removedConnectionSegments.isEmpty()
2431 && addedBranchPoints.isEmpty() && removedBranchPoints.isEmpty()
2432 && addedConnectionEntities.isEmpty() && removedConnectionEntities.isEmpty()
2433 && addedRouteGraphConnectionMap.isEmpty() && removedRouteGraphConnections.isEmpty()
2434 && !changes.elementOrderChanged;
2437 class DefaultConnectionSegmentAdapter implements ConnectionSegmentAdapter {
2440 public void getClass(AsyncReadGraph graph, EdgeResource edge, ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, IDiagram diagram, final AsyncProcedure<ElementClass> procedure) {
2441 if (info.connectionType != null) {
2442 NodeClassRequest request = new NodeClassRequest(canvas, diagram, info.connectionType, true);
2443 graph.asyncRequest(request, new CacheListener<ElementClass>(listenerSupport));
2444 graph.asyncRequest(request, procedure);
2446 procedure.execute(graph, null);
2451 public void load(AsyncReadGraph graph, final EdgeResource edge, final ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, final IDiagram diagram, final IElement element) {
2452 graph.asyncRequest(new Read<IElement>() {
2454 public IElement perform(ReadGraph graph) throws DatabaseException {
2455 //ITask task = ThreadLogger.getInstance().begin("LoadSegment");
2456 syncLoad(graph, edge, info, diagram, element);
2461 public String toString() {
2462 return "defaultConnectionSegmentAdapter";
2464 }, new DisposableListener<IElement>(listenerSupport) {
2467 public String toString() {
2468 return "DefaultConnectionSegmentAdapter listener for " + edge;
2472 public void execute(IElement loaded) {
2473 // Invoked when the element has been loaded.
2475 if (loaded == null) {
2480 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2482 if (DebugPolicy.DEBUG_EDGE_LISTENER)
2483 System.out.println("EDGE LoadListener for " + loaded + " " + data);
2485 if (addedElementMap.containsKey(data)) {
2486 // This element was just loaded, in
2487 // which case its hints need to
2488 // uploaded to the real mapped
2489 // element immediately.
2490 IElement mappedElement = getMappedElement(data);
2491 if (DebugPolicy.DEBUG_EDGE_LISTENER)
2492 System.out.println("LOADED ADDED EDGE, currently mapped element: " + mappedElement);
2493 if (mappedElement != null && (mappedElement instanceof Element)) {
2494 if (DebugPolicy.DEBUG_EDGE_LISTENER) {
2495 System.out.println(" mapped hints: " + mappedElement.getHints());
2496 System.out.println(" loaded hints: " + loaded.getHints());
2498 updateMappedElement((Element) mappedElement, loaded);
2501 // This element was already loaded.
2502 // Just schedule an update some time
2504 if (DebugPolicy.DEBUG_EDGE_LISTENER)
2505 System.out.println("PREVIOUSLY LOADED EDGE UPDATED, scheduling update into the future");
2506 offerGraphUpdate( edgeUpdater(element, loaded) );
2512 void syncLoad(ReadGraph graph, EdgeResource connectionSegment, ConnectionInfo info, IDiagram diagram, IElement element) throws DatabaseException {
2513 // Check that at least some data exists before continuing further.
2514 if (!graph.hasStatement(connectionSegment.first()) && !graph.hasStatement(connectionSegment.second())) {
2518 // Validate that both ends of the segment are
2519 // part of the same connection before loading.
2520 // This will happen for connections that are
2521 // modified through splitting and joining of
2522 // connection segments.
2523 Resource c = ConnectionUtil.tryGetConnection(graph, connectionSegment);
2525 // Ok, this segment is somehow invalid. Just don't load it.
2526 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2527 System.out.println("Skipping edge " + connectionSegment + ". Both segment ends are not part of the same connection.");
2531 if (!info.isValid()) {
2532 // This edge must be somehow invalid, don't proceed with loading.
2533 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2534 warning("Cannot load edge " + connectionSegment + ". ConnectionInfo " + info + " is invalid.", new DebugException("execution trace"));
2538 Element edge = (Element) element;
2539 edge.setHint(ElementHints.KEY_OBJECT, connectionSegment);
2541 // connectionSegment resources may currently be in a different
2542 // order than ConnectionInfo.firstEnd/secondEnd. Therefore the
2543 // segment ends must be resolved here.
2544 ConnectionSegmentEnd firstEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.first());
2545 ConnectionSegmentEnd secondEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.second());
2546 if (firstEnd == null || secondEnd == null) {
2547 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2548 warning("End attachments for edge " + connectionSegment + " are unresolved: (" + firstEnd + "," + secondEnd + ")", new DebugException("execution trace"));
2552 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2553 System.out.println("CONNECTION INFO: " + connectionSegment + " - " + info);
2554 DesignatedTerminal firstTerminal = DiagramGraphUtil.findDesignatedTerminal(
2555 graph, diagram, connectionSegment.first(), firstEnd);
2556 DesignatedTerminal secondTerminal = DiagramGraphUtil.findDesignatedTerminal(
2557 graph, diagram, connectionSegment.second(), secondEnd);
2559 // Edges must be connected at both ends in order for edge loading to succeed.
2560 String err = validateConnectivity(graph, connectionSegment, firstTerminal, secondTerminal);
2562 // Stop loading edge if the connectivity cannot be completely resolved.
2563 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2568 graph.syncRequest(new AsyncReadRequest() {
2570 public void run(AsyncReadGraph graph) {
2571 // NOTICE: Layer information is loaded from the connection entity resource
2572 // that is shared by all segments of the same connection.
2573 ElementFactoryUtil.loadLayersForElement(graph, layerManager, diagram, edge, info.connection,
2574 new AsyncProcedureAdapter<IElement>() {
2576 public void exception(AsyncReadGraph graph, Throwable t) {
2577 error("failed to load layers for connection segment", t);
2583 edge.setHintWithoutNotification(KEY_CONNECTION_BEGIN_PLACEHOLDER, new PlaceholderConnection(
2585 firstTerminal.element.getHint(ElementHints.KEY_OBJECT),
2586 firstTerminal.terminal));
2587 edge.setHintWithoutNotification(KEY_CONNECTION_END_PLACEHOLDER, new PlaceholderConnection(
2589 secondTerminal.element.getHint(ElementHints.KEY_OBJECT),
2590 secondTerminal.terminal));
2592 IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);
2593 if (modelingRules != null) {
2594 ConnectionVisualsLoader loader = diagram.getHint(DiagramModelHints.KEY_CONNECTION_VISUALS_LOADER);
2596 loader.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
2598 DiagramGraphUtil.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
2602 private String validateConnectivity(ReadGraph graph, EdgeResource edge,
2603 DesignatedTerminal firstTerminal,
2604 DesignatedTerminal secondTerminal)
2605 throws DatabaseException {
2606 boolean firstLoose = firstTerminal == null;
2607 boolean secondLoose = secondTerminal == null;
2608 boolean stray = firstLoose && secondLoose;
2609 if (firstTerminal == null || secondTerminal == null) {
2610 StringBuilder sb = new StringBuilder();
2611 sb.append("encountered ");
2612 sb.append(stray ? "stray" : "loose");
2613 sb.append(" connection segment, ");
2615 sb.append("first ");
2619 sb.append("second ");
2620 sb.append("end disconnected: ");
2621 sb.append(edge.toString(graph));
2623 sb.append(edge.toString());
2624 return sb.toString();
2631 ConnectionSegmentAdapter connectionSegmentAdapter = new DefaultConnectionSegmentAdapter();
2635 private static final Double DIAGRAM_UPDATE_DIAGRAM_PRIORITY = 1d;
2636 private static final Double DIAGRAM_UPDATE_NODE_PRIORITY = 2d;
2637 private static final Double DIAGRAM_UPDATE_CONNECTION_PRIORITY = 3d;
2638 private static final Double DIAGRAM_UPDATE_EDGE_PRIORITY = 4d;
2640 interface DiagramUpdater extends Runnable {
2641 Double getPriority();
2643 Comparator<DiagramUpdater> DIAGRAM_UPDATER_COMPARATOR = new Comparator<DiagramUpdater>() {
2645 public int compare(DiagramUpdater o1, DiagramUpdater o2) {
2646 return o1.getPriority().compareTo(o2.getPriority());
2651 interface GraphUpdateReactor {
2652 DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException;
2655 static abstract class AbstractDiagramUpdater implements DiagramUpdater, GraphUpdateReactor {
2656 protected final Double priority;
2657 protected final String runnerName;
2659 public AbstractDiagramUpdater(Double priority, String runnerName) {
2660 if (priority == null)
2661 throw new NullPointerException("null priority");
2662 if (runnerName == null)
2663 throw new NullPointerException("null runner name");
2664 this.priority = priority;
2665 this.runnerName = runnerName;
2669 public Double getPriority() {
2674 public AbstractDiagramUpdater graphUpdate(ReadGraph graph) {
2680 Object task = Timing.BEGIN(runnerName);
2685 protected void forDiagram() {
2689 public String toString() {
2690 return runnerName + "@" + System.identityHashCode(this) + " [" + priority + "]";
2695 * @param content the new contents of the diagram, must not be
2696 * <code>null</code>.
2698 private GraphUpdateReactor diagramGraphUpdater(final DiagramContents content) {
2699 if (content == null)
2700 throw new NullPointerException("null diagram content");
2702 return new GraphUpdateReactor() {
2704 public String toString() {
2705 return "DiagramGraphUpdater@" + System.identityHashCode(this);
2709 public DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException {
2710 // Never do anything here if the canvas has already been disposed.
2711 if (!GraphToDiagramSynchronizer.this.isAlive())
2714 // We must be prepared for the following changes in the diagram graph
2716 // - the diagram has been completely removed
2717 // - elements have been added
2718 // - elements have been removed
2720 // Element-specific changes are handled by the element query listeners:
2721 // - elements have been modified
2722 // - element position has changed
2723 // - element class (e.g. image) has changed
2725 diagramUpdateLock.lock();
2727 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2728 System.out.println("In diagramGraphUpdater:");
2730 // Find out what has changed since the last query.
2731 Object task = Timing.BEGIN("diagramContentDifference");
2732 DiagramContents lastContent = previousContent;
2733 DiagramContentChanges changes = content.differenceFrom(previousContent);
2734 previousContent = content;
2736 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2737 System.out.println(" changes: " + changes);
2739 // Bail out if there are no changes to react to.
2740 if (changes.isEmpty())
2743 // Load everything that needs to be loaded from the graph,
2744 // but don't update the UI model in this thread yet.
2745 task = Timing.BEGIN("updater.process");
2746 GraphToDiagramUpdater updater = new GraphToDiagramUpdater(lastContent, content, changes);
2747 GraphToDiagramSynchronizer.this.currentUpdater = updater;
2749 updater.process(graph);
2751 GraphToDiagramSynchronizer.this.currentUpdater = null;
2755 if (updater.isEmpty())
2758 // Return an updater that will update the UI run-time model.
2759 return diagramUpdater(updater);
2761 diagramUpdateLock.unlock();
2767 DiagramUpdater diagramUpdater(final GraphToDiagramUpdater updater) {
2768 return new AbstractDiagramUpdater(DIAGRAM_UPDATE_DIAGRAM_PRIORITY, "updateDiagram") {
2770 protected void forDiagram() {
2771 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2772 System.out.println("running diagram updater: " + this);
2774 // DiagramUtils.testDiagram(diagram);
2775 Set<IElement> dirty = new HashSet<IElement>();
2777 Object task2 = Timing.BEGIN("Preprocess connection changes");
2778 Map<ConnectionEntity, ConnectionChildren> connectionChangeData = new HashMap<ConnectionEntity, ConnectionChildren>(updater.changedConnectionEntities.size());
2779 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2780 connectionChangeData.put(cd.impl, cd.impl.getConnectionChildren());
2784 task2 = Timing.BEGIN("removeRouteGraphConnections");
2785 for (IElement removedRouteGraphConnection : updater.removedRouteGraphConnections) {
2786 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2787 System.out.println("removing route graph connection: " + removedRouteGraphConnection);
2789 ConnectionEntity ce = removedRouteGraphConnection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
2792 Object connectionData = ElementUtils.getObject(removedRouteGraphConnection);
2793 tempConnections.clear();
2794 for (Connection conn : ce.getTerminalConnections(tempConnections)) {
2795 ((Element) conn.node).removeHintWithoutNotification(new TerminalKeyOf(conn.terminal, connectionData, Connection.class));
2796 // To be sure the view will be up-to-date, mark the node
2797 // connected to the removed connection dirty.
2798 dirty.add(conn.node);
2803 task2 = Timing.BEGIN("removeBranchPoints");
2804 for (IElement removed : updater.removedBranchPoints) {
2805 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2806 System.out.println("removing branch point: " + removed);
2808 unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2809 removeNodeTopologyHints((Element) removed);
2811 IElement connection = ElementUtils.getParent(removed);
2812 if (connection != null) {
2813 dirty.add(connection);
2818 task2 = Timing.BEGIN("removeConnectionSegments");
2819 for (IElement removed : updater.removedConnectionSegments) {
2820 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2821 System.out.println("removing segment: " + removed);
2823 unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2824 removeEdgeTopologyHints((Element) removed);
2826 IElement connection = ElementUtils.getParent(removed);
2827 if (connection != null) {
2828 dirty.add(connection);
2833 task2 = Timing.BEGIN("removeElements");
2834 for (IElement removed : updater.removedElements) {
2835 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2836 System.out.println("removing element: " + removed);
2838 removed.setHint(KEY_REMOVE_RELATIONSHIPS, Boolean.TRUE);
2839 if (diagram.containsElement(removed)) {
2840 diagram.removeElement(removed);
2842 unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2843 removeNodeTopologyHints((Element) removed);
2845 // No use marking removed elements dirty.
2846 dirty.remove(removed);
2850 // TODO: get rid of this
2851 task2 = Timing.BEGIN("removeConnectionEntities");
2852 for (Resource ce : updater.removedConnectionEntities) {
2853 unmapConnection(ce);
2857 task2 = Timing.BEGIN("setConnectionData");
2858 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2859 cd.impl.setData(cd.segments, cd.branchPoints);
2863 // TODO: get rid of this
2864 task2 = Timing.BEGIN("addConnectionEntities");
2865 for (Map.Entry<Resource, ConnectionEntityImpl> entry : updater.addedConnectionEntities
2867 mapConnection(entry.getKey(), entry.getValue());
2871 task2 = Timing.BEGIN("addBranchPoints");
2872 for (IElement added : updater.addedBranchPoints) {
2873 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2874 System.out.println("adding branch point: " + added);
2876 mapElement(ElementUtils.getObject(added), added);
2878 IElement connection = ElementUtils.getParent(added);
2879 if (connection != null) {
2880 dirty.add(connection);
2885 // Add new elements at end of diagram, element order will be synchronized later.
2886 task2 = Timing.BEGIN("addElements");
2887 for(Resource r : updater.content.elements) {
2888 IElement added = updater.addedElementMap.get(r);
2890 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2891 System.out.println("adding element: " + added);
2893 //Object task3 = BEGIN("mapElement " + added);
2894 Object task3 = Timing.BEGIN("mapElement");
2895 mapElement(added.getHint(ElementHints.KEY_OBJECT), added);
2898 //task3 = BEGIN("addElement " + added);
2899 task3 = Timing.BEGIN("addElement");
2900 //System.out.println("diagram.addElement: " + added + " - " + diagram);
2901 diagram.addElement(added);
2908 // We've ensured that all nodes must have been and
2909 // mapped before reaching this.
2910 task2 = Timing.BEGIN("addConnectionSegments");
2911 for (IElement added : updater.addedConnectionSegments) {
2912 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2913 System.out.println("adding segment: " + added);
2915 PlaceholderConnection cb = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_BEGIN_PLACEHOLDER);
2916 PlaceholderConnection ce = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_END_PLACEHOLDER);
2917 if (cb == null || ce == null) {
2918 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2919 warning("ignoring connection segment " + added + ", connectivity was not resolved (begin=" + cb + ", end=" + ce +")", null);
2923 mapElement(ElementUtils.getObject(added), added);
2925 IElement beginNode = assertMappedElement(cb.node);
2926 IElement endNode = assertMappedElement(ce.node);
2929 connect(added, cb.end, beginNode, cb.terminal);
2931 connect(added, ce.end, endNode, ce.terminal);
2933 IElement connection = ElementUtils.getParent(added);
2934 if (connection != null) {
2935 dirty.add(connection);
2940 // We've ensured that all nodes must have been and
2941 // mapped before reaching this.
2943 task2 = Timing.BEGIN("handle dirty RouteGraph connections");
2944 for (IElement addedRouteGraphConnection : updater.addedRouteGraphConnectionMap.values()) {
2945 updateDirtyRouteGraphConnection(addedRouteGraphConnection, dirty);
2949 // Prevent memory leaks
2950 tempConnections.clear();
2952 // Make sure that the diagram element order matches that of the database.
2953 final TObjectIntHashMap<IElement> orderMap = new TObjectIntHashMap<IElement>(2 * updater.content.elements.size());
2955 for (Resource r : updater.content.elements) {
2956 IElement e = getMappedElement(r);
2961 diagram.sort(new Comparator<IElement>() {
2963 public int compare(IElement e1, IElement e2) {
2964 int o1 = orderMap.get(e1);
2965 int o2 = orderMap.get(e2);
2970 // TODO: consider removing this. The whole thing should work without it and
2971 // this "fix" will only be hiding the real problems.
2972 task2 = Timing.BEGIN("fixChangedConnections");
2973 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2978 task2 = Timing.BEGIN("validateAndFix");
2979 DiagramUtils.validateAndFix(diagram, dirty);
2982 // This will fire connection entity change listeners
2983 task2 = Timing.BEGIN("Postprocess connection changes");
2984 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2985 ConnectionChildren oldChildren = connectionChangeData.get(cd.impl);
2986 if (oldChildren != null) {
2987 ConnectionChildren currentChildren = cd.impl.getConnectionChildren();
2988 cd.impl.fireListener(oldChildren, currentChildren);
2993 task2 = Timing.BEGIN("setDirty");
2994 for (IElement e : dirty) {
2995 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2996 System.out.println("MARKING ELEMENT DIRTY: " + e);
2997 e.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3001 // Signal about possible changes in the z-order of diagram elements.
3002 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
3003 System.out.println("MARKING DIAGRAM DIRTY: " + diagram);
3004 diagram.setHint(Hints.KEY_DIRTY, Hints.VALUE_Z_ORDER_CHANGED);
3006 // Mark the updater as "processed".
3009 // Inform listeners that the diagram has been updated.
3010 diagram.setHint(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, Boolean.TRUE);
3019 private void updateDirtyRouteGraphConnection(IElement connection, Set<IElement> dirtySet) {
3020 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
3021 System.out.println("updating dirty route graph connection: " + connection);
3023 ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
3027 tempConnections.clear();
3028 Object connectionData = ElementUtils.getObject(connection);
3029 for (Connection conn : ce.getTerminalConnections(tempConnections)) {
3030 ((Element) conn.node).setHintWithoutNotification(
3031 new TerminalKeyOf(conn.terminal, connectionData, Connection.class),
3033 if (dirtySet != null)
3034 dirtySet.add(conn.node);
3037 // Prevent memory leaks.
3038 tempConnections.clear();
3041 abstract class ElementUpdater extends AbstractDiagramUpdater {
3042 private final IElement newElement;
3044 public ElementUpdater(Double priority, String runnerName, IElement newElement) {
3045 super(priority, runnerName);
3046 if (newElement == null)
3047 throw new NullPointerException("null element");
3048 this.newElement = newElement;
3052 public String toString() {
3053 return super.toString() + "[" + newElement + "]";
3058 // System.out.println("ElementUpdateRunner new=" + newElement);
3059 Object elementResource = newElement.getHint(ElementHints.KEY_OBJECT);
3060 // System.out.println("ElementUpdateRunner res=" + elementResource);
3061 final Element mappedElement = (Element) getMappedElement(elementResource);
3062 // System.out.println("ElementUpdateRunner mapped=" + mappedElement);
3063 if (mappedElement == null) {
3064 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE) {
3065 System.out.println("SKIP DIAGRAM UPDATE " + this + " for element resource " + elementResource + ", no mapped element (newElement=" + newElement + ")");
3067 // Indicates the element has been removed from the graph.
3071 Object task = Timing.BEGIN(runnerName);
3072 forMappedElement(mappedElement);
3076 protected abstract void forMappedElement(Element mappedElement);
3079 ElementUpdater nodeUpdater(final Resource resource, final IElement newElement) {
3081 return new ElementUpdater(DIAGRAM_UPDATE_NODE_PRIORITY, "updateNode", newElement) {
3083 Collection<Terminal> getTerminals(IElement e) {
3084 Collection<Terminal> ts = Collections.emptyList();
3085 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
3087 ts = new ArrayList<Terminal>();
3088 tt.getTerminals(newElement, ts);
3094 protected void forMappedElement(final Element mappedElement) {
3095 if (DebugPolicy.DEBUG_NODE_UPDATE)
3096 System.out.println("running node updater: " + this + " - new element: " + newElement);
3098 // Newly loaded node elements NEVER contain topology-related
3099 // hints, i.e. TerminalKeyOf hints. Instead all connections are
3100 // actually set into element hints when connection edges are
3103 Collection<Terminal> oldTerminals = getTerminals(mappedElement);
3104 Collection<Terminal> newTerminals = getTerminals(newElement);
3105 if (!oldTerminals.equals(newTerminals)) {
3106 // Okay, there are differences in the terminals. Need to fix
3107 // the TerminalKeyOf hint values to use the new terminal
3108 // instances when correspondences exist.
3110 // If there is no correspondence for an old terminal, we
3111 // are simply forced to remove the hints related to this
3114 Map<Terminal, Terminal> newTerminalMap = new HashMap<Terminal, Terminal>(newTerminals.size());
3115 for (Terminal t : newTerminals) {
3116 newTerminalMap.put(t, t);
3119 for (Map.Entry<TerminalKeyOf, Object> entry : mappedElement.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3120 TerminalKeyOf key = entry.getKey();
3121 Connection c = (Connection) entry.getValue();
3122 if (c.node == mappedElement) {
3123 Terminal newTerminal = newTerminalMap.get(c.terminal);
3124 if (newTerminal != null) {
3125 c = new Connection(c.edge, c.end, c.node, newTerminal);
3126 ((Element) c.edge).setHintWithoutNotification(EndKeyOf.get(c.end), c);
3128 mappedElement.removeHintWithoutNotification(key);
3134 updateMappedElement(mappedElement, newElement);
3135 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3140 ElementUpdater connectionUpdater(final Object data, final IElement newElement) {
3141 return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateConnection", newElement) {
3143 public void forMappedElement(Element mappedElement) {
3144 if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
3145 System.out.println("running connection updater: " + this + " - new element: " + newElement
3146 + " with data " + data);
3148 // This is kept up-to-date by GDS, make sure not to overwrite it
3149 // from the mapped element.
3150 newElement.removeHint(ElementHints.KEY_CONNECTION_ENTITY);
3152 updateMappedElement(mappedElement, newElement);
3153 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3158 ElementUpdater edgeUpdater(final Object data, final IElement newElement) {
3159 return new ElementUpdater(DIAGRAM_UPDATE_EDGE_PRIORITY, "updateEdge", newElement) {
3161 public void forMappedElement(Element mappedElement) {
3162 if (DebugPolicy.DEBUG_EDGE_UPDATE)
3163 System.out.println("running edge updater: " + this + " - new element: " + newElement
3164 + " with data " + data);
3166 updateMappedElement(mappedElement, newElement);
3167 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3172 ElementUpdater routeGraphConnectionUpdater(final Object data, final IElement newElement, final Set<Object> dirtyNodes) {
3173 return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateRouteGraphConnection", newElement) {
3175 public void forMappedElement(Element mappedElement) {
3176 if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
3177 System.out.println("running route graph connection updater: " + this + " - new element: " + newElement
3178 + " with data " + data);
3180 // Remove all TerminalKeyOf hints from nodes that were
3181 // previously connected to the connection (mappedElement)
3182 // before updating mappedElement with new topology information.
3184 for (Object dirtyNodeObject : dirtyNodes) {
3185 Element dirtyNode = (Element) getMappedElement(dirtyNodeObject);
3186 if (dirtyNode == null)
3188 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
3189 System.out.println("preparing node with dirty connectivity: " + dirtyNode);
3191 for (Map.Entry<TerminalKeyOf, Object> entry : dirtyNode.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3192 Connection conn = (Connection) entry.getValue();
3193 Object connectionNode = conn.edge.getHint(ElementHints.KEY_OBJECT);
3194 if (data.equals(connectionNode)) {
3195 dirtyNode.removeHintWithoutNotification(entry.getKey());
3200 // Update connection information, including topology
3201 updateMappedElement(mappedElement, newElement);
3203 // Reinstall TerminalKeyOf hints into nodes that are now connected
3204 // to mappedElement to keep diagram run-time model properly in sync
3205 // with the database.
3206 updateDirtyRouteGraphConnection(mappedElement, null);
3208 // TODO: should mark dirty nodes' scene graph dirty ?
3210 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3216 * Copies hints from <code>newElement</code> to <code>mappedElement</code>
3217 * asserting some validity conditions at the same time.
3219 * @param mappedElement
3222 static void updateMappedElement(Element mappedElement, IElement newElement) {
3223 if (mappedElement == newElement)
3224 // Can't update anything if the two elements are the same.
3227 ElementClass oldClass = mappedElement.getElementClass();
3228 ElementClass newClass = newElement.getElementClass();
3230 Object mappedData = mappedElement.getHint(ElementHints.KEY_OBJECT);
3231 Object newData = newElement.getHint(ElementHints.KEY_OBJECT);
3233 assert mappedData != null;
3234 assert newData != null;
3235 assert mappedData.equals(newData);
3237 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3238 System.out.println("Updating mapped element, setting hints\n from: " + newElement + "\n into: " + mappedElement);
3241 // TODO: consider if this equals check is a waste of time or does it pay
3242 // off due to having to reinitialize per-class caches for the new
3243 // ElementClass that are constructed on the fly?
3244 if (!newClass.equals(oldClass)) {
3245 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3246 System.out.println(" old element class: " + oldClass);
3247 System.out.println(" new element class: " + newClass);
3249 mappedElement.setElementClass(newClass);
3252 // Tuukka@2010-02-19: replaced with notifications for making
3253 // the graph synchronizer more transparent to the client.
3255 // Hint notifications will not work when this is used.
3256 //mappedElement.setHintsWithoutNotification(newElement.getHints());
3258 Map<DiscardableKey, Object> discardableHints = mappedElement.getHintsOfClass(DiscardableKey.class);
3260 // Set all hints from newElement to mappedElement.
3261 // Leave any hints in mappedElement but not in newElement as is.
3262 Map<Key, Object> hints = newElement.getHints();
3263 Map<Key, Object> newHints = new HashMap<Key, Object>();
3264 for (Map.Entry<Key, Object> entry : hints.entrySet()) {
3265 Key key = entry.getKey();
3266 Object newValue = entry.getValue();
3267 Object oldValue = mappedElement.getHint(key);
3268 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE_DETAIL) {
3269 System.out.println(" hint " + key + " compare values: " + oldValue + " -> " + newValue);
3271 if (!newValue.equals(oldValue)) {
3272 newHints.put(key, newValue);
3273 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3274 System.out.format(" %-42s : %64s -> %-64s\n", key, oldValue, newValue);
3277 // If the hint value has not changed but the hint still exists
3278 // we don't need to discard it even if it is considered
3280 discardableHints.remove(key);
3284 // Set all hints at once and send notifications after setting the values.
3285 if (!discardableHints.isEmpty()) {
3286 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE)
3287 System.out.println("Discarding " + discardableHints.size() + " discardable hints:\n " + discardableHints);
3288 mappedElement.removeHints(discardableHints.keySet());
3290 if (!newHints.isEmpty()) {
3291 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3292 System.out.println("Updating mapped element, setting new hints:\n\t"
3293 + EString.implode(newHints.entrySet(), "\n\t") + "\nto replace old hints\n\t"
3294 + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
3296 mappedElement.setHints(newHints);
3298 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3299 System.out.println("All hints after update:\n\t"
3300 + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
3304 class TransactionListener extends SessionEventListenerAdapter {
3307 public void writeTransactionStarted() {
3308 startTime = System.nanoTime();
3309 if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
3310 System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionStarted");
3311 inWriteTransaction.set(true);
3314 public void writeTransactionFinished() {
3315 long endTime = System.nanoTime();
3316 if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
3317 System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionFinished: " + (endTime - startTime)*1e-6 + " ms");
3318 inWriteTransaction.set(false);
3319 scheduleGraphUpdates();
3323 Object graphUpdateLock = new Object();
3324 TransactionListener sessionListener = null;
3325 AtomicBoolean inWriteTransaction = new AtomicBoolean(false);
3326 AtomicBoolean graphUpdateRequestScheduled = new AtomicBoolean(false);
3327 List<GraphUpdateReactor> queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();
3329 private void offerGraphUpdate(GraphUpdateReactor update) {
3330 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3331 System.out.println("offerGraphUpdate: " + update);
3332 boolean inWrite = inWriteTransaction.get();
3333 synchronized (graphUpdateLock) {
3334 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3335 System.out.println("queueing graph update: " + update);
3336 queuedGraphUpdates.add(update);
3339 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3340 System.out.println("scheduling queued graph update immediately: " + update);
3341 scheduleGraphUpdates();
3345 private Collection<GraphUpdateReactor> scrubGraphUpdates() {
3346 synchronized (graphUpdateLock) {
3347 if (queuedGraphUpdates.isEmpty())
3348 return Collections.emptyList();
3349 final List<GraphUpdateReactor> updates = queuedGraphUpdates;
3350 queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();
3355 private void scheduleGraphUpdates() {
3356 synchronized (graphUpdateLock) {
3357 if (queuedGraphUpdates.isEmpty())
3359 if (!graphUpdateRequestScheduled.compareAndSet(false, true))
3363 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3364 System.out.println("scheduling " + queuedGraphUpdates.size() + " queued graph updates with ");
3366 session.asyncRequest(new ReadRequest() {
3368 public void run(final ReadGraph graph) throws DatabaseException {
3369 Collection<GraphUpdateReactor> updates;
3370 synchronized (graphUpdateLock) {
3371 graphUpdateRequestScheduled.set(false);
3372 updates = scrubGraphUpdates();
3375 if (!GraphToDiagramSynchronizer.this.isAlive())
3378 processGraphUpdates(graph, updates);
3380 }, new ProcedureAdapter<Object>() {
3382 public void exception(Throwable t) {
3388 private void processGraphUpdates(ReadGraph graph, final Collection<GraphUpdateReactor> graphUpdates)
3389 throws DatabaseException {
3390 final List<DiagramUpdater> diagramUpdates = new ArrayList<DiagramUpdater>(graphUpdates.size());
3392 // Run GraphUpdaters and gather DiagramUpdaters.
3393 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3394 System.out.println("Running GRAPH updates: " + graphUpdates);
3395 for (GraphUpdateReactor graphUpdate : graphUpdates) {
3396 DiagramUpdater diagramUpdate = graphUpdate.graphUpdate(graph);
3397 if (diagramUpdate != null) {
3398 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3399 System.out.println(graphUpdate + " => " + diagramUpdate);
3400 diagramUpdates.add(diagramUpdate);
3404 if (diagramUpdates.isEmpty())
3407 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3408 System.out.println("Diagram updates: " + diagramUpdates);
3409 Collections.sort(diagramUpdates, DiagramUpdater.DIAGRAM_UPDATER_COMPARATOR);
3410 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3411 System.out.println("Sorted diagram updates: " + diagramUpdates);
3413 ThreadUtils.asyncExec(canvas.getThreadAccess(), new StateRunnable() {
3416 if (GraphToDiagramSynchronizer.this.isAlive() && getState() != State.DISPOSED)
3417 safeRunInState(State.UPDATING_DIAGRAM, this);
3421 public void execute() throws InvocationTargetException {
3422 // Block out diagram write transactions.
3423 DiagramUtils.inDiagramTransaction(diagram, TransactionType.READ, new Runnable() {
3426 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3427 System.out.println("Running DIAGRAM updates: " + diagramUpdates);
3428 for (DiagramUpdater update : diagramUpdates) {
3429 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3430 System.out.println("Running DIAGRAM update: " + update);
3441 private void attachSessionListener(Session session) {
3442 SessionEventSupport support = session.peekService(SessionEventSupport.class);
3443 if (support != null) {
3444 sessionListener = new TransactionListener();
3445 support.addListener(sessionListener);
3449 private void detachSessionListener() {
3450 if (sessionListener != null) {
3451 session.getService(SessionEventSupport.class).removeListener(sessionListener);
3452 sessionListener = null;
3457 // ------------------------------------------------------------------------
3458 // GRAPH TO DIAGRAM SYNCHRONIZATION LOGIC END
3459 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3461 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3462 // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
3463 // This does not try to synchronize anything back into the graph.
3464 // ------------------------------------------------------------------------
3466 IHintListener elementHintValidator = new HintListenerAdapter() {
3468 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
3469 if (!(sender instanceof Element))
3470 throw new IllegalStateException("invalid sender: " + sender);
3471 Element e = (Element) sender;
3472 if (newValue != null) {
3473 if (key instanceof TerminalKeyOf) {
3474 Connection c = (Connection) newValue;
3476 throw new IllegalStateException("TerminalKeyOf hint of node " + e + " refers to a different node " + c.node + ". Should be the same.");
3477 Object edgeObject = ElementUtils.getObject(c.edge);
3478 if (!(edgeObject instanceof EdgeResource))
3479 throw new IllegalStateException("EndKeyOf hint of edge " + c.edge + " refers contains an invalid object: " + edgeObject);
3480 } else if (key instanceof EndKeyOf) {
3481 Connection c = (Connection) newValue;
3483 throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers to a different edge " + c.edge + ". Should be the same.");
3484 Object edgeObject = ElementUtils.getObject(c.edge);
3485 if (!(edgeObject instanceof EdgeResource))
3486 throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers contains an invalid object: " + edgeObject);
3492 class DiagramListener implements CompositionListener, CompositionVetoListener {
3494 public boolean beforeElementAdded(IDiagram d, IElement e) {
3495 // Make sure that MutatedElements NEVER get added to the diagram.
3497 if (!(e instanceof Element)) {
3498 // THIS IS NOT GOOD!
3499 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"));
3500 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.");
3504 // Perform sanity checks that might veto the element addition.
3505 boolean pass = true;
3507 // Check that all elements added to the diagram are adaptable to Resource
3508 ElementClass ec = e.getElementClass();
3509 Resource resource = ElementUtils.adapt(ec, Resource.class);
3510 if (resource == null) {
3512 LOGGER.error("", new Exception("Attempted to add an element to the diagram that is not adaptable to Resource: " + e + ", class: " + ec));
3515 // Sanity check connection hints
3516 for (Map.Entry<TerminalKeyOf, Object> entry : e.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3517 Connection c = (Connection) entry.getValue();
3518 Object edgeObject = ElementUtils.getObject(c.edge);
3520 System.err.println("Invalid node in TerminalKeyOf hint: " + entry.getKey() + "=" + entry.getValue());
3521 System.err.println("\tconnection.edge=" + c.edge);
3522 System.err.println("\tconnection.node=" + c.node);
3523 System.err.println("\tconnection.end=" + c.end);
3524 System.err.println("\telement=" + e);
3525 System.err.println("\telement class=" + e.getElementClass());
3528 if (!(edgeObject instanceof EdgeResource)) {
3529 System.err.println("Invalid object in TerminalKeyOf hint edge: " + entry.getKey() + "=" + entry.getValue());
3530 System.err.println("\tconnection.edge=" + c.edge);
3531 System.err.println("\tconnection.node=" + c.node);
3532 System.err.println("\tconnection.end=" + c.end);
3533 System.err.println("\telement=" + e);
3534 System.err.println("\telement class=" + e.getElementClass());
3545 public boolean beforeElementRemoved(IDiagram d, IElement e) {
3546 // Never veto diagram changes.
3551 public void onElementAdded(IDiagram d, IElement e) {
3552 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
3553 System.out.println("[" + d + "] element added: " + e);
3555 if (USE_ELEMENT_VALIDATING_LISTENERS)
3556 e.addHintListener(elementHintValidator);
3560 public void onElementRemoved(IDiagram d, IElement e) {
3561 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
3562 System.out.println("[" + d + "] element removed: " + e);
3564 if (USE_ELEMENT_VALIDATING_LISTENERS)
3565 e.removeHintListener(elementHintValidator);
3567 if (e.containsHint(KEY_REMOVE_RELATIONSHIPS))
3568 relationshipHandler.denyAll(diagram, e);
3572 DiagramListener diagramListener = new DiagramListener();
3574 static void removeNodeTopologyHints(Element node) {
3575 Set<TerminalKeyOf> terminalKeys = node.getHintsOfClass(TerminalKeyOf.class).keySet();
3576 if (!terminalKeys.isEmpty()) {
3577 removeNodeTopologyHints(node, terminalKeys);
3581 static void removeNodeTopologyHints(Element node, Collection<TerminalKeyOf> terminalKeys) {
3582 for (TerminalKeyOf key : terminalKeys) {
3583 Connection c = node.removeHintWithoutNotification(key);
3585 removeEdgeTopologyHints((Element) c.edge);
3590 static void removeEdgeTopologyHints(Element edge) {
3591 Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
3592 for (EndKeyOf key : EndKeyOf.KEYS) {
3593 Connection c = edge.removeHintWithoutNotification(key);
3595 ((Element) c.node).removeHintWithoutNotification(new TerminalKeyOf(c.terminal, edgeData, Connection.class));
3600 // ------------------------------------------------------------------------
3601 // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
3602 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3604 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3605 // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC BEGIN
3606 // ------------------------------------------------------------------------
3608 void adaptDiagramClass(AsyncReadGraph graph, Resource diagram, final AsyncProcedure<DiagramClass> procedure) {
3609 graph.forAdapted(diagram, DiagramClass.class, new AsyncProcedure<DiagramClass>() {
3611 public void exception(AsyncReadGraph graph, Throwable throwable) {
3612 procedure.exception(graph, throwable);
3616 public void execute(AsyncReadGraph graph, DiagramClass dc) {
3617 // To move TopologyImpl out of here, we need a separate
3618 // DiagramClassFactory that takes a canvas context as an argument.
3619 // DataElementMapImpl, ElementFactoryImpl and diagramLifeCycle can
3620 // safely stay here.
3621 procedure.execute(graph, dc.newClassWith(
3622 // This handler takes care of the topology of the diagram model.
3623 // It sets and fixes element hints related to describing the
3624 // connectivity of elements.
3626 // TODO: not quite sure whether this can prove itself useful or not.
3628 // This map provides a bidirectional mapping between
3629 // IElement and back-end objects.
3631 // This handler provides a facility to adapt an element class
3632 // to work properly with a diagram synchronized using this
3633 // GraphToDiagramSynchronizer.
3634 substituteElementClass,
3635 // These handlers provide a way to create simple identified
3636 // uni- and bidirectional relationships between any diagram
3637 // objects/elements.
3638 relationshipHandler));
3643 static Connection connect(IElement edge, EdgeEnd end, IElement element, Terminal terminal) {
3644 Connection c = new Connection(edge, end, element, terminal);
3646 Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
3647 if (DebugPolicy.DEBUG_CONNECTION) {
3648 System.out.println("[connect](edge=" + edge + ", edgeData=" + edgeData + ", end=" + end + ", element="
3649 + element + ", terminal=" + terminal + ")");
3652 TerminalKeyOf key = new TerminalKeyOf(terminal, edgeData, Connection.class);
3653 element.setHint(key, c);
3655 EndKeyOf key2 = EndKeyOf.get(end);
3656 edge.setHint(key2, c);
3661 static class ElementFactoryImpl implements ElementFactory {
3663 public IElement spawnNew(ElementClass clazz) {
3664 IElement e = Element.spawnNew(clazz);
3669 ElementFactoryImpl elementFactory = new ElementFactoryImpl();
3671 public static final Object FIRST_TIME = new Object() {
3673 public String toString() {
3674 return "FIRST_TIME";
3680 * A base for all listeners of graph requests performed internally by
3681 * GraphToDiagramSynchronizer.
3683 * @param <T> type of stored data element
3684 * @param <Result> query result type
3686 abstract class BaseListener<T, Result> implements AsyncListener<Result> {
3688 protected final T data;
3690 private Object oldResult = FIRST_TIME;
3692 protected boolean disposed = false;
3694 final ICanvasContext canvas;
3696 public BaseListener(T data) {
3697 this.canvas = GraphToDiagramSynchronizer.this.canvas;
3702 public void exception(AsyncReadGraph graph, Throwable throwable) {
3703 // Exceptions are always expected to mean that the listener should
3704 // be considered disposed once a query fails.
3708 abstract void execute(AsyncReadGraph graph, Object oldResult, Object newResult);
3711 public void execute(AsyncReadGraph graph, Result result) {
3712 if (DebugPolicy.DEBUG_LISTENER_BASE)
3713 System.out.println("BaseListener: " + result);
3716 if (DebugPolicy.DEBUG_LISTENER_BASE)
3717 System.out.println("BaseListener: execute invoked although listener is disposed!");
3721 // A null result will permanently mark this listener disposed!
3722 if (result == null) {
3724 if (DebugPolicy.DEBUG_LISTENER_BASE)
3725 System.out.println(this + " null result, listener marked disposed");
3728 if (oldResult == FIRST_TIME) {
3730 if (DebugPolicy.DEBUG_LISTENER_BASE)
3731 System.out.println(this + " first result computed: " + result);
3733 if (DebugPolicy.DEBUG_LISTENER_BASE)
3734 System.out.println(this + " result changed from '" + oldResult + "' to '" + result + "'");
3736 execute(graph, oldResult, result);
3744 public boolean isDisposed() {
3748 boolean alive = isAlive();
3749 //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", isAlive=" + alive);
3752 // If a mapping no longer exists for this element, dispose of this
3754 //IElement e = getMappedElement(resource);
3755 //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", element=" + e);
3761 // protected void finalize() throws Throwable {
3762 // System.out.println("finalize listener: " + this);
3763 // super.finalize();
3767 class DiagramClassRequest extends BaseRequest2<Resource, DiagramClass> {
3768 public DiagramClassRequest(Resource resource) {
3769 super(GraphToDiagramSynchronizer.this.canvas, resource);
3773 public void perform(AsyncReadGraph graph, AsyncProcedure<DiagramClass> procedure) {
3774 adaptDiagramClass(graph, data, procedure);
3778 public class DiagramContentListener extends BaseListener<Resource, DiagramContents> {
3780 public DiagramContentListener(Resource resource) {
3785 public void execute(final AsyncReadGraph graph, Object oldResult, Object newResult) {
3786 final DiagramContents newContent = (newResult == null) ? new DiagramContents()
3787 : (DiagramContents) newResult;
3789 // diagramGraphUpdater is called synchronously during
3790 // loading. The first result will not get updated through
3791 // this listener but through loadDiagram.
3793 if (DebugPolicy.DISABLE_DIAGRAM_UPDATES) {
3794 System.out.println("Skipped diagram content update: " + newResult);
3798 if (DebugPolicy.DEBUG_DIAGRAM_LISTENER)
3799 System.out.println("diagram contents changed: " + oldResult + " => " + newResult);
3801 offerGraphUpdate( diagramGraphUpdater(newContent) );
3805 public boolean isDisposed() {
3810 public void exception(AsyncReadGraph graph, Throwable t) {
3811 super.exception(graph, t);
3812 error("DiagramContentRequest failed", t);
3816 // ------------------------------------------------------------------------
3817 // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC END
3818 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3820 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3821 // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER BEGIN
3822 // ------------------------------------------------------------------------
3824 static class TopologyImpl implements Topology {
3827 public Connection getConnection(IElement edge, EdgeEnd end) {
3828 Key key = EndKeyOf.get(end);
3829 Connection c = edge.getHint(key);
3836 public void getConnections(IElement node, Terminal terminal, Collection<Connection> connections) {
3837 // IDiagram d = ElementUtils.getDiagram(node);
3838 for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3839 // First check that the terminal matches.
3840 TerminalKeyOf key = entry.getKey();
3841 if (!key.getTerminal().equals(terminal))
3844 Connection c = (Connection) entry.getValue();
3852 public void connect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
3853 if (node != null && terminal != null)
3854 GraphToDiagramSynchronizer.connect(edge, end, node, terminal);
3856 if (DebugPolicy.DEBUG_CONNECTION) {
3857 if (end == EdgeEnd.Begin)
3858 System.out.println("Connection started from: " + edge + ", " + end + ", " + node + ", " + terminal);
3860 System.out.println("Creating connection to: " + edge + ", " + end + ", " + node + ", " + terminal);
3865 public void disconnect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
3866 EndKeyOf edgeKey = EndKeyOf.get(end);
3867 Connection c = edge.getHint(edgeKey);
3869 throw new UnsupportedOperationException("cannot disconnect, no Connection in edge " + edge);
3871 for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3872 Connection cc = (Connection) entry.getValue();
3874 node.removeHint(entry.getKey());
3875 edge.removeHint(edgeKey);
3880 throw new UnsupportedOperationException("cannot disconnect, no connection between found between edge "
3881 + edge + " and node " + node);
3885 Topology diagramTopology = new TopologyImpl();
3887 // ------------------------------------------------------------------------
3888 // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER END
3889 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3891 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3892 // DIAGRAM OBJECT RELATIONSHIP HANDLER BEGIN
3893 // ------------------------------------------------------------------------
3895 RelationshipHandler relationshipHandler = new RelationshipHandler() {
3897 AssociativeMap map = new AssociativeMap(Associativity.of(true, false, false));
3899 Object getPossibleObjectOrElement(Object o) {
3900 if (o instanceof IElement) {
3901 IElement e = (IElement) o;
3902 Object oo = e.getHint(ElementHints.KEY_OBJECT);
3903 return oo != null ? oo : e;
3908 IElement getElement(Object o) {
3909 if (o instanceof IElement)
3910 return (IElement) o;
3911 return getMappedElement(o);
3915 public void claim(IDiagram diagram, Object subject,
3916 Relationship predicate, Object object) {
3917 Object sd = getPossibleObjectOrElement(subject);
3918 Object od = getPossibleObjectOrElement(object);
3920 Collection<Tuple> ts = null;
3921 Relationship inverse = predicate.getInverse();
3922 if (inverse != null)
3923 ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od, inverse, sd));
3925 ts = Collections.singletonList(new Tuple(sd, predicate, od));
3927 synchronized (this) {
3931 if (DebugPolicy.DEBUG_RELATIONSHIP) {
3932 new Exception().printStackTrace();
3933 System.out.println("Claimed relationships:");
3935 System.out.println("\t" + t);
3939 private void doDeny(IDiagram diagram, Object subject,
3940 Relationship predicate, Object object) {
3941 Object sd = getPossibleObjectOrElement(subject);
3942 Object od = getPossibleObjectOrElement(object);
3943 if (sd == subject || od == object) {
3945 .println("WARNING: denying relationship '"
3947 + "' between diagram element(s), not back-end object(s): "
3948 + sd + " -> " + od);
3951 Collection<Tuple> ts = null;
3952 Relationship inverse = predicate.getInverse();
3953 if (inverse != null)
3954 ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od,
3957 ts = Collections.singleton(new Tuple(sd, predicate, od));
3959 synchronized (this) {
3963 if (DebugPolicy.DEBUG_RELATIONSHIP) {
3964 new Exception().printStackTrace();
3965 System.out.println("Denied relationships:");
3967 System.out.println("\t" + t);
3972 public void deny(IDiagram diagram, Object subject,
3973 Relationship predicate, Object object) {
3974 synchronized (this) {
3975 doDeny(diagram, subject, predicate, object);
3980 public void deny(IDiagram diagram, Relation relation) {
3981 synchronized (this) {
3982 doDeny(diagram, relation.getSubject(), relation
3983 .getRelationship(), relation.getObject());
3988 public void denyAll(IDiagram diagram, Object element) {
3989 synchronized (this) {
3990 for (Relation relation : getRelations(diagram, element, null)) {
3991 doDeny(diagram, relation.getSubject(), relation
3992 .getRelationship(), relation.getObject());
3998 public Collection<Relation> getRelations(IDiagram diagram,
3999 Object element, Collection<Relation> result) {
4000 if (DebugPolicy.DEBUG_GET_RELATIONSHIP)
4001 System.out.println("getRelations(" + element + ")");
4002 Object e = getPossibleObjectOrElement(element);
4004 Collection<Tuple> tuples = null;
4005 synchronized (this) {
4006 tuples = map.get(new Tuple(e, null, null), null);
4009 if (DebugPolicy.DEBUG_GET_RELATIONSHIP) {
4010 System.out.println("Result size: " + tuples.size());
4011 for (Tuple t : tuples)
4012 System.out.println("\t" + t);
4015 if (tuples.isEmpty())
4016 return Collections.emptyList();
4018 result = new ArrayList<Relation>(tuples.size());
4019 for (Tuple t : tuples) {
4020 Object obj = t.getField(2);
4021 IElement el = getElement(obj);
4022 Relationship r = (Relationship) t.getField(1);
4023 result.add(new Relation(element, r, el != null ? el : obj));
4030 // ------------------------------------------------------------------------
4031 // DIAGRAM ELEMENT RELATIONSHIP HANDLER END
4032 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>