1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.diagram.adapter;
\r
14 import gnu.trove.map.hash.TObjectIntHashMap;
\r
15 import gnu.trove.set.hash.THashSet;
\r
17 import java.awt.geom.AffineTransform;
\r
18 import java.lang.reflect.InvocationTargetException;
\r
19 import java.util.ArrayDeque;
\r
20 import java.util.ArrayList;
\r
21 import java.util.Arrays;
\r
22 import java.util.Collection;
\r
23 import java.util.Collections;
\r
24 import java.util.Comparator;
\r
25 import java.util.Deque;
\r
26 import java.util.EnumSet;
\r
27 import java.util.HashMap;
\r
28 import java.util.HashSet;
\r
29 import java.util.List;
\r
30 import java.util.Map;
\r
31 import java.util.Queue;
\r
32 import java.util.Set;
\r
33 import java.util.concurrent.ConcurrentHashMap;
\r
34 import java.util.concurrent.ConcurrentMap;
\r
35 import java.util.concurrent.atomic.AtomicBoolean;
\r
36 import java.util.concurrent.locks.Condition;
\r
37 import java.util.concurrent.locks.ReentrantLock;
\r
39 import org.eclipse.core.runtime.IProgressMonitor;
\r
40 import org.eclipse.core.runtime.SubMonitor;
\r
41 import org.simantics.db.AsyncReadGraph;
\r
42 import org.simantics.db.ReadGraph;
\r
43 import org.simantics.db.RequestProcessor;
\r
44 import org.simantics.db.Resource;
\r
45 import org.simantics.db.Session;
\r
46 import org.simantics.db.common.ResourceArray;
\r
47 import org.simantics.db.common.exception.DebugException;
\r
48 import org.simantics.db.common.procedure.adapter.AsyncProcedureAdapter;
\r
49 import org.simantics.db.common.procedure.adapter.CacheListener;
\r
50 import org.simantics.db.common.procedure.adapter.ListenerSupport;
\r
51 import org.simantics.db.common.procedure.adapter.ProcedureAdapter;
\r
52 import org.simantics.db.common.request.AsyncReadRequest;
\r
53 import org.simantics.db.common.request.ReadRequest;
\r
54 import org.simantics.db.common.session.SessionEventListenerAdapter;
\r
55 import org.simantics.db.common.utils.NameUtils;
\r
56 import org.simantics.db.exception.CancelTransactionException;
\r
57 import org.simantics.db.exception.DatabaseException;
\r
58 import org.simantics.db.exception.NoSingleResultException;
\r
59 import org.simantics.db.exception.ServiceException;
\r
60 import org.simantics.db.procedure.AsyncListener;
\r
61 import org.simantics.db.procedure.AsyncProcedure;
\r
62 import org.simantics.db.procedure.Listener;
\r
63 import org.simantics.db.procedure.Procedure;
\r
64 import org.simantics.db.request.Read;
\r
65 import org.simantics.db.service.SessionEventSupport;
\r
66 import org.simantics.diagram.connection.ConnectionSegmentEnd;
\r
67 import org.simantics.diagram.content.Change;
\r
68 import org.simantics.diagram.content.ConnectionUtil;
\r
69 import org.simantics.diagram.content.DesignatedTerminal;
\r
70 import org.simantics.diagram.content.DiagramContentChanges;
\r
71 import org.simantics.diagram.content.DiagramContents;
\r
72 import org.simantics.diagram.content.EdgeResource;
\r
73 import org.simantics.diagram.content.ResourceTerminal;
\r
74 import org.simantics.diagram.internal.DebugPolicy;
\r
75 import org.simantics.diagram.internal.timing.GTask;
\r
76 import org.simantics.diagram.internal.timing.Timing;
\r
77 import org.simantics.diagram.profile.ProfileKeys;
\r
78 import org.simantics.diagram.synchronization.CollectingModificationQueue;
\r
79 import org.simantics.diagram.synchronization.CompositeModification;
\r
80 import org.simantics.diagram.synchronization.CopyAdvisor;
\r
81 import org.simantics.diagram.synchronization.ErrorHandler;
\r
82 import org.simantics.diagram.synchronization.IHintSynchronizer;
\r
83 import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;
\r
84 import org.simantics.diagram.synchronization.IModification;
\r
85 import org.simantics.diagram.synchronization.LogErrorHandler;
\r
86 import org.simantics.diagram.synchronization.ModificationAdapter;
\r
87 import org.simantics.diagram.synchronization.SynchronizationHints;
\r
88 import org.simantics.diagram.synchronization.graph.AddElement;
\r
89 import org.simantics.diagram.synchronization.graph.BasicResources;
\r
90 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
\r
91 import org.simantics.diagram.synchronization.graph.ElementLoader;
\r
92 import org.simantics.diagram.synchronization.graph.ElementReorder;
\r
93 import org.simantics.diagram.synchronization.graph.ElementWriter;
\r
94 import org.simantics.diagram.synchronization.graph.GraphSynchronizationContext;
\r
95 import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;
\r
96 import org.simantics.diagram.synchronization.graph.ModificationQueue;
\r
97 import org.simantics.diagram.synchronization.graph.TagChange;
\r
98 import org.simantics.diagram.synchronization.graph.TransformElement;
\r
99 import org.simantics.diagram.synchronization.graph.layer.GraphLayer;
\r
100 import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;
\r
101 import org.simantics.diagram.ui.DiagramModelHints;
\r
102 import org.simantics.g2d.canvas.Hints;
\r
103 import org.simantics.g2d.canvas.ICanvasContext;
\r
104 import org.simantics.g2d.connection.ConnectionEntity;
\r
105 import org.simantics.g2d.connection.EndKeyOf;
\r
106 import org.simantics.g2d.connection.TerminalKeyOf;
\r
107 import org.simantics.g2d.diagram.DiagramClass;
\r
108 import org.simantics.g2d.diagram.DiagramHints;
\r
109 import org.simantics.g2d.diagram.DiagramMutator;
\r
110 import org.simantics.g2d.diagram.DiagramUtils;
\r
111 import org.simantics.g2d.diagram.IDiagram;
\r
112 import org.simantics.g2d.diagram.IDiagram.CompositionListener;
\r
113 import org.simantics.g2d.diagram.IDiagram.CompositionVetoListener;
\r
114 import org.simantics.g2d.diagram.handler.DataElementMap;
\r
115 import org.simantics.g2d.diagram.handler.ElementFactory;
\r
116 import org.simantics.g2d.diagram.handler.Relationship;
\r
117 import org.simantics.g2d.diagram.handler.RelationshipHandler;
\r
118 import org.simantics.g2d.diagram.handler.SubstituteElementClass;
\r
119 import org.simantics.g2d.diagram.handler.Topology;
\r
120 import org.simantics.g2d.diagram.handler.Topology.Connection;
\r
121 import org.simantics.g2d.diagram.handler.Topology.Terminal;
\r
122 import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType;
\r
123 import org.simantics.g2d.diagram.impl.Diagram;
\r
124 import org.simantics.g2d.diagram.participant.ElementPainter;
\r
125 import org.simantics.g2d.element.ElementClass;
\r
126 import org.simantics.g2d.element.ElementHints;
\r
127 import org.simantics.g2d.element.ElementHints.DiscardableKey;
\r
128 import org.simantics.g2d.element.ElementUtils;
\r
129 import org.simantics.g2d.element.IElement;
\r
130 import org.simantics.g2d.element.IElementClassProvider;
\r
131 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
\r
132 import org.simantics.g2d.element.handler.ElementHandler;
\r
133 import org.simantics.g2d.element.handler.ElementLayerListener;
\r
134 import org.simantics.g2d.element.handler.TerminalTopology;
\r
135 import org.simantics.g2d.element.impl.Element;
\r
136 import org.simantics.g2d.layers.ILayer;
\r
137 import org.simantics.g2d.layers.ILayersEditor;
\r
138 import org.simantics.g2d.routing.RouterFactory;
\r
139 import org.simantics.scenegraph.INode;
\r
140 import org.simantics.scenegraph.profile.DataNodeConstants;
\r
141 import org.simantics.scenegraph.profile.DataNodeMap;
\r
142 import org.simantics.scenegraph.profile.common.ProfileObserver;
\r
143 import org.simantics.structural2.modelingRules.IModelingRules;
\r
144 import org.simantics.utils.datastructures.ArrayMap;
\r
145 import org.simantics.utils.datastructures.MapSet;
\r
146 import org.simantics.utils.datastructures.Pair;
\r
147 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
\r
148 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
\r
149 import org.simantics.utils.datastructures.hints.IHintContext.Key;
\r
150 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
\r
151 import org.simantics.utils.datastructures.hints.IHintListener;
\r
152 import org.simantics.utils.datastructures.hints.IHintObservable;
\r
153 import org.simantics.utils.datastructures.map.AssociativeMap;
\r
154 import org.simantics.utils.datastructures.map.Associativity;
\r
155 import org.simantics.utils.datastructures.map.Tuple;
\r
156 import org.simantics.utils.strings.EString;
\r
157 import org.simantics.utils.threads.ThreadUtils;
\r
158 import org.simantics.utils.threads.logger.ITask;
\r
159 import org.simantics.utils.threads.logger.ThreadLogger;
\r
162 * This class loads a diagram contained in the graph database into the runtime
\r
163 * diagram model and synchronizes changes in the graph into the run-time
\r
164 * diagram. Any modifications to the graph model will be reflected to the
\r
165 * run-time diagram model. Hence the name GraphToDiagramSynchronizer.
\r
168 * This class does not in itself support modification of the graph diagram
\r
169 * model. This manipulation is meant to be performed through a
\r
170 * {@link DiagramMutator} implementation that is installed into the diagram
\r
171 * using the {@link DiagramHints#KEY_MUTATOR} hint key.
\r
173 * This implementations is built to only support diagrams defined in the graph
\r
174 * model as indicated in <a
\r
175 * href="https://www.simantics.org/wiki/index.php/Org.simantics.diagram" >this
\r
179 * The synchronizer in itself is an {@link IDiagramLoader} which means that it
\r
180 * can be used for loading a diagram from the graph. In order for the
\r
181 * synchronizer to keep tracking the graph diagram model for changes the diagram
\r
182 * must be loaded with it. The tracking is implemented using graph database
\r
183 * queries. If you just want to load the diagram but detach it from the
\r
184 * synchronizer's tracking mechanisms, all you need to do is to dispose the
\r
185 * synchronizer after loading the diagram.
\r
188 * This class guarantees that a single diagram element (IElement) representing a
\r
189 * single back-end object ({@link ElementHints#KEY_OBJECT}) will stay the same
\r
190 * object for the same back-end object.
\r
193 * TODO: Currently it just happens that {@link GraphToDiagramSynchronizer}
\r
194 * contains {@link DefaultDiagramMutator} which depends on some internal details
\r
195 * of {@link GraphToDiagramSynchronizer} but it should be moved out of here by
\r
196 * introducing new interfaces.
\r
198 * <h2>Basic usage example</h2>
\r
200 * This example shows how to initialize {@link GraphToDiagramSynchronizer} for a
\r
201 * specified {@link ICanvasContext} and load a diagram from a specified diagram
\r
202 * resource in the graph.
\r
205 * IDiagram loadDiagram(final ICanvasContext canvasContext, RequestProcessor processor, Resource diagramResource,
\r
206 * ResourceArray structuralPath) throws DatabaseException {
\r
207 * GraphToDiagramSynchronizer synchronizer = processor.syncRequest(new Read<GraphToDiagramSynchronizer>() {
\r
208 * public GraphToDiagramSynchronizer perform(ReadGraph graph) throws DatabaseException {
\r
209 * return new GraphToDiagramSynchronizer(graph, canvasContext, createElementClassProvider(graph));
\r
212 * IDiagram d = requestProcessor
\r
213 * .syncRequest(new DiagramLoadQuery(diagramResource, structuralPath, synchronizer, null));
\r
217 * protected IElementClassProvider createElementClassProvider(ReadGraph graph) {
\r
218 * DiagramResource dr = DiagramResource.getInstance(graph);
\r
219 * return ElementClassProviders.mappedProvider(ElementClasses.CONNECTION, DefaultConnectionClassFactory.CLASS
\r
220 * .newClassWith(new ResourceAdapterImpl(dr.Connection)), ElementClasses.FLAG, FlagClassFactory
\r
221 * .createFlagClass(dr.Flag));
\r
226 * TODO: make GraphToDiagramSynchronizer a canvas participant to make it more
\r
227 * uniform with the rest of the canvas system. This does not mean that G2DS must
\r
228 * be attached to an ICanvasContext in order to be used, rather that it can be
\r
232 * TODO: test that detaching the synchronizer via {@link #dispose()} actually
\r
235 * TODO: remove {@link DefaultDiagramMutator} and all {@link DiagramMutator}
\r
239 * TODO: diagram connection loading has no listener
\r
241 * @author Tuukka Lehtonen
\r
243 * @see GraphElementClassFactory
\r
244 * @see GraphElementFactory
\r
245 * @see ElementLoader
\r
246 * @see ElementWriter
\r
247 * @see IHintSynchronizer
\r
250 public class GraphToDiagramSynchronizer extends AbstractDisposable implements IDiagramLoader, IModifiableSynchronizationContext {
\r
253 * Controls whether the class adds hint listeners to each diagram element
\r
254 * that try to perform basic sanity checks on changes happening in element
\r
255 * hints. Having this will immediately inform you of bugs that corrupt the
\r
256 * diagram model within the element hints in some way.
\r
258 private static final boolean USE_ELEMENT_VALIDATING_LISTENERS = false;
\r
261 * These keys are used to hang on to Connection instances of edges that will
\r
262 * be later installed as EndKeyOf/TerminalKeyOf hints into the loaded
\r
263 * element during the "graph to diagram update transaction".
\r
265 private static final Key KEY_CONNECTION_BEGIN_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_BEGIN_PLACEHOLDER");
\r
266 private static final Key KEY_CONNECTION_END_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_END_PLACEHOLDER");
\r
269 * Stored into an edge node during connection edge requests using the
\r
270 * KEY_CONNECTION_BEGIN_PLACEHOLDER and KEY_CONNECTION_END_PLACEHOLDER keys.
\r
272 static class PlaceholderConnection {
\r
273 public final EdgeEnd end;
\r
274 public final Object node;
\r
275 public final Terminal terminal;
\r
276 public PlaceholderConnection(EdgeEnd end, Object node, Terminal terminal) {
\r
279 this.terminal = terminal;
\r
284 * Indicates to the diagram CompositionListener of this synchronizer that is
\r
285 * should deny all relationships for the element this hint is attached to.
\r
287 private static final Key KEY_REMOVE_RELATIONSHIPS = new KeyOf(Boolean.class, "REMOVE_RELATIONSHIPS");
\r
289 static ErrorHandler errorHandler = LogErrorHandler.INSTANCE;
\r
292 * The canvas context which is being synchronized with the graph. Received
\r
293 * during construction.
\r
296 * Is not nulled during disposal of this class since internal listener's
\r
297 * life-cycles depend on canvas.isDisposed.
\r
299 ICanvasContext canvas;
\r
302 * The session used by this synchronizer. Received during construction.
\r
307 * Locked while updating diagram contents from the graph.
\r
309 ReentrantLock diagramUpdateLock = new ReentrantLock();
\r
311 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
312 // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING BEGIN
\r
313 // ------------------------------------------------------------------------
\r
316 * Holds a GraphToDiagramUpdater instance while a diagram content update is
\r
317 * currently in progress.
\r
320 * Basically this is a hack solution to the problem of properly finding
\r
321 * newly added Resource<->IElement mappings while loading the diagram
\r
322 * contents. See {@link DataElementMapImpl} for why this is necessary.
\r
324 GraphToDiagramUpdater currentUpdater = null;
\r
327 * A map from data objects to elements. Elements should already contain the
\r
328 * data objects as {@link ElementHints#KEY_OBJECT} hints.
\r
330 ConcurrentMap<Object, IElement> dataElement = new ConcurrentHashMap<Object, IElement>();
\r
333 * Temporary structure for single-threaded use in #{@link DiagramUpdater}.
\r
335 Collection<Connection> tempConnections = new ArrayList<Connection>();
\r
338 * A dummy class of which an instance will be given to each new edge element
\r
339 * to make {@link TerminalKeyOf} keys unique for each edge.
\r
341 static class TransientElementObject {
\r
343 public String toString() {
\r
344 return "MUTATOR GENERATED (hash=" + System.identityHashCode(this) + ")";
\r
348 private static class ConnectionChildren {
\r
349 public Set<IElement> branchPoints;
\r
350 public Set<IElement> segments;
\r
352 public ConnectionChildren(Set<IElement> branchPoints, Set<IElement> segments) {
\r
353 this.branchPoints = branchPoints;
\r
354 this.segments = segments;
\r
358 ListenerSupport canvasListenerSupport = new ListenerSupport() {
\r
360 public void exception(Throwable t) {
\r
365 public boolean isDisposed() {
\r
366 return !isAlive() || canvas.isDisposed();
\r
371 * @see ElementHints#KEY_CONNECTION_ENTITY
\r
373 class ConnectionEntityImpl implements ConnectionEntity {
\r
376 * The connection instance resource in the graph backend.
\r
378 * May be <code>null</code> if the connection has not been synchronized
\r
381 Resource connection;
\r
384 * The connection type resource in the graph backend.
\r
386 * May be <code>null</code> if the connection has not been synchronized
\r
389 Resource connectionType;
\r
392 * The connection entity element which is a part of the diagram.
\r
394 IElement connectionElement;
\r
397 * List of backend-synchronized branch points that are part of this
\r
400 Collection<Resource> branchPoints = Collections.emptyList();
\r
403 * List of backend-synchronized edges that are part of this connection.
\r
405 Collection<EdgeResource> segments = Collections.emptyList();
\r
407 Set<Object> removedBranchPoints = new HashSet<Object>(4);
\r
409 Set<Object> removedSegments = new HashSet<Object>(4);
\r
412 * List of non-backend-synchronized branch point element that are part
\r
413 * of this connection.
\r
415 List<IElement> branchPointElements = new ArrayList<IElement>(1);
\r
418 * List of non-backend-synchronized edge element that are part of this
\r
421 List<IElement> segmentElements = new ArrayList<IElement>(2);
\r
423 ConnectionListener listener;
\r
425 ConnectionEntityImpl(Resource connection, Resource connectionType, IElement connectionElement) {
\r
426 this.connection = connection;
\r
427 this.connectionType = connectionType;
\r
428 this.connectionElement = connectionElement;
\r
431 ConnectionEntityImpl(Resource connectionType, IElement connectionElement) {
\r
432 this.connectionType = connectionType;
\r
433 this.connectionElement = connectionElement;
\r
436 ConnectionEntityImpl(ReadGraph graph, Resource connection, IElement connectionElement)
\r
437 throws NoSingleResultException, ServiceException {
\r
438 this.connection = connection;
\r
439 this.connectionType = graph.getSingleType(connection, br.DIA.Connection);
\r
440 this.connectionElement = connectionElement;
\r
444 public IElement getConnection() {
\r
445 return connectionElement;
\r
448 public Object getConnectionObject() {
\r
452 public IElement getConnectionElement() {
\r
453 if (connectionElement == null)
\r
454 return getMappedConnectionElement();
\r
455 return connectionElement;
\r
458 private IElement getMappedConnectionElement() {
\r
459 IElement ce = null;
\r
460 if (connection != null)
\r
461 ce = getMappedElement(connection);
\r
462 return ce == null ? connectionElement : ce;
\r
466 Collection<IElement> segments = getSegments(null);
\r
468 // Remove all TerminalKeyOf hints from branch points that do not
\r
470 ArrayList<TerminalKeyOf> pruned = null;
\r
471 for (IElement bp : getBranchPoints(null)) {
\r
472 if (pruned == null)
\r
473 pruned = new ArrayList<TerminalKeyOf>(4);
\r
475 for (Map.Entry<TerminalKeyOf, Object> entry : bp.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
\r
476 // First check that the terminal matches.
\r
477 Connection c = (Connection) entry.getValue();
\r
478 if (!segments.contains(c.edge))
\r
479 pruned.add(entry.getKey());
\r
481 removeNodeTopologyHints((Element) bp, pruned);
\r
485 public ConnectionChildren getConnectionChildren() {
\r
486 Set<IElement> bps = Collections.emptySet();
\r
487 Set<IElement> segs = Collections.emptySet();
\r
488 if (!branchPoints.isEmpty()) {
\r
489 bps = new HashSet<IElement>(branchPoints.size());
\r
490 for (Resource bp : branchPoints) {
\r
491 IElement e = getMappedElement(bp);
\r
496 if (!segments.isEmpty()) {
\r
497 segs = new HashSet<IElement>(segments.size());
\r
498 for (EdgeResource seg : segments) {
\r
499 IElement e = getMappedElement(seg);
\r
504 return new ConnectionChildren(bps, segs);
\r
507 public void setData(Collection<EdgeResource> segments, Collection<Resource> branchPoints) {
\r
508 // System.out.println("setData " + segments.size());
\r
509 this.branchPoints = branchPoints;
\r
510 this.segments = segments;
\r
512 // Reset the added/removed state of segments and branchpoints.
\r
513 this.removedBranchPoints = new HashSet<Object>(4);
\r
514 this.removedSegments = new HashSet<Object>(4);
\r
515 this.branchPointElements = new ArrayList<IElement>(4);
\r
516 this.segmentElements = new ArrayList<IElement>(4);
\r
519 public void fireListener(ConnectionChildren old, ConnectionChildren current) {
\r
520 if (listener != null) {
\r
521 List<IElement> removed = new ArrayList<IElement>();
\r
522 List<IElement> added = new ArrayList<IElement>();
\r
524 for (IElement oldBp : old.branchPoints)
\r
525 if (!current.branchPoints.contains(oldBp))
\r
526 removed.add(oldBp);
\r
527 for (IElement oldSeg : old.segments)
\r
528 if (!current.segments.contains(oldSeg))
\r
529 removed.add(oldSeg);
\r
531 for (IElement bp : current.branchPoints)
\r
532 if (!old.branchPoints.contains(bp))
\r
534 for (IElement seg : current.segments)
\r
535 if (!old.segments.contains(seg))
\r
538 if (!removed.isEmpty() || !added.isEmpty()) {
\r
539 listener.connectionChanged(new ConnectionEvent(this.connectionElement, removed, added));
\r
545 public Collection<IElement> getBranchPoints(Collection<IElement> result) {
\r
546 if (result == null)
\r
547 result = new ArrayList<IElement>(branchPoints.size());
\r
548 for (Resource bp : branchPoints) {
\r
549 if (!removedBranchPoints.contains(bp)) {
\r
550 IElement e = getMappedElement(bp);
\r
555 result.addAll(branchPointElements);
\r
560 public Collection<IElement> getSegments(Collection<IElement> result) {
\r
561 if (result == null)
\r
562 result = new ArrayList<IElement>(segments.size());
\r
563 for (EdgeResource seg : segments) {
\r
564 if (!removedSegments.contains(seg)) {
\r
565 IElement e = getMappedElement(seg);
\r
570 result.addAll(segmentElements);
\r
575 public Collection<Connection> getTerminalConnections(Collection<Connection> result) {
\r
576 if (result == null)
\r
577 result = new ArrayList<Connection>(segments.size() * 2);
\r
578 Set<org.simantics.utils.datastructures.Pair<IElement, Terminal>> processed = new HashSet<org.simantics.utils.datastructures.Pair<IElement, Terminal>>();
\r
579 for (EdgeResource seg : segments) {
\r
580 IElement edge = getMappedElement(seg);
\r
581 if (edge != null) {
\r
582 for (EndKeyOf key : EndKeyOf.KEYS) {
\r
583 Connection c = edge.getHint(key);
\r
584 if (c != null && (c.terminal instanceof ResourceTerminal) && processed.add(Pair.make(c.node, c.terminal)))
\r
593 public void setListener(ConnectionListener listener) {
\r
594 this.listener = listener;
\r
598 public String toString() {
\r
599 return getClass().getSimpleName() + "[resource=" + connection + ", branch points=" + branchPoints
\r
600 + ", segments=" + segments + ", connectionElement=" + connectionElement
\r
601 + ", branch point elements=" + branchPointElements + ", segment elements=" + segmentElements
\r
602 + ", removed branch points=" + removedBranchPoints + ", removed segments=" + removedSegments + "]";
\r
608 * A map from connection data objects to connection entities. The connection
\r
609 * part elements should already contain the data objects as
\r
610 * {@link ElementHints#KEY_OBJECT} hints.
\r
612 ConcurrentMap<Object, ConnectionEntityImpl> dataConnection = new ConcurrentHashMap<Object, ConnectionEntityImpl>();
\r
618 void mapElement(final Object data, final IElement element) {
\r
619 if (!(element instanceof Element)) {
\r
620 throw new IllegalArgumentException("mapElement: expected instance of Element, got " + element + " with data " + data);
\r
622 assert data != null;
\r
623 assert element != null;
\r
624 if (DebugPolicy.DEBUG_MAPPING)
\r
625 new Exception(Thread.currentThread() + " MAPPING: " + data + " -> " + element).printStackTrace();
\r
626 dataElement.put(data, element);
\r
633 IElement getMappedElement(final Object data) {
\r
634 assert (data != null);
\r
635 IElement element = dataElement.get(data);
\r
639 IElement getMappedElementByElementObject(IElement e) {
\r
642 Object o = e.getHint(ElementHints.KEY_OBJECT);
\r
645 return getMappedElement(o);
\r
652 IElement assertMappedElement(final Object data) {
\r
653 IElement element = dataElement.get(data);
\r
654 assert element != null;
\r
662 IElement unmapElement(final Object data) {
\r
663 IElement element = dataElement.remove(data);
\r
664 if (DebugPolicy.DEBUG_MAPPING)
\r
665 new Exception(Thread.currentThread() + " UN-MAPPED: " + data + " -> " + element).printStackTrace();
\r
673 void mapConnection(final Object data, final ConnectionEntityImpl connection) {
\r
674 assert data != null;
\r
675 assert connection != null;
\r
676 if (DebugPolicy.DEBUG_MAPPING)
\r
677 System.out.println(Thread.currentThread() + " MAPPING CONNECTION: " + data + " -> " + connection);
\r
678 dataConnection.put(data, connection);
\r
685 ConnectionEntityImpl getMappedConnection(final Object data) {
\r
686 ConnectionEntityImpl connection = dataConnection.get(data);
\r
694 ConnectionEntityImpl assertMappedConnection(final Object data) {
\r
695 ConnectionEntityImpl connection = getMappedConnection(data);
\r
696 assert connection != null;
\r
704 ConnectionEntityImpl unmapConnection(final Object data) {
\r
705 ConnectionEntityImpl connection = dataConnection.remove(data);
\r
706 if (DebugPolicy.DEBUG_MAPPING)
\r
707 System.out.println(Thread.currentThread() + " UN-MAPPED CONNECTION: " + data + " -> " + connection);
\r
711 class DataElementMapImpl implements DataElementMap {
\r
713 public Object getData(IDiagram d, IElement element) {
\r
715 throw new NullPointerException("null diagram");
\r
716 if (element == null)
\r
717 throw new NullPointerException("null element");
\r
719 assert ElementUtils.getDiagram(element) == d;
\r
720 return element.getHint(ElementHints.KEY_OBJECT);
\r
724 public IElement getElement(IDiagram d, Object data) {
\r
726 throw new NullPointerException("null diagram");
\r
728 throw new NullPointerException("null data");
\r
730 GraphToDiagramUpdater updater = currentUpdater;
\r
731 if (updater != null) {
\r
732 // This HACK is for allowing GraphElementFactory implementations
\r
733 // to find the IElements they are related to.
\r
734 IElement e = updater.addedElementMap.get(data);
\r
739 IElement e = getMappedElement(data);
\r
746 class SubstituteElementClassImpl implements SubstituteElementClass {
\r
748 public ElementClass substitute(IDiagram d, ElementClass ec) {
\r
750 throw new IllegalArgumentException("specified diagram does not have this SubstituteElementClass handler");
\r
752 // If the element class is our own, there's no point in creating
\r
754 if (ec.contains(elementLayerListener))
\r
757 List<ElementHandler> all = ec.getAll();
\r
758 List<ElementHandler> result = new ArrayList<ElementHandler>(all.size());
\r
759 for (ElementHandler eh : all) {
\r
760 if (eh instanceof ElementLayerListenerImpl)
\r
761 result.add(elementLayerListener);
\r
765 return ElementClass.compile(result).setId(ec.getId());
\r
769 final DataElementMapImpl dataElementMap = new DataElementMapImpl();
\r
771 final SubstituteElementClassImpl substituteElementClass = new SubstituteElementClassImpl();
\r
773 // ------------------------------------------------------------------------
\r
774 // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING END
\r
775 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\r
777 void warning(String message, Exception e) {
\r
778 errorHandler.warning(message, e);
\r
781 void warning(Exception e) {
\r
782 errorHandler.warning(e.getMessage(), e);
\r
785 void error(String message, Throwable e) {
\r
786 errorHandler.error(message, e);
\r
789 void error(Throwable e) {
\r
790 errorHandler.error(e.getMessage(), e);
\r
793 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
794 // GRAPH MODIFICATION QUEUE BEGIN
\r
795 // ------------------------------------------------------------------------
\r
797 ModificationQueue modificationQueue;
\r
798 IModifiableSynchronizationContext synchronizationContext;
\r
801 public <T> T set(Key key, Object value) {
\r
802 if (synchronizationContext == null)
\r
804 return synchronizationContext.set(key, value);
\r
808 public <T> T get(Key key) {
\r
809 if (synchronizationContext == null)
\r
811 return synchronizationContext.get(key);
\r
814 // ------------------------------------------------------------------------
\r
815 // GRAPH MODIFICATION QUEUE END
\r
816 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\r
819 * The previously loaded version of the diagram content. This is needed to
\r
820 * calculate the difference between new and old content on each
\r
821 * {@link #diagramGraphUpdater(DiagramContents)} invocation.
\r
823 DiagramContents previousContent;
\r
826 * The diagram instance that this synchronizer is synchronizing with the
\r
832 * An observer for diagram profile entries. Has a life-cycle that must be
\r
833 * bound to the life-cycle of this GraphToDiagramSynchronizer instance.
\r
834 * Disposed if synchronizer is detached in {@link #doDispose()} or finally
\r
835 * when the canvas is disposed.
\r
837 ProfileObserver profileObserver;
\r
839 IElementClassProvider elementClassProvider;
\r
843 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
844 // Internal state machine handling BEGIN
\r
845 // ------------------------------------------------------------------------
\r
848 * An indicator for the current state of this synchronizer. This is a simple
\r
849 * state machine with the following possible state transitions:
\r
852 * <li>INITIAL -> LOADING, DISPOSED</li>
\r
853 * <li>LOADING -> IDLE</li>
\r
854 * <li>IDLE -> UPDATING_DIAGRAM, DISPOSED</li>
\r
855 * <li>UPDATING_DIAGRAM -> IDLE</li>
\r
858 * Start states: INITIAL
\r
859 * End states: DISPOSED
\r
861 static enum State {
\r
863 * The initial state of the synchronizer.
\r
867 * The synchronizer is performing load-time initialization. During this
\r
868 * time no canvas refreshes should be forced.
\r
872 * The synchronizer is performing updates to the diagram model. This
\r
873 * process goes on in the canvas context thread.
\r
877 * The synchronizer is doing nothing.
\r
881 * The synchronized diagram is being disposed, which means that this
\r
882 * synchronizer should not accept any further actions.
\r
887 public static final EnumSet<State> FROM_INITIAL = EnumSet.of(State.LOADING, State.DISPOSED);
\r
888 public static final EnumSet<State> FROM_LOADING = EnumSet.of(State.IDLE);
\r
889 public static final EnumSet<State> FROM_UPDATING_DIAGRAM = EnumSet.of(State.IDLE);
\r
890 public static final EnumSet<State> FROM_IDLE = EnumSet.of(State.UPDATING_DIAGRAM, State.DISPOSED);
\r
891 public static final EnumSet<State> NO_STATES = EnumSet.noneOf(State.class);
\r
893 private EnumSet<State> validTargetStates(State start) {
\r
895 case INITIAL: return FROM_INITIAL;
\r
896 case LOADING: return FROM_LOADING;
\r
897 case UPDATING_DIAGRAM: return FROM_UPDATING_DIAGRAM;
\r
898 case IDLE: return FROM_IDLE;
\r
899 case DISPOSED: return NO_STATES;
\r
901 throw new IllegalArgumentException("unrecognized state " + start);
\r
904 private String validateStateChange(State start, State end) {
\r
905 EnumSet<State> validTargets = validTargetStates(start);
\r
906 if (!validTargets.contains(end))
\r
907 return "Cannot transition from " + start + " state to " + end + ".";
\r
912 * The current state of the synchronizer. At start it is
\r
913 * {@link State#INITIAL} and after loading it is {@link State#IDLE}.
\r
915 State synchronizerState = State.INITIAL;
\r
918 * A condition variable used to synchronize synchronizer state changes.
\r
920 ReentrantLock stateLock = new ReentrantLock();
\r
923 * A condition that is signaled when the synchronizer state changes to IDLE.
\r
925 Condition idleCondition = stateLock.newCondition();
\r
928 return synchronizerState;
\r
932 * Activates the desired state after making sure that the synchronizer has
\r
933 * been IDLE in between its current state and this invocation.
\r
935 * @param newState the new state to activate
\r
936 * @throws InterruptedException if waiting for IDLE state gets interrupted
\r
937 * @throws IllegalStateException if the requested transition from the
\r
938 * current state to the desired state would be illegal.
\r
940 void activateState(State newState, boolean waitForIdle) throws InterruptedException {
\r
943 // Wait until the state of the synchronizer IDLEs if necessary.
\r
944 if (waitForIdle && synchronizerState != State.IDLE) {
\r
945 String error = validateStateChange(synchronizerState, State.IDLE);
\r
947 throw new IllegalStateException(error);
\r
949 while (synchronizerState != State.IDLE) {
\r
950 if (DebugPolicy.DEBUG_STATE)
\r
951 System.out.println(Thread.currentThread() + " waiting for IDLE state, current="
\r
952 + synchronizerState);
\r
953 idleCondition.await();
\r
957 String error = validateStateChange(synchronizerState, newState);
\r
959 throw new IllegalStateException(error);
\r
961 if (DebugPolicy.DEBUG_STATE)
\r
962 System.out.println(Thread.currentThread() + " activated state " + newState);
\r
963 this.synchronizerState = newState;
\r
965 if (newState == State.IDLE)
\r
966 idleCondition.signalAll();
\r
968 stateLock.unlock();
\r
972 void idle() throws IllegalStateException, InterruptedException {
\r
973 activateState(State.IDLE, false);
\r
976 static interface StateRunnable extends Runnable {
\r
977 void execute() throws InvocationTargetException;
\r
979 public abstract class Stub implements StateRunnable {
\r
981 public void run() {
\r
985 public final void execute() throws InvocationTargetException {
\r
988 } catch (Exception e) {
\r
989 throw new InvocationTargetException(e);
\r
990 } catch (LinkageError e) {
\r
991 throw new InvocationTargetException(e);
\r
995 protected abstract void perform() throws Exception;
\r
999 protected void runInState(State state, StateRunnable runnable) throws InvocationTargetException {
\r
1001 activateState(state, true);
\r
1003 runnable.execute();
\r
1007 } catch (IllegalStateException e) {
\r
1008 throw new InvocationTargetException(e);
\r
1009 } catch (InterruptedException e) {
\r
1010 throw new InvocationTargetException(e);
\r
1014 protected void safeRunInState(State state, StateRunnable runnable) {
\r
1016 runInState(state, runnable);
\r
1017 } catch (InvocationTargetException e) {
\r
1018 error("Failed to run runnable " + runnable + " in state " + state + ". See exception for details.", e
\r
1023 // ------------------------------------------------------------------------
\r
1024 // Internal state machine handling END
\r
1025 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\r
1028 * @param processor
\r
1030 * @param elementClassProvider
\r
1031 * @throws DatabaseException
\r
1033 public GraphToDiagramSynchronizer(RequestProcessor processor, ICanvasContext canvas, IElementClassProvider elementClassProvider) throws DatabaseException {
\r
1034 if (processor == null)
\r
1035 throw new IllegalArgumentException("null processor");
\r
1036 if (canvas == null)
\r
1037 throw new IllegalArgumentException("null canvas");
\r
1038 if (elementClassProvider == null)
\r
1039 throw new IllegalArgumentException("null element class provider");
\r
1041 this.session = processor.getSession();
\r
1042 this.canvas = canvas;
\r
1043 this.modificationQueue = new ModificationQueue(session, errorHandler);
\r
1045 processor.syncRequest(new ReadRequest() {
\r
1047 public void run(ReadGraph graph) throws DatabaseException {
\r
1048 initializeResources(graph);
\r
1052 this.elementClassProvider = elementClassProvider;
\r
1053 synchronizationContext.set(SynchronizationHints.ELEMENT_CLASS_PROVIDER, elementClassProvider);
\r
1055 attachSessionListener(processor.getSession());
\r
1061 public IElementClassProvider getElementClassProvider() {
\r
1062 return elementClassProvider;
\r
1065 public Session getSession() {
\r
1069 public ICanvasContext getCanvasContext() {
\r
1073 public IDiagram getDiagram() {
\r
1077 void setCanvasDirty() {
\r
1078 ICanvasContext c = canvas;
\r
1079 if (synchronizerState != State.LOADING && c != null && !c.isDisposed()) {
\r
1080 // TODO: Consider adding an invocation limiter here, to prevent
\r
1081 // calling setDirty too often if enough time hasn't passed yet since
\r
1082 // the last invocation.
\r
1083 c.getContentContext().setDirty();
\r
1088 * @param elementType
\r
1090 * @throws DatabaseException if ElementClass cannot be retrieved
\r
1092 public ElementClass getNodeClass(Resource elementType) throws DatabaseException {
\r
1093 return getNodeClass(session, elementType);
\r
1096 public ElementClass getNodeClass(RequestProcessor processor, Resource elementType) throws DatabaseException {
\r
1097 ElementClass ec = processor.syncRequest(new NodeClassRequest(canvas, diagram, elementType, true));
\r
1102 protected void doDispose() {
\r
1106 boolean isInitial = getState() == State.INITIAL;
\r
1107 activateState(State.DISPOSED, !isInitial);
\r
1109 stateLock.unlock();
\r
1111 } catch (InterruptedException e) {
\r
1112 // Shouldn't happen.
\r
1113 e.printStackTrace();
\r
1115 detachSessionListener();
\r
1117 if (profileObserver != null) {
\r
1118 profileObserver.dispose();
\r
1119 profileObserver = null;
\r
1122 if (diagram != null) {
\r
1123 diagram.removeCompositionListener(diagramListener);
\r
1124 diagram.removeCompositionVetoListener(diagramListener);
\r
1127 // TODO: we should probably leave the dataElement map as is since DataElementMap needs it even after the synchronizer has been disposed.
\r
1128 // Currently the diagram's DataElementMap will be broken after disposal.
\r
1129 // dataElement.clear();
\r
1130 // dataConnection.clear();
\r
1132 if (layerManager != null) {
\r
1133 layerManager.dispose();
\r
1137 modificationQueue.dispose();
\r
1141 void initializeResources(ReadGraph graph) {
\r
1142 this.br = new BasicResources(graph);
\r
1144 // Initialize synchronization context
\r
1145 synchronizationContext = new GraphSynchronizationContext(graph, modificationQueue);
\r
1146 synchronizationContext.set(SynchronizationHints.ERROR_HANDLER, errorHandler);
\r
1149 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
1151 // ------------------------------------------------------------------------
\r
1153 GraphLayerManager layerManager;
\r
1156 * A common handler for all elements that is used to listen to changes in
\r
1157 * element visibility and focusability on diagram layers.
\r
1159 class ElementLayerListenerImpl implements ElementLayerListener {
\r
1160 private static final long serialVersionUID = -3410052116598828129L;
\r
1163 public void visibilityChanged(IElement e, ILayer layer, boolean visible) {
\r
1166 if (DebugPolicy.DEBUG_LAYERS)
\r
1167 System.out.println("visibility changed: " + e + ", " + layer + ", " + visible);
\r
1168 GraphLayer gl = layerManager.getGraphLayer(layer.getName());
\r
1170 changeTag(e, gl.getVisible(), visible);
\r
1175 public void focusabilityChanged(IElement e, ILayer layer, boolean focusable) {
\r
1178 if (DebugPolicy.DEBUG_LAYERS)
\r
1179 System.out.println("focusability changed: " + e + ", " + layer + ", " + focusable);
\r
1180 GraphLayer gl = layerManager.getGraphLayer(layer.getName());
\r
1182 changeTag(e, gl.getFocusable(), focusable);
\r
1186 void changeTag(IElement e, Resource tag, boolean set) {
\r
1187 Object object = e.getHint(ElementHints.KEY_OBJECT);
\r
1188 Resource tagged = null;
\r
1189 if (object instanceof Resource) {
\r
1190 tagged = (Resource) object;
\r
1191 } else if (object instanceof EdgeResource) {
\r
1192 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
1193 if (ce instanceof ConnectionEntityImpl) {
\r
1194 tagged = ((ConnectionEntityImpl) ce).connection;
\r
1197 if (tagged == null)
\r
1200 modificationQueue.async(new TagChange(tagged, tag, set), null);
\r
1204 ElementLayerListenerImpl elementLayerListener = new ElementLayerListenerImpl();
\r
1206 // ------------------------------------------------------------------------
\r
1208 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\r
1211 public IDiagram loadDiagram(IProgressMonitor progressMonitor, ReadGraph g, final String modelURI, final Resource diagram, final Resource runtime, final ResourceArray structuralPath,
\r
1212 IHintObservable initialHints) throws DatabaseException {
\r
1213 if (DebugPolicy.DEBUG_LOAD)
\r
1214 System.out.println(Thread.currentThread() + " loadDiagram: " + NameUtils.getSafeName(g, diagram));
\r
1216 SubMonitor monitor = SubMonitor.convert(progressMonitor, "Load Diagram", 100);
\r
1218 Object loadTask = Timing.BEGIN("GDS.loadDiagram");
\r
1221 activateState(State.LOADING, false);
\r
1222 } catch (IllegalStateException e) {
\r
1223 // Disposed already before loading even began.
\r
1224 this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
\r
1225 return this.diagram;
\r
1228 // Query for diagram class
\r
1229 Resource diagramClassResource = g.getPossibleType(diagram, br.DIA.Composite);
\r
1230 if (diagramClassResource != null) {
\r
1231 // Spawn new diagram
\r
1232 Object task = Timing.BEGIN("GDS.DiagramClassRequest");
\r
1233 final DiagramClass diagramClass = g.syncRequest(new DiagramClassRequest(diagram));
\r
1235 final IDiagram d = Diagram.spawnNew(diagramClass);
\r
1237 d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, diagram);
\r
1238 if (runtime != null)
\r
1239 d.setHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE, runtime);
\r
1240 if (modelURI != null)
\r
1241 d.setHint(DiagramModelHints.KEY_DIAGRAM_MODEL_URI, modelURI);
\r
1242 d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE_ARRAY, structuralPath);
\r
1244 // Set dumb default routing when DiagramClass does not
\r
1245 // predefine the default connection routing for the diagram.
\r
1246 if (!d.containsHint(DiagramHints.ROUTE_ALGORITHM))
\r
1247 d.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, false));
\r
1249 d.setHint(SynchronizationHints.CONTEXT, this);
\r
1251 // Initialize hints with hints from initialHints if given
\r
1252 if (initialHints != null) {
\r
1253 d.setHints(initialHints.getHints());
\r
1257 // ITask task2 = ThreadLogger.getInstance().begin("loadLayers");
\r
1258 monitor.subTask("Layers");
\r
1260 this.layerManager = new GraphLayerManager(g, modificationQueue, diagram);
\r
1261 synchronizationContext.set(GraphSynchronizationHints.GRAPH_LAYER_MANAGER, this.layerManager);
\r
1262 ILayersEditor layers = layerManager.loadLayers(d, g, diagram);
\r
1263 // task2.finish();
\r
1265 d.setHint(DiagramHints.KEY_LAYERS, layers);
\r
1266 d.setHint(DiagramHints.KEY_LAYERS_EDITOR, layers);
\r
1268 d.addCompositionVetoListener(diagramListener);
\r
1269 d.addCompositionListener(diagramListener);
\r
1273 d.setHint(DiagramHints.KEY_MUTATOR, new DefaultDiagramMutator(d, diagram, synchronizationContext));
\r
1275 // Add default layer if no layers exist.
\r
1276 // NOTE: this must be done after this.diagram has been set
\r
1277 // as it will trigger a graph modification which needs the
\r
1278 // diagram resource.
\r
1279 // ITask task3 = ThreadLogger.getInstance().begin("addDefaultLayer");
\r
1280 // if (layers.getLayers().isEmpty()) {
\r
1281 // if (DebugPolicy.DEBUG_LAYERS)
\r
1282 // System.out.println("No layers, creating default layer '"
\r
1283 // + DiagramConstants.DEFAULT_LAYER_NAME + "'");
\r
1284 // SimpleLayer defaultLayer = new SimpleLayer(DiagramConstants.DEFAULT_LAYER_NAME);
\r
1285 // layers.addLayer(defaultLayer);
\r
1286 // layers.activate(defaultLayer);
\r
1288 // // task3.finish();
\r
1290 monitor.worked(10);
\r
1292 monitor.subTask("Contents");
\r
1293 // Discover the plain resources that form the content of the
\r
1294 // diagram through a separate query. This allows us to
\r
1296 // track changes to the diagram structure itself, not the
\r
1297 // substructures contained by the structure elements.
\r
1298 ITask task4 = ThreadLogger.getInstance().begin("DiagramContentRequest1");
\r
1299 DiagramContentRequest query = new DiagramContentRequest(canvas, diagram, errorHandler);
\r
1300 g.syncRequest(query, new DiagramContentListener(diagram));
\r
1302 // ITask task5 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
\r
1303 ITask task42 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
\r
1304 DiagramContents contents = g.syncRequest(query);
\r
1306 // task5.finish();
\r
1307 monitor.worked(10);
\r
1309 monitor.subTask("Graphical elements");
\r
1311 Object applyDiagramContents = Timing.BEGIN("GDS.applyDiagramContents");
\r
1312 ITask task6 = ThreadLogger.getInstance().begin("applyDiagramContents");
\r
1313 processGraphUpdates(g, Collections.singleton(diagramGraphUpdater(contents)));
\r
1315 Timing.END(applyDiagramContents);
\r
1317 monitor.worked(80);
\r
1319 DataNodeMap dn = new DataNodeMap() {
\r
1321 public INode getNode(Object data) {
\r
1322 if (DataNodeConstants.CANVAS_ROOT == data)
\r
1323 return canvas.getCanvasNode();
\r
1324 if (DataNodeConstants.DIAGRAM_ELEMENT_PARENT == data) {
\r
1325 ElementPainter ep = canvas.getAtMostOneItemOfClass(ElementPainter.class);
\r
1326 return ep != null ? ep.getDiagramElementParentNode() : null;
\r
1329 DataElementMap emap = GraphToDiagramSynchronizer.this.diagram.getDiagramClass().getSingleItem(DataElementMap.class);
\r
1330 IElement element = emap.getElement(GraphToDiagramSynchronizer.this.diagram, data);
\r
1331 if(element == null) return null;
\r
1332 return element.getHint(ElementHints.KEY_SG_NODE);
\r
1336 profileObserver = new ProfileObserver(g.getSession(), runtime,
\r
1337 canvas.getThreadAccess(), canvas, canvas.getSceneGraph(), diagram,
\r
1338 ArrayMap.keys(ProfileKeys.DIAGRAM, ProfileKeys.CANVAS, ProfileKeys.NODE_MAP).values(GraphToDiagramSynchronizer.this.diagram, canvas, dn),
\r
1339 new CanvasNotification(canvas));
\r
1341 profileObserver.listen(g, GraphToDiagramSynchronizer.this);
\r
1347 this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
\r
1348 return this.diagram;
\r
1353 } catch (InterruptedException e) {
\r
1354 throw new RuntimeException(e);
\r
1355 } catch (IllegalStateException e) {
\r
1356 // If the synchronizer was disposed ahead of time, it was done
\r
1357 // for a reason, such as the user having closed the owner editor.
\r
1359 throw new CancelTransactionException(e);
\r
1360 throw new RuntimeException(e);
\r
1362 Timing.END(loadTask);
\r
1366 static class CanvasNotification implements Runnable {
\r
1368 final private ICanvasContext canvas;
\r
1370 public CanvasNotification(ICanvasContext canvas) {
\r
1371 this.canvas = canvas;
\r
1374 public void run() {
\r
1375 canvas.getContentContext().setDirty();
\r
1380 ArrayList<IModification> pendingModifications = new ArrayList<IModification>();
\r
1381 MapSet<IElement, IModification> modificationIndex = new MapSet.Hash<IElement, IModification>();
\r
1383 void addModification(IElement element, IModification modification) {
\r
1384 pendingModifications.add(modification);
\r
1385 if (element != null)
\r
1386 modificationIndex.add(element, modification);
\r
1389 class DefaultDiagramMutator implements DiagramMutator {
\r
1391 Map<IElement, Resource> creation = new HashMap<IElement, Resource>();
\r
1396 IModifiableSynchronizationContext synchronizationContext;
\r
1398 public DefaultDiagramMutator(IDiagram d, Resource diagram, IModifiableSynchronizationContext synchronizationContext) {
\r
1400 this.diagram = diagram;
\r
1401 this.synchronizationContext = synchronizationContext;
\r
1403 if (synchronizationContext.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER) == null)
\r
1404 throw new IllegalArgumentException("SynchronizationHints.ELEMENT_CLASS_PROVIDER not available");
\r
1407 void assertNotDisposed() {
\r
1409 throw new IllegalStateException(getClass().getSimpleName() + " is disposed");
\r
1413 public IElement newElement(ElementClass clazz) {
\r
1414 assertNotDisposed();
\r
1415 ElementFactory ef = d.getDiagramClass().getAtMostOneItemOfClass(ElementFactory.class);
\r
1416 IElement element = null;
\r
1418 element = ef.spawnNew(clazz);
\r
1420 element = Element.spawnNew(clazz);
\r
1422 element.setHint(ElementHints.KEY_OBJECT, new TransientElementObject());
\r
1424 addModification(element, new AddElement(synchronizationContext, d, element));
\r
1430 public void commit() {
\r
1431 assertNotDisposed();
\r
1432 if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
\r
1433 System.out.println("DiagramMutator is about to commit changes:");
\r
1434 for (IModification mod : pendingModifications)
\r
1435 System.out.println("\t- " + mod);
\r
1438 Collections.sort(pendingModifications);
\r
1440 if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
\r
1441 if (pendingModifications.size() > 1) {
\r
1442 System.out.println("* changes were re-ordered to:");
\r
1443 for (IModification mod : pendingModifications)
\r
1444 System.out.println("\t" + mod);
\r
1448 Timing.safeTimed(errorHandler, "QUEUE AND WAIT FOR MODIFICATIONS TO FINISH", new GTask() {
\r
1450 public void run() throws DatabaseException {
\r
1451 // Performs a separate write request and query result update
\r
1452 // for each modification
\r
1453 // for (IModification mod : pendingModifications) {
\r
1455 // modificationQueue.sync(mod);
\r
1456 // } catch (InterruptedException e) {
\r
1457 // error("Pending diagram modification " + mod
\r
1458 // + " was interrupted. See exception for details.", e);
\r
1462 // NOTE: this is still under testing, the author is not
\r
1463 // truly certain that it should work in all cases ATM.
\r
1465 // Performs all modifications with in a single write request
\r
1466 for (IModification mod : pendingModifications) {
\r
1467 modificationQueue.offer(mod, null);
\r
1470 // Perform the modifications in a single request.
\r
1471 modificationQueue.finish();
\r
1472 } catch (InterruptedException e) {
\r
1473 errorHandler.error("Diagram modification finishing was interrupted. See exception for details.", e);
\r
1477 pendingModifications.clear();
\r
1478 modificationIndex.clear();
\r
1480 if (DebugPolicy.DEBUG_MUTATOR_COMMIT)
\r
1481 System.out.println("DiagramMutator has committed");
\r
1485 public void clear() {
\r
1486 assertNotDisposed();
\r
1487 pendingModifications.clear();
\r
1488 modificationIndex.clear();
\r
1490 if (DebugPolicy.DEBUG_MUTATOR)
\r
1491 System.out.println("DiagramMutator has been cleared");
\r
1495 public void modifyTransform(IElement element) {
\r
1496 assertNotDisposed();
\r
1497 Resource resource = backendObject(element);
\r
1498 AffineTransform tr = element.getHint(ElementHints.KEY_TRANSFORM);
\r
1499 if (resource != null && tr != null) {
\r
1500 addModification(element, new TransformElement(resource, tr));
\r
1505 public void synchronizeHintsToBackend(IElement element) {
\r
1506 assertNotDisposed();
\r
1507 IHintSynchronizer synchronizer = element.getHint(SynchronizationHints.HINT_SYNCHRONIZER);
\r
1508 if (synchronizer != null) {
\r
1509 CollectingModificationQueue queue = new CollectingModificationQueue();
\r
1510 synchronizer.synchronize(synchronizationContext, element);
\r
1511 addModification(element, new CompositeModification(ModificationAdapter.LOW_PRIORITY, queue.getQueue()));
\r
1516 public void synchronizeElementOrder() {
\r
1517 assertNotDisposed();
\r
1518 List<IElement> snapshot = d.getSnapshot();
\r
1519 addModification(null, new ElementReorder(d, snapshot));
\r
1523 public void register(IElement element, Object object) {
\r
1524 creation.put(element, (Resource) object);
\r
1527 @SuppressWarnings("unchecked")
\r
1529 public <T> T backendObject(IElement element) {
\r
1530 Object object = ElementUtils.getObject(element);
\r
1531 if (object instanceof Resource)
\r
1532 return (T) object;
\r
1534 return (T) creation.get(element);
\r
1539 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
1540 // GRAPH TO DIAGRAM SYCHRONIZATION LOGIC BEGIN
\r
1541 // ------------------------------------------------------------------------
\r
1543 static class ConnectionData {
\r
1544 ConnectionEntityImpl impl;
\r
1545 List<Resource> branchPoints = new ArrayList<Resource>();
\r
1546 List<EdgeResource> segments = new ArrayList<EdgeResource>();
\r
1548 ConnectionData(ConnectionEntityImpl ce) {
\r
1552 void addBranchPoint(Resource bp) {
\r
1553 branchPoints.add(bp);
\r
1556 void addSegment(EdgeResource seg) {
\r
1557 segments.add(seg);
\r
1561 class GraphToDiagramUpdater {
\r
1562 DiagramContents lastContent;
\r
1563 DiagramContents content;
\r
1564 DiagramContentChanges changes;
\r
1566 final List<IElement> addedElements;
\r
1567 final List<IElement> removedElements;
\r
1569 final List<IElement> addedConnectionSegments;
\r
1570 final List<IElement> removedConnectionSegments;
\r
1572 final List<IElement> addedBranchPoints;
\r
1573 final List<IElement> removedBranchPoints;
\r
1575 final Map<Object, IElement> addedElementMap;
\r
1576 final Map<Resource, IElement> addedConnectionMap;
\r
1577 final Map<Resource, ConnectionEntityImpl> addedConnectionEntities;
\r
1578 final List<Resource> removedConnectionEntities;
\r
1579 final Map<ConnectionEntityImpl, ConnectionData> changedConnectionEntities;
\r
1581 final Map<Resource, IElement> addedRouteGraphConnectionMap;
\r
1582 final List<IElement> removedRouteGraphConnections;
\r
1585 GraphToDiagramUpdater(DiagramContents lastContent, DiagramContents content, DiagramContentChanges changes) {
\r
1586 this.lastContent = lastContent;
\r
1587 this.content = content;
\r
1588 this.changes = changes;
\r
1590 this.addedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
\r
1591 this.removedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
\r
1592 this.addedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
\r
1593 this.removedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
\r
1594 this.addedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
\r
1595 this.removedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
\r
1596 this.addedElementMap = new HashMap<Object, IElement>();
\r
1597 this.addedConnectionMap = new HashMap<Resource, IElement>();
\r
1598 this.addedConnectionEntities = new HashMap<Resource, ConnectionEntityImpl>();
\r
1599 this.removedConnectionEntities = new ArrayList<Resource>(changes.connections.size());
\r
1600 this.changedConnectionEntities = new HashMap<ConnectionEntityImpl, ConnectionData>();
\r
1601 this.addedRouteGraphConnectionMap = new HashMap<Resource, IElement>();
\r
1602 this.removedRouteGraphConnections = new ArrayList<IElement>(changes.routeGraphConnections.size());
\r
1605 public void clear() {
\r
1606 // Prevent DiagramContents leakage through DisposableListeners.
\r
1607 lastContent = null;
\r
1611 this.addedElements.clear();
\r
1612 this.removedElements.clear();
\r
1613 this.addedConnectionSegments.clear();
\r
1614 this.removedConnectionSegments.clear();
\r
1615 this.addedBranchPoints.clear();
\r
1616 this.removedBranchPoints.clear();
\r
1617 this.addedElementMap.clear();
\r
1618 this.addedConnectionMap.clear();
\r
1619 this.addedConnectionEntities.clear();
\r
1620 this.removedConnectionEntities.clear();
\r
1621 this.changedConnectionEntities.clear();
\r
1622 this.addedRouteGraphConnectionMap.clear();
\r
1623 this.removedRouteGraphConnections.clear();
\r
1626 void processNodes(AsyncReadGraph graph) {
\r
1628 for (Map.Entry<Resource, Change> entry : changes.elements.entrySet()) {
\r
1630 final Resource element = entry.getKey();
\r
1631 Change change = entry.getValue();
\r
1635 IElement mappedElement = getMappedElement(element);
\r
1636 if (mappedElement == null) {
\r
1637 if (DebugPolicy.DEBUG_NODE_LOAD)
\r
1638 graph.asyncRequest(new ReadRequest() {
\r
1640 public void run(ReadGraph graph) throws DatabaseException {
\r
1641 System.out.println(" EXTERNALLY ADDED ELEMENT: "
\r
1642 + NameUtils.getSafeName(graph, element) + " ("
\r
1643 + element.getResourceId() + ")");
\r
1647 if (content.connectionSet.contains(element)) {
\r
1649 // TODO: Connection loading has no listening, changes :Connection will not be noticed by this code!
\r
1650 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
\r
1652 public String toString() {
\r
1653 return "Connection load listener for " + element;
\r
1656 public void execute(IElement loaded) {
\r
1657 // Invoked when the element has been loaded.
\r
1658 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
\r
1659 System.out.println("CONNECTION LoadListener for " + loaded);
\r
1661 if (loaded == null) {
\r
1662 disposeListener();
\r
1666 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
\r
1668 // Logic for disposing listener
\r
1669 if (!previousContent.connectionSet.contains(data)) {
\r
1670 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
\r
1671 System.out.println("CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
\r
1672 disposeListener();
\r
1676 if (addedElementMap.containsKey(data)) {
\r
1677 // This element was just loaded, in
\r
1678 // which case its hints need to
\r
1679 // uploaded to the real mapped
\r
1680 // element immediately.
\r
1681 IElement mappedElement = getMappedElement(data);
\r
1682 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
\r
1683 System.out.println("LOADED ADDED CONNECTION, currently mapped connection: " + mappedElement);
\r
1684 if (mappedElement != null && (mappedElement instanceof Element)) {
\r
1685 if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
\r
1686 System.out.println(" mapped hints: " + mappedElement.getHints());
\r
1687 System.out.println(" loaded hints: " + loaded.getHints());
\r
1689 updateMappedElement((Element) mappedElement, loaded);
\r
1692 // This element was already loaded.
\r
1693 // Just schedule an update some time
\r
1695 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
\r
1696 System.out.println("PREVIOUSLY LOADED CONNECTION UPDATED, scheduling update into the future");
\r
1697 offerGraphUpdate( connectionUpdater(element, loaded) );
\r
1702 graph.asyncRequest(new ConnectionRequest(canvas, diagram, element, errorHandler, loadListener), new AsyncProcedure<IElement>() {
\r
1704 public void execute(AsyncReadGraph graph, final IElement e) {
\r
1708 //System.out.println("ConnectionRequestProcedure " + e);
\r
1709 mapElement(element, e);
\r
1710 synchronized (GraphToDiagramUpdater.this) {
\r
1711 addedElements.add(e);
\r
1712 addedElementMap.put(element, e);
\r
1713 addedConnectionMap.put(element, e);
\r
1716 // Read connection type
\r
1717 graph.forSingleType(element, br.DIA.Connection, new Procedure<Resource>() {
\r
1719 public void exception(Throwable t) {
\r
1724 public void execute(Resource connectionType) {
\r
1725 synchronized (GraphToDiagramUpdater.this) {
\r
1726 //System.out.println("new connection entity " + e);
\r
1727 ConnectionEntityImpl entity = new ConnectionEntityImpl(element, connectionType, e);
\r
1728 e.setHint(ElementHints.KEY_CONNECTION_ENTITY, entity);
\r
1729 addedConnectionEntities.put(element, entity);
\r
1736 public void exception(AsyncReadGraph graph, Throwable throwable) {
\r
1740 } else if (content.nodeSet.contains(element)) {
\r
1742 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
\r
1744 public String toString() {
\r
1745 return "Node load listener for " + element;
\r
1748 public void execute(IElement loaded) {
\r
1749 // Invoked when the element has been loaded.
\r
1750 if (DebugPolicy.DEBUG_NODE_LISTENER)
\r
1751 System.out.println("NODE LoadListener for " + loaded);
\r
1753 if (loaded == null) {
\r
1754 disposeListener();
\r
1758 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
\r
1760 // Logic for disposing listener
\r
1761 if (!previousContent.nodeSet.contains(data)) {
\r
1762 if (DebugPolicy.DEBUG_NODE_LISTENER)
\r
1763 System.out.println("NODE LoadListener, node not in current content: " + data + ". Disposing.");
\r
1764 disposeListener();
\r
1768 if (addedElementMap.containsKey(data)) {
\r
1769 // This element was just loaded, in
\r
1770 // which case its hints need to
\r
1771 // uploaded to the real mapped
\r
1772 // element immediately.
\r
1773 IElement mappedElement = getMappedElement(data);
\r
1774 if (DebugPolicy.DEBUG_NODE_LISTENER)
\r
1775 System.out.println("LOADED ADDED ELEMENT, currently mapped element: " + mappedElement);
\r
1776 if (mappedElement != null && (mappedElement instanceof Element)) {
\r
1777 if (DebugPolicy.DEBUG_NODE_LISTENER) {
\r
1778 System.out.println(" mapped hints: " + mappedElement.getHints());
\r
1779 System.out.println(" loaded hints: " + loaded.getHints());
\r
1781 updateMappedElement((Element) mappedElement, loaded);
\r
1784 // This element was already loaded.
\r
1785 // Just schedule an update some time
\r
1787 if (DebugPolicy.DEBUG_NODE_LISTENER)
\r
1788 System.out.println("PREVIOUSLY LOADED NODE UPDATED, scheduling update into the future");
\r
1789 offerGraphUpdate( nodeUpdater(element, loaded) );
\r
1794 //System.out.println("NODE REQUEST: " + element);
\r
1795 graph.asyncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
\r
1797 public void execute(AsyncReadGraph graph, IElement e) {
\r
1801 // This is invoked before the element is actually loaded.
\r
1802 //System.out.println("NodeRequestProcedure " + e);
\r
1803 if (DebugPolicy.DEBUG_NODE_LOAD)
\r
1804 System.out.println("MAPPING ADDED NODE: " + element + " -> " + e);
\r
1805 mapElement(element, e);
\r
1806 synchronized (GraphToDiagramUpdater.this) {
\r
1807 addedElements.add(e);
\r
1808 addedElementMap.put(element, e);
\r
1813 public void exception(AsyncReadGraph graph, Throwable throwable) {
\r
1819 // warning("Diagram elements must be either elements or connections, "
\r
1820 // + NameUtils.getSafeName(g, element) + " is neither",
\r
1821 // new AssumptionException(""));
\r
1827 IElement e = getMappedElement(element);
\r
1828 if (DebugPolicy.DEBUG_NODE_LOAD)
\r
1829 graph.asyncRequest(new ReadRequest() {
\r
1831 public void run(ReadGraph graph) throws DatabaseException {
\r
1832 System.out.println(" EXTERNALLY REMOVED ELEMENT: "
\r
1833 + NameUtils.getSafeName(graph, element) + " ("
\r
1834 + element.getResourceId() + ")");
\r
1838 removedElements.add(e);
\r
1846 void gatherChangedConnectionParts(Map<?, Change> changes) {
\r
1847 for (Map.Entry<?, Change> entry : changes.entrySet()) {
\r
1848 Object part = entry.getKey();
\r
1849 Change change = entry.getValue();
\r
1853 synchronized (GraphToDiagramUpdater.this) {
\r
1854 Resource connection = content.partToConnection.get(part);
\r
1855 assert connection != null;
\r
1857 IElement ce = getMappedElement(connection);
\r
1859 ce = addedElementMap.get(connection);
\r
1862 markConnectionChanged(ce);
\r
1867 if (lastContent == null)
\r
1869 Resource connection = lastContent.partToConnection.get(part);
\r
1870 if (connection != null && content.connectionSet.contains(connection)) {
\r
1871 markConnectionChanged(connection);
\r
1879 void markConnectionChanged(Resource connection) {
\r
1880 // System.out.println("markConnectionChanged");
\r
1881 ConnectionEntityImpl ce = getMappedConnection(connection);
\r
1883 markConnectionChanged(ce);
\r
1886 error("WARNING: marking connection entity " + connection
\r
1887 + " changed, but the connection was not previously mapped",
\r
1888 new Exception("created exception to get a stack trace"));
\r
1891 void markConnectionChanged(IElement connection) {
\r
1892 ConnectionEntityImpl entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
1893 if (entity != null)
\r
1894 markConnectionChanged(entity);
\r
1897 void markConnectionChanged(ConnectionEntityImpl ce) {
\r
1898 if (!changedConnectionEntities.containsKey(ce)) {
\r
1899 changedConnectionEntities.put(ce, new ConnectionData(ce));
\r
1903 void processConnections() {
\r
1904 // Find added/removed connection segments/branch points
\r
1905 // in order to find all changed connection entities.
\r
1906 gatherChangedConnectionParts(changes.connectionSegments);
\r
1907 gatherChangedConnectionParts(changes.branchPoints);
\r
1909 // Find removed connection entities
\r
1910 for (Map.Entry<Resource, Change> entry : changes.connections.entrySet()) {
\r
1911 Resource ce = entry.getKey();
\r
1912 Change change = entry.getValue();
\r
1916 removedConnectionEntities.add(ce);
\r
1921 // Generate update data of changed connection entities.
\r
1922 // This ConnectionData will be applied in the canvas thread
\r
1923 // diagram updater.
\r
1924 for (ConnectionData cd : changedConnectionEntities.values()) {
\r
1925 for (Object part : content.connectionToParts.getValuesUnsafe(cd.impl.connection)) {
\r
1926 if (part instanceof Resource) {
\r
1927 cd.branchPoints.add((Resource) part);
\r
1928 } else if (part instanceof EdgeResource) {
\r
1929 cd.segments.add((EdgeResource) part);
\r
1935 void processRouteGraphConnections(AsyncReadGraph graph) {
\r
1936 for (Map.Entry<Resource, Change> entry : changes.routeGraphConnections.entrySet()) {
\r
1937 final Resource connection = entry.getKey();
\r
1939 Change change = entry.getValue();
\r
1942 IElement mappedElement = getMappedElement(connection);
\r
1943 if (mappedElement != null)
\r
1946 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
\r
1948 public String toString() {
\r
1949 return "processRouteGraphConnections " + connection;
\r
1952 public void execute(IElement loaded) {
\r
1953 // Invoked when the element has been loaded.
\r
1954 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
\r
1955 System.out.println("ROUTE GRAPH CONNECTION LoadListener for " + loaded);
\r
1957 if (loaded == null) {
\r
1958 disposeListener();
\r
1962 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
\r
1964 // Logic for disposing listener
\r
1965 if (!previousContent.routeGraphConnectionSet.contains(data)) {
\r
1966 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
\r
1967 System.out.println("ROUTE GRAPH CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
\r
1968 disposeListener();
\r
1972 if (addedElementMap.containsKey(data)) {
\r
1973 // This element was just loaded, in
\r
1974 // which case its hints need to
\r
1975 // uploaded to the real mapped
\r
1976 // element immediately.
\r
1977 IElement mappedElement = getMappedElement(data);
\r
1978 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
\r
1979 System.out.println("LOADED ADDED ROUTE GRAPH CONNECTION, currently mapped connection: " + mappedElement);
\r
1980 if (mappedElement instanceof Element) {
\r
1981 if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
\r
1982 System.out.println(" mapped hints: " + mappedElement.getHints());
\r
1983 System.out.println(" loaded hints: " + loaded.getHints());
\r
1985 updateMappedElement((Element) mappedElement, loaded);
\r
1988 // This element was already loaded.
\r
1989 // Just schedule an update some time
\r
1991 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
\r
1992 System.out.println("PREVIOUSLY LOADED ROUTE GRAPH CONNECTION UPDATED, scheduling update into the future: " + connection);
\r
1994 Set<Object> dirtyNodes = new THashSet<Object>(4);
\r
1995 IElement mappedElement = getMappedElement(connection);
\r
1996 ConnectionEntity ce = mappedElement.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
1998 for (Connection conn : ce.getTerminalConnections(null)) {
\r
1999 Object o = conn.node.getHint(ElementHints.KEY_OBJECT);
\r
2001 dirtyNodes.add(o);
\r
2002 if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
\r
2003 System.out.println("Marked connectivity dirty for node: " + conn.node);
\r
2008 offerGraphUpdate( routeGraphConnectionUpdater(connection, loaded, dirtyNodes) );
\r
2013 graph.asyncRequest(new ConnectionRequest(canvas, diagram, connection, errorHandler, loadListener), new Procedure<IElement>() {
\r
2015 public void execute(final IElement e) {
\r
2019 //System.out.println("ConnectionRequestProcedure " + e);
\r
2020 if (DebugPolicy.DEBUG_NODE_LOAD)
\r
2021 System.out.println("MAPPING ADDED ROUTE GRAPH CONNECTION: " + connection + " -> " + e);
\r
2022 mapElement(connection, e);
\r
2023 synchronized (GraphToDiagramUpdater.this) {
\r
2024 addedElements.add(e);
\r
2025 addedElementMap.put(connection, e);
\r
2026 addedRouteGraphConnectionMap.put(connection, e);
\r
2030 public void exception(Throwable throwable) {
\r
2037 IElement e = getMappedElement(connection);
\r
2039 removedRouteGraphConnections.add(e);
\r
2046 ConnectionEntityImpl getConnectionEntity(Object connectionPart) {
\r
2047 Resource connection = content.partToConnection.get(connectionPart);
\r
2048 assert connection != null;
\r
2049 ConnectionEntityImpl ce = addedConnectionEntities.get(connection);
\r
2052 return assertMappedConnection(connection);
\r
2055 void processBranchPoints(AsyncReadGraph graph) {
\r
2056 for (Map.Entry<Resource, Change> entry : changes.branchPoints.entrySet()) {
\r
2058 final Resource element = entry.getKey();
\r
2059 Change change = entry.getValue();
\r
2063 IElement mappedElement = getMappedElement(element);
\r
2064 if (mappedElement == null) {
\r
2065 if (DebugPolicy.DEBUG_NODE_LOAD)
\r
2066 graph.asyncRequest(new ReadRequest() {
\r
2068 public void run(ReadGraph graph) throws DatabaseException {
\r
2069 System.out.println(" EXTERNALLY ADDED BRANCH POINT: "
\r
2070 + NameUtils.getSafeName(graph, element) + " ("
\r
2071 + element.getResourceId() + ")");
\r
2075 Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
\r
2077 public String toString() {
\r
2078 return "processBranchPoints for " + element;
\r
2081 public void execute(IElement loaded) {
\r
2082 // Invoked when the element has been loaded.
\r
2083 if (DebugPolicy.DEBUG_NODE_LISTENER)
\r
2084 System.out.println("BRANCH POINT LoadListener for " + loaded);
\r
2086 if (loaded == null) {
\r
2087 disposeListener();
\r
2091 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
\r
2092 if (addedElementMap.containsKey(data)) {
\r
2093 // This element was just loaded, in
\r
2094 // which case its hints need to
\r
2095 // uploaded to the real mapped
\r
2096 // element immediately.
\r
2097 IElement mappedElement = getMappedElement(data);
\r
2098 if (DebugPolicy.DEBUG_NODE_LISTENER)
\r
2099 System.out.println("LOADED ADDED BRANCH POINT, currently mapped element: " + mappedElement);
\r
2100 if (mappedElement != null && (mappedElement instanceof Element)) {
\r
2101 if (DebugPolicy.DEBUG_NODE_LISTENER) {
\r
2102 System.out.println(" mapped hints: " + mappedElement.getHints());
\r
2103 System.out.println(" loaded hints: " + loaded.getHints());
\r
2105 updateMappedElement((Element) mappedElement, loaded);
\r
2108 // This element was already loaded.
\r
2109 // Just schedule an update some time
\r
2111 if (DebugPolicy.DEBUG_NODE_LISTENER)
\r
2112 System.out.println("PREVIOUSLY LOADED BRANCH POINT UPDATED, scheduling update into the future");
\r
2113 offerGraphUpdate( nodeUpdater(element, loaded) );
\r
2118 graph.asyncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
\r
2120 public void execute(AsyncReadGraph graph, IElement e) {
\r
2122 mapElement(element, e);
\r
2123 synchronized (GraphToDiagramUpdater.this) {
\r
2124 addedBranchPoints.add(e);
\r
2125 addedElementMap.put(element, e);
\r
2126 ConnectionEntityImpl ce = getConnectionEntity(element);
\r
2127 e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
\r
2128 e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
\r
2134 public void exception(AsyncReadGraph graph, Throwable throwable) {
\r
2142 IElement e = getMappedElement(element);
\r
2143 if (DebugPolicy.DEBUG_NODE_LOAD)
\r
2144 graph.asyncRequest(new ReadRequest() {
\r
2146 public void run(ReadGraph graph) throws DatabaseException {
\r
2147 System.out.println(" EXTERNALLY REMOVED BRANCH POINT: "
\r
2148 + NameUtils.getSafeName(graph, element) + " ("
\r
2149 + element.getResourceId() + ")");
\r
2153 removedBranchPoints.add(e);
\r
2161 void processConnectionSegments(AsyncReadGraph graph) {
\r
2162 ConnectionSegmentAdapter adapter = connectionSegmentAdapter;
\r
2164 for (Map.Entry<EdgeResource, Change> entry : changes.connectionSegments.entrySet()) {
\r
2165 final EdgeResource seg = entry.getKey();
\r
2166 Change change = entry.getValue();
\r
2170 IElement mappedElement = getMappedElement(seg);
\r
2171 if (mappedElement == null) {
\r
2172 if (DebugPolicy.DEBUG_EDGE_LOAD)
\r
2173 graph.asyncRequest(new ReadRequest() {
\r
2175 public void run(ReadGraph graph) throws DatabaseException {
\r
2176 System.out.println(" EXTERNALLY ADDED CONNECTION SEGMENT: " + seg.toString()
\r
2177 + " - " + seg.toString(graph));
\r
2181 graph.asyncRequest(new EdgeRequest(canvas, errorHandler, canvasListenerSupport, diagram, adapter, seg), new AsyncProcedure<IElement>() {
\r
2183 public void execute(AsyncReadGraph graph, IElement e) {
\r
2184 if (DebugPolicy.DEBUG_EDGE_LOAD)
\r
2185 System.out.println("ADDED EDGE LOADED: " + e);
\r
2187 mapElement(seg, e);
\r
2188 synchronized (GraphToDiagramUpdater.this) {
\r
2189 addedConnectionSegments.add(e);
\r
2190 addedElementMap.put(seg, e);
\r
2191 ConnectionEntityImpl ce = getConnectionEntity(seg);
\r
2192 e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
\r
2193 e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
\r
2199 public void exception(AsyncReadGraph graph, Throwable throwable) {
\r
2207 final IElement e = getMappedElement(seg);
\r
2208 if (DebugPolicy.DEBUG_EDGE_LOAD)
\r
2209 graph.asyncRequest(new ReadRequest() {
\r
2211 public void run(ReadGraph graph) throws DatabaseException {
\r
2212 System.out.println(" EXTERNALLY REMOVED CONNECTION SEGMENT: " + seg.toString() + " - "
\r
2213 + seg.toString(graph) + " -> " + e);
\r
2217 removedConnectionSegments.add(e);
\r
2225 void executeDeferredLoaders(ReadGraph graph) throws DatabaseException {
\r
2226 // The rest of the diagram loading passes
\r
2227 Deque<IElement> q1 = new ArrayDeque<IElement>();
\r
2228 Deque<IElement> q2 = new ArrayDeque<IElement>();
\r
2229 collectElementLoaders(q1, addedElements);
\r
2230 while (!q1.isEmpty()) {
\r
2231 //System.out.println("DEFFERED LOADERS: " + q1);
\r
2232 for (IElement e : q1) {
\r
2233 ElementLoader loader = e.removeHint(DiagramModelHints.KEY_ELEMENT_LOADER);
\r
2234 //System.out.println("EXECUTING DEFFERED LOADER: " + loader);
\r
2235 loader.load(graph, diagram, e);
\r
2238 collectElementLoaders(q2, q1);
\r
2239 Deque<IElement> qt = q1;
\r
2246 private void collectElementLoaders(Queue<IElement> queue, Collection<IElement> cs) {
\r
2247 for (IElement e : cs) {
\r
2248 ElementLoader loader = e.getHint(DiagramModelHints.KEY_ELEMENT_LOADER);
\r
2249 if (loader != null)
\r
2254 public void process(ReadGraph graph) throws DatabaseException {
\r
2255 // No changes? Do nothing.
\r
2256 if (changes.isEmpty())
\r
2259 // NOTE: This order is important.
\r
2260 Object task = Timing.BEGIN("processNodesConnections");
\r
2261 //System.out.println("---- PROCESS NODES & CONNECTIONS BEGIN");
\r
2262 if (!changes.elements.isEmpty()) {
\r
2263 graph.syncRequest(new AsyncReadRequest() {
\r
2265 public void run(AsyncReadGraph graph) {
\r
2266 processNodes(graph);
\r
2269 public String toString() {
\r
2270 return "processNodes";
\r
2274 //System.out.println("---- PROCESS NODES & CONNECTIONS END");
\r
2276 processConnections();
\r
2278 //System.out.println("---- PROCESS BRANCH POINTS BEGIN");
\r
2279 if (!changes.branchPoints.isEmpty()) {
\r
2280 graph.syncRequest(new AsyncReadRequest() {
\r
2282 public void run(AsyncReadGraph graph) {
\r
2283 processBranchPoints(graph);
\r
2286 public String toString() {
\r
2287 return "processBranchPoints";
\r
2291 //System.out.println("---- PROCESS BRANCH POINTS END");
\r
2294 task = Timing.BEGIN("processConnectionSegments");
\r
2296 //System.out.println("---- PROCESS CONNECTION SEGMENTS BEGIN");
\r
2297 if (!changes.connectionSegments.isEmpty()) {
\r
2298 graph.syncRequest(new AsyncReadRequest() {
\r
2300 public void run(AsyncReadGraph graph) {
\r
2301 processConnectionSegments(graph);
\r
2304 public String toString() {
\r
2305 return "processConnectionSegments";
\r
2309 //System.out.println("---- PROCESS CONNECTION SEGMENTS END");
\r
2313 task = Timing.BEGIN("processRouteGraphConnections");
\r
2314 if (!changes.routeGraphConnections.isEmpty()) {
\r
2315 graph.syncRequest(new AsyncReadRequest() {
\r
2317 public void run(AsyncReadGraph graph) {
\r
2318 processRouteGraphConnections(graph);
\r
2321 public String toString() {
\r
2322 return "processRouteGraphConnections";
\r
2328 //System.out.println("---- AFTER LOADING");
\r
2329 //for (IElement e : addedElements)
\r
2330 // System.out.println(" ADDED ELEMENT: " + e);
\r
2331 //for (IElement e : addedBranchPoints)
\r
2332 // System.out.println(" ADDED BRANCH POINTS: " + e);
\r
2334 task = Timing.BEGIN("executeDeferredLoaders");
\r
2335 executeDeferredLoaders(graph);
\r
2339 public boolean isEmpty() {
\r
2340 return addedElements.isEmpty() && removedElements.isEmpty()
\r
2341 && addedConnectionSegments.isEmpty() && removedConnectionSegments.isEmpty()
\r
2342 && addedBranchPoints.isEmpty() && removedBranchPoints.isEmpty()
\r
2343 && addedConnectionEntities.isEmpty() && removedConnectionEntities.isEmpty()
\r
2344 && addedRouteGraphConnectionMap.isEmpty() && removedRouteGraphConnections.isEmpty()
\r
2345 && !changes.elementOrderChanged;
\r
2348 class DefaultConnectionSegmentAdapter implements ConnectionSegmentAdapter {
\r
2351 public void getClass(AsyncReadGraph graph, EdgeResource edge, ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, IDiagram diagram, final AsyncProcedure<ElementClass> procedure) {
\r
2352 if (info.connectionType != null) {
\r
2353 NodeClassRequest request = new NodeClassRequest(canvas, diagram, info.connectionType, true);
\r
2354 graph.asyncRequest(request, new CacheListener<ElementClass>(listenerSupport));
\r
2355 graph.asyncRequest(request, procedure);
\r
2357 procedure.execute(graph, null);
\r
2362 public void load(AsyncReadGraph graph, final EdgeResource edge, final ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, final IDiagram diagram, final IElement element) {
\r
2363 graph.asyncRequest(new Read<IElement>() {
\r
2365 public IElement perform(ReadGraph graph) throws DatabaseException {
\r
2366 //ITask task = ThreadLogger.getInstance().begin("LoadSegment");
\r
2367 syncLoad(graph, edge, info, diagram, element);
\r
2372 public String toString() {
\r
2373 return "defaultConnectionSegmentAdapter";
\r
2375 }, new DisposableListener<IElement>(listenerSupport) {
\r
2378 public String toString() {
\r
2379 return "DefaultConnectionSegmentAdapter listener for " + edge;
\r
2383 public void execute(IElement loaded) {
\r
2384 // Invoked when the element has been loaded.
\r
2385 if (DebugPolicy.DEBUG_EDGE_LISTENER)
\r
2386 System.out.println("EDGE LoadListener for " + loaded);
\r
2388 if (loaded == null) {
\r
2389 disposeListener();
\r
2393 Object data = loaded.getHint(ElementHints.KEY_OBJECT);
\r
2394 if (addedElementMap.containsKey(data)) {
\r
2395 // This element was just loaded, in
\r
2396 // which case its hints need to
\r
2397 // uploaded to the real mapped
\r
2398 // element immediately.
\r
2399 IElement mappedElement = getMappedElement(data);
\r
2400 if (DebugPolicy.DEBUG_EDGE_LISTENER)
\r
2401 System.out.println("LOADED ADDED EDGE, currently mapped element: " + mappedElement);
\r
2402 if (mappedElement != null && (mappedElement instanceof Element)) {
\r
2403 if (DebugPolicy.DEBUG_EDGE_LISTENER) {
\r
2404 System.out.println(" mapped hints: " + mappedElement.getHints());
\r
2405 System.out.println(" loaded hints: " + loaded.getHints());
\r
2407 updateMappedElement((Element) mappedElement, loaded);
\r
2410 // This element was already loaded.
\r
2411 // Just schedule an update some time
\r
2413 if (DebugPolicy.DEBUG_EDGE_LISTENER)
\r
2414 System.out.println("PREVIOUSLY LOADED EDGE UPDATED, scheduling update into the future");
\r
2415 offerGraphUpdate( edgeUpdater(element, loaded) );
\r
2421 void syncLoad(ReadGraph graph, EdgeResource connectionSegment, ConnectionInfo info, IDiagram diagram, IElement element) throws DatabaseException {
\r
2422 // Check that at least some data exists before continuing further.
\r
2423 if (!graph.hasStatement(connectionSegment.first()) && !graph.hasStatement(connectionSegment.second())) {
\r
2427 // Validate that both ends of the segment are
\r
2428 // part of the same connection before loading.
\r
2429 // This will happen for connections that are
\r
2430 // modified through splitting and joining of
\r
2431 // connection segments.
\r
2432 Resource c = ConnectionUtil.tryGetConnection(graph, connectionSegment);
\r
2434 // Ok, this segment is somehow invalid. Just don't load it.
\r
2435 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
\r
2436 System.out.println("Skipping edge " + connectionSegment + ". Both segment ends are not part of the same connection.");
\r
2440 if (!info.isValid()) {
\r
2441 // This edge must be somehow invalid, don't proceed with loading.
\r
2442 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
\r
2443 warning("Cannot load edge " + connectionSegment + ". ConnectionInfo " + info + " is invalid.", new DebugException("execution trace"));
\r
2447 Element edge = (Element) element;
\r
2448 edge.setHint(ElementHints.KEY_OBJECT, connectionSegment);
\r
2450 // connectionSegment resources may currently be in a different
\r
2451 // order than ConnectionInfo.firstEnd/secondEnd. Therefore the
\r
2452 // segment ends must be resolved here.
\r
2453 ConnectionSegmentEnd firstEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.first());
\r
2454 ConnectionSegmentEnd secondEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.second());
\r
2455 if (firstEnd == null || secondEnd == null) {
\r
2456 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
\r
2457 warning("End attachments for edge " + connectionSegment + " are unresolved: (" + firstEnd + "," + secondEnd + ")", new DebugException("execution trace"));
\r
2461 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
\r
2462 System.out.println("CONNECTION INFO: " + connectionSegment + " - " + info);
\r
2463 DesignatedTerminal firstTerminal = DiagramGraphUtil.findDesignatedTerminal(
\r
2464 graph, diagram, connectionSegment.first(), firstEnd);
\r
2465 DesignatedTerminal secondTerminal = DiagramGraphUtil.findDesignatedTerminal(
\r
2466 graph, diagram, connectionSegment.second(), secondEnd);
\r
2468 // Edges must be connected at both ends in order for edge loading to succeed.
\r
2469 String err = validateConnectivity(graph, connectionSegment, firstTerminal, secondTerminal);
\r
2470 if (err != null) {
\r
2471 // Stop loading edge if the connectivity cannot be completely resolved.
\r
2472 if (DebugPolicy.DEBUG_CONNECTION_LOAD)
\r
2473 warning(err, null);
\r
2477 // NOTICE: Layer information is loaded from the connection entity resource
\r
2478 // that is shared by all segments of the same connection.
\r
2479 ElementFactoryUtil.loadLayersForElement(graph, layerManager, diagram, edge, info.connection,
\r
2480 new AsyncProcedureAdapter<IElement>() {
\r
2482 public void exception(AsyncReadGraph graph, Throwable t) {
\r
2483 error("failed to load layers for connection segment", t);
\r
2487 edge.setHintWithoutNotification(KEY_CONNECTION_BEGIN_PLACEHOLDER, new PlaceholderConnection(
\r
2489 firstTerminal.element.getHint(ElementHints.KEY_OBJECT),
\r
2490 firstTerminal.terminal));
\r
2491 edge.setHintWithoutNotification(KEY_CONNECTION_END_PLACEHOLDER, new PlaceholderConnection(
\r
2493 secondTerminal.element.getHint(ElementHints.KEY_OBJECT),
\r
2494 secondTerminal.terminal));
\r
2496 IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);
\r
2497 if (modelingRules != null) {
\r
2498 ConnectionVisualsLoader loader = diagram.getHint(DiagramModelHints.KEY_CONNECTION_VISUALS_LOADER);
\r
2499 if (loader != null)
\r
2500 loader.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
\r
2502 DiagramGraphUtil.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
\r
2506 private String validateConnectivity(ReadGraph graph, EdgeResource edge,
\r
2507 DesignatedTerminal firstTerminal,
\r
2508 DesignatedTerminal secondTerminal)
\r
2509 throws DatabaseException {
\r
2510 boolean firstLoose = firstTerminal == null;
\r
2511 boolean secondLoose = secondTerminal == null;
\r
2512 boolean stray = firstLoose && secondLoose;
\r
2513 if (firstTerminal == null || secondTerminal == null) {
\r
2514 StringBuilder sb = new StringBuilder();
\r
2515 sb.append("encountered ");
\r
2516 sb.append(stray ? "stray" : "loose");
\r
2517 sb.append(" connection segment, ");
\r
2519 sb.append("first ");
\r
2521 sb.append("and ");
\r
2523 sb.append("second ");
\r
2524 sb.append("end disconnected: ");
\r
2525 sb.append(edge.toString(graph));
\r
2527 sb.append(edge.toString());
\r
2528 return sb.toString();
\r
2535 ConnectionSegmentAdapter connectionSegmentAdapter = new DefaultConnectionSegmentAdapter();
\r
2539 private static final Double DIAGRAM_UPDATE_DIAGRAM_PRIORITY = 1d;
\r
2540 private static final Double DIAGRAM_UPDATE_NODE_PRIORITY = 2d;
\r
2541 private static final Double DIAGRAM_UPDATE_CONNECTION_PRIORITY = 3d;
\r
2542 private static final Double DIAGRAM_UPDATE_EDGE_PRIORITY = 4d;
\r
2544 interface DiagramUpdater extends Runnable {
\r
2545 Double getPriority();
\r
2547 Comparator<DiagramUpdater> DIAGRAM_UPDATER_COMPARATOR = new Comparator<DiagramUpdater>() {
\r
2549 public int compare(DiagramUpdater o1, DiagramUpdater o2) {
\r
2550 return o1.getPriority().compareTo(o2.getPriority());
\r
2555 interface GraphUpdateReactor {
\r
2556 DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException;
\r
2559 static abstract class AbstractDiagramUpdater implements DiagramUpdater, GraphUpdateReactor {
\r
2560 protected final Double priority;
\r
2561 protected final String runnerName;
\r
2563 public AbstractDiagramUpdater(Double priority, String runnerName) {
\r
2564 if (priority == null)
\r
2565 throw new NullPointerException("null priority");
\r
2566 if (runnerName == null)
\r
2567 throw new NullPointerException("null runner name");
\r
2568 this.priority = priority;
\r
2569 this.runnerName = runnerName;
\r
2573 public Double getPriority() {
\r
2578 public AbstractDiagramUpdater graphUpdate(ReadGraph graph) {
\r
2583 public void run() {
\r
2584 Object task = Timing.BEGIN(runnerName);
\r
2589 protected void forDiagram() {
\r
2593 public String toString() {
\r
2594 return runnerName + "@" + System.identityHashCode(this) + " [" + priority + "]";
\r
2599 * @param content the new contents of the diagram, must not be
\r
2600 * <code>null</code>.
\r
2602 private GraphUpdateReactor diagramGraphUpdater(final DiagramContents content) {
\r
2603 if (content == null)
\r
2604 throw new NullPointerException("null diagram content");
\r
2606 return new GraphUpdateReactor() {
\r
2608 public String toString() {
\r
2609 return "DiagramGraphUpdater@" + System.identityHashCode(this);
\r
2613 public DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException {
\r
2614 // Never do anything here if the canvas has already been disposed.
\r
2615 if (!GraphToDiagramSynchronizer.this.isAlive())
\r
2618 // We must be prepared for the following changes in the diagram graph
\r
2620 // - the diagram has been completely removed
\r
2621 // - elements have been added
\r
2622 // - elements have been removed
\r
2624 // Element-specific changes are handled by the element query listeners:
\r
2625 // - elements have been modified
\r
2626 // - element position has changed
\r
2627 // - element class (e.g. image) has changed
\r
2629 diagramUpdateLock.lock();
\r
2631 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
\r
2632 System.out.println("In diagramGraphUpdater:");
\r
2634 // Find out what has changed since the last query.
\r
2635 Object task = Timing.BEGIN("diagramContentDifference");
\r
2636 DiagramContents lastContent = previousContent;
\r
2637 DiagramContentChanges changes = content.differenceFrom(previousContent);
\r
2638 previousContent = content;
\r
2640 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
\r
2641 System.out.println(" changes: " + changes);
\r
2643 // Bail out if there are no changes to react to.
\r
2644 if (changes.isEmpty())
\r
2647 // Load everything that needs to be loaded from the graph,
\r
2648 // but don't update the UI model in this thread yet.
\r
2649 task = Timing.BEGIN("updater.process");
\r
2650 GraphToDiagramUpdater updater = new GraphToDiagramUpdater(lastContent, content, changes);
\r
2651 GraphToDiagramSynchronizer.this.currentUpdater = updater;
\r
2653 updater.process(graph);
\r
2655 GraphToDiagramSynchronizer.this.currentUpdater = null;
\r
2659 if (updater.isEmpty())
\r
2662 // Return an updater that will update the UI run-time model.
\r
2663 return diagramUpdater(updater);
\r
2665 diagramUpdateLock.unlock();
\r
2671 DiagramUpdater diagramUpdater(final GraphToDiagramUpdater updater) {
\r
2672 return new AbstractDiagramUpdater(DIAGRAM_UPDATE_DIAGRAM_PRIORITY, "updateDiagram") {
\r
2674 protected void forDiagram() {
\r
2675 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
\r
2676 System.out.println("running diagram updater: " + this);
\r
2678 // DiagramUtils.testDiagram(diagram);
\r
2679 Set<IElement> dirty = new HashSet<IElement>();
\r
2681 Object task2 = Timing.BEGIN("Preprocess connection changes");
\r
2682 Map<ConnectionEntity, ConnectionChildren> connectionChangeData = new HashMap<ConnectionEntity, ConnectionChildren>(updater.changedConnectionEntities.size());
\r
2683 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
\r
2684 connectionChangeData.put(cd.impl, cd.impl.getConnectionChildren());
\r
2686 Timing.END(task2);
\r
2688 task2 = Timing.BEGIN("removeRouteGraphConnections");
\r
2689 for (IElement removedRouteGraphConnection : updater.removedRouteGraphConnections) {
\r
2690 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
\r
2691 System.out.println("removing route graph connection: " + removedRouteGraphConnection);
\r
2693 ConnectionEntity ce = removedRouteGraphConnection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
2696 Object connectionData = ElementUtils.getObject(removedRouteGraphConnection);
\r
2697 tempConnections.clear();
\r
2698 for (Connection conn : ce.getTerminalConnections(tempConnections)) {
\r
2699 ((Element) conn.node).removeHintWithoutNotification(new TerminalKeyOf(conn.terminal, connectionData, Connection.class));
\r
2700 // To be sure the view will be up-to-date, mark the node
\r
2701 // connected to the removed connection dirty.
\r
2702 dirty.add(conn.node);
\r
2705 Timing.END(task2);
\r
2707 task2 = Timing.BEGIN("removeBranchPoints");
\r
2708 for (IElement removed : updater.removedBranchPoints) {
\r
2709 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
\r
2710 System.out.println("removing branch point: " + removed);
\r
2712 unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
\r
2713 removeNodeTopologyHints((Element) removed);
\r
2715 IElement connection = ElementUtils.getParent(removed);
\r
2716 if (connection != null) {
\r
2717 dirty.add(connection);
\r
2720 Timing.END(task2);
\r
2722 task2 = Timing.BEGIN("removeConnectionSegments");
\r
2723 for (IElement removed : updater.removedConnectionSegments) {
\r
2724 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
\r
2725 System.out.println("removing segment: " + removed);
\r
2727 unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
\r
2728 removeEdgeTopologyHints((Element) removed);
\r
2730 IElement connection = ElementUtils.getParent(removed);
\r
2731 if (connection != null) {
\r
2732 dirty.add(connection);
\r
2735 Timing.END(task2);
\r
2737 task2 = Timing.BEGIN("removeElements");
\r
2738 for (IElement removed : updater.removedElements) {
\r
2739 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
\r
2740 System.out.println("removing element: " + removed);
\r
2742 removed.setHint(KEY_REMOVE_RELATIONSHIPS, Boolean.TRUE);
\r
2743 if (diagram.containsElement(removed)) {
\r
2744 diagram.removeElement(removed);
\r
2746 unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
\r
2747 removeNodeTopologyHints((Element) removed);
\r
2749 // No use marking removed elements dirty.
\r
2750 dirty.remove(removed);
\r
2752 Timing.END(task2);
\r
2754 // TODO: get rid of this
\r
2755 task2 = Timing.BEGIN("removeConnectionEntities");
\r
2756 for (Resource ce : updater.removedConnectionEntities) {
\r
2757 unmapConnection(ce);
\r
2759 Timing.END(task2);
\r
2761 task2 = Timing.BEGIN("setConnectionData");
\r
2762 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
\r
2763 cd.impl.setData(cd.segments, cd.branchPoints);
\r
2765 Timing.END(task2);
\r
2767 // TODO: get rid of this
\r
2768 task2 = Timing.BEGIN("addConnectionEntities");
\r
2769 for (Map.Entry<Resource, ConnectionEntityImpl> entry : updater.addedConnectionEntities
\r
2771 mapConnection(entry.getKey(), entry.getValue());
\r
2773 Timing.END(task2);
\r
2775 task2 = Timing.BEGIN("addBranchPoints");
\r
2776 for (IElement added : updater.addedBranchPoints) {
\r
2777 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
\r
2778 System.out.println("adding branch point: " + added);
\r
2780 mapElement(ElementUtils.getObject(added), added);
\r
2782 IElement connection = ElementUtils.getParent(added);
\r
2783 if (connection != null) {
\r
2784 dirty.add(connection);
\r
2787 Timing.END(task2);
\r
2789 // Add new elements at end of diagram, element order will be synchronized later.
\r
2790 task2 = Timing.BEGIN("addElements");
\r
2791 for(Resource r : updater.content.elements) {
\r
2792 IElement added = updater.addedElementMap.get(r);
\r
2793 if(added != null) {
\r
2794 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
\r
2795 System.out.println("adding element: " + added);
\r
2797 //Object task3 = BEGIN("mapElement " + added);
\r
2798 Object task3 = Timing.BEGIN("mapElement");
\r
2799 mapElement(added.getHint(ElementHints.KEY_OBJECT), added);
\r
2800 Timing.END(task3);
\r
2802 //task3 = BEGIN("addElement " + added);
\r
2803 task3 = Timing.BEGIN("addElement");
\r
2804 //System.out.println("diagram.addElement: " + added + " - " + diagram);
\r
2805 diagram.addElement(added);
\r
2807 Timing.END(task3);
\r
2810 Timing.END(task2);
\r
2812 // We've ensured that all nodes must have been and
\r
2813 // mapped before reaching this.
\r
2814 task2 = Timing.BEGIN("addConnectionSegments");
\r
2815 for (IElement added : updater.addedConnectionSegments) {
\r
2816 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
\r
2817 System.out.println("adding segment: " + added);
\r
2819 PlaceholderConnection cb = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_BEGIN_PLACEHOLDER);
\r
2820 PlaceholderConnection ce = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_END_PLACEHOLDER);
\r
2821 if (cb == null || ce == null) {
\r
2822 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
\r
2823 warning("ignoring connection segment " + added + ", connectivity was not resolved (begin=" + cb + ", end=" + ce +")", null);
\r
2827 mapElement(ElementUtils.getObject(added), added);
\r
2829 IElement beginNode = assertMappedElement(cb.node);
\r
2830 IElement endNode = assertMappedElement(ce.node);
\r
2833 connect(added, cb.end, beginNode, cb.terminal);
\r
2835 connect(added, ce.end, endNode, ce.terminal);
\r
2837 IElement connection = ElementUtils.getParent(added);
\r
2838 if (connection != null) {
\r
2839 dirty.add(connection);
\r
2842 Timing.END(task2);
\r
2844 // We've ensured that all nodes must have been and
\r
2845 // mapped before reaching this.
\r
2847 task2 = Timing.BEGIN("handle dirty RouteGraph connections");
\r
2848 for (IElement addedRouteGraphConnection : updater.addedRouteGraphConnectionMap.values()) {
\r
2849 updateDirtyRouteGraphConnection(addedRouteGraphConnection, dirty);
\r
2851 Timing.END(task2);
\r
2853 // Prevent memory leaks
\r
2854 tempConnections.clear();
\r
2856 // Make sure that the diagram element order matches that of the database.
\r
2857 final TObjectIntHashMap<IElement> orderMap = new TObjectIntHashMap<IElement>(2 * updater.content.elements.size());
\r
2859 for (Resource r : updater.content.elements) {
\r
2860 IElement e = getMappedElement(r);
\r
2862 orderMap.put(e, i);
\r
2865 diagram.sort(new Comparator<IElement>() {
\r
2867 public int compare(IElement e1, IElement e2) {
\r
2868 int o1 = orderMap.get(e1);
\r
2869 int o2 = orderMap.get(e2);
\r
2874 // TODO: consider removing this. The whole thing should work without it and
\r
2875 // this "fix" will only be hiding the real problems.
\r
2876 task2 = Timing.BEGIN("fixChangedConnections");
\r
2877 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
\r
2880 Timing.END(task2);
\r
2882 task2 = Timing.BEGIN("validateAndFix");
\r
2883 DiagramUtils.validateAndFix(diagram, dirty);
\r
2884 Timing.END(task2);
\r
2886 // This will fire connection entity change listeners
\r
2887 task2 = Timing.BEGIN("Postprocess connection changes");
\r
2888 for (ConnectionData cd : updater.changedConnectionEntities.values()) {
\r
2889 ConnectionChildren oldChildren = connectionChangeData.get(cd.impl);
\r
2890 if (oldChildren != null) {
\r
2891 ConnectionChildren currentChildren = cd.impl.getConnectionChildren();
\r
2892 cd.impl.fireListener(oldChildren, currentChildren);
\r
2895 Timing.END(task2);
\r
2897 task2 = Timing.BEGIN("setDirty");
\r
2898 for (IElement e : dirty) {
\r
2899 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
\r
2900 System.out.println("MARKING ELEMENT DIRTY: " + e);
\r
2901 e.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
\r
2903 Timing.END(task2);
\r
2905 // Signal about possible changes in the z-order of diagram elements.
\r
2906 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
\r
2907 System.out.println("MARKING DIAGRAM DIRTY: " + diagram);
\r
2908 diagram.setHint(Hints.KEY_DIRTY, Hints.VALUE_Z_ORDER_CHANGED);
\r
2910 // Mark the updater as "processed".
\r
2913 // Inform listeners that the diagram has been updated.
\r
2914 diagram.setHint(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, Boolean.TRUE);
\r
2920 * @param connection
\r
2923 private void updateDirtyRouteGraphConnection(IElement connection, Set<IElement> dirtySet) {
\r
2924 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
\r
2925 System.out.println("updating dirty route graph connection: " + connection);
\r
2927 ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
2931 tempConnections.clear();
\r
2932 Object connectionData = ElementUtils.getObject(connection);
\r
2933 for (Connection conn : ce.getTerminalConnections(tempConnections)) {
\r
2934 ((Element) conn.node).setHintWithoutNotification(
\r
2935 new TerminalKeyOf(conn.terminal, connectionData, Connection.class),
\r
2937 if (dirtySet != null)
\r
2938 dirtySet.add(conn.node);
\r
2941 // Prevent memory leaks.
\r
2942 tempConnections.clear();
\r
2945 abstract class ElementUpdater extends AbstractDiagramUpdater {
\r
2946 private final IElement newElement;
\r
2948 public ElementUpdater(Double priority, String runnerName, IElement newElement) {
\r
2949 super(priority, runnerName);
\r
2950 if (newElement == null)
\r
2951 throw new NullPointerException("null element");
\r
2952 this.newElement = newElement;
\r
2956 public String toString() {
\r
2957 return super.toString() + "[" + newElement + "]";
\r
2961 public void run() {
\r
2962 // System.out.println("ElementUpdateRunner new=" + newElement);
\r
2963 Object elementResource = newElement.getHint(ElementHints.KEY_OBJECT);
\r
2964 // System.out.println("ElementUpdateRunner res=" + elementResource);
\r
2965 final Element mappedElement = (Element) getMappedElement(elementResource);
\r
2966 // System.out.println("ElementUpdateRunner mapped=" + mappedElement);
\r
2967 if (mappedElement == null) {
\r
2968 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE) {
\r
2969 System.out.println("SKIP DIAGRAM UPDATE " + this + " for element resource " + elementResource + ", no mapped element (newElement=" + newElement + ")");
\r
2971 // Indicates the element has been removed from the graph.
\r
2975 Object task = Timing.BEGIN(runnerName);
\r
2976 forMappedElement(mappedElement);
\r
2980 protected abstract void forMappedElement(Element mappedElement);
\r
2983 ElementUpdater nodeUpdater(final Resource resource, final IElement newElement) {
\r
2985 return new ElementUpdater(DIAGRAM_UPDATE_NODE_PRIORITY, "updateNode", newElement) {
\r
2987 Collection<Terminal> getTerminals(IElement e) {
\r
2988 Collection<Terminal> ts = Collections.emptyList();
\r
2989 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
\r
2991 ts = new ArrayList<Terminal>();
\r
2992 tt.getTerminals(newElement, ts);
\r
2998 protected void forMappedElement(final Element mappedElement) {
\r
2999 if (DebugPolicy.DEBUG_NODE_UPDATE)
\r
3000 System.out.println("running node updater: " + this + " - new element: " + newElement);
\r
3002 // Newly loaded node elements NEVER contain topology-related
\r
3003 // hints, i.e. TerminalKeyOf hints. Instead all connections are
\r
3004 // actually set into element hints when connection edges are
\r
3007 Collection<Terminal> oldTerminals = getTerminals(mappedElement);
\r
3008 Collection<Terminal> newTerminals = getTerminals(newElement);
\r
3009 if (!oldTerminals.equals(newTerminals)) {
\r
3010 // Okay, there are differences in the terminals. Need to fix
\r
3011 // the TerminalKeyOf hint values to use the new terminal
\r
3012 // instances when correspondences exist.
\r
3014 // If there is no correspondence for an old terminal, we
\r
3015 // are simply forced to remove the hints related to this
\r
3018 Map<Terminal, Terminal> newTerminalMap = new HashMap<Terminal, Terminal>(newTerminals.size());
\r
3019 for (Terminal t : newTerminals) {
\r
3020 newTerminalMap.put(t, t);
\r
3023 for (Map.Entry<TerminalKeyOf, Object> entry : mappedElement.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
\r
3024 TerminalKeyOf key = entry.getKey();
\r
3025 Connection c = (Connection) entry.getValue();
\r
3026 if (c.node == mappedElement) {
\r
3027 Terminal newTerminal = newTerminalMap.get(c.terminal);
\r
3028 if (newTerminal != null) {
\r
3029 c = new Connection(c.edge, c.end, c.node, newTerminal);
\r
3030 ((Element) c.edge).setHintWithoutNotification(EndKeyOf.get(c.end), c);
\r
3032 mappedElement.removeHintWithoutNotification(key);
\r
3038 updateMappedElement(mappedElement, newElement);
\r
3039 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
\r
3044 ElementUpdater connectionUpdater(final Object data, final IElement newElement) {
\r
3045 return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateConnection", newElement) {
\r
3047 public void forMappedElement(Element mappedElement) {
\r
3048 if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
\r
3049 System.out.println("running connection updater: " + this + " - new element: " + newElement
\r
3050 + " with data " + data);
\r
3052 // This is kept up-to-date by GDS, make sure not to overwrite it
\r
3053 // from the mapped element.
\r
3054 newElement.removeHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
3056 updateMappedElement(mappedElement, newElement);
\r
3057 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
\r
3062 ElementUpdater edgeUpdater(final Object data, final IElement newElement) {
\r
3063 return new ElementUpdater(DIAGRAM_UPDATE_EDGE_PRIORITY, "updateEdge", newElement) {
\r
3065 public void forMappedElement(Element mappedElement) {
\r
3066 if (DebugPolicy.DEBUG_EDGE_UPDATE)
\r
3067 System.out.println("running edge updater: " + this + " - new element: " + newElement
\r
3068 + " with data " + data);
\r
3070 updateMappedElement(mappedElement, newElement);
\r
3071 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
\r
3076 ElementUpdater routeGraphConnectionUpdater(final Object data, final IElement newElement, final Set<Object> dirtyNodes) {
\r
3077 return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateRouteGraphConnection", newElement) {
\r
3079 public void forMappedElement(Element mappedElement) {
\r
3080 if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
\r
3081 System.out.println("running route graph connection updater: " + this + " - new element: " + newElement
\r
3082 + " with data " + data);
\r
3084 // Remove all TerminalKeyOf hints from nodes that were
\r
3085 // previously connected to the connection (mappedElement)
\r
3086 // before updating mappedElement with new topology information.
\r
3088 for (Object dirtyNodeObject : dirtyNodes) {
\r
3089 Element dirtyNode = (Element) getMappedElement(dirtyNodeObject);
\r
3090 if (dirtyNode == null)
\r
3092 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
\r
3093 System.out.println("preparing node with dirty connectivity: " + dirtyNode);
\r
3095 for (Map.Entry<TerminalKeyOf, Object> entry : dirtyNode.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
\r
3096 Connection conn = (Connection) entry.getValue();
\r
3097 Object connectionNode = conn.edge.getHint(ElementHints.KEY_OBJECT);
\r
3098 if (data.equals(connectionNode)) {
\r
3099 dirtyNode.removeHintWithoutNotification(entry.getKey());
\r
3104 // Update connection information, including topology
\r
3105 updateMappedElement(mappedElement, newElement);
\r
3107 // Reinstall TerminalKeyOf hints into nodes that are now connected
\r
3108 // to mappedElement to keep diagram run-time model properly in sync
\r
3109 // with the database.
\r
3110 updateDirtyRouteGraphConnection(mappedElement, null);
\r
3112 // TODO: should mark dirty nodes' scene graph dirty ?
\r
3114 mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
\r
3120 * Copies hints from <code>newElement</code> to <code>mappedElement</code>
\r
3121 * asserting some validity conditions at the same time.
\r
3123 * @param mappedElement
\r
3124 * @param newElement
\r
3126 static void updateMappedElement(Element mappedElement, IElement newElement) {
\r
3127 if (mappedElement == newElement)
\r
3128 // Can't update anything if the two elements are the same.
\r
3131 ElementClass oldClass = mappedElement.getElementClass();
\r
3132 ElementClass newClass = newElement.getElementClass();
\r
3134 Object mappedData = mappedElement.getHint(ElementHints.KEY_OBJECT);
\r
3135 Object newData = newElement.getHint(ElementHints.KEY_OBJECT);
\r
3137 assert mappedData != null;
\r
3138 assert newData != null;
\r
3139 assert mappedData.equals(newData);
\r
3141 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
\r
3142 System.out.println("Updating mapped element, setting hints\n from: " + newElement + "\n into: " + mappedElement);
\r
3145 // TODO: consider if this equals check is a waste of time or does it pay
\r
3146 // off due to having to reinitialize per-class caches for the new
\r
3147 // ElementClass that are constructed on the fly?
\r
3148 if (!newClass.equals(oldClass)) {
\r
3149 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
\r
3150 System.out.println(" old element class: " + oldClass);
\r
3151 System.out.println(" new element class: " + newClass);
\r
3153 mappedElement.setElementClass(newClass);
\r
3156 // Tuukka@2010-02-19: replaced with notifications for making
\r
3157 // the graph synchronizer more transparent to the client.
\r
3159 // Hint notifications will not work when this is used.
\r
3160 //mappedElement.setHintsWithoutNotification(newElement.getHints());
\r
3162 Map<DiscardableKey, Object> discardableHints = mappedElement.getHintsOfClass(DiscardableKey.class);
\r
3164 // Set all hints from newElement to mappedElement.
\r
3165 // Leave any hints in mappedElement but not in newElement as is.
\r
3166 Map<Key, Object> hints = newElement.getHints();
\r
3167 Map<Key, Object> newHints = new HashMap<Key, Object>();
\r
3168 for (Map.Entry<Key, Object> entry : hints.entrySet()) {
\r
3169 Key key = entry.getKey();
\r
3170 Object newValue = entry.getValue();
\r
3171 Object oldValue = mappedElement.getHint(key);
\r
3172 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE_DETAIL) {
\r
3173 System.out.println(" hint " + key + " compare values: " + oldValue + " -> " + newValue);
\r
3175 if (!newValue.equals(oldValue)) {
\r
3176 newHints.put(key, newValue);
\r
3177 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
\r
3178 System.out.format(" %-42s : %64s -> %-64s\n", key, oldValue, newValue);
\r
3181 // If the hint value has not changed but the hint still exists
\r
3182 // we don't need to discard it even if it is considered
\r
3184 discardableHints.remove(key);
\r
3188 // Set all hints at once and send notifications after setting the values.
\r
3189 if (!discardableHints.isEmpty()) {
\r
3190 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE)
\r
3191 System.out.println("Discarding " + discardableHints.size() + " discardable hints:\n " + discardableHints);
\r
3192 mappedElement.removeHints(discardableHints.keySet());
\r
3194 if (!newHints.isEmpty()) {
\r
3195 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
\r
3196 System.out.println("Updating mapped element, setting new hints:\n\t"
\r
3197 + EString.implode(newHints.entrySet(), "\n\t") + "\nto replace old hints\n\t"
\r
3198 + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
\r
3200 mappedElement.setHints(newHints);
\r
3202 if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
\r
3203 System.out.println("All hints after update:\n\t"
\r
3204 + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
\r
3208 class TransactionListener extends SessionEventListenerAdapter {
\r
3211 public void writeTransactionStarted() {
\r
3212 startTime = System.nanoTime();
\r
3213 if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
\r
3214 System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionStarted");
\r
3215 inWriteTransaction.set(true);
\r
3218 public void writeTransactionFinished() {
\r
3219 long endTime = System.nanoTime();
\r
3220 if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
\r
3221 System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionFinished: " + (endTime - startTime)*1e-6 + " ms");
\r
3222 inWriteTransaction.set(false);
\r
3223 scheduleGraphUpdates();
\r
3227 Object graphUpdateLock = new Object();
\r
3228 TransactionListener sessionListener = null;
\r
3229 AtomicBoolean inWriteTransaction = new AtomicBoolean(false);
\r
3230 AtomicBoolean graphUpdateRequestScheduled = new AtomicBoolean(false);
\r
3231 List<GraphUpdateReactor> queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();
\r
3233 private void offerGraphUpdate(GraphUpdateReactor update) {
\r
3234 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
\r
3235 System.out.println("offerGraphUpdate: " + update);
\r
3236 boolean inWrite = inWriteTransaction.get();
\r
3237 synchronized (graphUpdateLock) {
\r
3238 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
\r
3239 System.out.println("queueing graph update: " + update);
\r
3240 queuedGraphUpdates.add(update);
\r
3243 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
\r
3244 System.out.println("scheduling queued graph update immediately: " + update);
\r
3245 scheduleGraphUpdates();
\r
3249 private Collection<GraphUpdateReactor> scrubGraphUpdates() {
\r
3250 synchronized (graphUpdateLock) {
\r
3251 if (queuedGraphUpdates.isEmpty())
\r
3252 return Collections.emptyList();
\r
3253 final List<GraphUpdateReactor> updates = queuedGraphUpdates;
\r
3254 queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();
\r
3259 private void scheduleGraphUpdates() {
\r
3260 synchronized (graphUpdateLock) {
\r
3261 if (queuedGraphUpdates.isEmpty())
\r
3263 if (!graphUpdateRequestScheduled.compareAndSet(false, true))
\r
3267 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
\r
3268 System.out.println("scheduling " + queuedGraphUpdates.size() + " queued graph updates with ");
\r
3270 session.asyncRequest(new ReadRequest() {
\r
3272 public void run(final ReadGraph graph) throws DatabaseException {
\r
3273 Collection<GraphUpdateReactor> updates;
\r
3274 synchronized (graphUpdateLock) {
\r
3275 graphUpdateRequestScheduled.set(false);
\r
3276 updates = scrubGraphUpdates();
\r
3279 if (!GraphToDiagramSynchronizer.this.isAlive())
\r
3282 processGraphUpdates(graph, updates);
\r
3284 }, new ProcedureAdapter<Object>() {
\r
3286 public void exception(Throwable t) {
\r
3292 private void processGraphUpdates(ReadGraph graph, final Collection<GraphUpdateReactor> graphUpdates)
\r
3293 throws DatabaseException {
\r
3294 final List<DiagramUpdater> diagramUpdates = new ArrayList<DiagramUpdater>(graphUpdates.size());
\r
3296 // Run GraphUpdaters and gather DiagramUpdaters.
\r
3297 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
\r
3298 System.out.println("Running GRAPH updates: " + graphUpdates);
\r
3299 for (GraphUpdateReactor graphUpdate : graphUpdates) {
\r
3300 DiagramUpdater diagramUpdate = graphUpdate.graphUpdate(graph);
\r
3301 if (diagramUpdate != null) {
\r
3302 if (DebugPolicy.DEBUG_GRAPH_UPDATE)
\r
3303 System.out.println(graphUpdate + " => " + diagramUpdate);
\r
3304 diagramUpdates.add(diagramUpdate);
\r
3308 if (diagramUpdates.isEmpty())
\r
3311 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
\r
3312 System.out.println("Diagram updates: " + diagramUpdates);
\r
3313 Collections.sort(diagramUpdates, DiagramUpdater.DIAGRAM_UPDATER_COMPARATOR);
\r
3314 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
\r
3315 System.out.println("Sorted diagram updates: " + diagramUpdates);
\r
3317 ThreadUtils.asyncExec(canvas.getThreadAccess(), new StateRunnable() {
\r
3319 public void run() {
\r
3320 if (GraphToDiagramSynchronizer.this.isAlive() && getState() != State.DISPOSED)
\r
3321 safeRunInState(State.UPDATING_DIAGRAM, this);
\r
3325 public void execute() throws InvocationTargetException {
\r
3326 // Block out diagram write transactions.
\r
3327 DiagramUtils.inDiagramTransaction(diagram, TransactionType.READ, new Runnable() {
\r
3329 public void run() {
\r
3330 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
\r
3331 System.out.println("Running DIAGRAM updates: " + diagramUpdates);
\r
3332 for (DiagramUpdater update : diagramUpdates) {
\r
3333 if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
\r
3334 System.out.println("Running DIAGRAM update: " + update);
\r
3345 private void attachSessionListener(Session session) {
\r
3346 SessionEventSupport support = session.peekService(SessionEventSupport.class);
\r
3347 if (support != null) {
\r
3348 sessionListener = new TransactionListener();
\r
3349 support.addListener(sessionListener);
\r
3353 private void detachSessionListener() {
\r
3354 if (sessionListener != null) {
\r
3355 session.getService(SessionEventSupport.class).removeListener(sessionListener);
\r
3356 sessionListener = null;
\r
3361 // ------------------------------------------------------------------------
\r
3362 // GRAPH TO DIAGRAM SYNCHRONIZATION LOGIC END
\r
3363 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\r
3365 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
3366 // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
\r
3367 // This does not try to synchronize anything back into the graph.
\r
3368 // ------------------------------------------------------------------------
\r
3370 IHintListener elementHintValidator = new HintListenerAdapter() {
\r
3372 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
\r
3373 if (!(sender instanceof Element))
\r
3374 throw new IllegalStateException("invalid sender: " + sender);
\r
3375 Element e = (Element) sender;
\r
3376 if (newValue != null) {
\r
3377 if (key instanceof TerminalKeyOf) {
\r
3378 Connection c = (Connection) newValue;
\r
3380 throw new IllegalStateException("TerminalKeyOf hint of node " + e + " refers to a different node " + c.node + ". Should be the same.");
\r
3381 Object edgeObject = ElementUtils.getObject(c.edge);
\r
3382 if (!(edgeObject instanceof EdgeResource))
\r
3383 throw new IllegalStateException("EndKeyOf hint of edge " + c.edge + " refers contains an invalid object: " + edgeObject);
\r
3384 } else if (key instanceof EndKeyOf) {
\r
3385 Connection c = (Connection) newValue;
\r
3387 throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers to a different edge " + c.edge + ". Should be the same.");
\r
3388 Object edgeObject = ElementUtils.getObject(c.edge);
\r
3389 if (!(edgeObject instanceof EdgeResource))
\r
3390 throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers contains an invalid object: " + edgeObject);
\r
3396 class DiagramListener implements CompositionListener, CompositionVetoListener {
\r
3398 public boolean beforeElementAdded(IDiagram d, IElement e) {
\r
3399 // Make sure that MutatedElements NEVER get added to the diagram.
\r
3400 if (d == diagram) {
\r
3401 if (!(e instanceof Element)) {
\r
3402 // THIS IS NOT GOOD!
\r
3403 error("Attempting to add another implementation of IElement besides Element (=" + e.getElementClass().getClass().getName() + ") to the synchronized diagram which means that there is a bug somewhere! See stack trace to find out who is doing this!", new Exception("stacktrace"));
\r
3404 System.err.println("Attempting to add another implementation of IElement besides Element (=" + e.getElementClass().getClass().getName() + ") to the synchronized diagram which means that there is a bug somewhere! See Error Log.");
\r
3408 // Perform sanity checks that might veto the element addition.
\r
3409 boolean pass = true;
\r
3411 // Check that all elements added to the diagram are adaptable to Resource
\r
3412 ElementClass ec = e.getElementClass();
\r
3413 Resource resource = ElementUtils.adapt(ec, Resource.class);
\r
3414 if (resource == null) {
\r
3416 new Exception("Attempted to add an element to the diagram that is not adaptable to Resource: " + e + ", class: " + ec).printStackTrace();
\r
3419 // Sanity check connection hints
\r
3420 for (Map.Entry<TerminalKeyOf, Object> entry : e.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
\r
3421 Connection c = (Connection) entry.getValue();
\r
3422 Object edgeObject = ElementUtils.getObject(c.edge);
\r
3423 if (e != c.node) {
\r
3424 System.err.println("Invalid node in TerminalKeyOf hint: " + entry.getKey() + "=" + entry.getValue());
\r
3425 System.err.println("\tconnection.edge=" + c.edge);
\r
3426 System.err.println("\tconnection.node=" + c.node);
\r
3427 System.err.println("\tconnection.end=" + c.end);
\r
3428 System.err.println("\telement=" + e);
\r
3429 System.err.println("\telement class=" + e.getElementClass());
\r
3432 if (!(edgeObject instanceof EdgeResource)) {
\r
3433 System.err.println("Invalid object in TerminalKeyOf hint edge: " + entry.getKey() + "=" + entry.getValue());
\r
3434 System.err.println("\tconnection.edge=" + c.edge);
\r
3435 System.err.println("\tconnection.node=" + c.node);
\r
3436 System.err.println("\tconnection.end=" + c.end);
\r
3437 System.err.println("\telement=" + e);
\r
3438 System.err.println("\telement class=" + e.getElementClass());
\r
3449 public boolean beforeElementRemoved(IDiagram d, IElement e) {
\r
3450 // Never veto diagram changes.
\r
3455 public void onElementAdded(IDiagram d, IElement e) {
\r
3456 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
\r
3457 System.out.println("[" + d + "] element added: " + e);
\r
3459 if (USE_ELEMENT_VALIDATING_LISTENERS)
\r
3460 e.addHintListener(elementHintValidator);
\r
3464 public void onElementRemoved(IDiagram d, IElement e) {
\r
3465 if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
\r
3466 System.out.println("[" + d + "] element removed: " + e);
\r
3468 if (USE_ELEMENT_VALIDATING_LISTENERS)
\r
3469 e.removeHintListener(elementHintValidator);
\r
3471 if (e.containsHint(KEY_REMOVE_RELATIONSHIPS))
\r
3472 relationshipHandler.denyAll(diagram, e);
\r
3476 DiagramListener diagramListener = new DiagramListener();
\r
3478 static void removeNodeTopologyHints(Element node) {
\r
3479 Set<TerminalKeyOf> terminalKeys = node.getHintsOfClass(TerminalKeyOf.class).keySet();
\r
3480 if (!terminalKeys.isEmpty()) {
\r
3481 removeNodeTopologyHints(node, terminalKeys);
\r
3485 static void removeNodeTopologyHints(Element node, Collection<TerminalKeyOf> terminalKeys) {
\r
3486 for (TerminalKeyOf key : terminalKeys) {
\r
3487 Connection c = node.removeHintWithoutNotification(key);
\r
3489 removeEdgeTopologyHints((Element) c.edge);
\r
3494 static void removeEdgeTopologyHints(Element edge) {
\r
3495 Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
\r
3496 for (EndKeyOf key : EndKeyOf.KEYS) {
\r
3497 Connection c = edge.removeHintWithoutNotification(key);
\r
3499 ((Element) c.node).removeHintWithoutNotification(new TerminalKeyOf(c.terminal, edgeData, Connection.class));
\r
3504 // ------------------------------------------------------------------------
\r
3505 // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
\r
3506 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\r
3508 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
3509 // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC BEGIN
\r
3510 // ------------------------------------------------------------------------
\r
3512 void adaptDiagramClass(AsyncReadGraph graph, Resource diagram, final AsyncProcedure<DiagramClass> procedure) {
\r
3513 graph.forAdapted(diagram, DiagramClass.class, new AsyncProcedure<DiagramClass>() {
\r
3515 public void exception(AsyncReadGraph graph, Throwable throwable) {
\r
3516 procedure.exception(graph, throwable);
\r
3520 public void execute(AsyncReadGraph graph, DiagramClass dc) {
\r
3521 // To move TopologyImpl out of here, we need a separate
\r
3522 // DiagramClassFactory that takes a canvas context as an argument.
\r
3523 // DataElementMapImpl, ElementFactoryImpl and diagramLifeCycle can
\r
3524 // safely stay here.
\r
3525 procedure.execute(graph, dc.newClassWith(
\r
3526 // This handler takes care of the topology of the diagram model.
\r
3527 // It sets and fixes element hints related to describing the
\r
3528 // connectivity of elements.
\r
3530 // TODO: not quite sure whether this can prove itself useful or not.
\r
3532 // This map provides a bidirectional mapping between
\r
3533 // IElement and back-end objects.
\r
3535 // This handler provides a facility to adapt an element class
\r
3536 // to work properly with a diagram synchronized using this
\r
3537 // GraphToDiagramSynchronizer.
\r
3538 substituteElementClass,
\r
3539 // These handlers provide a way to create simple identified
\r
3540 // uni- and bidirectional relationships between any diagram
\r
3541 // objects/elements.
\r
3542 relationshipHandler));
\r
3547 static Connection connect(IElement edge, EdgeEnd end, IElement element, Terminal terminal) {
\r
3548 Connection c = new Connection(edge, end, element, terminal);
\r
3550 Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
\r
3551 if (DebugPolicy.DEBUG_CONNECTION) {
\r
3552 System.out.println("[connect](edge=" + edge + ", edgeData=" + edgeData + ", end=" + end + ", element="
\r
3553 + element + ", terminal=" + terminal + ")");
\r
3556 TerminalKeyOf key = new TerminalKeyOf(terminal, edgeData, Connection.class);
\r
3557 element.setHint(key, c);
\r
3559 EndKeyOf key2 = EndKeyOf.get(end);
\r
3560 edge.setHint(key2, c);
\r
3565 static class ElementFactoryImpl implements ElementFactory {
\r
3567 public IElement spawnNew(ElementClass clazz) {
\r
3568 IElement e = Element.spawnNew(clazz);
\r
3573 ElementFactoryImpl elementFactory = new ElementFactoryImpl();
\r
3575 public static final Object FIRST_TIME = new Object() {
\r
3577 public String toString() {
\r
3578 return "FIRST_TIME";
\r
3584 * A base for all listeners of graph requests performed internally by
\r
3585 * GraphToDiagramSynchronizer.
\r
3587 * @param <T> type of stored data element
\r
3588 * @param <Result> query result type
\r
3590 abstract class BaseListener<T, Result> implements AsyncListener<Result> {
\r
3592 protected final T data;
\r
3594 private Object oldResult = FIRST_TIME;
\r
3596 protected boolean disposed = false;
\r
3598 final ICanvasContext canvas;
\r
3600 public BaseListener(T data) {
\r
3601 this.canvas = GraphToDiagramSynchronizer.this.canvas;
\r
3606 public void exception(AsyncReadGraph graph, Throwable throwable) {
\r
3607 // Exceptions are always expected to mean that the listener should
\r
3608 // be considered disposed once a query fails.
\r
3612 abstract void execute(AsyncReadGraph graph, Object oldResult, Object newResult);
\r
3615 public void execute(AsyncReadGraph graph, Result result) {
\r
3616 if (DebugPolicy.DEBUG_LISTENER_BASE)
\r
3617 System.out.println("BaseListener: " + result);
\r
3620 if (DebugPolicy.DEBUG_LISTENER_BASE)
\r
3621 System.out.println("BaseListener: execute invoked although listener is disposed!");
\r
3625 // A null result will permanently mark this listener disposed!
\r
3626 if (result == null) {
\r
3628 if (DebugPolicy.DEBUG_LISTENER_BASE)
\r
3629 System.out.println(this + " null result, listener marked disposed");
\r
3632 if (oldResult == FIRST_TIME) {
\r
3633 oldResult = result;
\r
3634 if (DebugPolicy.DEBUG_LISTENER_BASE)
\r
3635 System.out.println(this + " first result computed: " + result);
\r
3637 if (DebugPolicy.DEBUG_LISTENER_BASE)
\r
3638 System.out.println(this + " result changed from '" + oldResult + "' to '" + result + "'");
\r
3640 execute(graph, oldResult, result);
\r
3642 oldResult = result;
\r
3648 public boolean isDisposed() {
\r
3652 boolean alive = isAlive();
\r
3653 //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", isAlive=" + alive);
\r
3656 // If a mapping no longer exists for this element, dispose of this
\r
3658 //IElement e = getMappedElement(resource);
\r
3659 //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", element=" + e);
\r
3660 //return e == null;
\r
3665 // protected void finalize() throws Throwable {
\r
3666 // System.out.println("finalize listener: " + this);
\r
3667 // super.finalize();
\r
3671 class DiagramClassRequest extends BaseRequest2<Resource, DiagramClass> {
\r
3672 public DiagramClassRequest(Resource resource) {
\r
3673 super(GraphToDiagramSynchronizer.this.canvas, resource);
\r
3677 public void perform(AsyncReadGraph graph, AsyncProcedure<DiagramClass> procedure) {
\r
3678 adaptDiagramClass(graph, data, procedure);
\r
3682 public class DiagramContentListener extends BaseListener<Resource, DiagramContents> {
\r
3684 public DiagramContentListener(Resource resource) {
\r
3689 public void execute(final AsyncReadGraph graph, Object oldResult, Object newResult) {
\r
3690 final DiagramContents newContent = (newResult == null) ? new DiagramContents()
\r
3691 : (DiagramContents) newResult;
\r
3693 // diagramGraphUpdater is called synchronously during
\r
3694 // loading. The first result will not get updated through
\r
3695 // this listener but through loadDiagram.
\r
3697 if (DebugPolicy.DISABLE_DIAGRAM_UPDATES) {
\r
3698 System.out.println("Skipped diagram content update: " + newResult);
\r
3702 if (DebugPolicy.DEBUG_DIAGRAM_LISTENER)
\r
3703 System.out.println("diagram contents changed: " + oldResult + " => " + newResult);
\r
3705 offerGraphUpdate( diagramGraphUpdater(newContent) );
\r
3709 public boolean isDisposed() {
\r
3710 return !isAlive();
\r
3714 public void exception(AsyncReadGraph graph, Throwable t) {
\r
3715 super.exception(graph, t);
\r
3716 error("DiagramContentRequest failed", t);
\r
3720 // ------------------------------------------------------------------------
\r
3721 // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC END
\r
3722 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\r
3724 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
3725 // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER BEGIN
\r
3726 // ------------------------------------------------------------------------
\r
3728 static class TopologyImpl implements Topology {
\r
3731 public Connection getConnection(IElement edge, EdgeEnd end) {
\r
3732 Key key = EndKeyOf.get(end);
\r
3733 Connection c = edge.getHint(key);
\r
3740 public void getConnections(IElement node, Terminal terminal, Collection<Connection> connections) {
\r
3741 // IDiagram d = ElementUtils.getDiagram(node);
\r
3742 for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
\r
3743 // First check that the terminal matches.
\r
3744 TerminalKeyOf key = entry.getKey();
\r
3745 if (!key.getTerminal().equals(terminal))
\r
3748 Connection c = (Connection) entry.getValue();
\r
3750 connections.add(c);
\r
3756 public void connect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
\r
3757 if (node != null && terminal != null)
\r
3758 GraphToDiagramSynchronizer.connect(edge, end, node, terminal);
\r
3760 if (DebugPolicy.DEBUG_CONNECTION) {
\r
3761 if (end == EdgeEnd.Begin)
\r
3762 System.out.println("Connection started from: " + edge + ", " + end + ", " + node + ", " + terminal);
\r
3764 System.out.println("Creating connection to: " + edge + ", " + end + ", " + node + ", " + terminal);
\r
3769 public void disconnect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
\r
3770 EndKeyOf edgeKey = EndKeyOf.get(end);
\r
3771 Connection c = edge.getHint(edgeKey);
\r
3773 throw new UnsupportedOperationException("cannot disconnect, no Connection in edge " + edge);
\r
3775 for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
\r
3776 Connection cc = (Connection) entry.getValue();
\r
3778 node.removeHint(entry.getKey());
\r
3779 edge.removeHint(edgeKey);
\r
3784 throw new UnsupportedOperationException("cannot disconnect, no connection between found between edge "
\r
3785 + edge + " and node " + node);
\r
3789 Topology diagramTopology = new TopologyImpl();
\r
3791 // ------------------------------------------------------------------------
\r
3792 // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER END
\r
3793 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\r
3795 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
3796 // DIAGRAM OBJECT RELATIONSHIP HANDLER BEGIN
\r
3797 // ------------------------------------------------------------------------
\r
3799 RelationshipHandler relationshipHandler = new RelationshipHandler() {
\r
3801 AssociativeMap map = new AssociativeMap(Associativity.of(true, false, false));
\r
3803 Object getPossibleObjectOrElement(Object o) {
\r
3804 if (o instanceof IElement) {
\r
3805 IElement e = (IElement) o;
\r
3806 Object oo = e.getHint(ElementHints.KEY_OBJECT);
\r
3807 return oo != null ? oo : e;
\r
3812 IElement getElement(Object o) {
\r
3813 if (o instanceof IElement)
\r
3814 return (IElement) o;
\r
3815 return getMappedElement(o);
\r
3819 public void claim(IDiagram diagram, Object subject,
\r
3820 Relationship predicate, Object object) {
\r
3821 Object sd = getPossibleObjectOrElement(subject);
\r
3822 Object od = getPossibleObjectOrElement(object);
\r
3824 Collection<Tuple> ts = null;
\r
3825 Relationship inverse = predicate.getInverse();
\r
3826 if (inverse != null)
\r
3827 ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od, inverse, sd));
\r
3829 ts = Collections.singletonList(new Tuple(sd, predicate, od));
\r
3831 synchronized (this) {
\r
3835 if (DebugPolicy.DEBUG_RELATIONSHIP) {
\r
3836 new Exception().printStackTrace();
\r
3837 System.out.println("Claimed relationships:");
\r
3838 for (Tuple t : ts)
\r
3839 System.out.println("\t" + t);
\r
3843 private void doDeny(IDiagram diagram, Object subject,
\r
3844 Relationship predicate, Object object) {
\r
3845 Object sd = getPossibleObjectOrElement(subject);
\r
3846 Object od = getPossibleObjectOrElement(object);
\r
3847 if (sd == subject || od == object) {
\r
3849 .println("WARNING: denying relationship '"
\r
3851 + "' between diagram element(s), not back-end object(s): "
\r
3852 + sd + " -> " + od);
\r
3855 Collection<Tuple> ts = null;
\r
3856 Relationship inverse = predicate.getInverse();
\r
3857 if (inverse != null)
\r
3858 ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od,
\r
3861 ts = Collections.singleton(new Tuple(sd, predicate, od));
\r
3863 synchronized (this) {
\r
3867 if (DebugPolicy.DEBUG_RELATIONSHIP) {
\r
3868 new Exception().printStackTrace();
\r
3869 System.out.println("Denied relationships:");
\r
3870 for (Tuple t : ts)
\r
3871 System.out.println("\t" + t);
\r
3876 public void deny(IDiagram diagram, Object subject,
\r
3877 Relationship predicate, Object object) {
\r
3878 synchronized (this) {
\r
3879 doDeny(diagram, subject, predicate, object);
\r
3884 public void deny(IDiagram diagram, Relation relation) {
\r
3885 synchronized (this) {
\r
3886 doDeny(diagram, relation.getSubject(), relation
\r
3887 .getRelationship(), relation.getObject());
\r
3892 public void denyAll(IDiagram diagram, Object element) {
\r
3893 synchronized (this) {
\r
3894 for (Relation relation : getRelations(diagram, element, null)) {
\r
3895 doDeny(diagram, relation.getSubject(), relation
\r
3896 .getRelationship(), relation.getObject());
\r
3902 public Collection<Relation> getRelations(IDiagram diagram,
\r
3903 Object element, Collection<Relation> result) {
\r
3904 if (DebugPolicy.DEBUG_GET_RELATIONSHIP)
\r
3905 System.out.println("getRelations(" + element + ")");
\r
3906 Object e = getPossibleObjectOrElement(element);
\r
3908 Collection<Tuple> tuples = null;
\r
3909 synchronized (this) {
\r
3910 tuples = map.get(new Tuple(e, null, null), null);
\r
3913 if (DebugPolicy.DEBUG_GET_RELATIONSHIP) {
\r
3914 System.out.println("Result size: " + tuples.size());
\r
3915 for (Tuple t : tuples)
\r
3916 System.out.println("\t" + t);
\r
3919 if (tuples.isEmpty())
\r
3920 return Collections.emptyList();
\r
3921 if (result == null)
\r
3922 result = new ArrayList<Relation>(tuples.size());
\r
3923 for (Tuple t : tuples) {
\r
3924 Object obj = t.getField(2);
\r
3925 IElement el = getElement(obj);
\r
3926 Relationship r = (Relationship) t.getField(1);
\r
3927 result.add(new Relation(element, r, el != null ? el : obj));
\r
3934 // ------------------------------------------------------------------------
\r
3935 // DIAGRAM ELEMENT RELATIONSHIP HANDLER END
\r
3936 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\r