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.VirtualGraph;
44 import org.simantics.db.common.ResourceArray;
45 import org.simantics.db.common.exception.DebugException;
46 import org.simantics.db.common.procedure.adapter.AsyncProcedureAdapter;
47 import org.simantics.db.common.procedure.adapter.CacheListener;
48 import org.simantics.db.common.procedure.adapter.ListenerSupport;
49 import org.simantics.db.common.procedure.adapter.ProcedureAdapter;
50 import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;
51 import org.simantics.db.common.request.AsyncReadRequest;
52 import org.simantics.db.common.request.ReadRequest;
53 import org.simantics.db.common.session.SessionEventListenerAdapter;
54 import org.simantics.db.common.utils.NameUtils;
55 import org.simantics.db.exception.CancelTransactionException;
56 import org.simantics.db.exception.DatabaseException;
57 import org.simantics.db.exception.NoSingleResultException;
58 import org.simantics.db.exception.ServiceException;
59 import org.simantics.db.procedure.AsyncListener;
60 import org.simantics.db.procedure.AsyncProcedure;
61 import org.simantics.db.procedure.Listener;
62 import org.simantics.db.procedure.Procedure;
63 import org.simantics.db.request.Read;
64 import org.simantics.db.service.SessionEventSupport;
65 import org.simantics.diagram.connection.ConnectionSegmentEnd;
66 import org.simantics.diagram.content.Change;
67 import org.simantics.diagram.content.ConnectionUtil;
68 import org.simantics.diagram.content.DesignatedTerminal;
69 import org.simantics.diagram.content.DiagramContentChanges;
70 import org.simantics.diagram.content.DiagramContents;
71 import org.simantics.diagram.content.EdgeResource;
72 import org.simantics.diagram.content.ResourceTerminal;
73 import org.simantics.diagram.internal.DebugPolicy;
74 import org.simantics.diagram.internal.timing.GTask;
75 import org.simantics.diagram.internal.timing.Timing;
76 import org.simantics.diagram.profile.ProfileKeys;
77 import org.simantics.diagram.synchronization.CollectingModificationQueue;
78 import org.simantics.diagram.synchronization.CompositeModification;
79 import org.simantics.diagram.synchronization.CopyAdvisor;
80 import org.simantics.diagram.synchronization.ErrorHandler;
81 import org.simantics.diagram.synchronization.IHintSynchronizer;
82 import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;
83 import org.simantics.diagram.synchronization.IModification;
84 import org.simantics.diagram.synchronization.LogErrorHandler;
85 import org.simantics.diagram.synchronization.ModificationAdapter;
86 import org.simantics.diagram.synchronization.SynchronizationHints;
87 import org.simantics.diagram.synchronization.graph.AddElement;
88 import org.simantics.diagram.synchronization.graph.BasicResources;
89 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
90 import org.simantics.diagram.synchronization.graph.ElementLoader;
91 import org.simantics.diagram.synchronization.graph.ElementReorder;
92 import org.simantics.diagram.synchronization.graph.ElementWriter;
93 import org.simantics.diagram.synchronization.graph.GraphSynchronizationContext;
94 import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;
95 import org.simantics.diagram.synchronization.graph.ModificationQueue;
96 import org.simantics.diagram.synchronization.graph.TagChange;
97 import org.simantics.diagram.synchronization.graph.TransformElement;
98 import org.simantics.diagram.synchronization.graph.layer.GraphLayer;
99 import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;
100 import org.simantics.diagram.ui.DiagramModelHints;
101 import org.simantics.g2d.canvas.Hints;
102 import org.simantics.g2d.canvas.ICanvasContext;
103 import org.simantics.g2d.connection.ConnectionEntity;
104 import org.simantics.g2d.connection.EndKeyOf;
105 import org.simantics.g2d.connection.TerminalKeyOf;
106 import org.simantics.g2d.diagram.DiagramClass;
107 import org.simantics.g2d.diagram.DiagramHints;
108 import org.simantics.g2d.diagram.DiagramMutator;
109 import org.simantics.g2d.diagram.DiagramUtils;
110 import org.simantics.g2d.diagram.IDiagram;
111 import org.simantics.g2d.diagram.IDiagram.CompositionListener;
112 import org.simantics.g2d.diagram.IDiagram.CompositionVetoListener;
113 import org.simantics.g2d.diagram.handler.DataElementMap;
114 import org.simantics.g2d.diagram.handler.ElementFactory;
115 import org.simantics.g2d.diagram.handler.Relationship;
116 import org.simantics.g2d.diagram.handler.RelationshipHandler;
117 import org.simantics.g2d.diagram.handler.SubstituteElementClass;
118 import org.simantics.g2d.diagram.handler.Topology;
119 import org.simantics.g2d.diagram.handler.Topology.Connection;
120 import org.simantics.g2d.diagram.handler.Topology.Terminal;
121 import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType;
122 import org.simantics.g2d.diagram.impl.Diagram;
123 import org.simantics.g2d.diagram.participant.ElementPainter;
124 import org.simantics.g2d.element.ElementClass;
125 import org.simantics.g2d.element.ElementHints;
126 import org.simantics.g2d.element.ElementHints.DiscardableKey;
127 import org.simantics.g2d.element.ElementUtils;
128 import org.simantics.g2d.element.IElement;
129 import org.simantics.g2d.element.IElementClassProvider;
130 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
131 import org.simantics.g2d.element.handler.ElementHandler;
132 import org.simantics.g2d.element.handler.ElementLayerListener;
133 import org.simantics.g2d.element.handler.TerminalTopology;
134 import org.simantics.g2d.element.impl.Element;
135 import org.simantics.g2d.layers.ILayer;
136 import org.simantics.g2d.layers.ILayersEditor;
137 import org.simantics.g2d.routing.RouterFactory;
138 import org.simantics.scenegraph.INode;
139 import org.simantics.scenegraph.profile.DataNodeConstants;
140 import org.simantics.scenegraph.profile.DataNodeMap;
141 import org.simantics.scenegraph.profile.common.ProfileObserver;
142 import org.simantics.scl.runtime.tuple.Tuple3;
143 import org.simantics.structural2.modelingRules.IModelingRules;
144 import org.simantics.utils.datastructures.ArrayMap;
145 import org.simantics.utils.datastructures.MapSet;
146 import org.simantics.utils.datastructures.Pair;
147 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
148 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
149 import org.simantics.utils.datastructures.hints.IHintContext.Key;
150 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
151 import org.simantics.utils.datastructures.hints.IHintListener;
152 import org.simantics.utils.datastructures.hints.IHintObservable;
153 import org.simantics.utils.datastructures.map.AssociativeMap;
154 import org.simantics.utils.datastructures.map.Associativity;
155 import org.simantics.utils.datastructures.map.Tuple;
156 import org.simantics.utils.strings.EString;
157 import org.simantics.utils.threads.ThreadUtils;
158 import org.simantics.utils.threads.logger.ITask;
159 import org.simantics.utils.threads.logger.ThreadLogger;
160 import org.slf4j.Logger;
161 import org.slf4j.LoggerFactory;
163 import gnu.trove.map.hash.TObjectIntHashMap;
164 import gnu.trove.set.hash.THashSet;
167 * This class loads a diagram contained in the graph database into the runtime
168 * diagram model and synchronizes changes in the graph into the run-time
169 * diagram. Any modifications to the graph model will be reflected to the
170 * run-time diagram model. Hence the name GraphToDiagramSynchronizer.
173 * This class does not in itself support modification of the graph diagram
174 * model. This manipulation is meant to be performed through a
175 * {@link DiagramMutator} implementation that is installed into the diagram
176 * using the {@link DiagramHints#KEY_MUTATOR} hint key.
178 * This implementations is built to only support diagrams defined in the graph
179 * model as indicated in <a
180 * href="https://www.simantics.org/wiki/index.php/Org.simantics.diagram" >this
184 * The synchronizer in itself is an {@link IDiagramLoader} which means that it
185 * can be used for loading a diagram from the graph. In order for the
186 * synchronizer to keep tracking the graph diagram model for changes the diagram
187 * must be loaded with it. The tracking is implemented using graph database
188 * queries. If you just want to load the diagram but detach it from the
189 * synchronizer's tracking mechanisms, all you need to do is to dispose the
190 * synchronizer after loading the diagram.
193 * This class guarantees that a single diagram element (IElement) representing a
194 * single back-end object ({@link ElementHints#KEY_OBJECT}) will stay the same
195 * object for the same back-end object.
198 * TODO: Currently it just happens that {@link GraphToDiagramSynchronizer}
199 * contains {@link DefaultDiagramMutator} which depends on some internal details
200 * of {@link GraphToDiagramSynchronizer} but it should be moved out of here by
201 * introducing new interfaces.
203 * <h2>Basic usage example</h2>
205 * This example shows how to initialize {@link GraphToDiagramSynchronizer} for a
206 * specified {@link ICanvasContext} and load a diagram from a specified diagram
207 * resource in the graph.
210 * IDiagram loadDiagram(final ICanvasContext canvasContext, RequestProcessor processor, Resource diagramResource,
211 * ResourceArray structuralPath) throws DatabaseException {
212 * GraphToDiagramSynchronizer synchronizer = processor.syncRequest(new Read<GraphToDiagramSynchronizer>() {
213 * public GraphToDiagramSynchronizer perform(ReadGraph graph) throws DatabaseException {
214 * return new GraphToDiagramSynchronizer(graph, canvasContext, createElementClassProvider(graph));
217 * IDiagram d = requestProcessor
218 * .syncRequest(new DiagramLoadQuery(diagramResource, structuralPath, synchronizer, null));
222 * protected IElementClassProvider createElementClassProvider(ReadGraph graph) {
223 * DiagramResource dr = DiagramResource.getInstance(graph);
224 * return ElementClassProviders.mappedProvider(ElementClasses.CONNECTION, DefaultConnectionClassFactory.CLASS
225 * .newClassWith(new ResourceAdapterImpl(dr.Connection)), ElementClasses.FLAG, FlagClassFactory
226 * .createFlagClass(dr.Flag));
231 * TODO: make GraphToDiagramSynchronizer a canvas participant to make it more
232 * uniform with the rest of the canvas system. This does not mean that G2DS must
233 * be attached to an ICanvasContext in order to be used, rather that it can be
237 * TODO: test that detaching the synchronizer via {@link #dispose()} actually
240 * TODO: remove {@link DefaultDiagramMutator} and all {@link DiagramMutator}
244 * TODO: diagram connection loading has no listener
246 * @author Tuukka Lehtonen
248 * @see GraphElementClassFactory
249 * @see GraphElementFactory
252 * @see IHintSynchronizer
255 public class GraphToDiagramSynchronizer extends AbstractDisposable implements IDiagramLoader, IModifiableSynchronizationContext {
257 private static final Logger LOGGER = LoggerFactory.getLogger(GraphToDiagramSynchronizer.class);
260 * Controls whether the class adds hint listeners to each diagram element
261 * that try to perform basic sanity checks on changes happening in element
262 * hints. Having this will immediately inform you of bugs that corrupt the
263 * diagram model within the element hints in some way.
265 private static final boolean USE_ELEMENT_VALIDATING_LISTENERS = false;
268 * These keys are used to hang on to Connection instances of edges that will
269 * be later installed as EndKeyOf/TerminalKeyOf hints into the loaded
270 * element during the "graph to diagram update transaction".
272 private static final Key KEY_CONNECTION_BEGIN_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_BEGIN_PLACEHOLDER");
273 private static final Key KEY_CONNECTION_END_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_END_PLACEHOLDER");
276 * Stored into an edge node during connection edge requests using the
277 * KEY_CONNECTION_BEGIN_PLACEHOLDER and KEY_CONNECTION_END_PLACEHOLDER keys.
279 static class PlaceholderConnection {
280 public final EdgeEnd end;
281 public final Object node;
282 public final Terminal terminal;
283 public PlaceholderConnection(EdgeEnd end, Object node, Terminal terminal) {
286 this.terminal = terminal;
291 * Indicates to the diagram CompositionListener of this synchronizer that is
292 * should deny all relationships for the element this hint is attached to.
294 private static final Key KEY_REMOVE_RELATIONSHIPS = new KeyOf(Boolean.class, "REMOVE_RELATIONSHIPS");
296 static ErrorHandler errorHandler = LogErrorHandler.INSTANCE;
299 * The canvas context which is being synchronized with the graph. Received
300 * during construction.
303 * Is not nulled during disposal of this class since internal listener's
304 * life-cycles depend on canvas.isDisposed.
306 ICanvasContext canvas;
309 * The session used by this synchronizer. Received during construction.
314 * Locked while updating diagram contents from the graph.
316 ReentrantLock diagramUpdateLock = new ReentrantLock();
318 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
319 // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING BEGIN
320 // ------------------------------------------------------------------------
323 * Holds a GraphToDiagramUpdater instance while a diagram content update is
324 * currently in progress.
327 * Basically this is a hack solution to the problem of properly finding
328 * newly added Resource<->IElement mappings while loading the diagram
329 * contents. See {@link DataElementMapImpl} for why this is necessary.
331 GraphToDiagramUpdater currentUpdater = null;
334 * A map from data objects to elements. Elements should already contain the
335 * data objects as {@link ElementHints#KEY_OBJECT} hints.
337 ConcurrentMap<Object, IElement> dataElement = new ConcurrentHashMap<Object, IElement>();
340 * Temporary structure for single-threaded use in #{@link DiagramUpdater}.
342 Collection<Connection> tempConnections = new ArrayList<Connection>();
345 * A dummy class of which an instance will be given to each new edge element
346 * to make {@link TerminalKeyOf} keys unique for each edge.
348 static class TransientElementObject {
350 public String toString() {
351 return "MUTATOR GENERATED (hash=" + System.identityHashCode(this) + ")";
355 private static class ConnectionChildren {
356 public Set<IElement> branchPoints;
357 public Set<IElement> segments;
359 public ConnectionChildren(Set<IElement> branchPoints, Set<IElement> segments) {
360 this.branchPoints = branchPoints;
361 this.segments = segments;
365 ListenerSupport canvasListenerSupport = new ListenerSupport() {
367 public void exception(Throwable t) {
372 public boolean isDisposed() {
373 return !isAlive() || canvas.isDisposed();
378 * @see ElementHints#KEY_CONNECTION_ENTITY
380 class ConnectionEntityImpl implements ConnectionEntity {
383 * The connection instance resource in the graph backend.
385 * May be <code>null</code> if the connection has not been synchronized
391 * The connection type resource in the graph backend.
393 * May be <code>null</code> if the connection has not been synchronized
396 Resource connectionType;
399 * The connection entity element which is a part of the diagram.
401 IElement connectionElement;
404 * List of backend-synchronized branch points that are part of this
407 Collection<Resource> branchPoints = Collections.emptyList();
410 * List of backend-synchronized edges that are part of this connection.
412 Collection<EdgeResource> segments = Collections.emptyList();
414 Set<Object> removedBranchPoints = new HashSet<Object>(4);
416 Set<Object> removedSegments = new HashSet<Object>(4);
419 * List of non-backend-synchronized branch point element that are part
420 * of this connection.
422 List<IElement> branchPointElements = new ArrayList<IElement>(1);
425 * List of non-backend-synchronized edge element that are part of this
428 List<IElement> segmentElements = new ArrayList<IElement>(2);
430 ConnectionListener listener;
432 ConnectionEntityImpl(Resource connection, Resource connectionType, IElement connectionElement) {
433 this.connection = connection;
434 this.connectionType = connectionType;
435 this.connectionElement = connectionElement;
438 ConnectionEntityImpl(Resource connectionType, IElement connectionElement) {
439 this.connectionType = connectionType;
440 this.connectionElement = connectionElement;
443 ConnectionEntityImpl(ReadGraph graph, Resource connection, IElement connectionElement)
444 throws NoSingleResultException, ServiceException {
445 this.connection = connection;
446 this.connectionType = graph.getSingleType(connection, br.DIA.Connection);
447 this.connectionElement = connectionElement;
451 public IElement getConnection() {
452 return connectionElement;
455 public Object getConnectionObject() {
459 public IElement getConnectionElement() {
460 if (connectionElement == null)
461 return getMappedConnectionElement();
462 return connectionElement;
465 private IElement getMappedConnectionElement() {
467 if (connection != null)
468 ce = getMappedElement(connection);
469 return ce == null ? connectionElement : ce;
473 Collection<IElement> segments = getSegments(null);
475 // Remove all TerminalKeyOf hints from branch points that do not
477 ArrayList<TerminalKeyOf> pruned = null;
478 for (IElement bp : getBranchPoints(null)) {
480 pruned = new ArrayList<TerminalKeyOf>(4);
482 for (Map.Entry<TerminalKeyOf, Object> entry : bp.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
483 // First check that the terminal matches.
484 Connection c = (Connection) entry.getValue();
485 if (!segments.contains(c.edge))
486 pruned.add(entry.getKey());
488 removeNodeTopologyHints((Element) bp, pruned);
492 public ConnectionChildren getConnectionChildren() {
493 Set<IElement> bps = Collections.emptySet();
494 Set<IElement> segs = Collections.emptySet();
495 if (!branchPoints.isEmpty()) {
496 bps = new HashSet<IElement>(branchPoints.size());
497 for (Resource bp : branchPoints) {
498 IElement e = getMappedElement(bp);
503 if (!segments.isEmpty()) {
504 segs = new HashSet<IElement>(segments.size());
505 for (EdgeResource seg : segments) {
506 IElement e = getMappedElement(seg);
511 return new ConnectionChildren(bps, segs);
514 public void setData(Collection<EdgeResource> segments, Collection<Resource> branchPoints) {
515 // System.out.println("setData " + segments.size());
516 this.branchPoints = branchPoints;
517 this.segments = segments;
519 // Reset the added/removed state of segments and branchpoints.
520 this.removedBranchPoints = new HashSet<Object>(4);
521 this.removedSegments = new HashSet<Object>(4);
522 this.branchPointElements = new ArrayList<IElement>(4);
523 this.segmentElements = new ArrayList<IElement>(4);
526 public void fireListener(ConnectionChildren old, ConnectionChildren current) {
527 if (listener != null) {
528 List<IElement> removed = new ArrayList<IElement>();
529 List<IElement> added = new ArrayList<IElement>();
531 for (IElement oldBp : old.branchPoints)
532 if (!current.branchPoints.contains(oldBp))
534 for (IElement oldSeg : old.segments)
535 if (!current.segments.contains(oldSeg))
538 for (IElement bp : current.branchPoints)
539 if (!old.branchPoints.contains(bp))
541 for (IElement seg : current.segments)
542 if (!old.segments.contains(seg))
545 if (!removed.isEmpty() || !added.isEmpty()) {
546 listener.connectionChanged(new ConnectionEvent(this.connectionElement, removed, added));
552 public Collection<IElement> getBranchPoints(Collection<IElement> result) {
554 result = new ArrayList<IElement>(branchPoints.size());
555 for (Resource bp : branchPoints) {
556 if (!removedBranchPoints.contains(bp)) {
557 IElement e = getMappedElement(bp);
562 result.addAll(branchPointElements);
567 public Collection<IElement> getSegments(Collection<IElement> result) {
569 result = new ArrayList<IElement>(segments.size());
570 for (EdgeResource seg : segments) {
571 if (!removedSegments.contains(seg)) {
572 IElement e = getMappedElement(seg);
577 result.addAll(segmentElements);
582 public Collection<Connection> getTerminalConnections(Collection<Connection> result) {
584 result = new ArrayList<Connection>(segments.size() * 2);
585 Set<org.simantics.utils.datastructures.Pair<IElement, Terminal>> processed = new HashSet<org.simantics.utils.datastructures.Pair<IElement, Terminal>>();
586 for (EdgeResource seg : segments) {
587 IElement edge = getMappedElement(seg);
589 for (EndKeyOf key : EndKeyOf.KEYS) {
590 Connection c = edge.getHint(key);
591 if (c != null && (c.terminal instanceof ResourceTerminal) && processed.add(Pair.make(c.node, c.terminal)))
600 public void setListener(ConnectionListener listener) {
601 this.listener = listener;
605 public String toString() {
606 return getClass().getSimpleName() + "[resource=" + connection + ", branch points=" + branchPoints
607 + ", segments=" + segments + ", connectionElement=" + connectionElement
608 + ", branch point elements=" + branchPointElements + ", segment elements=" + segmentElements
609 + ", removed branch points=" + removedBranchPoints + ", removed segments=" + removedSegments + "]";
615 * A map from connection data objects to connection entities. The connection
616 * part elements should already contain the data objects as
617 * {@link ElementHints#KEY_OBJECT} hints.
619 ConcurrentMap<Object, ConnectionEntityImpl> dataConnection = new ConcurrentHashMap<Object, ConnectionEntityImpl>();
622 void mapElementIfNew(final Object data, final IElement element) {
623 IElement mapped = getMappedElement(data);
625 mapElement(data, element);
626 currentUpdater.addedElements.add(element);
627 currentUpdater.addedElementMap.put(data, element);
636 void mapElement(final Object data, final IElement element) {
637 if (!(element instanceof Element)) {
638 throw new IllegalArgumentException("mapElement: expected instance of Element, got " + element + " with data " + data);
641 assert element != null;
642 if (DebugPolicy.DEBUG_MAPPING)
643 new Exception(Thread.currentThread() + " MAPPING: " + data + " -> " + element).printStackTrace();
644 dataElement.put(data, element);
651 IElement getMappedElement(final Object data) {
652 assert (data != null);
653 IElement element = dataElement.get(data);
657 IElement getMappedElementByElementObject(IElement e) {
660 Object o = e.getHint(ElementHints.KEY_OBJECT);
663 return getMappedElement(o);
670 IElement assertMappedElement(final Object data) {
671 IElement element = dataElement.get(data);
672 assert element != null;
680 IElement unmapElement(final Object data) {
681 IElement element = dataElement.remove(data);
682 if (DebugPolicy.DEBUG_MAPPING)
683 new Exception(Thread.currentThread() + " UN-MAPPED: " + data + " -> " + element).printStackTrace();
691 void mapConnection(final Object data, final ConnectionEntityImpl connection) {
693 assert connection != null;
694 if (DebugPolicy.DEBUG_MAPPING)
695 System.out.println(Thread.currentThread() + " MAPPING CONNECTION: " + data + " -> " + connection);
696 dataConnection.put(data, connection);
703 ConnectionEntityImpl getMappedConnection(final Object data) {
704 ConnectionEntityImpl connection = dataConnection.get(data);
712 ConnectionEntityImpl assertMappedConnection(final Object data) {
713 ConnectionEntityImpl connection = getMappedConnection(data);
714 assert connection != null;
722 ConnectionEntityImpl unmapConnection(final Object data) {
723 ConnectionEntityImpl connection = dataConnection.remove(data);
724 if (DebugPolicy.DEBUG_MAPPING)
725 System.out.println(Thread.currentThread() + " UN-MAPPED CONNECTION: " + data + " -> " + connection);
729 class DataElementMapImpl implements DataElementMap {
731 public Object getData(IDiagram d, IElement element) {
733 throw new NullPointerException("null diagram");
735 throw new NullPointerException("null element");
737 assert ElementUtils.getDiagram(element) == d;
738 return element.getHint(ElementHints.KEY_OBJECT);
742 public IElement getElement(IDiagram d, Object data) {
744 throw new NullPointerException("null diagram");
746 throw new NullPointerException("null data");
748 GraphToDiagramUpdater updater = currentUpdater;
749 if (updater != null) {
750 // This HACK is for allowing GraphElementFactory implementations
751 // to find the IElements they are related to.
752 IElement e = updater.addedElementMap.get(data);
757 IElement e = getMappedElement(data);
764 class SubstituteElementClassImpl implements SubstituteElementClass {
766 public ElementClass substitute(IDiagram d, ElementClass ec) {
768 throw new IllegalArgumentException("specified diagram does not have this SubstituteElementClass handler");
770 // If the element class is our own, there's no point in creating
772 if (ec.contains(elementLayerListener))
775 List<ElementHandler> all = ec.getAll();
776 List<ElementHandler> result = new ArrayList<ElementHandler>(all.size());
777 for (ElementHandler eh : all) {
778 if (eh instanceof ElementLayerListenerImpl)
779 result.add(elementLayerListener);
783 return ElementClass.compile(result, false).setId(ec.getId());
787 final DataElementMapImpl dataElementMap = new DataElementMapImpl();
789 final SubstituteElementClassImpl substituteElementClass = new SubstituteElementClassImpl();
791 // ------------------------------------------------------------------------
792 // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING END
793 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
795 void warning(String message, Exception e) {
796 errorHandler.warning(message, e);
799 void warning(Exception e) {
800 errorHandler.warning(e.getMessage(), e);
803 void error(String message, Throwable e) {
804 errorHandler.error(message, e);
807 void error(Throwable e) {
808 errorHandler.error(e.getMessage(), e);
811 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
812 // GRAPH MODIFICATION QUEUE BEGIN
813 // ------------------------------------------------------------------------
815 ModificationQueue modificationQueue;
816 IModifiableSynchronizationContext synchronizationContext;
819 public <T> T set(Key key, Object value) {
820 if (synchronizationContext == null)
822 return synchronizationContext.set(key, value);
826 public <T> T get(Key key) {
827 if (synchronizationContext == null)
829 return synchronizationContext.get(key);
832 // ------------------------------------------------------------------------
833 // GRAPH MODIFICATION QUEUE END
834 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
837 * The previously loaded version of the diagram content. This is needed to
838 * calculate the difference between new and old content on each
839 * {@link #diagramGraphUpdater(DiagramContents)} invocation.
841 DiagramContents previousContent;
844 * The diagram instance that this synchronizer is synchronizing with the
850 * An observer for diagram profile entries. Has a life-cycle that must be
851 * bound to the life-cycle of this GraphToDiagramSynchronizer instance.
852 * Disposed if synchronizer is detached in {@link #doDispose()} or finally
853 * when the canvas is disposed.
855 ProfileObserver profileObserver;
857 IElementClassProvider elementClassProvider;
861 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
862 // Internal state machine handling BEGIN
863 // ------------------------------------------------------------------------
866 * An indicator for the current state of this synchronizer. This is a simple
867 * state machine with the following possible state transitions:
870 * <li>INITIAL -> LOADING, DISPOSED</li>
871 * <li>LOADING -> IDLE</li>
872 * <li>IDLE -> UPDATING_DIAGRAM, DISPOSED</li>
873 * <li>UPDATING_DIAGRAM -> IDLE</li>
876 * Start states: INITIAL
877 * End states: DISPOSED
881 * The initial state of the synchronizer.
885 * The synchronizer is performing load-time initialization. During this
886 * time no canvas refreshes should be forced.
890 * The synchronizer is performing updates to the diagram model. This
891 * process goes on in the canvas context thread.
895 * The synchronizer is doing nothing.
899 * The synchronized diagram is being disposed, which means that this
900 * synchronizer should not accept any further actions.
905 public static final EnumSet<State> FROM_INITIAL = EnumSet.of(State.LOADING, State.DISPOSED);
906 public static final EnumSet<State> FROM_LOADING = EnumSet.of(State.IDLE);
907 public static final EnumSet<State> FROM_UPDATING_DIAGRAM = EnumSet.of(State.IDLE);
908 public static final EnumSet<State> FROM_IDLE = EnumSet.of(State.UPDATING_DIAGRAM, State.DISPOSED);
909 public static final EnumSet<State> NO_STATES = EnumSet.noneOf(State.class);
911 private EnumSet<State> validTargetStates(State start) {
913 case INITIAL: return FROM_INITIAL;
914 case LOADING: return FROM_LOADING;
915 case UPDATING_DIAGRAM: return FROM_UPDATING_DIAGRAM;
916 case IDLE: return FROM_IDLE;
917 case DISPOSED: return NO_STATES;
919 throw new IllegalArgumentException("unrecognized state " + start);
922 private String validateStateChange(State start, State end) {
923 EnumSet<State> validTargets = validTargetStates(start);
924 if (!validTargets.contains(end))
925 return "Cannot transition from " + start + " state to " + end + ".";
930 * The current state of the synchronizer. At start it is
931 * {@link State#INITIAL} and after loading it is {@link State#IDLE}.
933 State synchronizerState = State.INITIAL;
936 * A condition variable used to synchronize synchronizer state changes.
938 ReentrantLock stateLock = new ReentrantLock();
941 * A condition that is signaled when the synchronizer state changes to IDLE.
943 Condition idleCondition = stateLock.newCondition();
946 return synchronizerState;
950 * Activates the desired state after making sure that the synchronizer has
951 * been IDLE in between its current state and this invocation.
953 * @param newState the new state to activate
954 * @throws InterruptedException if waiting for IDLE state gets interrupted
955 * @throws IllegalStateException if the requested transition from the
956 * current state to the desired state would be illegal.
958 void activateState(State newState, boolean waitForIdle) throws InterruptedException {
961 // Wait until the state of the synchronizer IDLEs if necessary.
962 if (waitForIdle && synchronizerState != State.IDLE) {
963 String error = validateStateChange(synchronizerState, State.IDLE);
965 throw new IllegalStateException(error);
967 while (synchronizerState != State.IDLE) {
968 if (DebugPolicy.DEBUG_STATE)
969 System.out.println(Thread.currentThread() + " waiting for IDLE state, current="
970 + synchronizerState);
971 idleCondition.await();
975 String error = validateStateChange(synchronizerState, newState);
977 throw new IllegalStateException(error);
979 if (DebugPolicy.DEBUG_STATE)
980 System.out.println(Thread.currentThread() + " activated state " + newState);
981 this.synchronizerState = newState;
983 if (newState == State.IDLE)
984 idleCondition.signalAll();
990 void idle() throws IllegalStateException, InterruptedException {
991 activateState(State.IDLE, false);
994 static interface StateRunnable extends Runnable {
995 void execute() throws InvocationTargetException;
997 public abstract class Stub implements StateRunnable {
1003 public final void execute() throws InvocationTargetException {
1006 } catch (Exception e) {
1007 throw new InvocationTargetException(e);
1008 } catch (LinkageError e) {
1009 throw new InvocationTargetException(e);
1013 protected abstract void perform() throws Exception;
1017 protected void runInState(State state, StateRunnable runnable) throws InvocationTargetException {
1019 activateState(state, true);
1025 } catch (IllegalStateException e) {
1026 throw new InvocationTargetException(e);
1027 } catch (InterruptedException e) {
1028 throw new InvocationTargetException(e);
1032 protected void safeRunInState(State state, StateRunnable runnable) {
1034 runInState(state, runnable);
1035 } catch (InvocationTargetException e) {
1036 error("Failed to run runnable " + runnable + " in state " + state + ". See exception for details.", e
1041 // ------------------------------------------------------------------------
1042 // Internal state machine handling END
1043 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1049 * @param elementClassProvider
1050 * @throws DatabaseException
1052 public GraphToDiagramSynchronizer(RequestProcessor processor, ICanvasContext canvas, IElementClassProvider elementClassProvider) throws DatabaseException {
1053 this(processor,canvas,elementClassProvider, null);
1055 public GraphToDiagramSynchronizer(RequestProcessor processor, ICanvasContext canvas, IElementClassProvider elementClassProvider, VirtualGraph virtualGraph) throws DatabaseException {
1056 if (processor == null)
1057 throw new IllegalArgumentException("null processor");
1059 throw new IllegalArgumentException("null canvas");
1060 if (elementClassProvider == null)
1061 throw new IllegalArgumentException("null element class provider");
1063 this.session = processor.getSession();
1064 this.canvas = canvas;
1065 this.modificationQueue = new ModificationQueue(session, errorHandler, virtualGraph);
1067 processor.syncRequest(new ReadRequest() {
1069 public void run(ReadGraph graph) throws DatabaseException {
1070 initializeResources(graph);
1074 this.elementClassProvider = elementClassProvider;
1075 synchronizationContext.set(SynchronizationHints.ELEMENT_CLASS_PROVIDER, elementClassProvider);
1077 attachSessionListener(processor.getSession());
1083 public IElementClassProvider getElementClassProvider() {
1084 return elementClassProvider;
1087 public Session getSession() {
1091 public ICanvasContext getCanvasContext() {
1095 public IDiagram getDiagram() {
1099 void setCanvasDirty() {
1100 ICanvasContext c = canvas;
1101 if (synchronizerState != State.LOADING && c != null && !c.isDisposed()) {
1102 // TODO: Consider adding an invocation limiter here, to prevent
1103 // calling setDirty too often if enough time hasn't passed yet since
1104 // the last invocation.
1105 c.getContentContext().setDirty();
1110 * @param elementType
1112 * @throws DatabaseException if ElementClass cannot be retrieved
1114 public ElementClass getNodeClass(Resource elementType) throws DatabaseException {
1115 return getNodeClass(session, elementType);
1118 public ElementClass getNodeClass(RequestProcessor processor, Resource elementType) throws DatabaseException {
1119 ElementClass ec = processor.syncRequest(new NodeClassRequest(canvas, diagram, elementType, true));
1124 protected void doDispose() {
1128 boolean isInitial = getState() == State.INITIAL;
1129 activateState(State.DISPOSED, !isInitial);
1133 } catch (InterruptedException e) {
1134 // Shouldn't happen.
1135 LOGGER.error("Dispose interrupted!", e);
1137 detachSessionListener();
1139 if (profileObserver != null) {
1140 profileObserver.dispose();
1141 profileObserver = null;
1144 if (diagram != null) {
1145 diagram.removeCompositionListener(diagramListener);
1146 diagram.removeCompositionVetoListener(diagramListener);
1149 // TODO: we should probably leave the dataElement map as is since DataElementMap needs it even after the synchronizer has been disposed.
1150 // Currently the diagram's DataElementMap will be broken after disposal.
1151 // dataElement.clear();
1152 // dataConnection.clear();
1154 if (layerManager != null) {
1155 layerManager.dispose();
1159 modificationQueue.dispose();
1163 void initializeResources(ReadGraph graph) {
1164 this.br = new BasicResources(graph);
1166 // Initialize synchronization context
1167 synchronizationContext = new GraphSynchronizationContext(graph, modificationQueue);
1168 synchronizationContext.set(SynchronizationHints.ERROR_HANDLER, errorHandler);
1171 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1173 // ------------------------------------------------------------------------
1175 GraphLayerManager layerManager;
1178 * A common handler for all elements that is used to listen to changes in
1179 * element visibility and focusability on diagram layers.
1181 class ElementLayerListenerImpl implements ElementLayerListener {
1182 private static final long serialVersionUID = -3410052116598828129L;
1185 public void visibilityChanged(IElement e, ILayer layer, boolean visible) {
1188 if (DebugPolicy.DEBUG_LAYERS)
1189 System.out.println("visibility changed: " + e + ", " + layer + ", " + visible);
1190 GraphLayer gl = layerManager.getGraphLayer(layer.getName());
1192 changeTag(e, gl.getVisible(), visible);
1197 public void focusabilityChanged(IElement e, ILayer layer, boolean focusable) {
1200 if (DebugPolicy.DEBUG_LAYERS)
1201 System.out.println("focusability changed: " + e + ", " + layer + ", " + focusable);
1202 GraphLayer gl = layerManager.getGraphLayer(layer.getName());
1204 changeTag(e, gl.getFocusable(), focusable);
1208 void changeTag(IElement e, Resource tag, boolean set) {
1209 Object object = e.getHint(ElementHints.KEY_OBJECT);
1210 Resource tagged = null;
1211 if (object instanceof Resource) {
1212 tagged = (Resource) object;
1213 } else if (object instanceof EdgeResource) {
1214 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
1215 if (ce instanceof ConnectionEntityImpl) {
1216 tagged = ((ConnectionEntityImpl) ce).connection;
1222 modificationQueue.async(new TagChange(tagged, tag, set), null);
1226 ElementLayerListenerImpl elementLayerListener = new ElementLayerListenerImpl();
1228 // ------------------------------------------------------------------------
1230 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1233 public IDiagram loadDiagram(IProgressMonitor progressMonitor, ReadGraph g, final String modelURI, final Resource diagram, final Resource runtime, final ResourceArray structuralPath,
1234 IHintObservable initialHints) throws DatabaseException {
1235 if (DebugPolicy.DEBUG_LOAD)
1236 System.out.println(Thread.currentThread() + " loadDiagram: " + NameUtils.getSafeName(g, diagram));
1238 SubMonitor monitor = SubMonitor.convert(progressMonitor, "Load Diagram", 100);
1240 Object loadTask = Timing.BEGIN("GDS.loadDiagram");
1243 activateState(State.LOADING, false);
1244 } catch (IllegalStateException e) {
1245 // Disposed already before loading even began.
1246 this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
1247 return this.diagram;
1250 // Query for diagram class
1251 Resource diagramClassResource = g.getPossibleType(diagram, br.DIA.Composite);
1252 if (diagramClassResource != null) {
1253 // Spawn new diagram
1254 Object task = Timing.BEGIN("GDS.DiagramClassRequest");
1255 final DiagramClass diagramClass = g.syncRequest(new DiagramClassRequest(diagram));
1257 final IDiagram d = Diagram.spawnNew(diagramClass);
1259 d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, diagram);
1260 if (runtime != null)
1261 d.setHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE, runtime);
1262 if (modelURI != null)
1263 d.setHint(DiagramModelHints.KEY_DIAGRAM_MODEL_URI, modelURI);
1264 d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE_ARRAY, structuralPath);
1266 // Set dumb default routing when DiagramClass does not
1267 // predefine the default connection routing for the diagram.
1268 if (!d.containsHint(DiagramHints.ROUTE_ALGORITHM))
1269 d.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, false));
1271 d.setHint(SynchronizationHints.CONTEXT, this);
1273 // Initialize hints with hints from initialHints if given
1274 if (initialHints != null) {
1275 d.setHints(initialHints.getHints());
1279 // ITask task2 = ThreadLogger.getInstance().begin("loadLayers");
1280 monitor.subTask("Layers");
1282 this.layerManager = new GraphLayerManager(g, modificationQueue, diagram);
1283 synchronizationContext.set(GraphSynchronizationHints.GRAPH_LAYER_MANAGER, this.layerManager);
1284 ILayersEditor layers = layerManager.loadLayers(d, g, diagram);
1287 d.setHint(DiagramHints.KEY_LAYERS, layers);
1288 d.setHint(DiagramHints.KEY_LAYERS_EDITOR, layers);
1290 d.addCompositionVetoListener(diagramListener);
1291 d.addCompositionListener(diagramListener);
1295 d.setHint(DiagramHints.KEY_MUTATOR, new DefaultDiagramMutator(d, diagram, synchronizationContext));
1297 // Add default layer if no layers exist.
1298 // NOTE: this must be done after this.diagram has been set
1299 // as it will trigger a graph modification which needs the
1300 // diagram resource.
1301 // ITask task3 = ThreadLogger.getInstance().begin("addDefaultLayer");
1302 // if (layers.getLayers().isEmpty()) {
1303 // if (DebugPolicy.DEBUG_LAYERS)
1304 // System.out.println("No layers, creating default layer '"
1305 // + DiagramConstants.DEFAULT_LAYER_NAME + "'");
1306 // SimpleLayer defaultLayer = new SimpleLayer(DiagramConstants.DEFAULT_LAYER_NAME);
1307 // layers.addLayer(defaultLayer);
1308 // layers.activate(defaultLayer);
1310 // // task3.finish();
1314 monitor.subTask("Contents");
1315 // Discover the plain resources that form the content of the
1316 // diagram through a separate query. This allows us to
1318 // track changes to the diagram structure itself, not the
1319 // substructures contained by the structure elements.
1320 ITask task4 = ThreadLogger.getInstance().begin("DiagramContentRequest1");
1321 DiagramContentRequest query = new DiagramContentRequest(canvas, diagram, errorHandler);
1322 g.syncRequest(query, new DiagramContentListener(diagram));
1324 // ITask task5 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
1325 ITask task42 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
1326 DiagramContents contents = g.syncRequest(query, TransientCacheAsyncListener.instance());
1327 //System.err.println("contents: " + contents);
1332 monitor.subTask("Graphical elements");
1334 Object applyDiagramContents = Timing.BEGIN("GDS.applyDiagramContents");
1335 ITask task6 = ThreadLogger.getInstance().begin("applyDiagramContents");
1336 processGraphUpdates(g, Collections.singleton(diagramGraphUpdater(contents)));
1338 Timing.END(applyDiagramContents);
1342 DataNodeMap dn = new DataNodeMap() {
1344 public INode getNode(Object data) {
1345 if (DataNodeConstants.CANVAS_ROOT == data)
1346 return canvas.getCanvasNode();
1347 if (DataNodeConstants.DIAGRAM_ELEMENT_PARENT == data) {
1348 ElementPainter ep = canvas.getAtMostOneItemOfClass(ElementPainter.class);
1349 return ep != null ? ep.getDiagramElementParentNode() : null;
1352 DataElementMap emap = GraphToDiagramSynchronizer.this.diagram.getDiagramClass().getSingleItem(DataElementMap.class);
1353 IElement element = emap.getElement(GraphToDiagramSynchronizer.this.diagram, data);
1354 if(element == null) return null;
1355 return element.getHint(ElementHints.KEY_SG_NODE);
1359 profileObserver = new ProfileObserver(g.getSession(), runtime,
1360 canvas.getThreadAccess(), canvas, canvas.getSceneGraph(), diagram,
1361 ArrayMap.keys(ProfileKeys.DIAGRAM, ProfileKeys.CANVAS, ProfileKeys.NODE_MAP).values(GraphToDiagramSynchronizer.this.diagram, canvas, dn),
1362 new CanvasNotification(canvas));
1364 g.getSession().asyncRequest(new AsyncReadRequest() {
1366 public void run(AsyncReadGraph graph) {
1367 ProfileObserver po = profileObserver;
1369 po.listen(graph, GraphToDiagramSynchronizer.this);
1371 LOGGER.info("profileObserver has been disposed already!");
1379 this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
1380 return this.diagram;
1385 } catch (InterruptedException e) {
1386 throw new RuntimeException(e);
1387 } catch (IllegalStateException e) {
1388 // If the synchronizer was disposed ahead of time, it was done
1389 // for a reason, such as the user having closed the owner editor.
1391 throw new CancelTransactionException(e);
1392 throw new RuntimeException(e);
1394 Timing.END(loadTask);
1398 static class CanvasNotification implements Runnable {
1400 final private ICanvasContext canvas;
1402 public CanvasNotification(ICanvasContext canvas) {
1403 this.canvas = canvas;
1407 canvas.getContentContext().setDirty();
1412 ArrayList<IModification> pendingModifications = new ArrayList<IModification>();
1413 MapSet<IElement, IModification> modificationIndex = new MapSet.Hash<IElement, IModification>();
1415 void addModification(IElement element, IModification modification) {
1416 pendingModifications.add(modification);
1417 if (element != null)
1418 modificationIndex.add(element, modification);
1421 class DefaultDiagramMutator implements DiagramMutator {
1423 Map<IElement, Resource> creation = new HashMap<IElement, Resource>();
1428 IModifiableSynchronizationContext synchronizationContext;
1430 public DefaultDiagramMutator(IDiagram d, Resource diagram, IModifiableSynchronizationContext synchronizationContext) {
1432 this.diagram = diagram;
1433 this.synchronizationContext = synchronizationContext;
1435 if (synchronizationContext.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER) == null)
1436 throw new IllegalArgumentException("SynchronizationHints.ELEMENT_CLASS_PROVIDER not available");
1439 void assertNotDisposed() {
1441 throw new IllegalStateException(getClass().getSimpleName() + " is disposed");
1445 public IElement newElement(ElementClass clazz) {
1446 assertNotDisposed();
1447 ElementFactory ef = d.getDiagramClass().getAtMostOneItemOfClass(ElementFactory.class);
1448 IElement element = null;
1450 element = ef.spawnNew(clazz);
1452 element = Element.spawnNew(clazz);
1454 element.setHint(ElementHints.KEY_OBJECT, new TransientElementObject());
1456 addModification(element, new AddElement(synchronizationContext, d, element));
1462 public void commit() {
1463 assertNotDisposed();
1464 if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
1465 System.out.println("DiagramMutator is about to commit changes:");
1466 for (IModification mod : pendingModifications)
1467 System.out.println("\t- " + mod);
1470 Collections.sort(pendingModifications);
1472 if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
1473 if (pendingModifications.size() > 1) {
1474 System.out.println("* changes were re-ordered to:");
1475 for (IModification mod : pendingModifications)
1476 System.out.println("\t" + mod);
1480 Timing.safeTimed(errorHandler, "QUEUE AND WAIT FOR MODIFICATIONS TO FINISH", new GTask() {
1482 public void run() throws DatabaseException {
1483 // Performs a separate write request and query result update
1484 // for each modification
1485 // for (IModification mod : pendingModifications) {
1487 // modificationQueue.sync(mod);
1488 // } catch (InterruptedException e) {
1489 // error("Pending diagram modification " + mod
1490 // + " was interrupted. See exception for details.", e);
1494 // NOTE: this is still under testing, the author is not
1495 // truly certain that it should work in all cases ATM.
1497 // Performs all modifications with in a single write request
1498 for (IModification mod : pendingModifications) {
1499 modificationQueue.offer(mod, null);
1502 // Perform the modifications in a single request.
1503 modificationQueue.finish();
1504 } catch (InterruptedException e) {
1505 errorHandler.error("Diagram modification finishing was interrupted. See exception for details.", e);
1509 pendingModifications.clear();
1510 modificationIndex.clear();
1512 if (DebugPolicy.DEBUG_MUTATOR_COMMIT)
1513 System.out.println("DiagramMutator has committed");
1517 public void clear() {
1518 assertNotDisposed();
1519 pendingModifications.clear();
1520 modificationIndex.clear();
1522 if (DebugPolicy.DEBUG_MUTATOR)
1523 System.out.println("DiagramMutator has been cleared");
1527 public void modifyTransform(IElement element) {
1528 assertNotDisposed();
1529 Resource resource = backendObject(element);
1530 AffineTransform tr = element.getHint(ElementHints.KEY_TRANSFORM);
1531 if (resource != null && tr != null) {
1532 addModification(element, new TransformElement(resource, tr));
1537 public void synchronizeHintsToBackend(IElement element) {
1538 assertNotDisposed();
1539 IHintSynchronizer synchronizer = element.getHint(SynchronizationHints.HINT_SYNCHRONIZER);
1540 if (synchronizer != null) {
1541 CollectingModificationQueue queue = new CollectingModificationQueue();
1542 synchronizer.synchronize(synchronizationContext, element);
1543 addModification(element, new CompositeModification(ModificationAdapter.LOW_PRIORITY, queue.getQueue()));
1548 public void synchronizeElementOrder() {
1549 assertNotDisposed();
1550 List<IElement> snapshot = d.getSnapshot();
1551 addModification(null, new ElementReorder(d, snapshot));
1555 public void register(IElement element, Object object) {
1556 creation.put(element, (Resource) object);
1559 @SuppressWarnings("unchecked")
1561 public <T> T backendObject(IElement element) {
1562 Object object = ElementUtils.getObject(element);
1563 if (object instanceof Resource)
1566 return (T) creation.get(element);
1571 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1572 // GRAPH TO DIAGRAM SYCHRONIZATION LOGIC BEGIN
1573 // ------------------------------------------------------------------------
1575 static class ConnectionData {
1576 ConnectionEntityImpl impl;
1577 List<Resource> branchPoints = new ArrayList<Resource>();
1578 List<EdgeResource> segments = new ArrayList<EdgeResource>();
1580 ConnectionData(ConnectionEntityImpl ce) {
1584 void addBranchPoint(Resource bp) {
1585 branchPoints.add(bp);
1588 void addSegment(EdgeResource seg) {
1593 class GraphToDiagramUpdater {
1594 DiagramContents lastContent;
1595 DiagramContents content;
1596 DiagramContentChanges changes;
1598 final List<IElement> addedElements;
1599 final List<IElement> removedElements;
1601 final List<IElement> addedConnectionSegments;
1602 final List<IElement> removedConnectionSegments;
1604 final List<IElement> addedBranchPoints;
1605 final List<IElement> removedBranchPoints;
1607 final Map<Object, IElement> addedElementMap;
1608 final Map<Resource, IElement> addedConnectionMap;
1609 final Map<Resource, ConnectionEntityImpl> addedConnectionEntities;
1610 final List<Resource> removedConnectionEntities;
1611 final Map<ConnectionEntityImpl, ConnectionData> changedConnectionEntities;
1613 final Map<Resource, IElement> addedRouteGraphConnectionMap;
1614 final List<IElement> removedRouteGraphConnections;
1617 GraphToDiagramUpdater(DiagramContents lastContent, DiagramContents content, DiagramContentChanges changes) {
1618 this.lastContent = lastContent;
1619 this.content = content;
1620 this.changes = changes;
1622 this.addedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
1623 this.removedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
1624 this.addedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
1625 this.removedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
1626 this.addedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
1627 this.removedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
1628 this.addedElementMap = new HashMap<Object, IElement>();
1629 this.addedConnectionMap = new HashMap<Resource, IElement>();
1630 this.addedConnectionEntities = new HashMap<Resource, ConnectionEntityImpl>();
1631 this.removedConnectionEntities = new ArrayList<Resource>(changes.connections.size());
1632 this.changedConnectionEntities = new HashMap<ConnectionEntityImpl, ConnectionData>();
1633 this.addedRouteGraphConnectionMap = new HashMap<Resource, IElement>();
1634 this.removedRouteGraphConnections = new ArrayList<IElement>(changes.routeGraphConnections.size());
1637 public void clear() {
1638 // Prevent DiagramContents leakage through DisposableListeners.
1643 this.addedElements.clear();
1644 this.removedElements.clear();
1645 this.addedConnectionSegments.clear();
1646 this.removedConnectionSegments.clear();
1647 this.addedBranchPoints.clear();
1648 this.removedBranchPoints.clear();
1649 this.addedElementMap.clear();
1650 this.addedConnectionMap.clear();
1651 this.addedConnectionEntities.clear();
1652 this.removedConnectionEntities.clear();
1653 this.changedConnectionEntities.clear();
1654 this.addedRouteGraphConnectionMap.clear();
1655 this.removedRouteGraphConnections.clear();
1658 class LoadNodeListener extends DisposableListener<IElement> {
1660 final Resource element;
1661 public IElement lastLoaded;
1663 public LoadNodeListener(ListenerSupport support, Resource element) {
1665 this.element = element;
1669 public String toString() {
1670 return "Node load listener for " + element;
1673 public void applyFirst(IElement loaded) {
1675 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1677 if (addedElementMap.containsKey(data)) {
1678 // This element was just loaded, in
1679 // which case its hints need to
1680 // uploaded to the real mapped
1681 // element immediately.
1682 IElement mappedElement = getMappedElement(data);
1683 if (DebugPolicy.DEBUG_NODE_LISTENER)
1684 System.out.println("LOADED ADDED ELEMENT, currently mapped element: " + mappedElement);
1685 if (mappedElement != null && (mappedElement instanceof Element)) {
1686 if (DebugPolicy.DEBUG_NODE_LISTENER) {
1687 System.out.println(" mapped hints: " + mappedElement.getHints());
1688 System.out.println(" loaded hints: " + loaded.getHints());
1690 updateMappedElement((Element) mappedElement, loaded);
1697 public void execute(IElement loaded) {
1699 // Invoked when the element has been loaded.
1700 if (DebugPolicy.DEBUG_NODE_LISTENER)
1701 System.out.println("NODE LoadListener for " + loaded);
1703 if (loaded == null) {
1709 boolean first = lastLoaded == null;
1711 lastLoaded = loaded;
1714 * The first invocation is postponed
1721 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1723 // Logic for disposing listener
1724 if (!previousContent.nodeSet.contains(data)) {
1725 if (DebugPolicy.DEBUG_NODE_LISTENER)
1726 System.out.println("NODE LoadListener, node not in current content: " + data + ". Disposing.");
1731 // This element was already loaded.
1732 // Just schedule an update some time
1734 if (DebugPolicy.DEBUG_NODE_LISTENER)
1735 System.out.println("PREVIOUSLY LOADED NODE UPDATED, scheduling update into the future");
1736 offerGraphUpdate( nodeUpdater(element, loaded) );
1742 void processNodes(AsyncReadGraph graph) throws DatabaseException {
1744 for (Map.Entry<Resource, Change> entry : changes.elements.entrySet()) {
1746 final Resource element = entry.getKey();
1747 Change change = entry.getValue();
1751 IElement mappedElement = getMappedElement(element);
1752 if (mappedElement == null) {
1753 if (DebugPolicy.DEBUG_NODE_LOAD)
1754 graph.syncRequest(new ReadRequest() {
1756 public void run(ReadGraph graph) throws DatabaseException {
1757 System.out.println(" EXTERNALLY ADDED ELEMENT: "
1758 + NameUtils.getSafeName(graph, element) + " ("
1759 + element.getResourceId() + ")");
1763 if (content.connectionSet.contains(element)) {
1765 // TODO: Connection loading has no listening, changes :Connection will not be noticed by this code!
1766 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1768 boolean firstTime = true;
1771 public String toString() {
1772 return "Connection load listener for " + element;
1775 public void execute(IElement loaded) {
1776 // Invoked when the element has been loaded.
1777 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1778 System.out.println("CONNECTION LoadListener for " + loaded);
1780 if (loaded == null) {
1787 mapElement(element, loaded);
1788 synchronized (GraphToDiagramUpdater.this) {
1789 addedElements.add(loaded);
1790 addedElementMap.put(element, loaded);
1791 addedConnectionMap.put(element, loaded);
1798 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1800 // Logic for disposing listener
1801 if (!previousContent.connectionSet.contains(data)) {
1802 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1803 System.out.println("CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
1808 if (addedElementMap.containsKey(data)) {
1809 // This element was just loaded, in
1810 // which case its hints need to
1811 // uploaded to the real mapped
1812 // element immediately.
1813 IElement mappedElement = getMappedElement(data);
1814 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1815 System.out.println("LOADED ADDED CONNECTION, currently mapped connection: " + mappedElement);
1816 if (mappedElement != null && (mappedElement instanceof Element)) {
1817 if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
1818 System.out.println(" mapped hints: " + mappedElement.getHints());
1819 System.out.println(" loaded hints: " + loaded.getHints());
1821 updateMappedElement((Element) mappedElement, loaded);
1824 // This element was already loaded.
1825 // Just schedule an update some time
1827 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1828 System.out.println("PREVIOUSLY LOADED CONNECTION UPDATED, scheduling update into the future");
1829 offerGraphUpdate( connectionUpdater(element, loaded) );
1834 graph.asyncRequest(new ConnectionRequest(canvas, diagram, element, errorHandler, loadListener), new AsyncProcedure<IElement>() {
1836 public void execute(AsyncReadGraph graph, final IElement e) {
1838 mapElement(element, e);
1839 synchronized (GraphToDiagramUpdater.this) {
1840 addedElements.add(e);
1841 addedElementMap.put(element, e);
1842 addedConnectionMap.put(element, e);
1845 // Read connection type
1846 graph.forSingleType(element, br.DIA.Connection, new Procedure<Resource>() {
1848 public void exception(Throwable t) {
1853 public void execute(Resource connectionType) {
1854 synchronized (GraphToDiagramUpdater.this) {
1855 IElement mapped = getMappedElement(element);
1856 assert(mapped != null);
1858 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1859 System.out.println("CONNECTION ENTITY CREATED " + e + " " + element);
1861 //System.out.println("new connection entity " + e);
1862 ConnectionEntityImpl entity = new ConnectionEntityImpl(element, connectionType, mapped);
1863 mapped.setHint(ElementHints.KEY_CONNECTION_ENTITY, entity);
1864 addedConnectionEntities.put(element, entity);
1872 public void exception(AsyncReadGraph graph, Throwable throwable) {
1876 } else if (content.nodeSet.contains(element)) {
1878 graph.asyncRequest(new ReadRequest() {
1881 public void run(ReadGraph graph) throws DatabaseException {
1883 LoadNodeListener loadListener = new LoadNodeListener(canvasListenerSupport, element);
1884 Tuple3 t = graph.syncRequest(new NodeRequest2(canvas, diagram, element));
1885 IElement e = (IElement)t.c0;
1886 ElementClass ec = (ElementClass)t.c1;
1887 org.simantics.diagram.adapter.ElementFactory ef = (org.simantics.diagram.adapter.ElementFactory)t.c2;
1891 // This is invoked before the element is actually loaded.
1892 //System.out.println("NodeRequestProcedure " + e);
1893 if (DebugPolicy.DEBUG_NODE_LOAD)
1894 System.out.println("MAPPING ADDED NODE: " + element + " -> " + e);
1895 mapElement(element, e);
1896 synchronized (GraphToDiagramUpdater.this) {
1897 addedElements.add(e);
1898 addedElementMap.put(element, e);
1901 graph.syncRequest(new LoadRequest(canvas, diagram, ef, ec, element), loadListener);
1908 // warning("Diagram elements must be either elements or connections, "
1909 // + NameUtils.getSafeName(g, element) + " is neither",
1910 // new AssumptionException(""));
1916 IElement e = getMappedElement(element);
1917 if (DebugPolicy.DEBUG_NODE_LOAD)
1918 graph.syncRequest(new ReadRequest() {
1920 public void run(ReadGraph graph) throws DatabaseException {
1921 System.out.println(" EXTERNALLY REMOVED ELEMENT: "
1922 + NameUtils.getSafeName(graph, element) + " ("
1923 + element.getResourceId() + ")");
1927 removedElements.add(e);
1936 void gatherChangedConnectionParts(Map<?, Change> changes) {
1937 for (Map.Entry<?, Change> entry : changes.entrySet()) {
1938 Object part = entry.getKey();
1939 Change change = entry.getValue();
1943 synchronized (GraphToDiagramUpdater.this) {
1944 Resource connection = content.partToConnection.get(part);
1945 assert connection != null;
1947 IElement ce = getMappedElement(connection);
1949 ce = addedElementMap.get(connection);
1952 markConnectionChanged(ce);
1957 if (lastContent == null)
1959 Resource connection = lastContent.partToConnection.get(part);
1960 if (connection != null && content.connectionSet.contains(connection)) {
1961 markConnectionChanged(connection);
1970 void markConnectionChanged(Resource connection) {
1971 // System.out.println("markConnectionChanged");
1972 ConnectionEntityImpl ce = getMappedConnection(connection);
1974 markConnectionChanged(ce);
1977 error("WARNING: marking connection entity " + connection
1978 + " changed, but the connection was not previously mapped",
1979 new Exception("created exception to get a stack trace"));
1982 void markConnectionChanged(IElement connection) {
1983 ConnectionEntityImpl entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
1985 markConnectionChanged(entity);
1988 void markConnectionChanged(ConnectionEntityImpl ce) {
1989 if (!changedConnectionEntities.containsKey(ce)) {
1990 changedConnectionEntities.put(ce, new ConnectionData(ce));
1994 void processConnections() {
1995 // Find added/removed connection segments/branch points
1996 // in order to find all changed connection entities.
1997 gatherChangedConnectionParts(changes.connectionSegments);
1998 gatherChangedConnectionParts(changes.branchPoints);
2000 // Find removed connection entities
2001 for (Map.Entry<Resource, Change> entry : changes.connections.entrySet()) {
2002 Resource ce = entry.getKey();
2003 Change change = entry.getValue();
2007 removedConnectionEntities.add(ce);
2013 // Generate update data of changed connection entities.
2014 // This ConnectionData will be applied in the canvas thread
2016 for (ConnectionData cd : changedConnectionEntities.values()) {
2017 for (Object part : content.connectionToParts.getValuesUnsafe(cd.impl.connection)) {
2018 if (part instanceof Resource) {
2019 cd.branchPoints.add((Resource) part);
2020 } else if (part instanceof EdgeResource) {
2021 cd.segments.add((EdgeResource) part);
2027 class LoadRouteGraphConnectionListener extends DisposableListener<IElement> {
2029 final Resource connection;
2030 public IElement lastLoaded;
2032 public LoadRouteGraphConnectionListener(ListenerSupport support, Resource connection) {
2034 this.connection = connection;
2038 public String toString() {
2039 return "processRouteGraphConnections " + connection;
2042 public void applyFirst(IElement loaded) {
2044 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2045 if (addedElementMap.containsKey(data)) {
2046 // This element was just loaded, in
2047 // which case its hints need to
2048 // uploaded to the real mapped
2049 // element immediately.
2050 IElement mappedElement = getMappedElement(data);
2051 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2052 System.out.println("LOADED ADDED ROUTE GRAPH CONNECTION, currently mapped connection: " + mappedElement);
2053 if (mappedElement instanceof Element) {
2054 if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
2055 System.out.println(" mapped hints: " + mappedElement.getHints());
2056 System.out.println(" loaded hints: " + loaded.getHints());
2058 updateMappedElement((Element) mappedElement, loaded);
2065 public void execute(IElement loaded) {
2067 // Invoked when the element has been loaded.
2068 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2069 System.out.println("ROUTE GRAPH CONNECTION LoadListener for " + loaded);
2071 if (loaded == null) {
2076 boolean first = lastLoaded == null;
2078 lastLoaded = loaded;
2081 * The first invocation is postponed
2088 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2090 // Logic for disposing listener
2091 if (!previousContent.routeGraphConnectionSet.contains(data)) {
2092 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2093 System.out.println("ROUTE GRAPH CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
2098 if (addedElementMap.containsKey(data)) {
2099 // This element was just loaded, in
2100 // which case its hints need to
2101 // uploaded to the real mapped
2102 // element immediately.
2103 IElement mappedElement = getMappedElement(data);
2104 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2105 System.out.println("LOADED ADDED ROUTE GRAPH CONNECTION, currently mapped connection: " + mappedElement);
2106 if (mappedElement instanceof Element) {
2107 if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
2108 System.out.println(" mapped hints: " + mappedElement.getHints());
2109 System.out.println(" loaded hints: " + loaded.getHints());
2111 updateMappedElement((Element) mappedElement, loaded);
2114 // This element was already loaded.
2115 // Just schedule an update some time
2117 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2118 System.out.println("PREVIOUSLY LOADED ROUTE GRAPH CONNECTION UPDATED, scheduling update into the future: " + connection);
2120 Set<Object> dirtyNodes = new THashSet<Object>(4);
2121 IElement mappedElement = getMappedElement(connection);
2122 ConnectionEntity ce = mappedElement.getHint(ElementHints.KEY_CONNECTION_ENTITY);
2124 for (Connection conn : ce.getTerminalConnections(null)) {
2125 Object o = conn.node.getHint(ElementHints.KEY_OBJECT);
2128 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2129 System.out.println("Marked connectivity dirty for node: " + conn.node);
2134 offerGraphUpdate( routeGraphConnectionUpdater(connection, loaded, dirtyNodes) );
2140 void processRouteGraphConnections(AsyncReadGraph graph) throws DatabaseException {
2141 for (Map.Entry<Resource, Change> entry : changes.routeGraphConnections.entrySet()) {
2142 final Resource connection = entry.getKey();
2144 Change change = entry.getValue();
2147 IElement mappedElement = getMappedElement(connection);
2148 if (mappedElement != null)
2152 graph.asyncRequest(new ReadRequest() {
2155 public void run(ReadGraph graph) throws DatabaseException {
2157 LoadRouteGraphConnectionListener loadListener = new LoadRouteGraphConnectionListener(canvasListenerSupport, connection);
2159 Tuple3 t = graph.syncRequest(new ConnectionRequest2(canvas, diagram, connection, errorHandler));
2160 IElement e = (IElement)t.c0;
2161 ElementClass ec = (ElementClass)t.c1;
2162 org.simantics.diagram.adapter.ElementFactory ef = (org.simantics.diagram.adapter.ElementFactory)t.c2;
2167 //System.out.println("ConnectionRequestProcedure " + e);
2168 if (DebugPolicy.DEBUG_NODE_LOAD)
2169 System.out.println("MAPPING ADDED ROUTE GRAPH CONNECTION: " + connection + " -> " + e);
2170 mapElement(connection, e);
2171 synchronized (GraphToDiagramUpdater.this) {
2172 addedElements.add(e);
2173 addedElementMap.put(connection, e);
2174 addedRouteGraphConnectionMap.put(connection, e);
2177 graph.syncRequest(new LoadRequest(canvas, diagram, ef, ec, connection), loadListener);
2187 IElement e = getMappedElement(connection);
2189 removedRouteGraphConnections.add(e);
2197 ConnectionEntityImpl getConnectionEntity(Object connectionPart) {
2198 Resource connection = content.partToConnection.get(connectionPart);
2199 assert connection != null;
2200 ConnectionEntityImpl ce = addedConnectionEntities.get(connection);
2203 return assertMappedConnection(connection);
2206 void processBranchPoints(AsyncReadGraph graph) throws DatabaseException {
2207 for (Map.Entry<Resource, Change> entry : changes.branchPoints.entrySet()) {
2209 final Resource element = entry.getKey();
2210 Change change = entry.getValue();
2214 IElement mappedElement = getMappedElement(element);
2215 if (mappedElement == null) {
2216 if (DebugPolicy.DEBUG_NODE_LOAD)
2217 graph.asyncRequest(new ReadRequest() {
2219 public void run(ReadGraph graph) throws DatabaseException {
2220 System.out.println(" EXTERNALLY ADDED BRANCH POINT: "
2221 + NameUtils.getSafeName(graph, element) + " ("
2222 + element.getResourceId() + ")");
2226 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
2229 public String toString() {
2230 return "processBranchPoints for " + element;
2233 public void execute(IElement loaded) {
2234 // Invoked when the element has been loaded.
2235 if (DebugPolicy.DEBUG_NODE_LISTENER)
2236 System.out.println("BRANCH POINT LoadListener for " + loaded);
2238 if (loaded == null) {
2243 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2244 if (addedElementMap.containsKey(data)) {
2245 // This element was just loaded, in
2246 // which case its hints need to
2247 // uploaded to the real mapped
2248 // element immediately.
2249 IElement mappedElement = getMappedElement(data);
2250 if (DebugPolicy.DEBUG_NODE_LISTENER)
2251 System.out.println("LOADED ADDED BRANCH POINT, currently mapped element: " + mappedElement);
2252 if (mappedElement != null && (mappedElement instanceof Element)) {
2253 if (DebugPolicy.DEBUG_NODE_LISTENER) {
2254 System.out.println(" mapped hints: " + mappedElement.getHints());
2255 System.out.println(" loaded hints: " + loaded.getHints());
2257 updateMappedElement((Element) mappedElement, loaded);
2260 // This element was already loaded.
2261 // Just schedule an update some time
2263 if (DebugPolicy.DEBUG_NODE_LISTENER)
2264 System.out.println("PREVIOUSLY LOADED BRANCH POINT UPDATED, scheduling update into the future");
2265 offerGraphUpdate( nodeUpdater(element, loaded) );
2270 graph.asyncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
2272 public void execute(AsyncReadGraph graph, IElement e) {
2276 public void exception(AsyncReadGraph graph, Throwable throwable) {
2284 IElement e = getMappedElement(element);
2285 if (DebugPolicy.DEBUG_NODE_LOAD)
2286 graph.syncRequest(new ReadRequest() {
2288 public void run(ReadGraph graph) throws DatabaseException {
2289 System.out.println(" EXTERNALLY REMOVED BRANCH POINT: "
2290 + NameUtils.getSafeName(graph, element) + " ("
2291 + element.getResourceId() + ")");
2295 removedBranchPoints.add(e);
2304 void processConnectionSegments(AsyncReadGraph graph) throws DatabaseException {
2305 ConnectionSegmentAdapter adapter = connectionSegmentAdapter;
2307 for (Map.Entry<EdgeResource, Change> entry : changes.connectionSegments.entrySet()) {
2308 final EdgeResource seg = entry.getKey();
2309 Change change = entry.getValue();
2313 IElement mappedElement = getMappedElement(seg);
2314 if (mappedElement == null) {
2315 if (DebugPolicy.DEBUG_EDGE_LOAD)
2316 graph.asyncRequest(new ReadRequest() {
2318 public void run(ReadGraph graph) throws DatabaseException {
2319 System.out.println(" EXTERNALLY ADDED CONNECTION SEGMENT: " + seg.toString()
2320 + " - " + seg.toString(graph));
2324 graph.asyncRequest(new EdgeRequest(GraphToDiagramSynchronizer.this, canvas, errorHandler, canvasListenerSupport, diagram, adapter, seg), new AsyncProcedure<IElement>() {
2326 public void execute(AsyncReadGraph graph, IElement e) {
2327 if (DebugPolicy.DEBUG_EDGE_LOAD)
2328 System.out.println("ADDED EDGE LOADED: " + e + " " + seg);
2331 synchronized (GraphToDiagramUpdater.this) {
2332 addedConnectionSegments.add(e);
2333 addedElementMap.put(seg, e);
2334 ConnectionEntityImpl ce = getConnectionEntity(seg);
2335 e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
2336 e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
2342 public void exception(AsyncReadGraph graph, Throwable throwable) {
2350 final IElement e = getMappedElement(seg);
2351 if (DebugPolicy.DEBUG_EDGE_LOAD)
2352 graph.asyncRequest(new ReadRequest() {
2354 public void run(ReadGraph graph) throws DatabaseException {
2355 System.out.println(" EXTERNALLY REMOVED CONNECTION SEGMENT: " + seg.toString() + " - "
2356 + seg.toString(graph) + " -> " + e);
2360 removedConnectionSegments.add(e);
2369 void executeDeferredLoaders(ReadGraph graph) throws DatabaseException {
2370 // The rest of the diagram loading passes
2371 Deque<IElement> q1 = new ArrayDeque<IElement>();
2372 Deque<IElement> q2 = new ArrayDeque<IElement>();
2373 collectElementLoaders(q1, addedElements);
2374 while (!q1.isEmpty()) {
2375 //System.out.println("DEFFERED LOADERS: " + q1);
2376 for (IElement e : q1) {
2377 ElementLoader loader = e.removeHint(DiagramModelHints.KEY_ELEMENT_LOADER);
2378 //System.out.println("EXECUTING DEFFERED LOADER: " + loader);
2379 loader.load(graph, diagram, e);
2382 collectElementLoaders(q2, q1);
2383 Deque<IElement> qt = q1;
2390 private void collectElementLoaders(Queue<IElement> queue, Collection<IElement> cs) {
2391 for (IElement e : cs) {
2392 ElementLoader loader = e.getHint(DiagramModelHints.KEY_ELEMENT_LOADER);
2398 public void process(ReadGraph graph) throws DatabaseException {
2399 // No changes? Do nothing.
2400 if (changes.isEmpty())
2403 ITask threadLog = ThreadLogger.task("processNodes");
2405 // NOTE: This order is important.
2406 Object task = Timing.BEGIN("processNodesConnections");
2407 //System.out.println("---- PROCESS NODES & CONNECTIONS BEGIN");
2408 if (!changes.elements.isEmpty()) {
2409 graph.syncRequest(new AsyncReadRequest() {
2411 public void run(AsyncReadGraph graph) throws DatabaseException {
2412 processNodes(graph);
2415 public String toString() {
2416 return "processNodes";
2420 //System.out.println("---- PROCESS NODES & CONNECTIONS END");
2424 threadLog = ThreadLogger.task("processConnections");
2426 processConnections();
2430 threadLog = ThreadLogger.task("processBranchPoints");
2432 //System.out.println("---- PROCESS BRANCH POINTS BEGIN");
2433 if (!changes.branchPoints.isEmpty()) {
2434 graph.syncRequest(new AsyncReadRequest() {
2436 public void run(AsyncReadGraph graph) throws DatabaseException {
2437 processBranchPoints(graph);
2440 public String toString() {
2441 return "processBranchPoints";
2446 //System.out.println("---- PROCESS BRANCH POINTS END");
2452 threadLog = ThreadLogger.task("processConnectionSegments");
2454 task = Timing.BEGIN("processConnectionSegments");
2456 //System.out.println("---- PROCESS CONNECTION SEGMENTS BEGIN");
2457 if (!changes.connectionSegments.isEmpty()) {
2458 graph.syncRequest(new AsyncReadRequest() {
2460 public void run(AsyncReadGraph graph) throws DatabaseException {
2461 processConnectionSegments(graph);
2464 public String toString() {
2465 return "processConnectionSegments";
2469 //System.out.println("---- PROCESS CONNECTION SEGMENTS END");
2475 threadLog = ThreadLogger.task("processRouteGraphConnections");
2477 task = Timing.BEGIN("processRouteGraphConnections");
2478 if (!changes.routeGraphConnections.isEmpty()) {
2479 graph.syncRequest(new AsyncReadRequest() {
2481 public void run(AsyncReadGraph graph) throws DatabaseException {
2482 processRouteGraphConnections(graph);
2485 public String toString() {
2486 return "processRouteGraphConnections";
2494 //System.out.println("---- AFTER LOADING");
2495 //for (IElement e : addedElements)
2496 // System.out.println(" ADDED ELEMENT: " + e);
2497 //for (IElement e : addedBranchPoints)
2498 // System.out.println(" ADDED BRANCH POINTS: " + e);
2500 task = Timing.BEGIN("executeDeferredLoaders");
2501 threadLog = ThreadLogger.task("executeDeferredLoaders");
2503 executeDeferredLoaders(graph);
2510 public boolean isEmpty() {
2511 return addedElements.isEmpty() && removedElements.isEmpty()
2512 && addedConnectionSegments.isEmpty() && removedConnectionSegments.isEmpty()
2513 && addedBranchPoints.isEmpty() && removedBranchPoints.isEmpty()
2514 && addedConnectionEntities.isEmpty() && removedConnectionEntities.isEmpty()
2515 && addedRouteGraphConnectionMap.isEmpty() && removedRouteGraphConnections.isEmpty()
2516 && !changes.elementOrderChanged;
2519 class DefaultConnectionSegmentAdapter implements ConnectionSegmentAdapter {
2522 public void getClass(AsyncReadGraph graph, EdgeResource edge, ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, IDiagram diagram, final AsyncProcedure<ElementClass> procedure) {
2523 if (info.connectionType != null) {
2524 NodeClassRequest request = new NodeClassRequest(canvas, diagram, info.connectionType, true);
2525 graph.asyncRequest(request, new CacheListener<ElementClass>(listenerSupport));
2526 graph.asyncRequest(request, procedure);
2528 procedure.execute(graph, null);
2533 public void load(AsyncReadGraph graph, final EdgeResource edge, final ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, final IDiagram diagram, final IElement element) {
2534 graph.asyncRequest(new Read<IElement>() {
2536 public IElement perform(ReadGraph graph) throws DatabaseException {
2537 //ITask task = ThreadLogger.getInstance().begin("LoadSegment");
2538 syncLoad(graph, edge, info, diagram, element);
2543 public String toString() {
2544 return "defaultConnectionSegmentAdapter";
2546 }, new DisposableListener<IElement>(listenerSupport) {
2549 public String toString() {
2550 return "DefaultConnectionSegmentAdapter listener for " + edge;
2554 public void execute(IElement loaded) {
2555 // Invoked when the element has been loaded.
2557 if (loaded == null) {
2562 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2564 if (DebugPolicy.DEBUG_EDGE_LISTENER)
2565 System.out.println("EDGE LoadListener for " + loaded + " " + data);
2567 if (addedElementMap.containsKey(data)) {
2568 // This element was just loaded, in
2569 // which case its hints need to
2570 // uploaded to the real mapped
2571 // element immediately.
2572 IElement mappedElement = getMappedElement(data);
2573 if (DebugPolicy.DEBUG_EDGE_LISTENER)
2574 System.out.println("LOADED ADDED EDGE, currently mapped element: " + mappedElement);
2575 if (mappedElement != null && (mappedElement instanceof Element)) {
2576 if (DebugPolicy.DEBUG_EDGE_LISTENER) {
2577 System.out.println(" mapped hints: " + mappedElement.getHints());
2578 System.out.println(" loaded hints: " + loaded.getHints());
2580 updateMappedElement((Element) mappedElement, loaded);
2583 // This element was already loaded.
2584 // Just schedule an update some time
2586 if (DebugPolicy.DEBUG_EDGE_LISTENER)
2587 System.out.println("PREVIOUSLY LOADED EDGE UPDATED, scheduling update into the future");
2588 offerGraphUpdate( edgeUpdater(element, loaded) );
2594 void syncLoad(ReadGraph graph, EdgeResource connectionSegment, ConnectionInfo info, IDiagram diagram, IElement element) throws DatabaseException {
2595 // Check that at least some data exists before continuing further.
2596 if (!graph.hasStatement(connectionSegment.first()) && !graph.hasStatement(connectionSegment.second())) {
2600 // Validate that both ends of the segment are
2601 // part of the same connection before loading.
2602 // This will happen for connections that are
2603 // modified through splitting and joining of
2604 // connection segments.
2605 Resource c = ConnectionUtil.tryGetConnection(graph, connectionSegment);
2607 // Ok, this segment is somehow invalid. Just don't load it.
2608 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2609 System.out.println("Skipping edge " + connectionSegment + ". Both segment ends are not part of the same connection.");
2613 if (!info.isValid()) {
2614 // This edge must be somehow invalid, don't proceed with loading.
2615 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2616 warning("Cannot load edge " + connectionSegment + ". ConnectionInfo " + info + " is invalid.", new DebugException("execution trace"));
2620 Element edge = (Element) element;
2621 edge.setHint(ElementHints.KEY_OBJECT, connectionSegment);
2623 // connectionSegment resources may currently be in a different
2624 // order than ConnectionInfo.firstEnd/secondEnd. Therefore the
2625 // segment ends must be resolved here.
2626 ConnectionSegmentEnd firstEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.first());
2627 ConnectionSegmentEnd secondEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.second());
2628 if (firstEnd == null || secondEnd == null) {
2629 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2630 warning("End attachments for edge " + connectionSegment + " are unresolved: (" + firstEnd + "," + secondEnd + ")", new DebugException("execution trace"));
2634 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2635 System.out.println("CONNECTION INFO: " + connectionSegment + " - " + info);
2636 DesignatedTerminal firstTerminal = DiagramGraphUtil.findDesignatedTerminal(
2637 graph, diagram, connectionSegment.first(), firstEnd);
2638 DesignatedTerminal secondTerminal = DiagramGraphUtil.findDesignatedTerminal(
2639 graph, diagram, connectionSegment.second(), secondEnd);
2641 // Edges must be connected at both ends in order for edge loading to succeed.
2642 String err = validateConnectivity(graph, connectionSegment, firstTerminal, secondTerminal);
2644 // Stop loading edge if the connectivity cannot be completely resolved.
2645 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2650 graph.syncRequest(new AsyncReadRequest() {
2652 public void run(AsyncReadGraph graph) {
2653 // NOTICE: Layer information is loaded from the connection entity resource
2654 // that is shared by all segments of the same connection.
2655 ElementFactoryUtil.loadLayersForElement(graph, layerManager, diagram, edge, info.connection,
2656 new AsyncProcedureAdapter<IElement>() {
2658 public void exception(AsyncReadGraph graph, Throwable t) {
2659 error("failed to load layers for connection segment", t);
2665 edge.setHintWithoutNotification(KEY_CONNECTION_BEGIN_PLACEHOLDER, new PlaceholderConnection(
2667 firstTerminal.element.getHint(ElementHints.KEY_OBJECT),
2668 firstTerminal.terminal));
2669 edge.setHintWithoutNotification(KEY_CONNECTION_END_PLACEHOLDER, new PlaceholderConnection(
2671 secondTerminal.element.getHint(ElementHints.KEY_OBJECT),
2672 secondTerminal.terminal));
2674 IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);
2675 if (modelingRules != null) {
2676 ConnectionVisualsLoader loader = diagram.getHint(DiagramModelHints.KEY_CONNECTION_VISUALS_LOADER);
2678 loader.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
2680 DiagramGraphUtil.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
2684 private String validateConnectivity(ReadGraph graph, EdgeResource edge,
2685 DesignatedTerminal firstTerminal,
2686 DesignatedTerminal secondTerminal)
2687 throws DatabaseException {
2688 boolean firstLoose = firstTerminal == null;
2689 boolean secondLoose = secondTerminal == null;
2690 boolean stray = firstLoose && secondLoose;
2691 if (firstTerminal == null || secondTerminal == null) {
2692 StringBuilder sb = new StringBuilder();
2693 sb.append("encountered ");
2694 sb.append(stray ? "stray" : "loose");
2695 sb.append(" connection segment, ");
2697 sb.append("first ");
2701 sb.append("second ");
2702 sb.append("end disconnected: ");
2703 sb.append(edge.toString(graph));
2705 sb.append(edge.toString());
2706 return sb.toString();
2713 ConnectionSegmentAdapter connectionSegmentAdapter = new DefaultConnectionSegmentAdapter();
2717 private static final Double DIAGRAM_UPDATE_DIAGRAM_PRIORITY = 1d;
2718 private static final Double DIAGRAM_UPDATE_NODE_PRIORITY = 2d;
2719 private static final Double DIAGRAM_UPDATE_CONNECTION_PRIORITY = 3d;
2720 private static final Double DIAGRAM_UPDATE_EDGE_PRIORITY = 4d;
2722 interface DiagramUpdater extends Runnable {
2723 Double getPriority();
2725 Comparator<DiagramUpdater> DIAGRAM_UPDATER_COMPARATOR = new Comparator<DiagramUpdater>() {
2727 public int compare(DiagramUpdater o1, DiagramUpdater o2) {
2728 return o1.getPriority().compareTo(o2.getPriority());
2733 interface GraphUpdateReactor {
2734 DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException;
2737 static abstract class AbstractDiagramUpdater implements DiagramUpdater, GraphUpdateReactor {
2738 protected final Double priority;
2739 protected final String runnerName;
2741 public AbstractDiagramUpdater(Double priority, String runnerName) {
2742 if (priority == null)
2743 throw new NullPointerException("null priority");
2744 if (runnerName == null)
2745 throw new NullPointerException("null runner name");
2746 this.priority = priority;
2747 this.runnerName = runnerName;
2751 public Double getPriority() {
2756 public AbstractDiagramUpdater graphUpdate(ReadGraph graph) {
2762 Object task = Timing.BEGIN(runnerName);
2767 protected void forDiagram() {
2771 public String toString() {
2772 return runnerName + "@" + System.identityHashCode(this) + " [" + priority + "]";
2777 * @param content the new contents of the diagram, must not be
2778 * <code>null</code>.
2780 private GraphUpdateReactor diagramGraphUpdater(final DiagramContents content) {
2781 if (content == null)
2782 throw new NullPointerException("null diagram content");
2784 return new GraphUpdateReactor() {
2786 public String toString() {
2787 return "DiagramGraphUpdater@" + System.identityHashCode(this);
2791 public DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException {
2792 // Never do anything here if the canvas has already been disposed.
2793 if (!GraphToDiagramSynchronizer.this.isAlive())
2796 // We must be prepared for the following changes in the diagram graph
2798 // - the diagram has been completely removed
2799 // - elements have been added
2800 // - elements have been removed
2802 // Element-specific changes are handled by the element query listeners:
2803 // - elements have been modified
2804 // - element position has changed
2805 // - element class (e.g. image) has changed
2807 diagramUpdateLock.lock();
2809 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE) {
2810 System.out.println("In diagramGraphUpdater:");
2811 System.out.println("-content = " + content);
2812 System.out.println("-previousContent = " + previousContent);
2815 // Find out what has changed since the last query.
2816 Object task = Timing.BEGIN("diagramContentDifference");
2817 DiagramContents lastContent = previousContent;
2818 DiagramContentChanges changes = content.differenceFrom(previousContent);
2819 previousContent = content;
2821 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2822 System.out.println(" changes: " + changes);
2824 // Bail out if there are no changes to react to.
2825 if (changes.isEmpty())
2828 // Load everything that needs to be loaded from the graph,
2829 // but don't update the UI model in this thread yet.
2830 task = Timing.BEGIN("updater.process");
2831 GraphToDiagramUpdater updater = new GraphToDiagramUpdater(lastContent, content, changes);
2832 GraphToDiagramSynchronizer.this.currentUpdater = updater;
2834 updater.process(graph);
2836 GraphToDiagramSynchronizer.this.currentUpdater = null;
2840 if (updater.isEmpty())
2843 // Return an updater that will update the UI run-time model.
2844 return diagramUpdater(updater);
2846 diagramUpdateLock.unlock();
2852 DiagramUpdater diagramUpdater(final GraphToDiagramUpdater updater) {
2853 return new AbstractDiagramUpdater(DIAGRAM_UPDATE_DIAGRAM_PRIORITY, "updateDiagram") {
2855 protected void forDiagram() {
2856 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2857 System.out.println("running diagram updater: " + this);
2859 // DiagramUtils.testDiagram(diagram);
2860 Set<IElement> dirty = new HashSet<IElement>();
2862 Object task2 = Timing.BEGIN("Preprocess connection changes");
2863 Map<ConnectionEntity, ConnectionChildren> connectionChangeData = new HashMap<ConnectionEntity, ConnectionChildren>(updater.changedConnectionEntities.size());
2864 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2865 connectionChangeData.put(cd.impl, cd.impl.getConnectionChildren());
2869 task2 = Timing.BEGIN("removeRouteGraphConnections");
2870 for (IElement removedRouteGraphConnection : updater.removedRouteGraphConnections) {
2871 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2872 System.out.println("removing route graph connection: " + removedRouteGraphConnection);
2874 ConnectionEntity ce = removedRouteGraphConnection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
2877 Object connectionData = ElementUtils.getObject(removedRouteGraphConnection);
2878 tempConnections.clear();
2879 for (Connection conn : ce.getTerminalConnections(tempConnections)) {
2880 ((Element) conn.node).removeHintWithoutNotification(new TerminalKeyOf(conn.terminal, connectionData, Connection.class));
2881 // To be sure the view will be up-to-date, mark the node
2882 // connected to the removed connection dirty.
2883 dirty.add(conn.node);
2888 task2 = Timing.BEGIN("removeBranchPoints");
2889 for (IElement removed : updater.removedBranchPoints) {
2890 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2891 System.out.println("removing branch point: " + removed);
2893 unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2894 removeNodeTopologyHints((Element) removed);
2896 IElement connection = ElementUtils.getParent(removed);
2897 if (connection != null) {
2898 dirty.add(connection);
2903 task2 = Timing.BEGIN("removeConnectionSegments");
2904 for (IElement removed : updater.removedConnectionSegments) {
2905 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2906 System.out.println("removing segment: " + removed);
2908 unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2909 removeEdgeTopologyHints((Element) removed);
2911 IElement connection = ElementUtils.getParent(removed);
2912 if (connection != null) {
2913 dirty.add(connection);
2918 task2 = Timing.BEGIN("removeElements");
2919 for (IElement removed : updater.removedElements) {
2920 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2921 System.out.println("removing element: " + removed);
2923 removed.setHint(KEY_REMOVE_RELATIONSHIPS, Boolean.TRUE);
2924 if (diagram.containsElement(removed)) {
2925 diagram.removeElement(removed);
2927 unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2928 removeNodeTopologyHints((Element) removed);
2930 // No use marking removed elements dirty.
2931 dirty.remove(removed);
2935 // TODO: get rid of this
2936 task2 = Timing.BEGIN("removeConnectionEntities");
2937 for (Resource ce : updater.removedConnectionEntities) {
2938 unmapConnection(ce);
2942 task2 = Timing.BEGIN("setConnectionData");
2943 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2944 cd.impl.setData(cd.segments, cd.branchPoints);
2948 // TODO: get rid of this
2949 task2 = Timing.BEGIN("addConnectionEntities");
2950 for (Map.Entry<Resource, ConnectionEntityImpl> entry : updater.addedConnectionEntities
2952 mapConnection(entry.getKey(), entry.getValue());
2956 task2 = Timing.BEGIN("addBranchPoints");
2957 for (IElement added : updater.addedBranchPoints) {
2958 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2959 System.out.println("adding branch point: " + added);
2961 mapElement(ElementUtils.getObject(added), added);
2963 IElement connection = ElementUtils.getParent(added);
2964 if (connection != null) {
2965 dirty.add(connection);
2970 // Add new elements at end of diagram, element order will be synchronized later.
2971 task2 = Timing.BEGIN("addElements");
2972 for(Resource r : updater.content.elements) {
2973 IElement added = updater.addedElementMap.get(r);
2975 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2976 System.out.println("adding element: " + added);
2978 //Object task3 = BEGIN("mapElement " + added);
2979 Object task3 = Timing.BEGIN("mapElement");
2980 mapElement(added.getHint(ElementHints.KEY_OBJECT), added);
2983 //task3 = BEGIN("addElement " + added);
2984 task3 = Timing.BEGIN("addElement");
2985 //System.out.println("diagram.addElement: " + added + " - " + diagram);
2986 diagram.addElement(added);
2993 // We've ensured that all nodes must have been and
2994 // mapped before reaching this.
2995 task2 = Timing.BEGIN("addConnectionSegments");
2996 for (IElement added : updater.addedConnectionSegments) {
2997 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2998 System.out.println("adding segment: " + added);
3000 PlaceholderConnection cb = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_BEGIN_PLACEHOLDER);
3001 PlaceholderConnection ce = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_END_PLACEHOLDER);
3002 if (cb == null || ce == null) {
3003 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
3004 warning("ignoring connection segment " + added + ", connectivity was not resolved (begin=" + cb + ", end=" + ce +")", null);
3008 mapElement(ElementUtils.getObject(added), added);
3010 IElement beginNode = assertMappedElement(cb.node);
3011 IElement endNode = assertMappedElement(ce.node);
3014 connect(added, cb.end, beginNode, cb.terminal);
3016 connect(added, ce.end, endNode, ce.terminal);
3018 IElement connection = ElementUtils.getParent(added);
3019 if (connection != null) {
3020 dirty.add(connection);
3025 // We've ensured that all nodes must have been and
3026 // mapped before reaching this.
3028 task2 = Timing.BEGIN("handle dirty RouteGraph connections");
3029 for (IElement addedRouteGraphConnection : updater.addedRouteGraphConnectionMap.values()) {
3030 updateDirtyRouteGraphConnection(addedRouteGraphConnection, dirty);
3034 // Prevent memory leaks
3035 tempConnections.clear();
3037 // Make sure that the diagram element order matches that of the database.
3038 final TObjectIntHashMap<IElement> orderMap = new TObjectIntHashMap<IElement>(2 * updater.content.elements.size());
3040 for (Resource r : updater.content.elements) {
3041 IElement e = getMappedElement(r);
3046 diagram.sort(new Comparator<IElement>() {
3048 public int compare(IElement e1, IElement e2) {
3049 int o1 = orderMap.get(e1);
3050 int o2 = orderMap.get(e2);
3055 // TODO: consider removing this. The whole thing should work without it and
3056 // this "fix" will only be hiding the real problems.
3057 task2 = Timing.BEGIN("fixChangedConnections");
3058 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
3063 task2 = Timing.BEGIN("validateAndFix");
3064 DiagramUtils.validateAndFix(diagram, dirty);
3067 // This will fire connection entity change listeners
3068 task2 = Timing.BEGIN("Postprocess connection changes");
3069 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
3070 ConnectionChildren oldChildren = connectionChangeData.get(cd.impl);
3071 if (oldChildren != null) {
3072 ConnectionChildren currentChildren = cd.impl.getConnectionChildren();
3073 cd.impl.fireListener(oldChildren, currentChildren);
3078 task2 = Timing.BEGIN("setDirty");
3079 for (IElement e : dirty) {
3080 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
3081 System.out.println("MARKING ELEMENT DIRTY: " + e);
3082 e.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3086 // Signal about possible changes in the z-order of diagram elements.
3087 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
3088 System.out.println("MARKING DIAGRAM DIRTY: " + diagram);
3089 diagram.setHint(Hints.KEY_DIRTY, Hints.VALUE_Z_ORDER_CHANGED);
3091 // Mark the updater as "processed".
3094 // Inform listeners that the diagram has been updated.
3095 diagram.setHint(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, Boolean.TRUE);
3104 private void updateDirtyRouteGraphConnection(IElement connection, Set<IElement> dirtySet) {
3105 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
3106 System.out.println("updating dirty route graph connection: " + connection);
3108 ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
3112 tempConnections.clear();
3113 Object connectionData = ElementUtils.getObject(connection);
3114 for (Connection conn : ce.getTerminalConnections(tempConnections)) {
3115 ((Element) conn.node).setHintWithoutNotification(
3116 new TerminalKeyOf(conn.terminal, connectionData, Connection.class),
3118 if (dirtySet != null)
3119 dirtySet.add(conn.node);
3122 // Prevent memory leaks.
3123 tempConnections.clear();
3126 abstract class ElementUpdater extends AbstractDiagramUpdater {
3127 private final IElement newElement;
3129 public ElementUpdater(Double priority, String runnerName, IElement newElement) {
3130 super(priority, runnerName);
3131 if (newElement == null)
3132 throw new NullPointerException("null element");
3133 this.newElement = newElement;
3137 public String toString() {
3138 return super.toString() + "[" + newElement + "]";
3143 // System.out.println("ElementUpdateRunner new=" + newElement);
3144 Object elementResource = newElement.getHint(ElementHints.KEY_OBJECT);
3145 // System.out.println("ElementUpdateRunner res=" + elementResource);
3146 final Element mappedElement = (Element) getMappedElement(elementResource);
3147 // System.out.println("ElementUpdateRunner mapped=" + mappedElement);
3148 if (mappedElement == null) {
3149 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE) {
3150 System.out.println("SKIP DIAGRAM UPDATE " + this + " for element resource " + elementResource + ", no mapped element (newElement=" + newElement + ")");
3152 // Indicates the element has been removed from the graph.
3156 Object task = Timing.BEGIN(runnerName);
3157 forMappedElement(mappedElement);
3161 protected abstract void forMappedElement(Element mappedElement);
3164 ElementUpdater nodeUpdater(final Resource resource, final IElement newElement) {
3166 return new ElementUpdater(DIAGRAM_UPDATE_NODE_PRIORITY, "updateNode", newElement) {
3168 Collection<Terminal> getTerminals(IElement e) {
3169 Collection<Terminal> ts = Collections.emptyList();
3170 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
3172 ts = new ArrayList<Terminal>();
3173 tt.getTerminals(newElement, ts);
3179 protected void forMappedElement(final Element mappedElement) {
3180 if (DebugPolicy.DEBUG_NODE_UPDATE)
3181 System.out.println("running node updater: " + this + " - new element: " + newElement);
3183 // Newly loaded node elements NEVER contain topology-related
3184 // hints, i.e. TerminalKeyOf hints. Instead all connections are
3185 // actually set into element hints when connection edges are
3188 Collection<Terminal> oldTerminals = getTerminals(mappedElement);
3189 Collection<Terminal> newTerminals = getTerminals(newElement);
3190 if (!oldTerminals.equals(newTerminals)) {
3191 // Okay, there are differences in the terminals. Need to fix
3192 // the TerminalKeyOf hint values to use the new terminal
3193 // instances when correspondences exist.
3195 // If there is no correspondence for an old terminal, we
3196 // are simply forced to remove the hints related to this
3199 Map<Terminal, Terminal> newTerminalMap = new HashMap<Terminal, Terminal>(newTerminals.size());
3200 for (Terminal t : newTerminals) {
3201 newTerminalMap.put(t, t);
3204 for (Map.Entry<TerminalKeyOf, Object> entry : mappedElement.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3205 TerminalKeyOf key = entry.getKey();
3206 Connection c = (Connection) entry.getValue();
3207 if (c.node == mappedElement) {
3208 Terminal newTerminal = newTerminalMap.get(c.terminal);
3209 if (newTerminal != null) {
3210 c = new Connection(c.edge, c.end, c.node, newTerminal);
3211 ((Element) c.edge).setHintWithoutNotification(EndKeyOf.get(c.end), c);
3213 mappedElement.removeHintWithoutNotification(key);
3219 updateMappedElement(mappedElement, newElement);
3220 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3225 ElementUpdater connectionUpdater(final Object data, final IElement newElement) {
3226 return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateConnection", newElement) {
3228 public void forMappedElement(Element mappedElement) {
3229 if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
3230 System.out.println("running connection updater: " + this + " - new element: " + newElement
3231 + " with data " + data);
3233 // This is kept up-to-date by GDS, make sure not to overwrite it
3234 // from the mapped element.
3235 newElement.removeHint(ElementHints.KEY_CONNECTION_ENTITY);
3237 updateMappedElement(mappedElement, newElement);
3238 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3243 ElementUpdater edgeUpdater(final Object data, final IElement newElement) {
3244 return new ElementUpdater(DIAGRAM_UPDATE_EDGE_PRIORITY, "updateEdge", newElement) {
3246 public void forMappedElement(Element mappedElement) {
3247 if (DebugPolicy.DEBUG_EDGE_UPDATE)
3248 System.out.println("running edge updater: " + this + " - new element: " + newElement
3249 + " with data " + data);
3251 updateMappedElement(mappedElement, newElement);
3252 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3257 ElementUpdater routeGraphConnectionUpdater(final Object data, final IElement newElement, final Set<Object> dirtyNodes) {
3258 return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateRouteGraphConnection", newElement) {
3260 public void forMappedElement(Element mappedElement) {
3261 if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
3262 System.out.println("running route graph connection updater: " + this + " - new element: " + newElement
3263 + " with data " + data);
3265 // Remove all TerminalKeyOf hints from nodes that were
3266 // previously connected to the connection (mappedElement)
3267 // before updating mappedElement with new topology information.
3269 for (Object dirtyNodeObject : dirtyNodes) {
3270 Element dirtyNode = (Element) getMappedElement(dirtyNodeObject);
3271 if (dirtyNode == null)
3273 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
3274 System.out.println("preparing node with dirty connectivity: " + dirtyNode);
3276 for (Map.Entry<TerminalKeyOf, Object> entry : dirtyNode.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3277 Connection conn = (Connection) entry.getValue();
3278 Object connectionNode = conn.edge.getHint(ElementHints.KEY_OBJECT);
3279 if (data.equals(connectionNode)) {
3280 dirtyNode.removeHintWithoutNotification(entry.getKey());
3285 // Update connection information, including topology
3286 updateMappedElement(mappedElement, newElement);
3288 // Reinstall TerminalKeyOf hints into nodes that are now connected
3289 // to mappedElement to keep diagram run-time model properly in sync
3290 // with the database.
3291 updateDirtyRouteGraphConnection(mappedElement, null);
3293 // TODO: should mark dirty nodes' scene graph dirty ?
3295 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3301 * Copies hints from <code>newElement</code> to <code>mappedElement</code>
3302 * asserting some validity conditions at the same time.
3304 * @param mappedElement
3307 static void updateMappedElement(Element mappedElement, IElement newElement) {
3308 if (mappedElement == newElement)
3309 // Can't update anything if the two elements are the same.
3312 ElementClass oldClass = mappedElement.getElementClass();
3313 ElementClass newClass = newElement.getElementClass();
3315 Object mappedData = mappedElement.getHint(ElementHints.KEY_OBJECT);
3316 Object newData = newElement.getHint(ElementHints.KEY_OBJECT);
3318 assert mappedData != null;
3319 assert newData != null;
3320 assert mappedData.equals(newData);
3322 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3323 System.out.println("Updating mapped element, setting hints\n from: " + newElement + "\n into: " + mappedElement);
3326 // TODO: consider if this equals check is a waste of time or does it pay
3327 // off due to having to reinitialize per-class caches for the new
3328 // ElementClass that are constructed on the fly?
3329 if (!newClass.equals(oldClass)) {
3330 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3331 System.out.println(" old element class: " + oldClass);
3332 System.out.println(" new element class: " + newClass);
3334 mappedElement.setElementClass(newClass);
3337 // Tuukka@2010-02-19: replaced with notifications for making
3338 // the graph synchronizer more transparent to the client.
3340 // Hint notifications will not work when this is used.
3341 //mappedElement.setHintsWithoutNotification(newElement.getHints());
3343 Map<DiscardableKey, Object> discardableHints = mappedElement.getHintsOfClass(DiscardableKey.class);
3345 // Set all hints from newElement to mappedElement.
3346 // Leave any hints in mappedElement but not in newElement as is.
3347 Map<Key, Object> hints = newElement.getHints();
3348 Map<Key, Object> newHints = new HashMap<Key, Object>();
3349 for (Map.Entry<Key, Object> entry : hints.entrySet()) {
3350 Key key = entry.getKey();
3351 Object newValue = entry.getValue();
3352 Object oldValue = mappedElement.getHint(key);
3353 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE_DETAIL) {
3354 System.out.println(" hint " + key + " compare values: " + oldValue + " -> " + newValue);
3356 if (!newValue.equals(oldValue)) {
3357 newHints.put(key, newValue);
3358 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3359 System.out.format(" %-42s : %64s -> %-64s\n", key, oldValue, newValue);
3362 // If the hint value has not changed but the hint still exists
3363 // we don't need to discard it even if it is considered
3365 discardableHints.remove(key);
3369 // Set all hints at once and send notifications after setting the values.
3370 if (!discardableHints.isEmpty()) {
3371 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE)
3372 System.out.println("Discarding " + discardableHints.size() + " discardable hints:\n " + discardableHints);
3373 mappedElement.removeHints(discardableHints.keySet());
3375 if (!newHints.isEmpty()) {
3376 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3377 System.out.println("Updating mapped element, setting new hints:\n\t"
3378 + EString.implode(newHints.entrySet(), "\n\t") + "\nto replace old hints\n\t"
3379 + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
3381 mappedElement.setHints(newHints);
3383 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3384 System.out.println("All hints after update:\n\t"
3385 + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
3389 class TransactionListener extends SessionEventListenerAdapter {
3392 public void writeTransactionStarted() {
3393 startTime = System.nanoTime();
3394 if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
3395 System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionStarted");
3396 inWriteTransaction.set(true);
3399 public void writeTransactionFinished() {
3400 long endTime = System.nanoTime();
3401 if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
3402 System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionFinished: " + (endTime - startTime)*1e-6 + " ms");
3403 inWriteTransaction.set(false);
3404 scheduleGraphUpdates();
3408 Object graphUpdateLock = new Object();
3409 TransactionListener sessionListener = null;
3410 AtomicBoolean inWriteTransaction = new AtomicBoolean(false);
3411 AtomicBoolean graphUpdateRequestScheduled = new AtomicBoolean(false);
3412 List<GraphUpdateReactor> queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();
3414 private void offerGraphUpdate(GraphUpdateReactor update) {
3415 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3416 System.out.println("offerGraphUpdate: " + update);
3417 boolean inWrite = inWriteTransaction.get();
3418 synchronized (graphUpdateLock) {
3419 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3420 System.out.println("queueing graph update: " + update);
3421 queuedGraphUpdates.add(update);
3424 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3425 System.out.println("scheduling queued graph update immediately: " + update);
3426 scheduleGraphUpdates();
3430 private Collection<GraphUpdateReactor> scrubGraphUpdates() {
3431 synchronized (graphUpdateLock) {
3432 if (queuedGraphUpdates.isEmpty())
3433 return Collections.emptyList();
3434 final List<GraphUpdateReactor> updates = queuedGraphUpdates;
3435 queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();
3440 private void scheduleGraphUpdates() {
3441 synchronized (graphUpdateLock) {
3442 if (queuedGraphUpdates.isEmpty())
3444 if (!graphUpdateRequestScheduled.compareAndSet(false, true))
3448 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3449 System.out.println("scheduling " + queuedGraphUpdates.size() + " queued graph updates with ");
3451 session.asyncRequest(new ReadRequest() {
3453 public void run(final ReadGraph graph) throws DatabaseException {
3454 Collection<GraphUpdateReactor> updates;
3455 synchronized (graphUpdateLock) {
3456 graphUpdateRequestScheduled.set(false);
3457 updates = scrubGraphUpdates();
3460 if (!GraphToDiagramSynchronizer.this.isAlive())
3463 processGraphUpdates(graph, updates);
3465 }, new ProcedureAdapter<Object>() {
3467 public void exception(Throwable t) {
3473 private void processGraphUpdates(ReadGraph graph, final Collection<GraphUpdateReactor> graphUpdates)
3474 throws DatabaseException {
3475 final List<DiagramUpdater> diagramUpdates = new ArrayList<DiagramUpdater>(graphUpdates.size());
3477 // Run GraphUpdaters and gather DiagramUpdaters.
3478 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3479 System.out.println("Running GRAPH updates: " + graphUpdates);
3480 for (GraphUpdateReactor graphUpdate : graphUpdates) {
3481 DiagramUpdater diagramUpdate = graphUpdate.graphUpdate(graph);
3482 if (diagramUpdate != null) {
3483 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3484 System.out.println(graphUpdate + " => " + diagramUpdate);
3485 diagramUpdates.add(diagramUpdate);
3489 if (diagramUpdates.isEmpty())
3492 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3493 System.out.println("Diagram updates: " + diagramUpdates);
3494 Collections.sort(diagramUpdates, DiagramUpdater.DIAGRAM_UPDATER_COMPARATOR);
3495 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3496 System.out.println("Sorted diagram updates: " + diagramUpdates);
3498 ThreadUtils.asyncExec(canvas.getThreadAccess(), new StateRunnable() {
3501 if (GraphToDiagramSynchronizer.this.isAlive() && getState() != State.DISPOSED)
3502 safeRunInState(State.UPDATING_DIAGRAM, this);
3506 public void execute() throws InvocationTargetException {
3507 // Block out diagram write transactions.
3508 DiagramUtils.inDiagramTransaction(diagram, TransactionType.READ, new Runnable() {
3511 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3512 System.out.println("Running DIAGRAM updates: " + diagramUpdates);
3513 for (DiagramUpdater update : diagramUpdates) {
3514 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3515 System.out.println("Running DIAGRAM update: " + update);
3526 private void attachSessionListener(Session session) {
3527 SessionEventSupport support = session.peekService(SessionEventSupport.class);
3528 if (support != null) {
3529 sessionListener = new TransactionListener();
3530 support.addListener(sessionListener);
3534 private void detachSessionListener() {
3535 if (sessionListener != null) {
3536 session.getService(SessionEventSupport.class).removeListener(sessionListener);
3537 sessionListener = null;
3542 // ------------------------------------------------------------------------
3543 // GRAPH TO DIAGRAM SYNCHRONIZATION LOGIC END
3544 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3546 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3547 // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
3548 // This does not try to synchronize anything back into the graph.
3549 // ------------------------------------------------------------------------
3551 IHintListener elementHintValidator = new HintListenerAdapter() {
3553 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
3554 if (!(sender instanceof Element))
3555 throw new IllegalStateException("invalid sender: " + sender);
3556 Element e = (Element) sender;
3557 if (newValue != null) {
3558 if (key instanceof TerminalKeyOf) {
3559 Connection c = (Connection) newValue;
3561 throw new IllegalStateException("TerminalKeyOf hint of node " + e + " refers to a different node " + c.node + ". Should be the same.");
3562 Object edgeObject = ElementUtils.getObject(c.edge);
3563 if (!(edgeObject instanceof EdgeResource))
3564 throw new IllegalStateException("EndKeyOf hint of edge " + c.edge + " refers contains an invalid object: " + edgeObject);
3565 } else if (key instanceof EndKeyOf) {
3566 Connection c = (Connection) newValue;
3568 throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers to a different edge " + c.edge + ". Should be the same.");
3569 Object edgeObject = ElementUtils.getObject(c.edge);
3570 if (!(edgeObject instanceof EdgeResource))
3571 throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers contains an invalid object: " + edgeObject);
3577 class DiagramListener implements CompositionListener, CompositionVetoListener {
3579 public boolean beforeElementAdded(IDiagram d, IElement e) {
3580 // Make sure that MutatedElements NEVER get added to the diagram.
3582 if (!(e instanceof Element)) {
3583 // THIS IS NOT GOOD!
3584 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"));
3585 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.");
3589 // Perform sanity checks that might veto the element addition.
3590 boolean pass = true;
3592 // Check that all elements added to the diagram are adaptable to Resource
3593 ElementClass ec = e.getElementClass();
3594 Resource resource = ElementUtils.adapt(ec, Resource.class);
3595 if (resource == null) {
3597 LOGGER.error("", new Exception("Attempted to add an element to the diagram that is not adaptable to Resource: " + e + ", class: " + ec));
3600 // Sanity check connection hints
3601 for (Map.Entry<TerminalKeyOf, Object> entry : e.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3602 Connection c = (Connection) entry.getValue();
3603 Object edgeObject = ElementUtils.getObject(c.edge);
3605 System.err.println("Invalid node in TerminalKeyOf hint: " + entry.getKey() + "=" + entry.getValue());
3606 System.err.println("\tconnection.edge=" + c.edge);
3607 System.err.println("\tconnection.node=" + c.node);
3608 System.err.println("\tconnection.end=" + c.end);
3609 System.err.println("\telement=" + e);
3610 System.err.println("\telement class=" + e.getElementClass());
3613 if (!(edgeObject instanceof EdgeResource)) {
3614 System.err.println("Invalid object in TerminalKeyOf hint edge: " + entry.getKey() + "=" + entry.getValue());
3615 System.err.println("\tconnection.edge=" + c.edge);
3616 System.err.println("\tconnection.node=" + c.node);
3617 System.err.println("\tconnection.end=" + c.end);
3618 System.err.println("\telement=" + e);
3619 System.err.println("\telement class=" + e.getElementClass());
3630 public boolean beforeElementRemoved(IDiagram d, IElement e) {
3631 // Never veto diagram changes.
3636 public void onElementAdded(IDiagram d, IElement e) {
3637 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
3638 System.out.println("[" + d + "] element added: " + e);
3640 if (USE_ELEMENT_VALIDATING_LISTENERS)
3641 e.addHintListener(elementHintValidator);
3645 public void onElementRemoved(IDiagram d, IElement e) {
3646 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
3647 System.out.println("[" + d + "] element removed: " + e);
3649 if (USE_ELEMENT_VALIDATING_LISTENERS)
3650 e.removeHintListener(elementHintValidator);
3652 if (e.containsHint(KEY_REMOVE_RELATIONSHIPS))
3653 relationshipHandler.denyAll(diagram, e);
3657 DiagramListener diagramListener = new DiagramListener();
3659 static void removeNodeTopologyHints(Element node) {
3660 Set<TerminalKeyOf> terminalKeys = node.getHintsOfClass(TerminalKeyOf.class).keySet();
3661 if (!terminalKeys.isEmpty()) {
3662 removeNodeTopologyHints(node, terminalKeys);
3666 static void removeNodeTopologyHints(Element node, Collection<TerminalKeyOf> terminalKeys) {
3667 for (TerminalKeyOf key : terminalKeys) {
3668 Connection c = node.removeHintWithoutNotification(key);
3670 removeEdgeTopologyHints((Element) c.edge);
3675 static void removeEdgeTopologyHints(Element edge) {
3676 Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
3677 for (EndKeyOf key : EndKeyOf.KEYS) {
3678 Connection c = edge.removeHintWithoutNotification(key);
3680 ((Element) c.node).removeHintWithoutNotification(new TerminalKeyOf(c.terminal, edgeData, Connection.class));
3685 // ------------------------------------------------------------------------
3686 // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
3687 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3689 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3690 // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC BEGIN
3691 // ------------------------------------------------------------------------
3693 void adaptDiagramClass(AsyncReadGraph graph, Resource diagram, final AsyncProcedure<DiagramClass> procedure) {
3694 graph.forAdapted(diagram, DiagramClass.class, new AsyncProcedure<DiagramClass>() {
3696 public void exception(AsyncReadGraph graph, Throwable throwable) {
3697 procedure.exception(graph, throwable);
3701 public void execute(AsyncReadGraph graph, DiagramClass dc) {
3702 // To move TopologyImpl out of here, we need a separate
3703 // DiagramClassFactory that takes a canvas context as an argument.
3704 // DataElementMapImpl, ElementFactoryImpl and diagramLifeCycle can
3705 // safely stay here.
3706 procedure.execute(graph, dc.newClassWith(
3707 // This handler takes care of the topology of the diagram model.
3708 // It sets and fixes element hints related to describing the
3709 // connectivity of elements.
3711 // TODO: not quite sure whether this can prove itself useful or not.
3713 // This map provides a bidirectional mapping between
3714 // IElement and back-end objects.
3716 // This handler provides a facility to adapt an element class
3717 // to work properly with a diagram synchronized using this
3718 // GraphToDiagramSynchronizer.
3719 substituteElementClass,
3720 // These handlers provide a way to create simple identified
3721 // uni- and bidirectional relationships between any diagram
3722 // objects/elements.
3723 relationshipHandler));
3728 static Connection connect(IElement edge, EdgeEnd end, IElement element, Terminal terminal) {
3729 Connection c = new Connection(edge, end, element, terminal);
3731 Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
3732 if (DebugPolicy.DEBUG_CONNECTION) {
3733 System.out.println("[connect](edge=" + edge + ", edgeData=" + edgeData + ", end=" + end + ", element="
3734 + element + ", terminal=" + terminal + ")");
3737 TerminalKeyOf key = new TerminalKeyOf(terminal, edgeData, Connection.class);
3738 element.setHint(key, c);
3740 EndKeyOf key2 = EndKeyOf.get(end);
3741 edge.setHint(key2, c);
3746 static class ElementFactoryImpl implements ElementFactory {
3748 public IElement spawnNew(ElementClass clazz) {
3749 IElement e = Element.spawnNew(clazz);
3754 ElementFactoryImpl elementFactory = new ElementFactoryImpl();
3756 public static final Object FIRST_TIME = new Object() {
3758 public String toString() {
3759 return "FIRST_TIME";
3765 * A base for all listeners of graph requests performed internally by
3766 * GraphToDiagramSynchronizer.
3768 * @param <T> type of stored data element
3769 * @param <Result> query result type
3771 abstract class BaseListener<T, Result> implements AsyncListener<Result> {
3773 protected final T data;
3775 private Object oldResult = FIRST_TIME;
3777 protected boolean disposed = false;
3779 final ICanvasContext canvas;
3781 public BaseListener(T data) {
3782 this.canvas = GraphToDiagramSynchronizer.this.canvas;
3787 public void exception(AsyncReadGraph graph, Throwable throwable) {
3788 // Exceptions are always expected to mean that the listener should
3789 // be considered disposed once a query fails.
3793 abstract void execute(AsyncReadGraph graph, Object oldResult, Object newResult);
3796 public void execute(AsyncReadGraph graph, Result result) {
3797 if (DebugPolicy.DEBUG_LISTENER_BASE)
3798 System.out.println("BaseListener: " + result);
3801 if (DebugPolicy.DEBUG_LISTENER_BASE)
3802 System.out.println("BaseListener: execute invoked although listener is disposed!");
3806 // A null result will permanently mark this listener disposed!
3807 if (result == null) {
3809 if (DebugPolicy.DEBUG_LISTENER_BASE)
3810 System.out.println(this + " null result, listener marked disposed");
3813 if (oldResult == FIRST_TIME) {
3815 if (DebugPolicy.DEBUG_LISTENER_BASE)
3816 System.out.println(this + " first result computed: " + result);
3818 if (DebugPolicy.DEBUG_LISTENER_BASE)
3819 System.out.println(this + " result changed from '" + oldResult + "' to '" + result + "'");
3821 execute(graph, oldResult, result);
3829 public boolean isDisposed() {
3833 boolean alive = isAlive();
3834 //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", isAlive=" + alive);
3837 // If a mapping no longer exists for this element, dispose of this
3839 //IElement e = getMappedElement(resource);
3840 //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", element=" + e);
3846 // protected void finalize() throws Throwable {
3847 // System.out.println("finalize listener: " + this);
3848 // super.finalize();
3852 class DiagramClassRequest extends BaseRequest2<Resource, DiagramClass> {
3853 public DiagramClassRequest(Resource resource) {
3854 super(GraphToDiagramSynchronizer.this.canvas, resource);
3858 public void perform(AsyncReadGraph graph, AsyncProcedure<DiagramClass> procedure) {
3859 adaptDiagramClass(graph, data, procedure);
3863 public class DiagramContentListener extends BaseListener<Resource, DiagramContents> {
3865 public DiagramContentListener(Resource resource) {
3870 public void execute(final AsyncReadGraph graph, Object oldResult, Object newResult) {
3871 final DiagramContents newContent = (newResult == null) ? new DiagramContents()
3872 : (DiagramContents) newResult;
3874 // diagramGraphUpdater is called synchronously during
3875 // loading. The first result will not get updated through
3876 // this listener but through loadDiagram.
3878 if (DebugPolicy.DISABLE_DIAGRAM_UPDATES) {
3879 System.out.println("Skipped diagram content update: " + newResult);
3883 if (DebugPolicy.DEBUG_DIAGRAM_LISTENER)
3884 System.out.println("diagram contents changed: " + oldResult + " => " + newResult);
3886 offerGraphUpdate( diagramGraphUpdater(newContent) );
3890 public boolean isDisposed() {
3895 public void exception(AsyncReadGraph graph, Throwable t) {
3896 super.exception(graph, t);
3897 error("DiagramContentRequest failed", t);
3901 // ------------------------------------------------------------------------
3902 // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC END
3903 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3905 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3906 // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER BEGIN
3907 // ------------------------------------------------------------------------
3909 static class TopologyImpl implements Topology {
3912 public Connection getConnection(IElement edge, EdgeEnd end) {
3913 Key key = EndKeyOf.get(end);
3914 Connection c = edge.getHint(key);
3921 public void getConnections(IElement node, Terminal terminal, Collection<Connection> connections) {
3922 // IDiagram d = ElementUtils.getDiagram(node);
3923 for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3924 // First check that the terminal matches.
3925 TerminalKeyOf key = entry.getKey();
3926 if (!key.getTerminal().equals(terminal))
3929 Connection c = (Connection) entry.getValue();
3937 public void connect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
3938 if (node != null && terminal != null)
3939 GraphToDiagramSynchronizer.connect(edge, end, node, terminal);
3941 if (DebugPolicy.DEBUG_CONNECTION) {
3942 if (end == EdgeEnd.Begin)
3943 System.out.println("Connection started from: " + edge + ", " + end + ", " + node + ", " + terminal);
3945 System.out.println("Creating connection to: " + edge + ", " + end + ", " + node + ", " + terminal);
3950 public void disconnect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
3951 EndKeyOf edgeKey = EndKeyOf.get(end);
3952 Connection c = edge.getHint(edgeKey);
3954 throw new UnsupportedOperationException("cannot disconnect, no Connection in edge " + edge);
3956 for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3957 Connection cc = (Connection) entry.getValue();
3959 node.removeHint(entry.getKey());
3960 edge.removeHint(edgeKey);
3965 throw new UnsupportedOperationException("cannot disconnect, no connection between found between edge "
3966 + edge + " and node " + node);
3970 Topology diagramTopology = new TopologyImpl();
3972 // ------------------------------------------------------------------------
3973 // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER END
3974 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3976 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3977 // DIAGRAM OBJECT RELATIONSHIP HANDLER BEGIN
3978 // ------------------------------------------------------------------------
3980 RelationshipHandler relationshipHandler = new RelationshipHandler() {
3982 AssociativeMap map = new AssociativeMap(Associativity.of(true, false, false));
3984 Object getPossibleObjectOrElement(Object o) {
3985 if (o instanceof IElement) {
3986 IElement e = (IElement) o;
3987 Object oo = e.getHint(ElementHints.KEY_OBJECT);
3988 return oo != null ? oo : e;
3993 IElement getElement(Object o) {
3994 if (o instanceof IElement)
3995 return (IElement) o;
3996 return getMappedElement(o);
4000 public void claim(IDiagram diagram, Object subject,
4001 Relationship predicate, Object object) {
4002 Object sd = getPossibleObjectOrElement(subject);
4003 Object od = getPossibleObjectOrElement(object);
4005 Collection<Tuple> ts = null;
4006 Relationship inverse = predicate.getInverse();
4007 if (inverse != null)
4008 ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od, inverse, sd));
4010 ts = Collections.singletonList(new Tuple(sd, predicate, od));
4012 synchronized (this) {
4016 if (DebugPolicy.DEBUG_RELATIONSHIP) {
4017 new Exception().printStackTrace();
4018 System.out.println("Claimed relationships:");
4020 System.out.println("\t" + t);
4024 private void doDeny(IDiagram diagram, Object subject,
4025 Relationship predicate, Object object) {
4026 Object sd = getPossibleObjectOrElement(subject);
4027 Object od = getPossibleObjectOrElement(object);
4028 if (sd == subject || od == object) {
4030 .println("WARNING: denying relationship '"
4032 + "' between diagram element(s), not back-end object(s): "
4033 + sd + " -> " + od);
4036 Collection<Tuple> ts = null;
4037 Relationship inverse = predicate.getInverse();
4038 if (inverse != null)
4039 ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od,
4042 ts = Collections.singleton(new Tuple(sd, predicate, od));
4044 synchronized (this) {
4048 if (DebugPolicy.DEBUG_RELATIONSHIP) {
4049 new Exception().printStackTrace();
4050 System.out.println("Denied relationships:");
4052 System.out.println("\t" + t);
4057 public void deny(IDiagram diagram, Object subject,
4058 Relationship predicate, Object object) {
4059 synchronized (this) {
4060 doDeny(diagram, subject, predicate, object);
4065 public void deny(IDiagram diagram, Relation relation) {
4066 synchronized (this) {
4067 doDeny(diagram, relation.getSubject(), relation
4068 .getRelationship(), relation.getObject());
4073 public void denyAll(IDiagram diagram, Object element) {
4074 synchronized (this) {
4075 for (Relation relation : getRelations(diagram, element, null)) {
4076 doDeny(diagram, relation.getSubject(), relation
4077 .getRelationship(), relation.getObject());
4083 public Collection<Relation> getRelations(IDiagram diagram,
4084 Object element, Collection<Relation> result) {
4085 if (DebugPolicy.DEBUG_GET_RELATIONSHIP)
4086 System.out.println("getRelations(" + element + ")");
4087 Object e = getPossibleObjectOrElement(element);
4089 Collection<Tuple> tuples = null;
4090 synchronized (this) {
4091 tuples = map.get(new Tuple(e, null, null), null);
4094 if (DebugPolicy.DEBUG_GET_RELATIONSHIP) {
4095 System.out.println("Result size: " + tuples.size());
4096 for (Tuple t : tuples)
4097 System.out.println("\t" + t);
4100 if (tuples.isEmpty())
4101 return Collections.emptyList();
4103 result = new ArrayList<Relation>(tuples.size());
4104 for (Tuple t : tuples) {
4105 Object obj = t.getField(2);
4106 IElement el = getElement(obj);
4107 Relationship r = (Relationship) t.getField(1);
4108 result.add(new Relation(element, r, el != null ? el : obj));
4115 // ------------------------------------------------------------------------
4116 // DIAGRAM ELEMENT RELATIONSHIP HANDLER END
4117 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>