1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 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 gnu.trove.map.hash.TObjectIntHashMap;
15 import gnu.trove.set.hash.THashSet;
17 import java.awt.geom.AffineTransform;
18 import java.lang.reflect.InvocationTargetException;
19 import java.util.ArrayDeque;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.Comparator;
25 import java.util.Deque;
26 import java.util.EnumSet;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.List;
31 import java.util.Queue;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.concurrent.ConcurrentMap;
35 import java.util.concurrent.atomic.AtomicBoolean;
36 import java.util.concurrent.locks.Condition;
37 import java.util.concurrent.locks.ReentrantLock;
39 import org.eclipse.core.runtime.IProgressMonitor;
40 import org.eclipse.core.runtime.SubMonitor;
41 import org.simantics.db.AsyncReadGraph;
42 import org.simantics.db.ReadGraph;
43 import org.simantics.db.RequestProcessor;
44 import org.simantics.db.Resource;
45 import org.simantics.db.Session;
46 import org.simantics.db.common.ResourceArray;
47 import org.simantics.db.common.exception.DebugException;
48 import org.simantics.db.common.procedure.adapter.AsyncProcedureAdapter;
49 import org.simantics.db.common.procedure.adapter.CacheListener;
50 import org.simantics.db.common.procedure.adapter.ListenerSupport;
51 import org.simantics.db.common.procedure.adapter.ProcedureAdapter;
52 import org.simantics.db.common.request.AsyncReadRequest;
53 import org.simantics.db.common.request.ReadRequest;
54 import org.simantics.db.common.session.SessionEventListenerAdapter;
55 import org.simantics.db.common.utils.NameUtils;
56 import org.simantics.db.exception.CancelTransactionException;
57 import org.simantics.db.exception.DatabaseException;
58 import org.simantics.db.exception.NoSingleResultException;
59 import org.simantics.db.exception.ServiceException;
60 import org.simantics.db.procedure.AsyncListener;
61 import org.simantics.db.procedure.AsyncProcedure;
62 import org.simantics.db.procedure.Listener;
63 import org.simantics.db.procedure.Procedure;
64 import org.simantics.db.request.Read;
65 import org.simantics.db.service.SessionEventSupport;
66 import org.simantics.diagram.connection.ConnectionSegmentEnd;
67 import org.simantics.diagram.content.Change;
68 import org.simantics.diagram.content.ConnectionUtil;
69 import org.simantics.diagram.content.DesignatedTerminal;
70 import org.simantics.diagram.content.DiagramContentChanges;
71 import org.simantics.diagram.content.DiagramContents;
72 import org.simantics.diagram.content.EdgeResource;
73 import org.simantics.diagram.content.ResourceTerminal;
74 import org.simantics.diagram.internal.DebugPolicy;
75 import org.simantics.diagram.internal.timing.GTask;
76 import org.simantics.diagram.internal.timing.Timing;
77 import org.simantics.diagram.profile.ProfileKeys;
78 import org.simantics.diagram.synchronization.CollectingModificationQueue;
79 import org.simantics.diagram.synchronization.CompositeModification;
80 import org.simantics.diagram.synchronization.CopyAdvisor;
81 import org.simantics.diagram.synchronization.ErrorHandler;
82 import org.simantics.diagram.synchronization.IHintSynchronizer;
83 import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;
84 import org.simantics.diagram.synchronization.IModification;
85 import org.simantics.diagram.synchronization.LogErrorHandler;
86 import org.simantics.diagram.synchronization.ModificationAdapter;
87 import org.simantics.diagram.synchronization.SynchronizationHints;
88 import org.simantics.diagram.synchronization.graph.AddElement;
89 import org.simantics.diagram.synchronization.graph.BasicResources;
90 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
91 import org.simantics.diagram.synchronization.graph.ElementLoader;
92 import org.simantics.diagram.synchronization.graph.ElementReorder;
93 import org.simantics.diagram.synchronization.graph.ElementWriter;
94 import org.simantics.diagram.synchronization.graph.GraphSynchronizationContext;
95 import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;
96 import org.simantics.diagram.synchronization.graph.ModificationQueue;
97 import org.simantics.diagram.synchronization.graph.TagChange;
98 import org.simantics.diagram.synchronization.graph.TransformElement;
99 import org.simantics.diagram.synchronization.graph.layer.GraphLayer;
100 import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;
101 import org.simantics.diagram.ui.DiagramModelHints;
102 import org.simantics.g2d.canvas.Hints;
103 import org.simantics.g2d.canvas.ICanvasContext;
104 import org.simantics.g2d.connection.ConnectionEntity;
105 import org.simantics.g2d.connection.EndKeyOf;
106 import org.simantics.g2d.connection.TerminalKeyOf;
107 import org.simantics.g2d.diagram.DiagramClass;
108 import org.simantics.g2d.diagram.DiagramHints;
109 import org.simantics.g2d.diagram.DiagramMutator;
110 import org.simantics.g2d.diagram.DiagramUtils;
111 import org.simantics.g2d.diagram.IDiagram;
112 import org.simantics.g2d.diagram.IDiagram.CompositionListener;
113 import org.simantics.g2d.diagram.IDiagram.CompositionVetoListener;
114 import org.simantics.g2d.diagram.handler.DataElementMap;
115 import org.simantics.g2d.diagram.handler.ElementFactory;
116 import org.simantics.g2d.diagram.handler.Relationship;
117 import org.simantics.g2d.diagram.handler.RelationshipHandler;
118 import org.simantics.g2d.diagram.handler.SubstituteElementClass;
119 import org.simantics.g2d.diagram.handler.Topology;
120 import org.simantics.g2d.diagram.handler.Topology.Connection;
121 import org.simantics.g2d.diagram.handler.Topology.Terminal;
122 import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType;
123 import org.simantics.g2d.diagram.impl.Diagram;
124 import org.simantics.g2d.diagram.participant.ElementPainter;
125 import org.simantics.g2d.element.ElementClass;
126 import org.simantics.g2d.element.ElementHints;
127 import org.simantics.g2d.element.ElementHints.DiscardableKey;
128 import org.simantics.g2d.element.ElementUtils;
129 import org.simantics.g2d.element.IElement;
130 import org.simantics.g2d.element.IElementClassProvider;
131 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
132 import org.simantics.g2d.element.handler.ElementHandler;
133 import org.simantics.g2d.element.handler.ElementLayerListener;
134 import org.simantics.g2d.element.handler.TerminalTopology;
135 import org.simantics.g2d.element.impl.Element;
136 import org.simantics.g2d.layers.ILayer;
137 import org.simantics.g2d.layers.ILayersEditor;
138 import org.simantics.g2d.routing.RouterFactory;
139 import org.simantics.scenegraph.INode;
140 import org.simantics.scenegraph.profile.DataNodeConstants;
141 import org.simantics.scenegraph.profile.DataNodeMap;
142 import org.simantics.scenegraph.profile.common.ProfileObserver;
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;
162 * This class loads a diagram contained in the graph database into the runtime
163 * diagram model and synchronizes changes in the graph into the run-time
164 * diagram. Any modifications to the graph model will be reflected to the
165 * run-time diagram model. Hence the name GraphToDiagramSynchronizer.
168 * This class does not in itself support modification of the graph diagram
169 * model. This manipulation is meant to be performed through a
170 * {@link DiagramMutator} implementation that is installed into the diagram
171 * using the {@link DiagramHints#KEY_MUTATOR} hint key.
173 * This implementations is built to only support diagrams defined in the graph
174 * model as indicated in <a
175 * href="https://www.simantics.org/wiki/index.php/Org.simantics.diagram" >this
179 * The synchronizer in itself is an {@link IDiagramLoader} which means that it
180 * can be used for loading a diagram from the graph. In order for the
181 * synchronizer to keep tracking the graph diagram model for changes the diagram
182 * must be loaded with it. The tracking is implemented using graph database
183 * queries. If you just want to load the diagram but detach it from the
184 * synchronizer's tracking mechanisms, all you need to do is to dispose the
185 * synchronizer after loading the diagram.
188 * This class guarantees that a single diagram element (IElement) representing a
189 * single back-end object ({@link ElementHints#KEY_OBJECT}) will stay the same
190 * object for the same back-end object.
193 * TODO: Currently it just happens that {@link GraphToDiagramSynchronizer}
194 * contains {@link DefaultDiagramMutator} which depends on some internal details
195 * of {@link GraphToDiagramSynchronizer} but it should be moved out of here by
196 * introducing new interfaces.
198 * <h2>Basic usage example</h2>
200 * This example shows how to initialize {@link GraphToDiagramSynchronizer} for a
201 * specified {@link ICanvasContext} and load a diagram from a specified diagram
202 * resource in the graph.
205 * IDiagram loadDiagram(final ICanvasContext canvasContext, RequestProcessor processor, Resource diagramResource,
206 * ResourceArray structuralPath) throws DatabaseException {
207 * GraphToDiagramSynchronizer synchronizer = processor.syncRequest(new Read<GraphToDiagramSynchronizer>() {
208 * public GraphToDiagramSynchronizer perform(ReadGraph graph) throws DatabaseException {
209 * return new GraphToDiagramSynchronizer(graph, canvasContext, createElementClassProvider(graph));
212 * IDiagram d = requestProcessor
213 * .syncRequest(new DiagramLoadQuery(diagramResource, structuralPath, synchronizer, null));
217 * protected IElementClassProvider createElementClassProvider(ReadGraph graph) {
218 * DiagramResource dr = DiagramResource.getInstance(graph);
219 * return ElementClassProviders.mappedProvider(ElementClasses.CONNECTION, DefaultConnectionClassFactory.CLASS
220 * .newClassWith(new ResourceAdapterImpl(dr.Connection)), ElementClasses.FLAG, FlagClassFactory
221 * .createFlagClass(dr.Flag));
226 * TODO: make GraphToDiagramSynchronizer a canvas participant to make it more
227 * uniform with the rest of the canvas system. This does not mean that G2DS must
228 * be attached to an ICanvasContext in order to be used, rather that it can be
232 * TODO: test that detaching the synchronizer via {@link #dispose()} actually
235 * TODO: remove {@link DefaultDiagramMutator} and all {@link DiagramMutator}
239 * TODO: diagram connection loading has no listener
241 * @author Tuukka Lehtonen
243 * @see GraphElementClassFactory
244 * @see GraphElementFactory
247 * @see IHintSynchronizer
250 public class GraphToDiagramSynchronizer extends AbstractDisposable implements IDiagramLoader, IModifiableSynchronizationContext {
253 * Controls whether the class adds hint listeners to each diagram element
254 * that try to perform basic sanity checks on changes happening in element
255 * hints. Having this will immediately inform you of bugs that corrupt the
256 * diagram model within the element hints in some way.
258 private static final boolean USE_ELEMENT_VALIDATING_LISTENERS = false;
261 * These keys are used to hang on to Connection instances of edges that will
262 * be later installed as EndKeyOf/TerminalKeyOf hints into the loaded
263 * element during the "graph to diagram update transaction".
265 private static final Key KEY_CONNECTION_BEGIN_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_BEGIN_PLACEHOLDER");
266 private static final Key KEY_CONNECTION_END_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_END_PLACEHOLDER");
269 * Stored into an edge node during connection edge requests using the
270 * KEY_CONNECTION_BEGIN_PLACEHOLDER and KEY_CONNECTION_END_PLACEHOLDER keys.
272 static class PlaceholderConnection {
273 public final EdgeEnd end;
274 public final Object node;
275 public final Terminal terminal;
276 public PlaceholderConnection(EdgeEnd end, Object node, Terminal terminal) {
279 this.terminal = terminal;
284 * Indicates to the diagram CompositionListener of this synchronizer that is
285 * should deny all relationships for the element this hint is attached to.
287 private static final Key KEY_REMOVE_RELATIONSHIPS = new KeyOf(Boolean.class, "REMOVE_RELATIONSHIPS");
289 static ErrorHandler errorHandler = LogErrorHandler.INSTANCE;
292 * The canvas context which is being synchronized with the graph. Received
293 * during construction.
296 * Is not nulled during disposal of this class since internal listener's
297 * life-cycles depend on canvas.isDisposed.
299 ICanvasContext canvas;
302 * The session used by this synchronizer. Received during construction.
307 * Locked while updating diagram contents from the graph.
309 ReentrantLock diagramUpdateLock = new ReentrantLock();
311 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
312 // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING BEGIN
313 // ------------------------------------------------------------------------
316 * Holds a GraphToDiagramUpdater instance while a diagram content update is
317 * currently in progress.
320 * Basically this is a hack solution to the problem of properly finding
321 * newly added Resource<->IElement mappings while loading the diagram
322 * contents. See {@link DataElementMapImpl} for why this is necessary.
324 GraphToDiagramUpdater currentUpdater = null;
327 * A map from data objects to elements. Elements should already contain the
328 * data objects as {@link ElementHints#KEY_OBJECT} hints.
330 ConcurrentMap<Object, IElement> dataElement = new ConcurrentHashMap<Object, IElement>();
333 * Temporary structure for single-threaded use in #{@link DiagramUpdater}.
335 Collection<Connection> tempConnections = new ArrayList<Connection>();
338 * A dummy class of which an instance will be given to each new edge element
339 * to make {@link TerminalKeyOf} keys unique for each edge.
341 static class TransientElementObject {
343 public String toString() {
344 return "MUTATOR GENERATED (hash=" + System.identityHashCode(this) + ")";
348 private static class ConnectionChildren {
349 public Set<IElement> branchPoints;
350 public Set<IElement> segments;
352 public ConnectionChildren(Set<IElement> branchPoints, Set<IElement> segments) {
353 this.branchPoints = branchPoints;
354 this.segments = segments;
358 ListenerSupport canvasListenerSupport = new ListenerSupport() {
360 public void exception(Throwable t) {
365 public boolean isDisposed() {
366 return !isAlive() || canvas.isDisposed();
371 * @see ElementHints#KEY_CONNECTION_ENTITY
373 class ConnectionEntityImpl implements ConnectionEntity {
376 * The connection instance resource in the graph backend.
378 * May be <code>null</code> if the connection has not been synchronized
384 * The connection type resource in the graph backend.
386 * May be <code>null</code> if the connection has not been synchronized
389 Resource connectionType;
392 * The connection entity element which is a part of the diagram.
394 IElement connectionElement;
397 * List of backend-synchronized branch points that are part of this
400 Collection<Resource> branchPoints = Collections.emptyList();
403 * List of backend-synchronized edges that are part of this connection.
405 Collection<EdgeResource> segments = Collections.emptyList();
407 Set<Object> removedBranchPoints = new HashSet<Object>(4);
409 Set<Object> removedSegments = new HashSet<Object>(4);
412 * List of non-backend-synchronized branch point element that are part
413 * of this connection.
415 List<IElement> branchPointElements = new ArrayList<IElement>(1);
418 * List of non-backend-synchronized edge element that are part of this
421 List<IElement> segmentElements = new ArrayList<IElement>(2);
423 ConnectionListener listener;
425 ConnectionEntityImpl(Resource connection, Resource connectionType, IElement connectionElement) {
426 this.connection = connection;
427 this.connectionType = connectionType;
428 this.connectionElement = connectionElement;
431 ConnectionEntityImpl(Resource connectionType, IElement connectionElement) {
432 this.connectionType = connectionType;
433 this.connectionElement = connectionElement;
436 ConnectionEntityImpl(ReadGraph graph, Resource connection, IElement connectionElement)
437 throws NoSingleResultException, ServiceException {
438 this.connection = connection;
439 this.connectionType = graph.getSingleType(connection, br.DIA.Connection);
440 this.connectionElement = connectionElement;
444 public IElement getConnection() {
445 return connectionElement;
448 public Object getConnectionObject() {
452 public IElement getConnectionElement() {
453 if (connectionElement == null)
454 return getMappedConnectionElement();
455 return connectionElement;
458 private IElement getMappedConnectionElement() {
460 if (connection != null)
461 ce = getMappedElement(connection);
462 return ce == null ? connectionElement : ce;
466 Collection<IElement> segments = getSegments(null);
468 // Remove all TerminalKeyOf hints from branch points that do not
470 ArrayList<TerminalKeyOf> pruned = null;
471 for (IElement bp : getBranchPoints(null)) {
473 pruned = new ArrayList<TerminalKeyOf>(4);
475 for (Map.Entry<TerminalKeyOf, Object> entry : bp.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
476 // First check that the terminal matches.
477 Connection c = (Connection) entry.getValue();
478 if (!segments.contains(c.edge))
479 pruned.add(entry.getKey());
481 removeNodeTopologyHints((Element) bp, pruned);
485 public ConnectionChildren getConnectionChildren() {
486 Set<IElement> bps = Collections.emptySet();
487 Set<IElement> segs = Collections.emptySet();
488 if (!branchPoints.isEmpty()) {
489 bps = new HashSet<IElement>(branchPoints.size());
490 for (Resource bp : branchPoints) {
491 IElement e = getMappedElement(bp);
496 if (!segments.isEmpty()) {
497 segs = new HashSet<IElement>(segments.size());
498 for (EdgeResource seg : segments) {
499 IElement e = getMappedElement(seg);
504 return new ConnectionChildren(bps, segs);
507 public void setData(Collection<EdgeResource> segments, Collection<Resource> branchPoints) {
508 // System.out.println("setData " + segments.size());
509 this.branchPoints = branchPoints;
510 this.segments = segments;
512 // Reset the added/removed state of segments and branchpoints.
513 this.removedBranchPoints = new HashSet<Object>(4);
514 this.removedSegments = new HashSet<Object>(4);
515 this.branchPointElements = new ArrayList<IElement>(4);
516 this.segmentElements = new ArrayList<IElement>(4);
519 public void fireListener(ConnectionChildren old, ConnectionChildren current) {
520 if (listener != null) {
521 List<IElement> removed = new ArrayList<IElement>();
522 List<IElement> added = new ArrayList<IElement>();
524 for (IElement oldBp : old.branchPoints)
525 if (!current.branchPoints.contains(oldBp))
527 for (IElement oldSeg : old.segments)
528 if (!current.segments.contains(oldSeg))
531 for (IElement bp : current.branchPoints)
532 if (!old.branchPoints.contains(bp))
534 for (IElement seg : current.segments)
535 if (!old.segments.contains(seg))
538 if (!removed.isEmpty() || !added.isEmpty()) {
539 listener.connectionChanged(new ConnectionEvent(this.connectionElement, removed, added));
545 public Collection<IElement> getBranchPoints(Collection<IElement> result) {
547 result = new ArrayList<IElement>(branchPoints.size());
548 for (Resource bp : branchPoints) {
549 if (!removedBranchPoints.contains(bp)) {
550 IElement e = getMappedElement(bp);
555 result.addAll(branchPointElements);
560 public Collection<IElement> getSegments(Collection<IElement> result) {
562 result = new ArrayList<IElement>(segments.size());
563 for (EdgeResource seg : segments) {
564 if (!removedSegments.contains(seg)) {
565 IElement e = getMappedElement(seg);
570 result.addAll(segmentElements);
575 public Collection<Connection> getTerminalConnections(Collection<Connection> result) {
577 result = new ArrayList<Connection>(segments.size() * 2);
578 Set<org.simantics.utils.datastructures.Pair<IElement, Terminal>> processed = new HashSet<org.simantics.utils.datastructures.Pair<IElement, Terminal>>();
579 for (EdgeResource seg : segments) {
580 IElement edge = getMappedElement(seg);
582 for (EndKeyOf key : EndKeyOf.KEYS) {
583 Connection c = edge.getHint(key);
584 if (c != null && (c.terminal instanceof ResourceTerminal) && processed.add(Pair.make(c.node, c.terminal)))
593 public void setListener(ConnectionListener listener) {
594 this.listener = listener;
598 public String toString() {
599 return getClass().getSimpleName() + "[resource=" + connection + ", branch points=" + branchPoints
600 + ", segments=" + segments + ", connectionElement=" + connectionElement
601 + ", branch point elements=" + branchPointElements + ", segment elements=" + segmentElements
602 + ", removed branch points=" + removedBranchPoints + ", removed segments=" + removedSegments + "]";
608 * A map from connection data objects to connection entities. The connection
609 * part elements should already contain the data objects as
610 * {@link ElementHints#KEY_OBJECT} hints.
612 ConcurrentMap<Object, ConnectionEntityImpl> dataConnection = new ConcurrentHashMap<Object, ConnectionEntityImpl>();
618 void mapElement(final Object data, final IElement element) {
619 if (!(element instanceof Element)) {
620 throw new IllegalArgumentException("mapElement: expected instance of Element, got " + element + " with data " + data);
623 assert element != null;
624 if (DebugPolicy.DEBUG_MAPPING)
625 new Exception(Thread.currentThread() + " MAPPING: " + data + " -> " + element).printStackTrace();
626 dataElement.put(data, element);
633 IElement getMappedElement(final Object data) {
634 assert (data != null);
635 IElement element = dataElement.get(data);
639 IElement getMappedElementByElementObject(IElement e) {
642 Object o = e.getHint(ElementHints.KEY_OBJECT);
645 return getMappedElement(o);
652 IElement assertMappedElement(final Object data) {
653 IElement element = dataElement.get(data);
654 assert element != null;
662 IElement unmapElement(final Object data) {
663 IElement element = dataElement.remove(data);
664 if (DebugPolicy.DEBUG_MAPPING)
665 new Exception(Thread.currentThread() + " UN-MAPPED: " + data + " -> " + element).printStackTrace();
673 void mapConnection(final Object data, final ConnectionEntityImpl connection) {
675 assert connection != null;
676 if (DebugPolicy.DEBUG_MAPPING)
677 System.out.println(Thread.currentThread() + " MAPPING CONNECTION: " + data + " -> " + connection);
678 dataConnection.put(data, connection);
685 ConnectionEntityImpl getMappedConnection(final Object data) {
686 ConnectionEntityImpl connection = dataConnection.get(data);
694 ConnectionEntityImpl assertMappedConnection(final Object data) {
695 ConnectionEntityImpl connection = getMappedConnection(data);
696 assert connection != null;
704 ConnectionEntityImpl unmapConnection(final Object data) {
705 ConnectionEntityImpl connection = dataConnection.remove(data);
706 if (DebugPolicy.DEBUG_MAPPING)
707 System.out.println(Thread.currentThread() + " UN-MAPPED CONNECTION: " + data + " -> " + connection);
711 class DataElementMapImpl implements DataElementMap {
713 public Object getData(IDiagram d, IElement element) {
715 throw new NullPointerException("null diagram");
717 throw new NullPointerException("null element");
719 assert ElementUtils.getDiagram(element) == d;
720 return element.getHint(ElementHints.KEY_OBJECT);
724 public IElement getElement(IDiagram d, Object data) {
726 throw new NullPointerException("null diagram");
728 throw new NullPointerException("null data");
730 GraphToDiagramUpdater updater = currentUpdater;
731 if (updater != null) {
732 // This HACK is for allowing GraphElementFactory implementations
733 // to find the IElements they are related to.
734 IElement e = updater.addedElementMap.get(data);
739 IElement e = getMappedElement(data);
746 class SubstituteElementClassImpl implements SubstituteElementClass {
748 public ElementClass substitute(IDiagram d, ElementClass ec) {
750 throw new IllegalArgumentException("specified diagram does not have this SubstituteElementClass handler");
752 // If the element class is our own, there's no point in creating
754 if (ec.contains(elementLayerListener))
757 List<ElementHandler> all = ec.getAll();
758 List<ElementHandler> result = new ArrayList<ElementHandler>(all.size());
759 for (ElementHandler eh : all) {
760 if (eh instanceof ElementLayerListenerImpl)
761 result.add(elementLayerListener);
765 return ElementClass.compile(result).setId(ec.getId());
769 final DataElementMapImpl dataElementMap = new DataElementMapImpl();
771 final SubstituteElementClassImpl substituteElementClass = new SubstituteElementClassImpl();
773 // ------------------------------------------------------------------------
774 // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING END
775 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
777 void warning(String message, Exception e) {
778 errorHandler.warning(message, e);
781 void warning(Exception e) {
782 errorHandler.warning(e.getMessage(), e);
785 void error(String message, Throwable e) {
786 errorHandler.error(message, e);
789 void error(Throwable e) {
790 errorHandler.error(e.getMessage(), e);
793 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
794 // GRAPH MODIFICATION QUEUE BEGIN
795 // ------------------------------------------------------------------------
797 ModificationQueue modificationQueue;
798 IModifiableSynchronizationContext synchronizationContext;
801 public <T> T set(Key key, Object value) {
802 if (synchronizationContext == null)
804 return synchronizationContext.set(key, value);
808 public <T> T get(Key key) {
809 if (synchronizationContext == null)
811 return synchronizationContext.get(key);
814 // ------------------------------------------------------------------------
815 // GRAPH MODIFICATION QUEUE END
816 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
819 * The previously loaded version of the diagram content. This is needed to
820 * calculate the difference between new and old content on each
821 * {@link #diagramGraphUpdater(DiagramContents)} invocation.
823 DiagramContents previousContent;
826 * The diagram instance that this synchronizer is synchronizing with the
832 * An observer for diagram profile entries. Has a life-cycle that must be
833 * bound to the life-cycle of this GraphToDiagramSynchronizer instance.
834 * Disposed if synchronizer is detached in {@link #doDispose()} or finally
835 * when the canvas is disposed.
837 ProfileObserver profileObserver;
839 IElementClassProvider elementClassProvider;
843 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
844 // Internal state machine handling BEGIN
845 // ------------------------------------------------------------------------
848 * An indicator for the current state of this synchronizer. This is a simple
849 * state machine with the following possible state transitions:
852 * <li>INITIAL -> LOADING, DISPOSED</li>
853 * <li>LOADING -> IDLE</li>
854 * <li>IDLE -> UPDATING_DIAGRAM, DISPOSED</li>
855 * <li>UPDATING_DIAGRAM -> IDLE</li>
858 * Start states: INITIAL
859 * End states: DISPOSED
863 * The initial state of the synchronizer.
867 * The synchronizer is performing load-time initialization. During this
868 * time no canvas refreshes should be forced.
872 * The synchronizer is performing updates to the diagram model. This
873 * process goes on in the canvas context thread.
877 * The synchronizer is doing nothing.
881 * The synchronized diagram is being disposed, which means that this
882 * synchronizer should not accept any further actions.
887 public static final EnumSet<State> FROM_INITIAL = EnumSet.of(State.LOADING, State.DISPOSED);
888 public static final EnumSet<State> FROM_LOADING = EnumSet.of(State.IDLE);
889 public static final EnumSet<State> FROM_UPDATING_DIAGRAM = EnumSet.of(State.IDLE);
890 public static final EnumSet<State> FROM_IDLE = EnumSet.of(State.UPDATING_DIAGRAM, State.DISPOSED);
891 public static final EnumSet<State> NO_STATES = EnumSet.noneOf(State.class);
893 private EnumSet<State> validTargetStates(State start) {
895 case INITIAL: return FROM_INITIAL;
896 case LOADING: return FROM_LOADING;
897 case UPDATING_DIAGRAM: return FROM_UPDATING_DIAGRAM;
898 case IDLE: return FROM_IDLE;
899 case DISPOSED: return NO_STATES;
901 throw new IllegalArgumentException("unrecognized state " + start);
904 private String validateStateChange(State start, State end) {
905 EnumSet<State> validTargets = validTargetStates(start);
906 if (!validTargets.contains(end))
907 return "Cannot transition from " + start + " state to " + end + ".";
912 * The current state of the synchronizer. At start it is
913 * {@link State#INITIAL} and after loading it is {@link State#IDLE}.
915 State synchronizerState = State.INITIAL;
918 * A condition variable used to synchronize synchronizer state changes.
920 ReentrantLock stateLock = new ReentrantLock();
923 * A condition that is signaled when the synchronizer state changes to IDLE.
925 Condition idleCondition = stateLock.newCondition();
928 return synchronizerState;
932 * Activates the desired state after making sure that the synchronizer has
933 * been IDLE in between its current state and this invocation.
935 * @param newState the new state to activate
936 * @throws InterruptedException if waiting for IDLE state gets interrupted
937 * @throws IllegalStateException if the requested transition from the
938 * current state to the desired state would be illegal.
940 void activateState(State newState, boolean waitForIdle) throws InterruptedException {
943 // Wait until the state of the synchronizer IDLEs if necessary.
944 if (waitForIdle && synchronizerState != State.IDLE) {
945 String error = validateStateChange(synchronizerState, State.IDLE);
947 throw new IllegalStateException(error);
949 while (synchronizerState != State.IDLE) {
950 if (DebugPolicy.DEBUG_STATE)
951 System.out.println(Thread.currentThread() + " waiting for IDLE state, current="
952 + synchronizerState);
953 idleCondition.await();
957 String error = validateStateChange(synchronizerState, newState);
959 throw new IllegalStateException(error);
961 if (DebugPolicy.DEBUG_STATE)
962 System.out.println(Thread.currentThread() + " activated state " + newState);
963 this.synchronizerState = newState;
965 if (newState == State.IDLE)
966 idleCondition.signalAll();
972 void idle() throws IllegalStateException, InterruptedException {
973 activateState(State.IDLE, false);
976 static interface StateRunnable extends Runnable {
977 void execute() throws InvocationTargetException;
979 public abstract class Stub implements StateRunnable {
985 public final void execute() throws InvocationTargetException {
988 } catch (Exception e) {
989 throw new InvocationTargetException(e);
990 } catch (LinkageError e) {
991 throw new InvocationTargetException(e);
995 protected abstract void perform() throws Exception;
999 protected void runInState(State state, StateRunnable runnable) throws InvocationTargetException {
1001 activateState(state, true);
1007 } catch (IllegalStateException e) {
1008 throw new InvocationTargetException(e);
1009 } catch (InterruptedException e) {
1010 throw new InvocationTargetException(e);
1014 protected void safeRunInState(State state, StateRunnable runnable) {
1016 runInState(state, runnable);
1017 } catch (InvocationTargetException e) {
1018 error("Failed to run runnable " + runnable + " in state " + state + ". See exception for details.", e
1023 // ------------------------------------------------------------------------
1024 // Internal state machine handling END
1025 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1030 * @param elementClassProvider
1031 * @throws DatabaseException
1033 public GraphToDiagramSynchronizer(RequestProcessor processor, ICanvasContext canvas, IElementClassProvider elementClassProvider) throws DatabaseException {
1034 if (processor == null)
1035 throw new IllegalArgumentException("null processor");
1037 throw new IllegalArgumentException("null canvas");
1038 if (elementClassProvider == null)
1039 throw new IllegalArgumentException("null element class provider");
1041 this.session = processor.getSession();
1042 this.canvas = canvas;
1043 this.modificationQueue = new ModificationQueue(session, errorHandler);
1045 processor.syncRequest(new ReadRequest() {
1047 public void run(ReadGraph graph) throws DatabaseException {
1048 initializeResources(graph);
1052 this.elementClassProvider = elementClassProvider;
1053 synchronizationContext.set(SynchronizationHints.ELEMENT_CLASS_PROVIDER, elementClassProvider);
1055 attachSessionListener(processor.getSession());
1061 public IElementClassProvider getElementClassProvider() {
1062 return elementClassProvider;
1065 public Session getSession() {
1069 public ICanvasContext getCanvasContext() {
1073 public IDiagram getDiagram() {
1077 void setCanvasDirty() {
1078 ICanvasContext c = canvas;
1079 if (synchronizerState != State.LOADING && c != null && !c.isDisposed()) {
1080 // TODO: Consider adding an invocation limiter here, to prevent
1081 // calling setDirty too often if enough time hasn't passed yet since
1082 // the last invocation.
1083 c.getContentContext().setDirty();
1088 * @param elementType
1090 * @throws DatabaseException if ElementClass cannot be retrieved
1092 public ElementClass getNodeClass(Resource elementType) throws DatabaseException {
1093 return getNodeClass(session, elementType);
1096 public ElementClass getNodeClass(RequestProcessor processor, Resource elementType) throws DatabaseException {
1097 ElementClass ec = processor.syncRequest(new NodeClassRequest(canvas, diagram, elementType, true));
1102 protected void doDispose() {
1106 boolean isInitial = getState() == State.INITIAL;
1107 activateState(State.DISPOSED, !isInitial);
1111 } catch (InterruptedException e) {
1112 // Shouldn't happen.
1113 e.printStackTrace();
1115 detachSessionListener();
1117 if (profileObserver != null) {
1118 profileObserver.dispose();
1119 profileObserver = null;
1122 if (diagram != null) {
1123 diagram.removeCompositionListener(diagramListener);
1124 diagram.removeCompositionVetoListener(diagramListener);
1127 // TODO: we should probably leave the dataElement map as is since DataElementMap needs it even after the synchronizer has been disposed.
1128 // Currently the diagram's DataElementMap will be broken after disposal.
1129 // dataElement.clear();
1130 // dataConnection.clear();
1132 if (layerManager != null) {
1133 layerManager.dispose();
1137 modificationQueue.dispose();
1141 void initializeResources(ReadGraph graph) {
1142 this.br = new BasicResources(graph);
1144 // Initialize synchronization context
1145 synchronizationContext = new GraphSynchronizationContext(graph, modificationQueue);
1146 synchronizationContext.set(SynchronizationHints.ERROR_HANDLER, errorHandler);
1149 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1151 // ------------------------------------------------------------------------
1153 GraphLayerManager layerManager;
1156 * A common handler for all elements that is used to listen to changes in
1157 * element visibility and focusability on diagram layers.
1159 class ElementLayerListenerImpl implements ElementLayerListener {
1160 private static final long serialVersionUID = -3410052116598828129L;
1163 public void visibilityChanged(IElement e, ILayer layer, boolean visible) {
1166 if (DebugPolicy.DEBUG_LAYERS)
1167 System.out.println("visibility changed: " + e + ", " + layer + ", " + visible);
1168 GraphLayer gl = layerManager.getGraphLayer(layer.getName());
1170 changeTag(e, gl.getVisible(), visible);
1175 public void focusabilityChanged(IElement e, ILayer layer, boolean focusable) {
1178 if (DebugPolicy.DEBUG_LAYERS)
1179 System.out.println("focusability changed: " + e + ", " + layer + ", " + focusable);
1180 GraphLayer gl = layerManager.getGraphLayer(layer.getName());
1182 changeTag(e, gl.getFocusable(), focusable);
1186 void changeTag(IElement e, Resource tag, boolean set) {
1187 Object object = e.getHint(ElementHints.KEY_OBJECT);
1188 Resource tagged = null;
1189 if (object instanceof Resource) {
1190 tagged = (Resource) object;
1191 } else if (object instanceof EdgeResource) {
1192 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
1193 if (ce instanceof ConnectionEntityImpl) {
1194 tagged = ((ConnectionEntityImpl) ce).connection;
1200 modificationQueue.async(new TagChange(tagged, tag, set), null);
1204 ElementLayerListenerImpl elementLayerListener = new ElementLayerListenerImpl();
1206 // ------------------------------------------------------------------------
1208 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1211 public IDiagram loadDiagram(IProgressMonitor progressMonitor, ReadGraph g, final String modelURI, final Resource diagram, final Resource runtime, final ResourceArray structuralPath,
1212 IHintObservable initialHints) throws DatabaseException {
1213 if (DebugPolicy.DEBUG_LOAD)
1214 System.out.println(Thread.currentThread() + " loadDiagram: " + NameUtils.getSafeName(g, diagram));
1216 SubMonitor monitor = SubMonitor.convert(progressMonitor, "Load Diagram", 100);
1218 Object loadTask = Timing.BEGIN("GDS.loadDiagram");
1221 activateState(State.LOADING, false);
1222 } catch (IllegalStateException e) {
1223 // Disposed already before loading even began.
1224 this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
1225 return this.diagram;
1228 // Query for diagram class
1229 Resource diagramClassResource = g.getPossibleType(diagram, br.DIA.Composite);
1230 if (diagramClassResource != null) {
1231 // Spawn new diagram
1232 Object task = Timing.BEGIN("GDS.DiagramClassRequest");
1233 final DiagramClass diagramClass = g.syncRequest(new DiagramClassRequest(diagram));
1235 final IDiagram d = Diagram.spawnNew(diagramClass);
1237 d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, diagram);
1238 if (runtime != null)
1239 d.setHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE, runtime);
1240 if (modelURI != null)
1241 d.setHint(DiagramModelHints.KEY_DIAGRAM_MODEL_URI, modelURI);
1242 d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE_ARRAY, structuralPath);
1244 // Set dumb default routing when DiagramClass does not
1245 // predefine the default connection routing for the diagram.
1246 if (!d.containsHint(DiagramHints.ROUTE_ALGORITHM))
1247 d.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, false));
1249 d.setHint(SynchronizationHints.CONTEXT, this);
1251 // Initialize hints with hints from initialHints if given
1252 if (initialHints != null) {
1253 d.setHints(initialHints.getHints());
1257 // ITask task2 = ThreadLogger.getInstance().begin("loadLayers");
1258 monitor.subTask("Layers");
1260 this.layerManager = new GraphLayerManager(g, modificationQueue, diagram);
1261 synchronizationContext.set(GraphSynchronizationHints.GRAPH_LAYER_MANAGER, this.layerManager);
1262 ILayersEditor layers = layerManager.loadLayers(d, g, diagram);
1265 d.setHint(DiagramHints.KEY_LAYERS, layers);
1266 d.setHint(DiagramHints.KEY_LAYERS_EDITOR, layers);
1268 d.addCompositionVetoListener(diagramListener);
1269 d.addCompositionListener(diagramListener);
1273 d.setHint(DiagramHints.KEY_MUTATOR, new DefaultDiagramMutator(d, diagram, synchronizationContext));
1275 // Add default layer if no layers exist.
1276 // NOTE: this must be done after this.diagram has been set
1277 // as it will trigger a graph modification which needs the
1278 // diagram resource.
1279 // ITask task3 = ThreadLogger.getInstance().begin("addDefaultLayer");
1280 // if (layers.getLayers().isEmpty()) {
1281 // if (DebugPolicy.DEBUG_LAYERS)
1282 // System.out.println("No layers, creating default layer '"
1283 // + DiagramConstants.DEFAULT_LAYER_NAME + "'");
1284 // SimpleLayer defaultLayer = new SimpleLayer(DiagramConstants.DEFAULT_LAYER_NAME);
1285 // layers.addLayer(defaultLayer);
1286 // layers.activate(defaultLayer);
1288 // // task3.finish();
1292 monitor.subTask("Contents");
1293 // Discover the plain resources that form the content of the
1294 // diagram through a separate query. This allows us to
1296 // track changes to the diagram structure itself, not the
1297 // substructures contained by the structure elements.
1298 ITask task4 = ThreadLogger.getInstance().begin("DiagramContentRequest1");
1299 DiagramContentRequest query = new DiagramContentRequest(canvas, diagram, errorHandler);
1300 g.syncRequest(query, new DiagramContentListener(diagram));
1302 // ITask task5 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
1303 ITask task42 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
1304 DiagramContents contents = g.syncRequest(query);
1305 System.err.println("contents: " + contents);
1310 monitor.subTask("Graphical elements");
1312 Object applyDiagramContents = Timing.BEGIN("GDS.applyDiagramContents");
1313 ITask task6 = ThreadLogger.getInstance().begin("applyDiagramContents");
1314 processGraphUpdates(g, Collections.singleton(diagramGraphUpdater(contents)));
1316 Timing.END(applyDiagramContents);
1320 DataNodeMap dn = new DataNodeMap() {
1322 public INode getNode(Object data) {
1323 if (DataNodeConstants.CANVAS_ROOT == data)
1324 return canvas.getCanvasNode();
1325 if (DataNodeConstants.DIAGRAM_ELEMENT_PARENT == data) {
1326 ElementPainter ep = canvas.getAtMostOneItemOfClass(ElementPainter.class);
1327 return ep != null ? ep.getDiagramElementParentNode() : null;
1330 DataElementMap emap = GraphToDiagramSynchronizer.this.diagram.getDiagramClass().getSingleItem(DataElementMap.class);
1331 IElement element = emap.getElement(GraphToDiagramSynchronizer.this.diagram, data);
1332 if(element == null) return null;
1333 return element.getHint(ElementHints.KEY_SG_NODE);
1337 profileObserver = new ProfileObserver(g.getSession(), runtime,
1338 canvas.getThreadAccess(), canvas, canvas.getSceneGraph(), diagram,
1339 ArrayMap.keys(ProfileKeys.DIAGRAM, ProfileKeys.CANVAS, ProfileKeys.NODE_MAP).values(GraphToDiagramSynchronizer.this.diagram, canvas, dn),
1340 new CanvasNotification(canvas));
1342 profileObserver.listen(g, GraphToDiagramSynchronizer.this);
1348 this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
1349 return this.diagram;
1354 } catch (InterruptedException e) {
1355 throw new RuntimeException(e);
1356 } catch (IllegalStateException e) {
1357 // If the synchronizer was disposed ahead of time, it was done
1358 // for a reason, such as the user having closed the owner editor.
1360 throw new CancelTransactionException(e);
1361 throw new RuntimeException(e);
1363 Timing.END(loadTask);
1367 static class CanvasNotification implements Runnable {
1369 final private ICanvasContext canvas;
1371 public CanvasNotification(ICanvasContext canvas) {
1372 this.canvas = canvas;
1376 canvas.getContentContext().setDirty();
1381 ArrayList<IModification> pendingModifications = new ArrayList<IModification>();
1382 MapSet<IElement, IModification> modificationIndex = new MapSet.Hash<IElement, IModification>();
1384 void addModification(IElement element, IModification modification) {
1385 pendingModifications.add(modification);
1386 if (element != null)
1387 modificationIndex.add(element, modification);
1390 class DefaultDiagramMutator implements DiagramMutator {
1392 Map<IElement, Resource> creation = new HashMap<IElement, Resource>();
1397 IModifiableSynchronizationContext synchronizationContext;
1399 public DefaultDiagramMutator(IDiagram d, Resource diagram, IModifiableSynchronizationContext synchronizationContext) {
1401 this.diagram = diagram;
1402 this.synchronizationContext = synchronizationContext;
1404 if (synchronizationContext.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER) == null)
1405 throw new IllegalArgumentException("SynchronizationHints.ELEMENT_CLASS_PROVIDER not available");
1408 void assertNotDisposed() {
1410 throw new IllegalStateException(getClass().getSimpleName() + " is disposed");
1414 public IElement newElement(ElementClass clazz) {
1415 assertNotDisposed();
1416 ElementFactory ef = d.getDiagramClass().getAtMostOneItemOfClass(ElementFactory.class);
1417 IElement element = null;
1419 element = ef.spawnNew(clazz);
1421 element = Element.spawnNew(clazz);
1423 element.setHint(ElementHints.KEY_OBJECT, new TransientElementObject());
1425 addModification(element, new AddElement(synchronizationContext, d, element));
1431 public void commit() {
1432 assertNotDisposed();
1433 if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
1434 System.out.println("DiagramMutator is about to commit changes:");
1435 for (IModification mod : pendingModifications)
1436 System.out.println("\t- " + mod);
1439 Collections.sort(pendingModifications);
1441 if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
1442 if (pendingModifications.size() > 1) {
1443 System.out.println("* changes were re-ordered to:");
1444 for (IModification mod : pendingModifications)
1445 System.out.println("\t" + mod);
1449 Timing.safeTimed(errorHandler, "QUEUE AND WAIT FOR MODIFICATIONS TO FINISH", new GTask() {
1451 public void run() throws DatabaseException {
1452 // Performs a separate write request and query result update
1453 // for each modification
1454 // for (IModification mod : pendingModifications) {
1456 // modificationQueue.sync(mod);
1457 // } catch (InterruptedException e) {
1458 // error("Pending diagram modification " + mod
1459 // + " was interrupted. See exception for details.", e);
1463 // NOTE: this is still under testing, the author is not
1464 // truly certain that it should work in all cases ATM.
1466 // Performs all modifications with in a single write request
1467 for (IModification mod : pendingModifications) {
1468 modificationQueue.offer(mod, null);
1471 // Perform the modifications in a single request.
1472 modificationQueue.finish();
1473 } catch (InterruptedException e) {
1474 errorHandler.error("Diagram modification finishing was interrupted. See exception for details.", e);
1478 pendingModifications.clear();
1479 modificationIndex.clear();
1481 if (DebugPolicy.DEBUG_MUTATOR_COMMIT)
1482 System.out.println("DiagramMutator has committed");
1486 public void clear() {
1487 assertNotDisposed();
1488 pendingModifications.clear();
1489 modificationIndex.clear();
1491 if (DebugPolicy.DEBUG_MUTATOR)
1492 System.out.println("DiagramMutator has been cleared");
1496 public void modifyTransform(IElement element) {
1497 assertNotDisposed();
1498 Resource resource = backendObject(element);
1499 AffineTransform tr = element.getHint(ElementHints.KEY_TRANSFORM);
1500 if (resource != null && tr != null) {
1501 addModification(element, new TransformElement(resource, tr));
1506 public void synchronizeHintsToBackend(IElement element) {
1507 assertNotDisposed();
1508 IHintSynchronizer synchronizer = element.getHint(SynchronizationHints.HINT_SYNCHRONIZER);
1509 if (synchronizer != null) {
1510 CollectingModificationQueue queue = new CollectingModificationQueue();
1511 synchronizer.synchronize(synchronizationContext, element);
1512 addModification(element, new CompositeModification(ModificationAdapter.LOW_PRIORITY, queue.getQueue()));
1517 public void synchronizeElementOrder() {
1518 assertNotDisposed();
1519 List<IElement> snapshot = d.getSnapshot();
1520 addModification(null, new ElementReorder(d, snapshot));
1524 public void register(IElement element, Object object) {
1525 creation.put(element, (Resource) object);
1528 @SuppressWarnings("unchecked")
1530 public <T> T backendObject(IElement element) {
1531 Object object = ElementUtils.getObject(element);
1532 if (object instanceof Resource)
1535 return (T) creation.get(element);
1540 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1541 // GRAPH TO DIAGRAM SYCHRONIZATION LOGIC BEGIN
1542 // ------------------------------------------------------------------------
1544 static class ConnectionData {
1545 ConnectionEntityImpl impl;
1546 List<Resource> branchPoints = new ArrayList<Resource>();
1547 List<EdgeResource> segments = new ArrayList<EdgeResource>();
1549 ConnectionData(ConnectionEntityImpl ce) {
1553 void addBranchPoint(Resource bp) {
1554 branchPoints.add(bp);
1557 void addSegment(EdgeResource seg) {
1562 class GraphToDiagramUpdater {
1563 DiagramContents lastContent;
1564 DiagramContents content;
1565 DiagramContentChanges changes;
1567 final List<IElement> addedElements;
1568 final List<IElement> removedElements;
1570 final List<IElement> addedConnectionSegments;
1571 final List<IElement> removedConnectionSegments;
1573 final List<IElement> addedBranchPoints;
1574 final List<IElement> removedBranchPoints;
1576 final Map<Object, IElement> addedElementMap;
1577 final Map<Resource, IElement> addedConnectionMap;
1578 final Map<Resource, ConnectionEntityImpl> addedConnectionEntities;
1579 final List<Resource> removedConnectionEntities;
1580 final Map<ConnectionEntityImpl, ConnectionData> changedConnectionEntities;
1582 final Map<Resource, IElement> addedRouteGraphConnectionMap;
1583 final List<IElement> removedRouteGraphConnections;
1586 GraphToDiagramUpdater(DiagramContents lastContent, DiagramContents content, DiagramContentChanges changes) {
1587 this.lastContent = lastContent;
1588 this.content = content;
1589 this.changes = changes;
1591 this.addedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
1592 this.removedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
1593 this.addedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
1594 this.removedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
1595 this.addedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
1596 this.removedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
1597 this.addedElementMap = new HashMap<Object, IElement>();
1598 this.addedConnectionMap = new HashMap<Resource, IElement>();
1599 this.addedConnectionEntities = new HashMap<Resource, ConnectionEntityImpl>();
1600 this.removedConnectionEntities = new ArrayList<Resource>(changes.connections.size());
1601 this.changedConnectionEntities = new HashMap<ConnectionEntityImpl, ConnectionData>();
1602 this.addedRouteGraphConnectionMap = new HashMap<Resource, IElement>();
1603 this.removedRouteGraphConnections = new ArrayList<IElement>(changes.routeGraphConnections.size());
1606 public void clear() {
1607 // Prevent DiagramContents leakage through DisposableListeners.
1612 this.addedElements.clear();
1613 this.removedElements.clear();
1614 this.addedConnectionSegments.clear();
1615 this.removedConnectionSegments.clear();
1616 this.addedBranchPoints.clear();
1617 this.removedBranchPoints.clear();
1618 this.addedElementMap.clear();
1619 this.addedConnectionMap.clear();
1620 this.addedConnectionEntities.clear();
1621 this.removedConnectionEntities.clear();
1622 this.changedConnectionEntities.clear();
1623 this.addedRouteGraphConnectionMap.clear();
1624 this.removedRouteGraphConnections.clear();
1627 void processNodes(ReadGraph graph) throws DatabaseException {
1629 for (Map.Entry<Resource, Change> entry : changes.elements.entrySet()) {
1631 final Resource element = entry.getKey();
1632 Change change = entry.getValue();
1636 IElement mappedElement = getMappedElement(element);
1637 if (mappedElement == null) {
1638 if (DebugPolicy.DEBUG_NODE_LOAD)
1639 graph.syncRequest(new ReadRequest() {
1641 public void run(ReadGraph graph) throws DatabaseException {
1642 System.out.println(" EXTERNALLY ADDED ELEMENT: "
1643 + NameUtils.getSafeName(graph, element) + " ("
1644 + element.getResourceId() + ")");
1648 if (content.connectionSet.contains(element)) {
1650 // TODO: Connection loading has no listening, changes :Connection will not be noticed by this code!
1651 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1653 public String toString() {
1654 return "Connection load listener for " + element;
1657 public void execute(IElement loaded) {
1658 // Invoked when the element has been loaded.
1659 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1660 System.out.println("CONNECTION LoadListener for " + loaded);
1662 if (loaded == null) {
1667 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1669 // Logic for disposing listener
1670 if (!previousContent.connectionSet.contains(data)) {
1671 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1672 System.out.println("CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
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_CONNECTION_LISTENER)
1684 System.out.println("LOADED ADDED CONNECTION, currently mapped connection: " + mappedElement);
1685 if (mappedElement != null && (mappedElement instanceof Element)) {
1686 if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
1687 System.out.println(" mapped hints: " + mappedElement.getHints());
1688 System.out.println(" loaded hints: " + loaded.getHints());
1690 updateMappedElement((Element) mappedElement, loaded);
1693 // This element was already loaded.
1694 // Just schedule an update some time
1696 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1697 System.out.println("PREVIOUSLY LOADED CONNECTION UPDATED, scheduling update into the future");
1698 offerGraphUpdate( connectionUpdater(element, loaded) );
1703 graph.syncRequest(new ConnectionRequest(canvas, diagram, element, errorHandler, loadListener), new AsyncProcedure<IElement>() {
1705 public void execute(AsyncReadGraph graph, final IElement e) {
1709 //System.out.println("ConnectionRequestProcedure " + e);
1710 mapElement(element, e);
1711 synchronized (GraphToDiagramUpdater.this) {
1712 addedElements.add(e);
1713 addedElementMap.put(element, e);
1714 addedConnectionMap.put(element, e);
1717 // Read connection type
1718 graph.forSingleType(element, br.DIA.Connection, new Procedure<Resource>() {
1720 public void exception(Throwable t) {
1725 public void execute(Resource connectionType) {
1726 synchronized (GraphToDiagramUpdater.this) {
1727 //System.out.println("new connection entity " + e);
1728 ConnectionEntityImpl entity = new ConnectionEntityImpl(element, connectionType, e);
1729 e.setHint(ElementHints.KEY_CONNECTION_ENTITY, entity);
1730 addedConnectionEntities.put(element, entity);
1737 public void exception(AsyncReadGraph graph, Throwable throwable) {
1741 } else if (content.nodeSet.contains(element)) {
1743 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1745 public String toString() {
1746 return "Node load listener for " + element;
1749 public void execute(IElement loaded) {
1750 // Invoked when the element has been loaded.
1751 if (DebugPolicy.DEBUG_NODE_LISTENER)
1752 System.out.println("NODE LoadListener for " + loaded);
1754 if (loaded == null) {
1759 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1761 // Logic for disposing listener
1762 if (!previousContent.nodeSet.contains(data)) {
1763 if (DebugPolicy.DEBUG_NODE_LISTENER)
1764 System.out.println("NODE LoadListener, node not in current content: " + data + ". Disposing.");
1769 if (addedElementMap.containsKey(data)) {
1770 // This element was just loaded, in
1771 // which case its hints need to
1772 // uploaded to the real mapped
1773 // element immediately.
1774 IElement mappedElement = getMappedElement(data);
1775 if (DebugPolicy.DEBUG_NODE_LISTENER)
1776 System.out.println("LOADED ADDED ELEMENT, currently mapped element: " + mappedElement);
1777 if (mappedElement != null && (mappedElement instanceof Element)) {
1778 if (DebugPolicy.DEBUG_NODE_LISTENER) {
1779 System.out.println(" mapped hints: " + mappedElement.getHints());
1780 System.out.println(" loaded hints: " + loaded.getHints());
1782 updateMappedElement((Element) mappedElement, loaded);
1785 // This element was already loaded.
1786 // Just schedule an update some time
1788 if (DebugPolicy.DEBUG_NODE_LISTENER)
1789 System.out.println("PREVIOUSLY LOADED NODE UPDATED, scheduling update into the future");
1790 offerGraphUpdate( nodeUpdater(element, loaded) );
1795 //System.out.println("NODE REQUEST: " + element);
1796 graph.syncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
1798 public void execute(AsyncReadGraph graph, IElement e) {
1802 // This is invoked before the element is actually loaded.
1803 //System.out.println("NodeRequestProcedure " + e);
1804 if (DebugPolicy.DEBUG_NODE_LOAD)
1805 System.out.println("MAPPING ADDED NODE: " + element + " -> " + e);
1806 mapElement(element, e);
1807 synchronized (GraphToDiagramUpdater.this) {
1808 addedElements.add(e);
1809 addedElementMap.put(element, e);
1814 public void exception(AsyncReadGraph graph, Throwable throwable) {
1820 // warning("Diagram elements must be either elements or connections, "
1821 // + NameUtils.getSafeName(g, element) + " is neither",
1822 // new AssumptionException(""));
1828 IElement e = getMappedElement(element);
1829 if (DebugPolicy.DEBUG_NODE_LOAD)
1830 graph.syncRequest(new ReadRequest() {
1832 public void run(ReadGraph graph) throws DatabaseException {
1833 System.out.println(" EXTERNALLY REMOVED ELEMENT: "
1834 + NameUtils.getSafeName(graph, element) + " ("
1835 + element.getResourceId() + ")");
1839 removedElements.add(e);
1847 void gatherChangedConnectionParts(Map<?, Change> changes) {
1848 for (Map.Entry<?, Change> entry : changes.entrySet()) {
1849 Object part = entry.getKey();
1850 Change change = entry.getValue();
1854 synchronized (GraphToDiagramUpdater.this) {
1855 Resource connection = content.partToConnection.get(part);
1856 assert connection != null;
1858 IElement ce = getMappedElement(connection);
1860 ce = addedElementMap.get(connection);
1863 markConnectionChanged(ce);
1868 if (lastContent == null)
1870 Resource connection = lastContent.partToConnection.get(part);
1871 if (connection != null && content.connectionSet.contains(connection)) {
1872 markConnectionChanged(connection);
1880 void markConnectionChanged(Resource connection) {
1881 // System.out.println("markConnectionChanged");
1882 ConnectionEntityImpl ce = getMappedConnection(connection);
1884 markConnectionChanged(ce);
1887 error("WARNING: marking connection entity " + connection
1888 + " changed, but the connection was not previously mapped",
1889 new Exception("created exception to get a stack trace"));
1892 void markConnectionChanged(IElement connection) {
1893 ConnectionEntityImpl entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
1895 markConnectionChanged(entity);
1898 void markConnectionChanged(ConnectionEntityImpl ce) {
1899 if (!changedConnectionEntities.containsKey(ce)) {
1900 changedConnectionEntities.put(ce, new ConnectionData(ce));
1904 void processConnections() {
1905 // Find added/removed connection segments/branch points
1906 // in order to find all changed connection entities.
1907 gatherChangedConnectionParts(changes.connectionSegments);
1908 gatherChangedConnectionParts(changes.branchPoints);
1910 // Find removed connection entities
1911 for (Map.Entry<Resource, Change> entry : changes.connections.entrySet()) {
1912 Resource ce = entry.getKey();
1913 Change change = entry.getValue();
1917 removedConnectionEntities.add(ce);
1922 // Generate update data of changed connection entities.
1923 // This ConnectionData will be applied in the canvas thread
1925 for (ConnectionData cd : changedConnectionEntities.values()) {
1926 for (Object part : content.connectionToParts.getValuesUnsafe(cd.impl.connection)) {
1927 if (part instanceof Resource) {
1928 cd.branchPoints.add((Resource) part);
1929 } else if (part instanceof EdgeResource) {
1930 cd.segments.add((EdgeResource) part);
1936 void processRouteGraphConnections(ReadGraph graph) throws DatabaseException {
1937 for (Map.Entry<Resource, Change> entry : changes.routeGraphConnections.entrySet()) {
1938 final Resource connection = entry.getKey();
1940 Change change = entry.getValue();
1943 IElement mappedElement = getMappedElement(connection);
1944 if (mappedElement != null)
1947 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
1949 public String toString() {
1950 return "processRouteGraphConnections " + connection;
1953 public void execute(IElement loaded) {
1954 // Invoked when the element has been loaded.
1955 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1956 System.out.println("ROUTE GRAPH CONNECTION LoadListener for " + loaded);
1958 if (loaded == null) {
1963 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
1965 // Logic for disposing listener
1966 if (!previousContent.routeGraphConnectionSet.contains(data)) {
1967 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1968 System.out.println("ROUTE GRAPH CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
1973 if (addedElementMap.containsKey(data)) {
1974 // This element was just loaded, in
1975 // which case its hints need to
1976 // uploaded to the real mapped
1977 // element immediately.
1978 IElement mappedElement = getMappedElement(data);
1979 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1980 System.out.println("LOADED ADDED ROUTE GRAPH CONNECTION, currently mapped connection: " + mappedElement);
1981 if (mappedElement instanceof Element) {
1982 if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
1983 System.out.println(" mapped hints: " + mappedElement.getHints());
1984 System.out.println(" loaded hints: " + loaded.getHints());
1986 updateMappedElement((Element) mappedElement, loaded);
1989 // This element was already loaded.
1990 // Just schedule an update some time
1992 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
1993 System.out.println("PREVIOUSLY LOADED ROUTE GRAPH CONNECTION UPDATED, scheduling update into the future: " + connection);
1995 Set<Object> dirtyNodes = new THashSet<Object>(4);
1996 IElement mappedElement = getMappedElement(connection);
1997 ConnectionEntity ce = mappedElement.getHint(ElementHints.KEY_CONNECTION_ENTITY);
1999 for (Connection conn : ce.getTerminalConnections(null)) {
2000 Object o = conn.node.getHint(ElementHints.KEY_OBJECT);
2003 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
2004 System.out.println("Marked connectivity dirty for node: " + conn.node);
2009 offerGraphUpdate( routeGraphConnectionUpdater(connection, loaded, dirtyNodes) );
2014 graph.syncRequest(new ConnectionRequest(canvas, diagram, connection, errorHandler, loadListener), new Procedure<IElement>() {
2016 public void execute(final IElement e) {
2020 //System.out.println("ConnectionRequestProcedure " + e);
2021 if (DebugPolicy.DEBUG_NODE_LOAD)
2022 System.out.println("MAPPING ADDED ROUTE GRAPH CONNECTION: " + connection + " -> " + e);
2023 mapElement(connection, e);
2024 synchronized (GraphToDiagramUpdater.this) {
2025 addedElements.add(e);
2026 addedElementMap.put(connection, e);
2027 addedRouteGraphConnectionMap.put(connection, e);
2031 public void exception(Throwable throwable) {
2038 IElement e = getMappedElement(connection);
2040 removedRouteGraphConnections.add(e);
2047 ConnectionEntityImpl getConnectionEntity(Object connectionPart) {
2048 Resource connection = content.partToConnection.get(connectionPart);
2049 assert connection != null;
2050 ConnectionEntityImpl ce = addedConnectionEntities.get(connection);
2053 return assertMappedConnection(connection);
2056 void processBranchPoints(ReadGraph graph) throws DatabaseException {
2057 for (Map.Entry<Resource, Change> entry : changes.branchPoints.entrySet()) {
2059 final Resource element = entry.getKey();
2060 Change change = entry.getValue();
2064 IElement mappedElement = getMappedElement(element);
2065 if (mappedElement == null) {
2066 if (DebugPolicy.DEBUG_NODE_LOAD)
2067 graph.syncRequest(new ReadRequest() {
2069 public void run(ReadGraph graph) throws DatabaseException {
2070 System.out.println(" EXTERNALLY ADDED BRANCH POINT: "
2071 + NameUtils.getSafeName(graph, element) + " ("
2072 + element.getResourceId() + ")");
2076 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
2078 public String toString() {
2079 return "processBranchPoints for " + element;
2082 public void execute(IElement loaded) {
2083 // Invoked when the element has been loaded.
2084 if (DebugPolicy.DEBUG_NODE_LISTENER)
2085 System.out.println("BRANCH POINT LoadListener for " + loaded);
2087 if (loaded == null) {
2092 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2093 if (addedElementMap.containsKey(data)) {
2094 // This element was just loaded, in
2095 // which case its hints need to
2096 // uploaded to the real mapped
2097 // element immediately.
2098 IElement mappedElement = getMappedElement(data);
2099 if (DebugPolicy.DEBUG_NODE_LISTENER)
2100 System.out.println("LOADED ADDED BRANCH POINT, currently mapped element: " + mappedElement);
2101 if (mappedElement != null && (mappedElement instanceof Element)) {
2102 if (DebugPolicy.DEBUG_NODE_LISTENER) {
2103 System.out.println(" mapped hints: " + mappedElement.getHints());
2104 System.out.println(" loaded hints: " + loaded.getHints());
2106 updateMappedElement((Element) mappedElement, loaded);
2109 // This element was already loaded.
2110 // Just schedule an update some time
2112 if (DebugPolicy.DEBUG_NODE_LISTENER)
2113 System.out.println("PREVIOUSLY LOADED BRANCH POINT UPDATED, scheduling update into the future");
2114 offerGraphUpdate( nodeUpdater(element, loaded) );
2119 graph.syncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
2121 public void execute(AsyncReadGraph graph, IElement e) {
2123 mapElement(element, e);
2124 synchronized (GraphToDiagramUpdater.this) {
2125 addedBranchPoints.add(e);
2126 addedElementMap.put(element, e);
2127 ConnectionEntityImpl ce = getConnectionEntity(element);
2128 e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
2129 e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
2135 public void exception(AsyncReadGraph graph, Throwable throwable) {
2143 IElement e = getMappedElement(element);
2144 if (DebugPolicy.DEBUG_NODE_LOAD)
2145 graph.syncRequest(new ReadRequest() {
2147 public void run(ReadGraph graph) throws DatabaseException {
2148 System.out.println(" EXTERNALLY REMOVED BRANCH POINT: "
2149 + NameUtils.getSafeName(graph, element) + " ("
2150 + element.getResourceId() + ")");
2154 removedBranchPoints.add(e);
2162 void processConnectionSegments(ReadGraph graph) throws DatabaseException {
2163 ConnectionSegmentAdapter adapter = connectionSegmentAdapter;
2165 for (Map.Entry<EdgeResource, Change> entry : changes.connectionSegments.entrySet()) {
2166 final EdgeResource seg = entry.getKey();
2167 Change change = entry.getValue();
2171 IElement mappedElement = getMappedElement(seg);
2172 if (mappedElement == null) {
2173 if (DebugPolicy.DEBUG_EDGE_LOAD)
2174 graph.syncRequest(new ReadRequest() {
2176 public void run(ReadGraph graph) throws DatabaseException {
2177 System.out.println(" EXTERNALLY ADDED CONNECTION SEGMENT: " + seg.toString()
2178 + " - " + seg.toString(graph));
2182 graph.syncRequest(new EdgeRequest(canvas, errorHandler, canvasListenerSupport, diagram, adapter, seg), new AsyncProcedure<IElement>() {
2184 public void execute(AsyncReadGraph graph, IElement e) {
2185 if (DebugPolicy.DEBUG_EDGE_LOAD)
2186 System.out.println("ADDED EDGE LOADED: " + e);
2189 synchronized (GraphToDiagramUpdater.this) {
2190 addedConnectionSegments.add(e);
2191 addedElementMap.put(seg, e);
2192 ConnectionEntityImpl ce = getConnectionEntity(seg);
2193 e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
2194 e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
2200 public void exception(AsyncReadGraph graph, Throwable throwable) {
2208 final IElement e = getMappedElement(seg);
2209 if (DebugPolicy.DEBUG_EDGE_LOAD)
2210 graph.syncRequest(new ReadRequest() {
2212 public void run(ReadGraph graph) throws DatabaseException {
2213 System.out.println(" EXTERNALLY REMOVED CONNECTION SEGMENT: " + seg.toString() + " - "
2214 + seg.toString(graph) + " -> " + e);
2218 removedConnectionSegments.add(e);
2226 void executeDeferredLoaders(ReadGraph graph) throws DatabaseException {
2227 // The rest of the diagram loading passes
2228 Deque<IElement> q1 = new ArrayDeque<IElement>();
2229 Deque<IElement> q2 = new ArrayDeque<IElement>();
2230 collectElementLoaders(q1, addedElements);
2231 while (!q1.isEmpty()) {
2232 //System.out.println("DEFFERED LOADERS: " + q1);
2233 for (IElement e : q1) {
2234 ElementLoader loader = e.removeHint(DiagramModelHints.KEY_ELEMENT_LOADER);
2235 //System.out.println("EXECUTING DEFFERED LOADER: " + loader);
2236 loader.load(graph, diagram, e);
2239 collectElementLoaders(q2, q1);
2240 Deque<IElement> qt = q1;
2247 private void collectElementLoaders(Queue<IElement> queue, Collection<IElement> cs) {
2248 for (IElement e : cs) {
2249 ElementLoader loader = e.getHint(DiagramModelHints.KEY_ELEMENT_LOADER);
2255 public void process(ReadGraph graph) throws DatabaseException {
2256 // No changes? Do nothing.
2257 if (changes.isEmpty())
2260 // NOTE: This order is important.
2261 Object task = Timing.BEGIN("processNodesConnections");
2262 //System.out.println("---- PROCESS NODES & CONNECTIONS BEGIN");
2263 if (!changes.elements.isEmpty()) {
2264 graph.syncRequest(new ReadRequest() {
2266 public void run(ReadGraph graph) throws DatabaseException {
2267 processNodes(graph);
2270 public String toString() {
2271 return "processNodes";
2275 //System.out.println("---- PROCESS NODES & CONNECTIONS END");
2277 processConnections();
2279 //System.out.println("---- PROCESS BRANCH POINTS BEGIN");
2280 if (!changes.branchPoints.isEmpty()) {
2281 graph.syncRequest(new ReadRequest() {
2283 public void run(ReadGraph graph) throws DatabaseException {
2284 processBranchPoints(graph);
2287 public String toString() {
2288 return "processBranchPoints";
2292 //System.out.println("---- PROCESS BRANCH POINTS END");
2295 task = Timing.BEGIN("processConnectionSegments");
2297 //System.out.println("---- PROCESS CONNECTION SEGMENTS BEGIN");
2298 if (!changes.connectionSegments.isEmpty()) {
2299 graph.syncRequest(new ReadRequest() {
2301 public void run(ReadGraph graph) throws DatabaseException {
2302 processConnectionSegments(graph);
2305 public String toString() {
2306 return "processConnectionSegments";
2310 //System.out.println("---- PROCESS CONNECTION SEGMENTS END");
2314 task = Timing.BEGIN("processRouteGraphConnections");
2315 if (!changes.routeGraphConnections.isEmpty()) {
2316 graph.syncRequest(new ReadRequest() {
2318 public void run(ReadGraph graph) throws DatabaseException {
2319 processRouteGraphConnections(graph);
2322 public String toString() {
2323 return "processRouteGraphConnections";
2329 //System.out.println("---- AFTER LOADING");
2330 //for (IElement e : addedElements)
2331 // System.out.println(" ADDED ELEMENT: " + e);
2332 //for (IElement e : addedBranchPoints)
2333 // System.out.println(" ADDED BRANCH POINTS: " + e);
2335 task = Timing.BEGIN("executeDeferredLoaders");
2336 executeDeferredLoaders(graph);
2340 public boolean isEmpty() {
2341 return addedElements.isEmpty() && removedElements.isEmpty()
2342 && addedConnectionSegments.isEmpty() && removedConnectionSegments.isEmpty()
2343 && addedBranchPoints.isEmpty() && removedBranchPoints.isEmpty()
2344 && addedConnectionEntities.isEmpty() && removedConnectionEntities.isEmpty()
2345 && addedRouteGraphConnectionMap.isEmpty() && removedRouteGraphConnections.isEmpty()
2346 && !changes.elementOrderChanged;
2349 class DefaultConnectionSegmentAdapter implements ConnectionSegmentAdapter {
2352 public void getClass(AsyncReadGraph graph, EdgeResource edge, ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, IDiagram diagram, final AsyncProcedure<ElementClass> procedure) {
2353 if (info.connectionType != null) {
2354 NodeClassRequest request = new NodeClassRequest(canvas, diagram, info.connectionType, true);
2355 graph.asyncRequest(request, new CacheListener<ElementClass>(listenerSupport));
2356 graph.asyncRequest(request, procedure);
2358 procedure.execute(graph, null);
2363 public void load(AsyncReadGraph graph, final EdgeResource edge, final ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, final IDiagram diagram, final IElement element) {
2364 graph.asyncRequest(new Read<IElement>() {
2366 public IElement perform(ReadGraph graph) throws DatabaseException {
2367 //ITask task = ThreadLogger.getInstance().begin("LoadSegment");
2368 syncLoad(graph, edge, info, diagram, element);
2373 public String toString() {
2374 return "defaultConnectionSegmentAdapter";
2376 }, new DisposableListener<IElement>(listenerSupport) {
2379 public String toString() {
2380 return "DefaultConnectionSegmentAdapter listener for " + edge;
2384 public void execute(IElement loaded) {
2385 // Invoked when the element has been loaded.
2386 if (DebugPolicy.DEBUG_EDGE_LISTENER)
2387 System.out.println("EDGE LoadListener for " + loaded);
2389 if (loaded == null) {
2394 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
2395 if (addedElementMap.containsKey(data)) {
2396 // This element was just loaded, in
2397 // which case its hints need to
2398 // uploaded to the real mapped
2399 // element immediately.
2400 IElement mappedElement = getMappedElement(data);
2401 if (DebugPolicy.DEBUG_EDGE_LISTENER)
2402 System.out.println("LOADED ADDED EDGE, currently mapped element: " + mappedElement);
2403 if (mappedElement != null && (mappedElement instanceof Element)) {
2404 if (DebugPolicy.DEBUG_EDGE_LISTENER) {
2405 System.out.println(" mapped hints: " + mappedElement.getHints());
2406 System.out.println(" loaded hints: " + loaded.getHints());
2408 updateMappedElement((Element) mappedElement, loaded);
2411 // This element was already loaded.
2412 // Just schedule an update some time
2414 if (DebugPolicy.DEBUG_EDGE_LISTENER)
2415 System.out.println("PREVIOUSLY LOADED EDGE UPDATED, scheduling update into the future");
2416 offerGraphUpdate( edgeUpdater(element, loaded) );
2422 void syncLoad(ReadGraph graph, EdgeResource connectionSegment, ConnectionInfo info, IDiagram diagram, IElement element) throws DatabaseException {
2423 // Check that at least some data exists before continuing further.
2424 if (!graph.hasStatement(connectionSegment.first()) && !graph.hasStatement(connectionSegment.second())) {
2428 // Validate that both ends of the segment are
2429 // part of the same connection before loading.
2430 // This will happen for connections that are
2431 // modified through splitting and joining of
2432 // connection segments.
2433 Resource c = ConnectionUtil.tryGetConnection(graph, connectionSegment);
2435 // Ok, this segment is somehow invalid. Just don't load it.
2436 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2437 System.out.println("Skipping edge " + connectionSegment + ". Both segment ends are not part of the same connection.");
2441 if (!info.isValid()) {
2442 // This edge must be somehow invalid, don't proceed with loading.
2443 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2444 warning("Cannot load edge " + connectionSegment + ". ConnectionInfo " + info + " is invalid.", new DebugException("execution trace"));
2448 Element edge = (Element) element;
2449 edge.setHint(ElementHints.KEY_OBJECT, connectionSegment);
2451 // connectionSegment resources may currently be in a different
2452 // order than ConnectionInfo.firstEnd/secondEnd. Therefore the
2453 // segment ends must be resolved here.
2454 ConnectionSegmentEnd firstEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.first());
2455 ConnectionSegmentEnd secondEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.second());
2456 if (firstEnd == null || secondEnd == null) {
2457 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2458 warning("End attachments for edge " + connectionSegment + " are unresolved: (" + firstEnd + "," + secondEnd + ")", new DebugException("execution trace"));
2462 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2463 System.out.println("CONNECTION INFO: " + connectionSegment + " - " + info);
2464 DesignatedTerminal firstTerminal = DiagramGraphUtil.findDesignatedTerminal(
2465 graph, diagram, connectionSegment.first(), firstEnd);
2466 DesignatedTerminal secondTerminal = DiagramGraphUtil.findDesignatedTerminal(
2467 graph, diagram, connectionSegment.second(), secondEnd);
2469 // Edges must be connected at both ends in order for edge loading to succeed.
2470 String err = validateConnectivity(graph, connectionSegment, firstTerminal, secondTerminal);
2472 // Stop loading edge if the connectivity cannot be completely resolved.
2473 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
2478 // NOTICE: Layer information is loaded from the connection entity resource
2479 // that is shared by all segments of the same connection.
2480 ElementFactoryUtil.loadLayersForElement(graph, layerManager, diagram, edge, info.connection,
2481 new AsyncProcedureAdapter<IElement>() {
2483 public void exception(AsyncReadGraph graph, Throwable t) {
2484 error("failed to load layers for connection segment", t);
2488 edge.setHintWithoutNotification(KEY_CONNECTION_BEGIN_PLACEHOLDER, new PlaceholderConnection(
2490 firstTerminal.element.getHint(ElementHints.KEY_OBJECT),
2491 firstTerminal.terminal));
2492 edge.setHintWithoutNotification(KEY_CONNECTION_END_PLACEHOLDER, new PlaceholderConnection(
2494 secondTerminal.element.getHint(ElementHints.KEY_OBJECT),
2495 secondTerminal.terminal));
2497 IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);
2498 if (modelingRules != null) {
2499 ConnectionVisualsLoader loader = diagram.getHint(DiagramModelHints.KEY_CONNECTION_VISUALS_LOADER);
2501 loader.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
2503 DiagramGraphUtil.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
2507 private String validateConnectivity(ReadGraph graph, EdgeResource edge,
2508 DesignatedTerminal firstTerminal,
2509 DesignatedTerminal secondTerminal)
2510 throws DatabaseException {
2511 boolean firstLoose = firstTerminal == null;
2512 boolean secondLoose = secondTerminal == null;
2513 boolean stray = firstLoose && secondLoose;
2514 if (firstTerminal == null || secondTerminal == null) {
2515 StringBuilder sb = new StringBuilder();
2516 sb.append("encountered ");
2517 sb.append(stray ? "stray" : "loose");
2518 sb.append(" connection segment, ");
2520 sb.append("first ");
2524 sb.append("second ");
2525 sb.append("end disconnected: ");
2526 sb.append(edge.toString(graph));
2528 sb.append(edge.toString());
2529 return sb.toString();
2536 ConnectionSegmentAdapter connectionSegmentAdapter = new DefaultConnectionSegmentAdapter();
2540 private static final Double DIAGRAM_UPDATE_DIAGRAM_PRIORITY = 1d;
2541 private static final Double DIAGRAM_UPDATE_NODE_PRIORITY = 2d;
2542 private static final Double DIAGRAM_UPDATE_CONNECTION_PRIORITY = 3d;
2543 private static final Double DIAGRAM_UPDATE_EDGE_PRIORITY = 4d;
2545 interface DiagramUpdater extends Runnable {
2546 Double getPriority();
2548 Comparator<DiagramUpdater> DIAGRAM_UPDATER_COMPARATOR = new Comparator<DiagramUpdater>() {
2550 public int compare(DiagramUpdater o1, DiagramUpdater o2) {
2551 return o1.getPriority().compareTo(o2.getPriority());
2556 interface GraphUpdateReactor {
2557 DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException;
2560 static abstract class AbstractDiagramUpdater implements DiagramUpdater, GraphUpdateReactor {
2561 protected final Double priority;
2562 protected final String runnerName;
2564 public AbstractDiagramUpdater(Double priority, String runnerName) {
2565 if (priority == null)
2566 throw new NullPointerException("null priority");
2567 if (runnerName == null)
2568 throw new NullPointerException("null runner name");
2569 this.priority = priority;
2570 this.runnerName = runnerName;
2574 public Double getPriority() {
2579 public AbstractDiagramUpdater graphUpdate(ReadGraph graph) {
2585 Object task = Timing.BEGIN(runnerName);
2590 protected void forDiagram() {
2594 public String toString() {
2595 return runnerName + "@" + System.identityHashCode(this) + " [" + priority + "]";
2600 * @param content the new contents of the diagram, must not be
2601 * <code>null</code>.
2603 private GraphUpdateReactor diagramGraphUpdater(final DiagramContents content) {
2604 if (content == null)
2605 throw new NullPointerException("null diagram content");
2607 return new GraphUpdateReactor() {
2609 public String toString() {
2610 return "DiagramGraphUpdater@" + System.identityHashCode(this);
2614 public DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException {
2615 // Never do anything here if the canvas has already been disposed.
2616 if (!GraphToDiagramSynchronizer.this.isAlive())
2619 // We must be prepared for the following changes in the diagram graph
2621 // - the diagram has been completely removed
2622 // - elements have been added
2623 // - elements have been removed
2625 // Element-specific changes are handled by the element query listeners:
2626 // - elements have been modified
2627 // - element position has changed
2628 // - element class (e.g. image) has changed
2630 diagramUpdateLock.lock();
2632 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2633 System.out.println("In diagramGraphUpdater:");
2635 // Find out what has changed since the last query.
2636 Object task = Timing.BEGIN("diagramContentDifference");
2637 DiagramContents lastContent = previousContent;
2638 DiagramContentChanges changes = content.differenceFrom(previousContent);
2639 previousContent = content;
2641 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2642 System.out.println(" changes: " + changes);
2644 // Bail out if there are no changes to react to.
2645 if (changes.isEmpty())
2648 // Load everything that needs to be loaded from the graph,
2649 // but don't update the UI model in this thread yet.
2650 task = Timing.BEGIN("updater.process");
2651 GraphToDiagramUpdater updater = new GraphToDiagramUpdater(lastContent, content, changes);
2652 GraphToDiagramSynchronizer.this.currentUpdater = updater;
2654 updater.process(graph);
2656 GraphToDiagramSynchronizer.this.currentUpdater = null;
2660 if (updater.isEmpty())
2663 // Return an updater that will update the UI run-time model.
2664 return diagramUpdater(updater);
2666 diagramUpdateLock.unlock();
2672 DiagramUpdater diagramUpdater(final GraphToDiagramUpdater updater) {
2673 return new AbstractDiagramUpdater(DIAGRAM_UPDATE_DIAGRAM_PRIORITY, "updateDiagram") {
2675 protected void forDiagram() {
2676 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
2677 System.out.println("running diagram updater: " + this);
2679 // DiagramUtils.testDiagram(diagram);
2680 Set<IElement> dirty = new HashSet<IElement>();
2682 Object task2 = Timing.BEGIN("Preprocess connection changes");
2683 Map<ConnectionEntity, ConnectionChildren> connectionChangeData = new HashMap<ConnectionEntity, ConnectionChildren>(updater.changedConnectionEntities.size());
2684 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2685 connectionChangeData.put(cd.impl, cd.impl.getConnectionChildren());
2689 task2 = Timing.BEGIN("removeRouteGraphConnections");
2690 for (IElement removedRouteGraphConnection : updater.removedRouteGraphConnections) {
2691 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2692 System.out.println("removing route graph connection: " + removedRouteGraphConnection);
2694 ConnectionEntity ce = removedRouteGraphConnection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
2697 Object connectionData = ElementUtils.getObject(removedRouteGraphConnection);
2698 tempConnections.clear();
2699 for (Connection conn : ce.getTerminalConnections(tempConnections)) {
2700 ((Element) conn.node).removeHintWithoutNotification(new TerminalKeyOf(conn.terminal, connectionData, Connection.class));
2701 // To be sure the view will be up-to-date, mark the node
2702 // connected to the removed connection dirty.
2703 dirty.add(conn.node);
2708 task2 = Timing.BEGIN("removeBranchPoints");
2709 for (IElement removed : updater.removedBranchPoints) {
2710 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2711 System.out.println("removing branch point: " + removed);
2713 unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2714 removeNodeTopologyHints((Element) removed);
2716 IElement connection = ElementUtils.getParent(removed);
2717 if (connection != null) {
2718 dirty.add(connection);
2723 task2 = Timing.BEGIN("removeConnectionSegments");
2724 for (IElement removed : updater.removedConnectionSegments) {
2725 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2726 System.out.println("removing segment: " + removed);
2728 unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2729 removeEdgeTopologyHints((Element) removed);
2731 IElement connection = ElementUtils.getParent(removed);
2732 if (connection != null) {
2733 dirty.add(connection);
2738 task2 = Timing.BEGIN("removeElements");
2739 for (IElement removed : updater.removedElements) {
2740 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2741 System.out.println("removing element: " + removed);
2743 removed.setHint(KEY_REMOVE_RELATIONSHIPS, Boolean.TRUE);
2744 if (diagram.containsElement(removed)) {
2745 diagram.removeElement(removed);
2747 unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
2748 removeNodeTopologyHints((Element) removed);
2750 // No use marking removed elements dirty.
2751 dirty.remove(removed);
2755 // TODO: get rid of this
2756 task2 = Timing.BEGIN("removeConnectionEntities");
2757 for (Resource ce : updater.removedConnectionEntities) {
2758 unmapConnection(ce);
2762 task2 = Timing.BEGIN("setConnectionData");
2763 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2764 cd.impl.setData(cd.segments, cd.branchPoints);
2768 // TODO: get rid of this
2769 task2 = Timing.BEGIN("addConnectionEntities");
2770 for (Map.Entry<Resource, ConnectionEntityImpl> entry : updater.addedConnectionEntities
2772 mapConnection(entry.getKey(), entry.getValue());
2776 task2 = Timing.BEGIN("addBranchPoints");
2777 for (IElement added : updater.addedBranchPoints) {
2778 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2779 System.out.println("adding branch point: " + added);
2781 mapElement(ElementUtils.getObject(added), added);
2783 IElement connection = ElementUtils.getParent(added);
2784 if (connection != null) {
2785 dirty.add(connection);
2790 // Add new elements at end of diagram, element order will be synchronized later.
2791 task2 = Timing.BEGIN("addElements");
2792 for(Resource r : updater.content.elements) {
2793 IElement added = updater.addedElementMap.get(r);
2795 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2796 System.out.println("adding element: " + added);
2798 //Object task3 = BEGIN("mapElement " + added);
2799 Object task3 = Timing.BEGIN("mapElement");
2800 mapElement(added.getHint(ElementHints.KEY_OBJECT), added);
2803 //task3 = BEGIN("addElement " + added);
2804 task3 = Timing.BEGIN("addElement");
2805 //System.out.println("diagram.addElement: " + added + " - " + diagram);
2806 diagram.addElement(added);
2813 // We've ensured that all nodes must have been and
2814 // mapped before reaching this.
2815 task2 = Timing.BEGIN("addConnectionSegments");
2816 for (IElement added : updater.addedConnectionSegments) {
2817 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2818 System.out.println("adding segment: " + added);
2820 PlaceholderConnection cb = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_BEGIN_PLACEHOLDER);
2821 PlaceholderConnection ce = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_END_PLACEHOLDER);
2822 if (cb == null || ce == null) {
2823 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2824 warning("ignoring connection segment " + added + ", connectivity was not resolved (begin=" + cb + ", end=" + ce +")", null);
2828 mapElement(ElementUtils.getObject(added), added);
2830 IElement beginNode = assertMappedElement(cb.node);
2831 IElement endNode = assertMappedElement(ce.node);
2834 connect(added, cb.end, beginNode, cb.terminal);
2836 connect(added, ce.end, endNode, ce.terminal);
2838 IElement connection = ElementUtils.getParent(added);
2839 if (connection != null) {
2840 dirty.add(connection);
2845 // We've ensured that all nodes must have been and
2846 // mapped before reaching this.
2848 task2 = Timing.BEGIN("handle dirty RouteGraph connections");
2849 for (IElement addedRouteGraphConnection : updater.addedRouteGraphConnectionMap.values()) {
2850 updateDirtyRouteGraphConnection(addedRouteGraphConnection, dirty);
2854 // Prevent memory leaks
2855 tempConnections.clear();
2857 // Make sure that the diagram element order matches that of the database.
2858 final TObjectIntHashMap<IElement> orderMap = new TObjectIntHashMap<IElement>(2 * updater.content.elements.size());
2860 for (Resource r : updater.content.elements) {
2861 IElement e = getMappedElement(r);
2866 diagram.sort(new Comparator<IElement>() {
2868 public int compare(IElement e1, IElement e2) {
2869 int o1 = orderMap.get(e1);
2870 int o2 = orderMap.get(e2);
2875 // TODO: consider removing this. The whole thing should work without it and
2876 // this "fix" will only be hiding the real problems.
2877 task2 = Timing.BEGIN("fixChangedConnections");
2878 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2883 task2 = Timing.BEGIN("validateAndFix");
2884 DiagramUtils.validateAndFix(diagram, dirty);
2887 // This will fire connection entity change listeners
2888 task2 = Timing.BEGIN("Postprocess connection changes");
2889 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
2890 ConnectionChildren oldChildren = connectionChangeData.get(cd.impl);
2891 if (oldChildren != null) {
2892 ConnectionChildren currentChildren = cd.impl.getConnectionChildren();
2893 cd.impl.fireListener(oldChildren, currentChildren);
2898 task2 = Timing.BEGIN("setDirty");
2899 for (IElement e : dirty) {
2900 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2901 System.out.println("MARKING ELEMENT DIRTY: " + e);
2902 e.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
2906 // Signal about possible changes in the z-order of diagram elements.
2907 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2908 System.out.println("MARKING DIAGRAM DIRTY: " + diagram);
2909 diagram.setHint(Hints.KEY_DIRTY, Hints.VALUE_Z_ORDER_CHANGED);
2911 // Mark the updater as "processed".
2914 // Inform listeners that the diagram has been updated.
2915 diagram.setHint(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, Boolean.TRUE);
2924 private void updateDirtyRouteGraphConnection(IElement connection, Set<IElement> dirtySet) {
2925 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
2926 System.out.println("updating dirty route graph connection: " + connection);
2928 ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
2932 tempConnections.clear();
2933 Object connectionData = ElementUtils.getObject(connection);
2934 for (Connection conn : ce.getTerminalConnections(tempConnections)) {
2935 ((Element) conn.node).setHintWithoutNotification(
2936 new TerminalKeyOf(conn.terminal, connectionData, Connection.class),
2938 if (dirtySet != null)
2939 dirtySet.add(conn.node);
2942 // Prevent memory leaks.
2943 tempConnections.clear();
2946 abstract class ElementUpdater extends AbstractDiagramUpdater {
2947 private final IElement newElement;
2949 public ElementUpdater(Double priority, String runnerName, IElement newElement) {
2950 super(priority, runnerName);
2951 if (newElement == null)
2952 throw new NullPointerException("null element");
2953 this.newElement = newElement;
2957 public String toString() {
2958 return super.toString() + "[" + newElement + "]";
2963 // System.out.println("ElementUpdateRunner new=" + newElement);
2964 Object elementResource = newElement.getHint(ElementHints.KEY_OBJECT);
2965 // System.out.println("ElementUpdateRunner res=" + elementResource);
2966 final Element mappedElement = (Element) getMappedElement(elementResource);
2967 // System.out.println("ElementUpdateRunner mapped=" + mappedElement);
2968 if (mappedElement == null) {
2969 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE) {
2970 System.out.println("SKIP DIAGRAM UPDATE " + this + " for element resource " + elementResource + ", no mapped element (newElement=" + newElement + ")");
2972 // Indicates the element has been removed from the graph.
2976 Object task = Timing.BEGIN(runnerName);
2977 forMappedElement(mappedElement);
2981 protected abstract void forMappedElement(Element mappedElement);
2984 ElementUpdater nodeUpdater(final Resource resource, final IElement newElement) {
2986 return new ElementUpdater(DIAGRAM_UPDATE_NODE_PRIORITY, "updateNode", newElement) {
2988 Collection<Terminal> getTerminals(IElement e) {
2989 Collection<Terminal> ts = Collections.emptyList();
2990 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
2992 ts = new ArrayList<Terminal>();
2993 tt.getTerminals(newElement, ts);
2999 protected void forMappedElement(final Element mappedElement) {
3000 if (DebugPolicy.DEBUG_NODE_UPDATE)
3001 System.out.println("running node updater: " + this + " - new element: " + newElement);
3003 // Newly loaded node elements NEVER contain topology-related
3004 // hints, i.e. TerminalKeyOf hints. Instead all connections are
3005 // actually set into element hints when connection edges are
3008 Collection<Terminal> oldTerminals = getTerminals(mappedElement);
3009 Collection<Terminal> newTerminals = getTerminals(newElement);
3010 if (!oldTerminals.equals(newTerminals)) {
3011 // Okay, there are differences in the terminals. Need to fix
3012 // the TerminalKeyOf hint values to use the new terminal
3013 // instances when correspondences exist.
3015 // If there is no correspondence for an old terminal, we
3016 // are simply forced to remove the hints related to this
3019 Map<Terminal, Terminal> newTerminalMap = new HashMap<Terminal, Terminal>(newTerminals.size());
3020 for (Terminal t : newTerminals) {
3021 newTerminalMap.put(t, t);
3024 for (Map.Entry<TerminalKeyOf, Object> entry : mappedElement.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3025 TerminalKeyOf key = entry.getKey();
3026 Connection c = (Connection) entry.getValue();
3027 if (c.node == mappedElement) {
3028 Terminal newTerminal = newTerminalMap.get(c.terminal);
3029 if (newTerminal != null) {
3030 c = new Connection(c.edge, c.end, c.node, newTerminal);
3031 ((Element) c.edge).setHintWithoutNotification(EndKeyOf.get(c.end), c);
3033 mappedElement.removeHintWithoutNotification(key);
3039 updateMappedElement(mappedElement, newElement);
3040 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3045 ElementUpdater connectionUpdater(final Object data, final IElement newElement) {
3046 return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateConnection", newElement) {
3048 public void forMappedElement(Element mappedElement) {
3049 if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
3050 System.out.println("running connection updater: " + this + " - new element: " + newElement
3051 + " with data " + data);
3053 // This is kept up-to-date by GDS, make sure not to overwrite it
3054 // from the mapped element.
3055 newElement.removeHint(ElementHints.KEY_CONNECTION_ENTITY);
3057 updateMappedElement(mappedElement, newElement);
3058 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3063 ElementUpdater edgeUpdater(final Object data, final IElement newElement) {
3064 return new ElementUpdater(DIAGRAM_UPDATE_EDGE_PRIORITY, "updateEdge", newElement) {
3066 public void forMappedElement(Element mappedElement) {
3067 if (DebugPolicy.DEBUG_EDGE_UPDATE)
3068 System.out.println("running edge updater: " + this + " - new element: " + newElement
3069 + " with data " + data);
3071 updateMappedElement(mappedElement, newElement);
3072 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3077 ElementUpdater routeGraphConnectionUpdater(final Object data, final IElement newElement, final Set<Object> dirtyNodes) {
3078 return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateRouteGraphConnection", newElement) {
3080 public void forMappedElement(Element mappedElement) {
3081 if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
3082 System.out.println("running route graph connection updater: " + this + " - new element: " + newElement
3083 + " with data " + data);
3085 // Remove all TerminalKeyOf hints from nodes that were
3086 // previously connected to the connection (mappedElement)
3087 // before updating mappedElement with new topology information.
3089 for (Object dirtyNodeObject : dirtyNodes) {
3090 Element dirtyNode = (Element) getMappedElement(dirtyNodeObject);
3091 if (dirtyNode == null)
3093 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
3094 System.out.println("preparing node with dirty connectivity: " + dirtyNode);
3096 for (Map.Entry<TerminalKeyOf, Object> entry : dirtyNode.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3097 Connection conn = (Connection) entry.getValue();
3098 Object connectionNode = conn.edge.getHint(ElementHints.KEY_OBJECT);
3099 if (data.equals(connectionNode)) {
3100 dirtyNode.removeHintWithoutNotification(entry.getKey());
3105 // Update connection information, including topology
3106 updateMappedElement(mappedElement, newElement);
3108 // Reinstall TerminalKeyOf hints into nodes that are now connected
3109 // to mappedElement to keep diagram run-time model properly in sync
3110 // with the database.
3111 updateDirtyRouteGraphConnection(mappedElement, null);
3113 // TODO: should mark dirty nodes' scene graph dirty ?
3115 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
3121 * Copies hints from <code>newElement</code> to <code>mappedElement</code>
3122 * asserting some validity conditions at the same time.
3124 * @param mappedElement
3127 static void updateMappedElement(Element mappedElement, IElement newElement) {
3128 if (mappedElement == newElement)
3129 // Can't update anything if the two elements are the same.
3132 ElementClass oldClass = mappedElement.getElementClass();
3133 ElementClass newClass = newElement.getElementClass();
3135 Object mappedData = mappedElement.getHint(ElementHints.KEY_OBJECT);
3136 Object newData = newElement.getHint(ElementHints.KEY_OBJECT);
3138 assert mappedData != null;
3139 assert newData != null;
3140 assert mappedData.equals(newData);
3142 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3143 System.out.println("Updating mapped element, setting hints\n from: " + newElement + "\n into: " + mappedElement);
3146 // TODO: consider if this equals check is a waste of time or does it pay
3147 // off due to having to reinitialize per-class caches for the new
3148 // ElementClass that are constructed on the fly?
3149 if (!newClass.equals(oldClass)) {
3150 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3151 System.out.println(" old element class: " + oldClass);
3152 System.out.println(" new element class: " + newClass);
3154 mappedElement.setElementClass(newClass);
3157 // Tuukka@2010-02-19: replaced with notifications for making
3158 // the graph synchronizer more transparent to the client.
3160 // Hint notifications will not work when this is used.
3161 //mappedElement.setHintsWithoutNotification(newElement.getHints());
3163 Map<DiscardableKey, Object> discardableHints = mappedElement.getHintsOfClass(DiscardableKey.class);
3165 // Set all hints from newElement to mappedElement.
3166 // Leave any hints in mappedElement but not in newElement as is.
3167 Map<Key, Object> hints = newElement.getHints();
3168 Map<Key, Object> newHints = new HashMap<Key, Object>();
3169 for (Map.Entry<Key, Object> entry : hints.entrySet()) {
3170 Key key = entry.getKey();
3171 Object newValue = entry.getValue();
3172 Object oldValue = mappedElement.getHint(key);
3173 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE_DETAIL) {
3174 System.out.println(" hint " + key + " compare values: " + oldValue + " -> " + newValue);
3176 if (!newValue.equals(oldValue)) {
3177 newHints.put(key, newValue);
3178 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3179 System.out.format(" %-42s : %64s -> %-64s\n", key, oldValue, newValue);
3182 // If the hint value has not changed but the hint still exists
3183 // we don't need to discard it even if it is considered
3185 discardableHints.remove(key);
3189 // Set all hints at once and send notifications after setting the values.
3190 if (!discardableHints.isEmpty()) {
3191 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE)
3192 System.out.println("Discarding " + discardableHints.size() + " discardable hints:\n " + discardableHints);
3193 mappedElement.removeHints(discardableHints.keySet());
3195 if (!newHints.isEmpty()) {
3196 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3197 System.out.println("Updating mapped element, setting new hints:\n\t"
3198 + EString.implode(newHints.entrySet(), "\n\t") + "\nto replace old hints\n\t"
3199 + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
3201 mappedElement.setHints(newHints);
3203 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
3204 System.out.println("All hints after update:\n\t"
3205 + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
3209 class TransactionListener extends SessionEventListenerAdapter {
3212 public void writeTransactionStarted() {
3213 startTime = System.nanoTime();
3214 if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
3215 System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionStarted");
3216 inWriteTransaction.set(true);
3219 public void writeTransactionFinished() {
3220 long endTime = System.nanoTime();
3221 if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
3222 System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionFinished: " + (endTime - startTime)*1e-6 + " ms");
3223 inWriteTransaction.set(false);
3224 scheduleGraphUpdates();
3228 Object graphUpdateLock = new Object();
3229 TransactionListener sessionListener = null;
3230 AtomicBoolean inWriteTransaction = new AtomicBoolean(false);
3231 AtomicBoolean graphUpdateRequestScheduled = new AtomicBoolean(false);
3232 List<GraphUpdateReactor> queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();
3234 private void offerGraphUpdate(GraphUpdateReactor update) {
3235 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3236 System.out.println("offerGraphUpdate: " + update);
3237 boolean inWrite = inWriteTransaction.get();
3238 synchronized (graphUpdateLock) {
3239 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3240 System.out.println("queueing graph update: " + update);
3241 queuedGraphUpdates.add(update);
3244 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3245 System.out.println("scheduling queued graph update immediately: " + update);
3246 scheduleGraphUpdates();
3250 private Collection<GraphUpdateReactor> scrubGraphUpdates() {
3251 synchronized (graphUpdateLock) {
3252 if (queuedGraphUpdates.isEmpty())
3253 return Collections.emptyList();
3254 final List<GraphUpdateReactor> updates = queuedGraphUpdates;
3255 queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();
3260 private void scheduleGraphUpdates() {
3261 synchronized (graphUpdateLock) {
3262 if (queuedGraphUpdates.isEmpty())
3264 if (!graphUpdateRequestScheduled.compareAndSet(false, true))
3268 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3269 System.out.println("scheduling " + queuedGraphUpdates.size() + " queued graph updates with ");
3271 session.asyncRequest(new ReadRequest() {
3273 public void run(final ReadGraph graph) throws DatabaseException {
3274 Collection<GraphUpdateReactor> updates;
3275 synchronized (graphUpdateLock) {
3276 graphUpdateRequestScheduled.set(false);
3277 updates = scrubGraphUpdates();
3280 if (!GraphToDiagramSynchronizer.this.isAlive())
3283 processGraphUpdates(graph, updates);
3285 }, new ProcedureAdapter<Object>() {
3287 public void exception(Throwable t) {
3293 private void processGraphUpdates(ReadGraph graph, final Collection<GraphUpdateReactor> graphUpdates)
3294 throws DatabaseException {
3295 final List<DiagramUpdater> diagramUpdates = new ArrayList<DiagramUpdater>(graphUpdates.size());
3297 // Run GraphUpdaters and gather DiagramUpdaters.
3298 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3299 System.out.println("Running GRAPH updates: " + graphUpdates);
3300 for (GraphUpdateReactor graphUpdate : graphUpdates) {
3301 DiagramUpdater diagramUpdate = graphUpdate.graphUpdate(graph);
3302 if (diagramUpdate != null) {
3303 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
3304 System.out.println(graphUpdate + " => " + diagramUpdate);
3305 diagramUpdates.add(diagramUpdate);
3309 if (diagramUpdates.isEmpty())
3312 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3313 System.out.println("Diagram updates: " + diagramUpdates);
3314 Collections.sort(diagramUpdates, DiagramUpdater.DIAGRAM_UPDATER_COMPARATOR);
3315 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3316 System.out.println("Sorted diagram updates: " + diagramUpdates);
3318 ThreadUtils.asyncExec(canvas.getThreadAccess(), new StateRunnable() {
3321 if (GraphToDiagramSynchronizer.this.isAlive() && getState() != State.DISPOSED)
3322 safeRunInState(State.UPDATING_DIAGRAM, this);
3326 public void execute() throws InvocationTargetException {
3327 // Block out diagram write transactions.
3328 DiagramUtils.inDiagramTransaction(diagram, TransactionType.READ, new Runnable() {
3331 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3332 System.out.println("Running DIAGRAM updates: " + diagramUpdates);
3333 for (DiagramUpdater update : diagramUpdates) {
3334 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
3335 System.out.println("Running DIAGRAM update: " + update);
3346 private void attachSessionListener(Session session) {
3347 SessionEventSupport support = session.peekService(SessionEventSupport.class);
3348 if (support != null) {
3349 sessionListener = new TransactionListener();
3350 support.addListener(sessionListener);
3354 private void detachSessionListener() {
3355 if (sessionListener != null) {
3356 session.getService(SessionEventSupport.class).removeListener(sessionListener);
3357 sessionListener = null;
3362 // ------------------------------------------------------------------------
3363 // GRAPH TO DIAGRAM SYNCHRONIZATION LOGIC END
3364 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3366 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3367 // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
3368 // This does not try to synchronize anything back into the graph.
3369 // ------------------------------------------------------------------------
3371 IHintListener elementHintValidator = new HintListenerAdapter() {
3373 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
3374 if (!(sender instanceof Element))
3375 throw new IllegalStateException("invalid sender: " + sender);
3376 Element e = (Element) sender;
3377 if (newValue != null) {
3378 if (key instanceof TerminalKeyOf) {
3379 Connection c = (Connection) newValue;
3381 throw new IllegalStateException("TerminalKeyOf hint of node " + e + " refers to a different node " + c.node + ". Should be the same.");
3382 Object edgeObject = ElementUtils.getObject(c.edge);
3383 if (!(edgeObject instanceof EdgeResource))
3384 throw new IllegalStateException("EndKeyOf hint of edge " + c.edge + " refers contains an invalid object: " + edgeObject);
3385 } else if (key instanceof EndKeyOf) {
3386 Connection c = (Connection) newValue;
3388 throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers to a different edge " + c.edge + ". Should be the same.");
3389 Object edgeObject = ElementUtils.getObject(c.edge);
3390 if (!(edgeObject instanceof EdgeResource))
3391 throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers contains an invalid object: " + edgeObject);
3397 class DiagramListener implements CompositionListener, CompositionVetoListener {
3399 public boolean beforeElementAdded(IDiagram d, IElement e) {
3400 // Make sure that MutatedElements NEVER get added to the diagram.
3402 if (!(e instanceof Element)) {
3403 // THIS IS NOT GOOD!
3404 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"));
3405 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.");
3409 // Perform sanity checks that might veto the element addition.
3410 boolean pass = true;
3412 // Check that all elements added to the diagram are adaptable to Resource
3413 ElementClass ec = e.getElementClass();
3414 Resource resource = ElementUtils.adapt(ec, Resource.class);
3415 if (resource == null) {
3417 new Exception("Attempted to add an element to the diagram that is not adaptable to Resource: " + e + ", class: " + ec).printStackTrace();
3420 // Sanity check connection hints
3421 for (Map.Entry<TerminalKeyOf, Object> entry : e.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3422 Connection c = (Connection) entry.getValue();
3423 Object edgeObject = ElementUtils.getObject(c.edge);
3425 System.err.println("Invalid node in TerminalKeyOf hint: " + entry.getKey() + "=" + entry.getValue());
3426 System.err.println("\tconnection.edge=" + c.edge);
3427 System.err.println("\tconnection.node=" + c.node);
3428 System.err.println("\tconnection.end=" + c.end);
3429 System.err.println("\telement=" + e);
3430 System.err.println("\telement class=" + e.getElementClass());
3433 if (!(edgeObject instanceof EdgeResource)) {
3434 System.err.println("Invalid object in TerminalKeyOf hint edge: " + entry.getKey() + "=" + entry.getValue());
3435 System.err.println("\tconnection.edge=" + c.edge);
3436 System.err.println("\tconnection.node=" + c.node);
3437 System.err.println("\tconnection.end=" + c.end);
3438 System.err.println("\telement=" + e);
3439 System.err.println("\telement class=" + e.getElementClass());
3450 public boolean beforeElementRemoved(IDiagram d, IElement e) {
3451 // Never veto diagram changes.
3456 public void onElementAdded(IDiagram d, IElement e) {
3457 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
3458 System.out.println("[" + d + "] element added: " + e);
3460 if (USE_ELEMENT_VALIDATING_LISTENERS)
3461 e.addHintListener(elementHintValidator);
3465 public void onElementRemoved(IDiagram d, IElement e) {
3466 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
3467 System.out.println("[" + d + "] element removed: " + e);
3469 if (USE_ELEMENT_VALIDATING_LISTENERS)
3470 e.removeHintListener(elementHintValidator);
3472 if (e.containsHint(KEY_REMOVE_RELATIONSHIPS))
3473 relationshipHandler.denyAll(diagram, e);
3477 DiagramListener diagramListener = new DiagramListener();
3479 static void removeNodeTopologyHints(Element node) {
3480 Set<TerminalKeyOf> terminalKeys = node.getHintsOfClass(TerminalKeyOf.class).keySet();
3481 if (!terminalKeys.isEmpty()) {
3482 removeNodeTopologyHints(node, terminalKeys);
3486 static void removeNodeTopologyHints(Element node, Collection<TerminalKeyOf> terminalKeys) {
3487 for (TerminalKeyOf key : terminalKeys) {
3488 Connection c = node.removeHintWithoutNotification(key);
3490 removeEdgeTopologyHints((Element) c.edge);
3495 static void removeEdgeTopologyHints(Element edge) {
3496 Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
3497 for (EndKeyOf key : EndKeyOf.KEYS) {
3498 Connection c = edge.removeHintWithoutNotification(key);
3500 ((Element) c.node).removeHintWithoutNotification(new TerminalKeyOf(c.terminal, edgeData, Connection.class));
3505 // ------------------------------------------------------------------------
3506 // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
3507 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3509 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3510 // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC BEGIN
3511 // ------------------------------------------------------------------------
3513 void adaptDiagramClass(AsyncReadGraph graph, Resource diagram, final AsyncProcedure<DiagramClass> procedure) {
3514 graph.forAdapted(diagram, DiagramClass.class, new AsyncProcedure<DiagramClass>() {
3516 public void exception(AsyncReadGraph graph, Throwable throwable) {
3517 procedure.exception(graph, throwable);
3521 public void execute(AsyncReadGraph graph, DiagramClass dc) {
3522 // To move TopologyImpl out of here, we need a separate
3523 // DiagramClassFactory that takes a canvas context as an argument.
3524 // DataElementMapImpl, ElementFactoryImpl and diagramLifeCycle can
3525 // safely stay here.
3526 procedure.execute(graph, dc.newClassWith(
3527 // This handler takes care of the topology of the diagram model.
3528 // It sets and fixes element hints related to describing the
3529 // connectivity of elements.
3531 // TODO: not quite sure whether this can prove itself useful or not.
3533 // This map provides a bidirectional mapping between
3534 // IElement and back-end objects.
3536 // This handler provides a facility to adapt an element class
3537 // to work properly with a diagram synchronized using this
3538 // GraphToDiagramSynchronizer.
3539 substituteElementClass,
3540 // These handlers provide a way to create simple identified
3541 // uni- and bidirectional relationships between any diagram
3542 // objects/elements.
3543 relationshipHandler));
3548 static Connection connect(IElement edge, EdgeEnd end, IElement element, Terminal terminal) {
3549 Connection c = new Connection(edge, end, element, terminal);
3551 Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
3552 if (DebugPolicy.DEBUG_CONNECTION) {
3553 System.out.println("[connect](edge=" + edge + ", edgeData=" + edgeData + ", end=" + end + ", element="
3554 + element + ", terminal=" + terminal + ")");
3557 TerminalKeyOf key = new TerminalKeyOf(terminal, edgeData, Connection.class);
3558 element.setHint(key, c);
3560 EndKeyOf key2 = EndKeyOf.get(end);
3561 edge.setHint(key2, c);
3566 static class ElementFactoryImpl implements ElementFactory {
3568 public IElement spawnNew(ElementClass clazz) {
3569 IElement e = Element.spawnNew(clazz);
3574 ElementFactoryImpl elementFactory = new ElementFactoryImpl();
3576 public static final Object FIRST_TIME = new Object() {
3578 public String toString() {
3579 return "FIRST_TIME";
3585 * A base for all listeners of graph requests performed internally by
3586 * GraphToDiagramSynchronizer.
3588 * @param <T> type of stored data element
3589 * @param <Result> query result type
3591 abstract class BaseListener<T, Result> implements AsyncListener<Result> {
3593 protected final T data;
3595 private Object oldResult = FIRST_TIME;
3597 protected boolean disposed = false;
3599 final ICanvasContext canvas;
3601 public BaseListener(T data) {
3602 this.canvas = GraphToDiagramSynchronizer.this.canvas;
3607 public void exception(AsyncReadGraph graph, Throwable throwable) {
3608 // Exceptions are always expected to mean that the listener should
3609 // be considered disposed once a query fails.
3613 abstract void execute(AsyncReadGraph graph, Object oldResult, Object newResult);
3616 public void execute(AsyncReadGraph graph, Result result) {
3617 if (DebugPolicy.DEBUG_LISTENER_BASE)
3618 System.out.println("BaseListener: " + result);
3621 if (DebugPolicy.DEBUG_LISTENER_BASE)
3622 System.out.println("BaseListener: execute invoked although listener is disposed!");
3626 // A null result will permanently mark this listener disposed!
3627 if (result == null) {
3629 if (DebugPolicy.DEBUG_LISTENER_BASE)
3630 System.out.println(this + " null result, listener marked disposed");
3633 if (oldResult == FIRST_TIME) {
3635 if (DebugPolicy.DEBUG_LISTENER_BASE)
3636 System.out.println(this + " first result computed: " + result);
3638 if (DebugPolicy.DEBUG_LISTENER_BASE)
3639 System.out.println(this + " result changed from '" + oldResult + "' to '" + result + "'");
3641 execute(graph, oldResult, result);
3649 public boolean isDisposed() {
3653 boolean alive = isAlive();
3654 //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", isAlive=" + alive);
3657 // If a mapping no longer exists for this element, dispose of this
3659 //IElement e = getMappedElement(resource);
3660 //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", element=" + e);
3666 // protected void finalize() throws Throwable {
3667 // System.out.println("finalize listener: " + this);
3668 // super.finalize();
3672 class DiagramClassRequest extends BaseRequest2<Resource, DiagramClass> {
3673 public DiagramClassRequest(Resource resource) {
3674 super(GraphToDiagramSynchronizer.this.canvas, resource);
3678 public void perform(AsyncReadGraph graph, AsyncProcedure<DiagramClass> procedure) {
3679 adaptDiagramClass(graph, data, procedure);
3683 public class DiagramContentListener extends BaseListener<Resource, DiagramContents> {
3685 public DiagramContentListener(Resource resource) {
3690 public void execute(final AsyncReadGraph graph, Object oldResult, Object newResult) {
3691 final DiagramContents newContent = (newResult == null) ? new DiagramContents()
3692 : (DiagramContents) newResult;
3694 // diagramGraphUpdater is called synchronously during
3695 // loading. The first result will not get updated through
3696 // this listener but through loadDiagram.
3698 if (DebugPolicy.DISABLE_DIAGRAM_UPDATES) {
3699 System.out.println("Skipped diagram content update: " + newResult);
3703 if (DebugPolicy.DEBUG_DIAGRAM_LISTENER)
3704 System.out.println("diagram contents changed: " + oldResult + " => " + newResult);
3706 offerGraphUpdate( diagramGraphUpdater(newContent) );
3710 public boolean isDisposed() {
3715 public void exception(AsyncReadGraph graph, Throwable t) {
3716 super.exception(graph, t);
3717 error("DiagramContentRequest failed", t);
3721 // ------------------------------------------------------------------------
3722 // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC END
3723 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3725 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3726 // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER BEGIN
3727 // ------------------------------------------------------------------------
3729 static class TopologyImpl implements Topology {
3732 public Connection getConnection(IElement edge, EdgeEnd end) {
3733 Key key = EndKeyOf.get(end);
3734 Connection c = edge.getHint(key);
3741 public void getConnections(IElement node, Terminal terminal, Collection<Connection> connections) {
3742 // IDiagram d = ElementUtils.getDiagram(node);
3743 for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3744 // First check that the terminal matches.
3745 TerminalKeyOf key = entry.getKey();
3746 if (!key.getTerminal().equals(terminal))
3749 Connection c = (Connection) entry.getValue();
3757 public void connect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
3758 if (node != null && terminal != null)
3759 GraphToDiagramSynchronizer.connect(edge, end, node, terminal);
3761 if (DebugPolicy.DEBUG_CONNECTION) {
3762 if (end == EdgeEnd.Begin)
3763 System.out.println("Connection started from: " + edge + ", " + end + ", " + node + ", " + terminal);
3765 System.out.println("Creating connection to: " + edge + ", " + end + ", " + node + ", " + terminal);
3770 public void disconnect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
3771 EndKeyOf edgeKey = EndKeyOf.get(end);
3772 Connection c = edge.getHint(edgeKey);
3774 throw new UnsupportedOperationException("cannot disconnect, no Connection in edge " + edge);
3776 for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
3777 Connection cc = (Connection) entry.getValue();
3779 node.removeHint(entry.getKey());
3780 edge.removeHint(edgeKey);
3785 throw new UnsupportedOperationException("cannot disconnect, no connection between found between edge "
3786 + edge + " and node " + node);
3790 Topology diagramTopology = new TopologyImpl();
3792 // ------------------------------------------------------------------------
3793 // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER END
3794 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3796 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
3797 // DIAGRAM OBJECT RELATIONSHIP HANDLER BEGIN
3798 // ------------------------------------------------------------------------
3800 RelationshipHandler relationshipHandler = new RelationshipHandler() {
3802 AssociativeMap map = new AssociativeMap(Associativity.of(true, false, false));
3804 Object getPossibleObjectOrElement(Object o) {
3805 if (o instanceof IElement) {
3806 IElement e = (IElement) o;
3807 Object oo = e.getHint(ElementHints.KEY_OBJECT);
3808 return oo != null ? oo : e;
3813 IElement getElement(Object o) {
3814 if (o instanceof IElement)
3815 return (IElement) o;
3816 return getMappedElement(o);
3820 public void claim(IDiagram diagram, Object subject,
3821 Relationship predicate, Object object) {
3822 Object sd = getPossibleObjectOrElement(subject);
3823 Object od = getPossibleObjectOrElement(object);
3825 Collection<Tuple> ts = null;
3826 Relationship inverse = predicate.getInverse();
3827 if (inverse != null)
3828 ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od, inverse, sd));
3830 ts = Collections.singletonList(new Tuple(sd, predicate, od));
3832 synchronized (this) {
3836 if (DebugPolicy.DEBUG_RELATIONSHIP) {
3837 new Exception().printStackTrace();
3838 System.out.println("Claimed relationships:");
3840 System.out.println("\t" + t);
3844 private void doDeny(IDiagram diagram, Object subject,
3845 Relationship predicate, Object object) {
3846 Object sd = getPossibleObjectOrElement(subject);
3847 Object od = getPossibleObjectOrElement(object);
3848 if (sd == subject || od == object) {
3850 .println("WARNING: denying relationship '"
3852 + "' between diagram element(s), not back-end object(s): "
3853 + sd + " -> " + od);
3856 Collection<Tuple> ts = null;
3857 Relationship inverse = predicate.getInverse();
3858 if (inverse != null)
3859 ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od,
3862 ts = Collections.singleton(new Tuple(sd, predicate, od));
3864 synchronized (this) {
3868 if (DebugPolicy.DEBUG_RELATIONSHIP) {
3869 new Exception().printStackTrace();
3870 System.out.println("Denied relationships:");
3872 System.out.println("\t" + t);
3877 public void deny(IDiagram diagram, Object subject,
3878 Relationship predicate, Object object) {
3879 synchronized (this) {
3880 doDeny(diagram, subject, predicate, object);
3885 public void deny(IDiagram diagram, Relation relation) {
3886 synchronized (this) {
3887 doDeny(diagram, relation.getSubject(), relation
3888 .getRelationship(), relation.getObject());
3893 public void denyAll(IDiagram diagram, Object element) {
3894 synchronized (this) {
3895 for (Relation relation : getRelations(diagram, element, null)) {
3896 doDeny(diagram, relation.getSubject(), relation
3897 .getRelationship(), relation.getObject());
3903 public Collection<Relation> getRelations(IDiagram diagram,
3904 Object element, Collection<Relation> result) {
3905 if (DebugPolicy.DEBUG_GET_RELATIONSHIP)
3906 System.out.println("getRelations(" + element + ")");
3907 Object e = getPossibleObjectOrElement(element);
3909 Collection<Tuple> tuples = null;
3910 synchronized (this) {
3911 tuples = map.get(new Tuple(e, null, null), null);
3914 if (DebugPolicy.DEBUG_GET_RELATIONSHIP) {
3915 System.out.println("Result size: " + tuples.size());
3916 for (Tuple t : tuples)
3917 System.out.println("\t" + t);
3920 if (tuples.isEmpty())
3921 return Collections.emptyList();
3923 result = new ArrayList<Relation>(tuples.size());
3924 for (Tuple t : tuples) {
3925 Object obj = t.getField(2);
3926 IElement el = getElement(obj);
3927 Relationship r = (Relationship) t.getField(1);
3928 result.add(new Relation(element, r, el != null ? el : obj));
3935 // ------------------------------------------------------------------------
3936 // DIAGRAM ELEMENT RELATIONSHIP HANDLER END
3937 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>