--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.diagram.adapter;\r
+\r
+import gnu.trove.map.hash.TObjectIntHashMap;\r
+import gnu.trove.set.hash.THashSet;\r
+\r
+import java.awt.geom.AffineTransform;\r
+import java.lang.reflect.InvocationTargetException;\r
+import java.util.ArrayDeque;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.Deque;\r
+import java.util.EnumSet;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Queue;\r
+import java.util.Set;\r
+import java.util.concurrent.ConcurrentHashMap;\r
+import java.util.concurrent.ConcurrentMap;\r
+import java.util.concurrent.atomic.AtomicBoolean;\r
+import java.util.concurrent.locks.Condition;\r
+import java.util.concurrent.locks.ReentrantLock;\r
+\r
+import org.eclipse.core.runtime.IProgressMonitor;\r
+import org.eclipse.core.runtime.SubMonitor;\r
+import org.simantics.db.AsyncReadGraph;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.RequestProcessor;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.Session;\r
+import org.simantics.db.common.ResourceArray;\r
+import org.simantics.db.common.exception.DebugException;\r
+import org.simantics.db.common.procedure.adapter.AsyncProcedureAdapter;\r
+import org.simantics.db.common.procedure.adapter.CacheListener;\r
+import org.simantics.db.common.procedure.adapter.ListenerSupport;\r
+import org.simantics.db.common.procedure.adapter.ProcedureAdapter;\r
+import org.simantics.db.common.request.AsyncReadRequest;\r
+import org.simantics.db.common.request.ReadRequest;\r
+import org.simantics.db.common.session.SessionEventListenerAdapter;\r
+import org.simantics.db.common.utils.NameUtils;\r
+import org.simantics.db.exception.CancelTransactionException;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.exception.NoSingleResultException;\r
+import org.simantics.db.exception.ServiceException;\r
+import org.simantics.db.procedure.AsyncListener;\r
+import org.simantics.db.procedure.AsyncProcedure;\r
+import org.simantics.db.procedure.Listener;\r
+import org.simantics.db.procedure.Procedure;\r
+import org.simantics.db.request.Read;\r
+import org.simantics.db.service.SessionEventSupport;\r
+import org.simantics.diagram.connection.ConnectionSegmentEnd;\r
+import org.simantics.diagram.content.Change;\r
+import org.simantics.diagram.content.ConnectionUtil;\r
+import org.simantics.diagram.content.DesignatedTerminal;\r
+import org.simantics.diagram.content.DiagramContentChanges;\r
+import org.simantics.diagram.content.DiagramContents;\r
+import org.simantics.diagram.content.EdgeResource;\r
+import org.simantics.diagram.content.ResourceTerminal;\r
+import org.simantics.diagram.internal.DebugPolicy;\r
+import org.simantics.diagram.internal.timing.GTask;\r
+import org.simantics.diagram.internal.timing.Timing;\r
+import org.simantics.diagram.profile.ProfileKeys;\r
+import org.simantics.diagram.synchronization.CollectingModificationQueue;\r
+import org.simantics.diagram.synchronization.CompositeModification;\r
+import org.simantics.diagram.synchronization.CopyAdvisor;\r
+import org.simantics.diagram.synchronization.ErrorHandler;\r
+import org.simantics.diagram.synchronization.IHintSynchronizer;\r
+import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;\r
+import org.simantics.diagram.synchronization.IModification;\r
+import org.simantics.diagram.synchronization.LogErrorHandler;\r
+import org.simantics.diagram.synchronization.ModificationAdapter;\r
+import org.simantics.diagram.synchronization.SynchronizationHints;\r
+import org.simantics.diagram.synchronization.graph.AddElement;\r
+import org.simantics.diagram.synchronization.graph.BasicResources;\r
+import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
+import org.simantics.diagram.synchronization.graph.ElementLoader;\r
+import org.simantics.diagram.synchronization.graph.ElementReorder;\r
+import org.simantics.diagram.synchronization.graph.ElementWriter;\r
+import org.simantics.diagram.synchronization.graph.GraphSynchronizationContext;\r
+import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;\r
+import org.simantics.diagram.synchronization.graph.ModificationQueue;\r
+import org.simantics.diagram.synchronization.graph.TagChange;\r
+import org.simantics.diagram.synchronization.graph.TransformElement;\r
+import org.simantics.diagram.synchronization.graph.layer.GraphLayer;\r
+import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;\r
+import org.simantics.diagram.ui.DiagramModelHints;\r
+import org.simantics.g2d.canvas.Hints;\r
+import org.simantics.g2d.canvas.ICanvasContext;\r
+import org.simantics.g2d.connection.ConnectionEntity;\r
+import org.simantics.g2d.connection.EndKeyOf;\r
+import org.simantics.g2d.connection.TerminalKeyOf;\r
+import org.simantics.g2d.diagram.DiagramClass;\r
+import org.simantics.g2d.diagram.DiagramHints;\r
+import org.simantics.g2d.diagram.DiagramMutator;\r
+import org.simantics.g2d.diagram.DiagramUtils;\r
+import org.simantics.g2d.diagram.IDiagram;\r
+import org.simantics.g2d.diagram.IDiagram.CompositionListener;\r
+import org.simantics.g2d.diagram.IDiagram.CompositionVetoListener;\r
+import org.simantics.g2d.diagram.handler.DataElementMap;\r
+import org.simantics.g2d.diagram.handler.ElementFactory;\r
+import org.simantics.g2d.diagram.handler.Relationship;\r
+import org.simantics.g2d.diagram.handler.RelationshipHandler;\r
+import org.simantics.g2d.diagram.handler.SubstituteElementClass;\r
+import org.simantics.g2d.diagram.handler.Topology;\r
+import org.simantics.g2d.diagram.handler.Topology.Connection;\r
+import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
+import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType;\r
+import org.simantics.g2d.diagram.impl.Diagram;\r
+import org.simantics.g2d.diagram.participant.ElementPainter;\r
+import org.simantics.g2d.element.ElementClass;\r
+import org.simantics.g2d.element.ElementHints;\r
+import org.simantics.g2d.element.ElementHints.DiscardableKey;\r
+import org.simantics.g2d.element.ElementUtils;\r
+import org.simantics.g2d.element.IElement;\r
+import org.simantics.g2d.element.IElementClassProvider;\r
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
+import org.simantics.g2d.element.handler.ElementHandler;\r
+import org.simantics.g2d.element.handler.ElementLayerListener;\r
+import org.simantics.g2d.element.handler.TerminalTopology;\r
+import org.simantics.g2d.element.impl.Element;\r
+import org.simantics.g2d.layers.ILayer;\r
+import org.simantics.g2d.layers.ILayersEditor;\r
+import org.simantics.g2d.routing.RouterFactory;\r
+import org.simantics.scenegraph.INode;\r
+import org.simantics.scenegraph.profile.DataNodeConstants;\r
+import org.simantics.scenegraph.profile.DataNodeMap;\r
+import org.simantics.scenegraph.profile.common.ProfileObserver;\r
+import org.simantics.structural2.modelingRules.IModelingRules;\r
+import org.simantics.utils.datastructures.ArrayMap;\r
+import org.simantics.utils.datastructures.MapSet;\r
+import org.simantics.utils.datastructures.Pair;\r
+import org.simantics.utils.datastructures.disposable.AbstractDisposable;\r
+import org.simantics.utils.datastructures.hints.HintListenerAdapter;\r
+import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
+import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
+import org.simantics.utils.datastructures.hints.IHintListener;\r
+import org.simantics.utils.datastructures.hints.IHintObservable;\r
+import org.simantics.utils.datastructures.map.AssociativeMap;\r
+import org.simantics.utils.datastructures.map.Associativity;\r
+import org.simantics.utils.datastructures.map.Tuple;\r
+import org.simantics.utils.strings.EString;\r
+import org.simantics.utils.threads.ThreadUtils;\r
+import org.simantics.utils.threads.logger.ITask;\r
+import org.simantics.utils.threads.logger.ThreadLogger;\r
+\r
+/**\r
+ * This class loads a diagram contained in the graph database into the runtime\r
+ * diagram model and synchronizes changes in the graph into the run-time\r
+ * diagram. Any modifications to the graph model will be reflected to the\r
+ * run-time diagram model. Hence the name GraphToDiagramSynchronizer.\r
+ * \r
+ * <p>\r
+ * This class does not in itself support modification of the graph diagram\r
+ * model. This manipulation is meant to be performed through a\r
+ * {@link DiagramMutator} implementation that is installed into the diagram\r
+ * using the {@link DiagramHints#KEY_MUTATOR} hint key.\r
+ * \r
+ * This implementations is built to only support diagrams defined in the graph\r
+ * model as indicated in <a\r
+ * href="https://www.simantics.org/wiki/index.php/Org.simantics.diagram" >this\r
+ * </a> document.\r
+ * \r
+ * <p>\r
+ * The synchronizer in itself is an {@link IDiagramLoader} which means that it\r
+ * can be used for loading a diagram from the graph. In order for the\r
+ * synchronizer to keep tracking the graph diagram model for changes the diagram\r
+ * must be loaded with it. The tracking is implemented using graph database\r
+ * queries. If you just want to load the diagram but detach it from the\r
+ * synchronizer's tracking mechanisms, all you need to do is to dispose the\r
+ * synchronizer after loading the diagram.\r
+ * \r
+ * <p>\r
+ * This class guarantees that a single diagram element (IElement) representing a\r
+ * single back-end object ({@link ElementHints#KEY_OBJECT}) will stay the same\r
+ * object for the same back-end object.\r
+ * \r
+ * <p>\r
+ * TODO: Currently it just happens that {@link GraphToDiagramSynchronizer}\r
+ * contains {@link DefaultDiagramMutator} which depends on some internal details\r
+ * of {@link GraphToDiagramSynchronizer} but it should be moved out of here by\r
+ * introducing new interfaces.\r
+ * \r
+ * <h2>Basic usage example</h2>\r
+ * <p>\r
+ * This example shows how to initialize {@link GraphToDiagramSynchronizer} for a\r
+ * specified {@link ICanvasContext} and load a diagram from a specified diagram\r
+ * resource in the graph.\r
+ * \r
+ * <pre>\r
+ * IDiagram loadDiagram(final ICanvasContext canvasContext, RequestProcessor processor, Resource diagramResource,\r
+ * ResourceArray structuralPath) throws DatabaseException {\r
+ * GraphToDiagramSynchronizer synchronizer = processor.syncRequest(new Read<GraphToDiagramSynchronizer>() {\r
+ * public GraphToDiagramSynchronizer perform(ReadGraph graph) throws DatabaseException {\r
+ * return new GraphToDiagramSynchronizer(graph, canvasContext, createElementClassProvider(graph));\r
+ * }\r
+ * });\r
+ * IDiagram d = requestProcessor\r
+ * .syncRequest(new DiagramLoadQuery(diagramResource, structuralPath, synchronizer, null));\r
+ * return d;\r
+ * }\r
+ * \r
+ * protected IElementClassProvider createElementClassProvider(ReadGraph graph) {\r
+ * DiagramResource dr = DiagramResource.getInstance(graph);\r
+ * return ElementClassProviders.mappedProvider(ElementClasses.CONNECTION, DefaultConnectionClassFactory.CLASS\r
+ * .newClassWith(new ResourceAdapterImpl(dr.Connection)), ElementClasses.FLAG, FlagClassFactory\r
+ * .createFlagClass(dr.Flag));\r
+ * }\r
+ * </pre>\r
+ * \r
+ * <p>\r
+ * TODO: make GraphToDiagramSynchronizer a canvas participant to make it more\r
+ * uniform with the rest of the canvas system. This does not mean that G2DS must\r
+ * be attached to an ICanvasContext in order to be used, rather that it can be\r
+ * attached to one.\r
+ * \r
+ * <p>\r
+ * TODO: test that detaching the synchronizer via {@link #dispose()} actually\r
+ * works.\r
+ * <p>\r
+ * TODO: remove {@link DefaultDiagramMutator} and all {@link DiagramMutator}\r
+ * stuff altogether\r
+ * \r
+ * <p>\r
+ * TODO: diagram connection loading has no listener\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ * \r
+ * @see GraphElementClassFactory\r
+ * @see GraphElementFactory\r
+ * @see ElementLoader\r
+ * @see ElementWriter\r
+ * @see IHintSynchronizer\r
+ * @see CopyAdvisor\r
+ */\r
+public class GraphToDiagramSynchronizer extends AbstractDisposable implements IDiagramLoader, IModifiableSynchronizationContext {\r
+\r
+ /**\r
+ * Controls whether the class adds hint listeners to each diagram element\r
+ * that try to perform basic sanity checks on changes happening in element\r
+ * hints. Having this will immediately inform you of bugs that corrupt the\r
+ * diagram model within the element hints in some way.\r
+ */\r
+ private static final boolean USE_ELEMENT_VALIDATING_LISTENERS = false;\r
+\r
+ /**\r
+ * These keys are used to hang on to Connection instances of edges that will\r
+ * be later installed as EndKeyOf/TerminalKeyOf hints into the loaded\r
+ * element during the "graph to diagram update transaction".\r
+ */\r
+ private static final Key KEY_CONNECTION_BEGIN_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_BEGIN_PLACEHOLDER");\r
+ private static final Key KEY_CONNECTION_END_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_END_PLACEHOLDER");\r
+\r
+ /**\r
+ * Stored into an edge node during connection edge requests using the\r
+ * KEY_CONNECTION_BEGIN_PLACEHOLDER and KEY_CONNECTION_END_PLACEHOLDER keys.\r
+ */\r
+ static class PlaceholderConnection {\r
+ public final EdgeEnd end;\r
+ public final Object node;\r
+ public final Terminal terminal;\r
+ public PlaceholderConnection(EdgeEnd end, Object node, Terminal terminal) {\r
+ this.end = end;\r
+ this.node = node;\r
+ this.terminal = terminal;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Indicates to the diagram CompositionListener of this synchronizer that is\r
+ * should deny all relationships for the element this hint is attached to.\r
+ */\r
+ private static final Key KEY_REMOVE_RELATIONSHIPS = new KeyOf(Boolean.class, "REMOVE_RELATIONSHIPS");\r
+\r
+ static ErrorHandler errorHandler = LogErrorHandler.INSTANCE;\r
+\r
+ /**\r
+ * The canvas context which is being synchronized with the graph. Received\r
+ * during construction.\r
+ * \r
+ * <p>\r
+ * Is not nulled during disposal of this class since internal listener's\r
+ * life-cycles depend on canvas.isDisposed.\r
+ */\r
+ ICanvasContext canvas;\r
+\r
+ /**\r
+ * The session used by this synchronizer. Received during construction.\r
+ */\r
+ Session session;\r
+\r
+ /**\r
+ * Locked while updating diagram contents from the graph.\r
+ */\r
+ ReentrantLock diagramUpdateLock = new ReentrantLock();\r
+\r
+ // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
+ // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING BEGIN\r
+ // ------------------------------------------------------------------------\r
+\r
+ /**\r
+ * Holds a GraphToDiagramUpdater instance while a diagram content update is\r
+ * currently in progress.\r
+ *\r
+ * <p>\r
+ * Basically this is a hack solution to the problem of properly finding\r
+ * newly added Resource<->IElement mappings while loading the diagram\r
+ * contents. See {@link DataElementMapImpl} for why this is necessary.\r
+ */\r
+ GraphToDiagramUpdater currentUpdater = null;\r
+\r
+ /**\r
+ * A map from data objects to elements. Elements should already contain the\r
+ * data objects as {@link ElementHints#KEY_OBJECT} hints.\r
+ */\r
+ ConcurrentMap<Object, IElement> dataElement = new ConcurrentHashMap<Object, IElement>();\r
+\r
+ /**\r
+ * Temporary structure for single-threaded use in #{@link DiagramUpdater}.\r
+ */\r
+ Collection<Connection> tempConnections = new ArrayList<Connection>();\r
+\r
+ /**\r
+ * A dummy class of which an instance will be given to each new edge element\r
+ * to make {@link TerminalKeyOf} keys unique for each edge.\r
+ */\r
+ static class TransientElementObject {\r
+ @Override\r
+ public String toString() {\r
+ return "MUTATOR GENERATED (hash=" + System.identityHashCode(this) + ")";\r
+ }\r
+ }\r
+\r
+ private static class ConnectionChildren {\r
+ public Set<IElement> branchPoints;\r
+ public Set<IElement> segments;\r
+\r
+ public ConnectionChildren(Set<IElement> branchPoints, Set<IElement> segments) {\r
+ this.branchPoints = branchPoints;\r
+ this.segments = segments;\r
+ }\r
+ }\r
+\r
+ ListenerSupport canvasListenerSupport = new ListenerSupport() {\r
+ @Override\r
+ public void exception(Throwable t) {\r
+ error(t);\r
+ }\r
+\r
+ @Override\r
+ public boolean isDisposed() {\r
+ return !isAlive() || canvas.isDisposed();\r
+ }\r
+ };\r
+\r
+ /**\r
+ * @see ElementHints#KEY_CONNECTION_ENTITY\r
+ */\r
+ class ConnectionEntityImpl implements ConnectionEntity {\r
+\r
+ /**\r
+ * The connection instance resource in the graph backend.\r
+ *\r
+ * May be <code>null</code> if the connection has not been synchronized\r
+ * yet.\r
+ */\r
+ Resource connection;\r
+\r
+ /**\r
+ * The connection type resource in the graph backend.\r
+ *\r
+ * May be <code>null</code> if the connection has not been synchronized\r
+ * yet.\r
+ */\r
+ Resource connectionType;\r
+\r
+ /**\r
+ * The connection entity element which is a part of the diagram.\r
+ */\r
+ IElement connectionElement;\r
+\r
+ /**\r
+ * List of backend-synchronized branch points that are part of this\r
+ * connection.\r
+ */\r
+ Collection<Resource> branchPoints = Collections.emptyList();\r
+\r
+ /**\r
+ * List of backend-synchronized edges that are part of this connection.\r
+ */\r
+ Collection<EdgeResource> segments = Collections.emptyList();\r
+\r
+ Set<Object> removedBranchPoints = new HashSet<Object>(4);\r
+\r
+ Set<Object> removedSegments = new HashSet<Object>(4);\r
+\r
+ /**\r
+ * List of non-backend-synchronized branch point element that are part\r
+ * of this connection.\r
+ */\r
+ List<IElement> branchPointElements = new ArrayList<IElement>(1);\r
+\r
+ /**\r
+ * List of non-backend-synchronized edge element that are part of this\r
+ * connection.\r
+ */\r
+ List<IElement> segmentElements = new ArrayList<IElement>(2);\r
+\r
+ ConnectionListener listener;\r
+\r
+ ConnectionEntityImpl(Resource connection, Resource connectionType, IElement connectionElement) {\r
+ this.connection = connection;\r
+ this.connectionType = connectionType;\r
+ this.connectionElement = connectionElement;\r
+ }\r
+\r
+ ConnectionEntityImpl(Resource connectionType, IElement connectionElement) {\r
+ this.connectionType = connectionType;\r
+ this.connectionElement = connectionElement;\r
+ }\r
+\r
+ ConnectionEntityImpl(ReadGraph graph, Resource connection, IElement connectionElement)\r
+ throws NoSingleResultException, ServiceException {\r
+ this.connection = connection;\r
+ this.connectionType = graph.getSingleType(connection, br.DIA.Connection);\r
+ this.connectionElement = connectionElement;\r
+ }\r
+\r
+ @Override\r
+ public IElement getConnection() {\r
+ return connectionElement;\r
+ }\r
+\r
+ public Object getConnectionObject() {\r
+ return connection;\r
+ }\r
+\r
+ public IElement getConnectionElement() {\r
+ if (connectionElement == null)\r
+ return getMappedConnectionElement();\r
+ return connectionElement;\r
+ }\r
+\r
+ private IElement getMappedConnectionElement() {\r
+ IElement ce = null;\r
+ if (connection != null)\r
+ ce = getMappedElement(connection);\r
+ return ce == null ? connectionElement : ce;\r
+ }\r
+\r
+ void fix() {\r
+ Collection<IElement> segments = getSegments(null);\r
+\r
+ // Remove all TerminalKeyOf hints from branch points that do not\r
+ // match\r
+ ArrayList<TerminalKeyOf> pruned = null;\r
+ for (IElement bp : getBranchPoints(null)) {\r
+ if (pruned == null)\r
+ pruned = new ArrayList<TerminalKeyOf>(4);\r
+ pruned.clear();\r
+ for (Map.Entry<TerminalKeyOf, Object> entry : bp.getHintsOfClass(TerminalKeyOf.class).entrySet()) {\r
+ // First check that the terminal matches.\r
+ Connection c = (Connection) entry.getValue();\r
+ if (!segments.contains(c.edge))\r
+ pruned.add(entry.getKey());\r
+ }\r
+ removeNodeTopologyHints((Element) bp, pruned);\r
+ }\r
+ }\r
+\r
+ public ConnectionChildren getConnectionChildren() {\r
+ Set<IElement> bps = Collections.emptySet();\r
+ Set<IElement> segs = Collections.emptySet();\r
+ if (!branchPoints.isEmpty()) {\r
+ bps = new HashSet<IElement>(branchPoints.size());\r
+ for (Resource bp : branchPoints) {\r
+ IElement e = getMappedElement(bp);\r
+ if (e != null)\r
+ bps.add(e);\r
+ }\r
+ }\r
+ if (!segments.isEmpty()) {\r
+ segs = new HashSet<IElement>(segments.size());\r
+ for (EdgeResource seg : segments) {\r
+ IElement e = getMappedElement(seg);\r
+ if (e != null)\r
+ segs.add(e);\r
+ }\r
+ }\r
+ return new ConnectionChildren(bps, segs);\r
+ }\r
+\r
+ public void setData(Collection<EdgeResource> segments, Collection<Resource> branchPoints) {\r
+ // System.out.println("setData " + segments.size());\r
+ this.branchPoints = branchPoints;\r
+ this.segments = segments;\r
+\r
+ // Reset the added/removed state of segments and branchpoints.\r
+ this.removedBranchPoints = new HashSet<Object>(4);\r
+ this.removedSegments = new HashSet<Object>(4);\r
+ this.branchPointElements = new ArrayList<IElement>(4);\r
+ this.segmentElements = new ArrayList<IElement>(4);\r
+ }\r
+\r
+ public void fireListener(ConnectionChildren old, ConnectionChildren current) {\r
+ if (listener != null) {\r
+ List<IElement> removed = new ArrayList<IElement>();\r
+ List<IElement> added = new ArrayList<IElement>();\r
+\r
+ for (IElement oldBp : old.branchPoints)\r
+ if (!current.branchPoints.contains(oldBp))\r
+ removed.add(oldBp);\r
+ for (IElement oldSeg : old.segments)\r
+ if (!current.segments.contains(oldSeg))\r
+ removed.add(oldSeg);\r
+\r
+ for (IElement bp : current.branchPoints)\r
+ if (!old.branchPoints.contains(bp))\r
+ added.add(bp);\r
+ for (IElement seg : current.segments)\r
+ if (!old.segments.contains(seg))\r
+ added.add(seg);\r
+\r
+ if (!removed.isEmpty() || !added.isEmpty()) {\r
+ listener.connectionChanged(new ConnectionEvent(this.connectionElement, removed, added));\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public Collection<IElement> getBranchPoints(Collection<IElement> result) {\r
+ if (result == null)\r
+ result = new ArrayList<IElement>(branchPoints.size());\r
+ for (Resource bp : branchPoints) {\r
+ if (!removedBranchPoints.contains(bp)) {\r
+ IElement e = getMappedElement(bp);\r
+ if (e != null)\r
+ result.add(e);\r
+ }\r
+ }\r
+ result.addAll(branchPointElements);\r
+ return result;\r
+ }\r
+\r
+ @Override\r
+ public Collection<IElement> getSegments(Collection<IElement> result) {\r
+ if (result == null)\r
+ result = new ArrayList<IElement>(segments.size());\r
+ for (EdgeResource seg : segments) {\r
+ if (!removedSegments.contains(seg)) {\r
+ IElement e = getMappedElement(seg);\r
+ if (e != null)\r
+ result.add(e);\r
+ }\r
+ }\r
+ result.addAll(segmentElements);\r
+ return result;\r
+ }\r
+\r
+ @Override\r
+ public Collection<Connection> getTerminalConnections(Collection<Connection> result) {\r
+ if (result == null)\r
+ result = new ArrayList<Connection>(segments.size() * 2);\r
+ Set<org.simantics.utils.datastructures.Pair<IElement, Terminal>> processed = new HashSet<org.simantics.utils.datastructures.Pair<IElement, Terminal>>();\r
+ for (EdgeResource seg : segments) {\r
+ IElement edge = getMappedElement(seg);\r
+ if (edge != null) {\r
+ for (EndKeyOf key : EndKeyOf.KEYS) {\r
+ Connection c = edge.getHint(key);\r
+ if (c != null && (c.terminal instanceof ResourceTerminal) && processed.add(Pair.make(c.node, c.terminal)))\r
+ result.add(c);\r
+ }\r
+ }\r
+ }\r
+ return result;\r
+ }\r
+\r
+ @Override\r
+ public void setListener(ConnectionListener listener) {\r
+ this.listener = listener;\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return getClass().getSimpleName() + "[resource=" + connection + ", branch points=" + branchPoints\r
+ + ", segments=" + segments + ", connectionElement=" + connectionElement\r
+ + ", branch point elements=" + branchPointElements + ", segment elements=" + segmentElements\r
+ + ", removed branch points=" + removedBranchPoints + ", removed segments=" + removedSegments + "]";\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * A map from connection data objects to connection entities. The connection\r
+ * part elements should already contain the data objects as\r
+ * {@link ElementHints#KEY_OBJECT} hints.\r
+ */\r
+ ConcurrentMap<Object, ConnectionEntityImpl> dataConnection = new ConcurrentHashMap<Object, ConnectionEntityImpl>();\r
+\r
+ /**\r
+ * @param data\r
+ * @param element\r
+ */\r
+ void mapElement(final Object data, final IElement element) {\r
+ if (!(element instanceof Element)) {\r
+ throw new IllegalArgumentException("mapElement: expected instance of Element, got " + element + " with data " + data);\r
+ }\r
+ assert data != null;\r
+ assert element != null;\r
+ if (DebugPolicy.DEBUG_MAPPING)\r
+ new Exception(Thread.currentThread() + " MAPPING: " + data + " -> " + element).printStackTrace();\r
+ dataElement.put(data, element);\r
+ }\r
+\r
+ /**\r
+ * @param data\r
+ * @return\r
+ */\r
+ IElement getMappedElement(final Object data) {\r
+ assert (data != null);\r
+ IElement element = dataElement.get(data);\r
+ return element;\r
+ }\r
+\r
+ IElement getMappedElementByElementObject(IElement e) {\r
+ if (e == null)\r
+ return null;\r
+ Object o = e.getHint(ElementHints.KEY_OBJECT);\r
+ if (o == null)\r
+ return null;\r
+ return getMappedElement(o);\r
+ }\r
+\r
+ /**\r
+ * @param data\r
+ * @return\r
+ */\r
+ IElement assertMappedElement(final Object data) {\r
+ IElement element = dataElement.get(data);\r
+ assert element != null;\r
+ return element;\r
+ }\r
+\r
+ /**\r
+ * @param data\r
+ * @return\r
+ */\r
+ IElement unmapElement(final Object data) {\r
+ IElement element = dataElement.remove(data);\r
+ if (DebugPolicy.DEBUG_MAPPING)\r
+ new Exception(Thread.currentThread() + " UN-MAPPED: " + data + " -> " + element).printStackTrace();\r
+ return element;\r
+ }\r
+\r
+ /**\r
+ * @param data\r
+ * @param element\r
+ */\r
+ void mapConnection(final Object data, final ConnectionEntityImpl connection) {\r
+ assert data != null;\r
+ assert connection != null;\r
+ if (DebugPolicy.DEBUG_MAPPING)\r
+ System.out.println(Thread.currentThread() + " MAPPING CONNECTION: " + data + " -> " + connection);\r
+ dataConnection.put(data, connection);\r
+ }\r
+\r
+ /**\r
+ * @param data\r
+ * @return\r
+ */\r
+ ConnectionEntityImpl getMappedConnection(final Object data) {\r
+ ConnectionEntityImpl connection = dataConnection.get(data);\r
+ return connection;\r
+ }\r
+\r
+ /**\r
+ * @param data\r
+ * @return\r
+ */\r
+ ConnectionEntityImpl assertMappedConnection(final Object data) {\r
+ ConnectionEntityImpl connection = getMappedConnection(data);\r
+ assert connection != null;\r
+ return connection;\r
+ }\r
+\r
+ /**\r
+ * @param data\r
+ * @return\r
+ */\r
+ ConnectionEntityImpl unmapConnection(final Object data) {\r
+ ConnectionEntityImpl connection = dataConnection.remove(data);\r
+ if (DebugPolicy.DEBUG_MAPPING)\r
+ System.out.println(Thread.currentThread() + " UN-MAPPED CONNECTION: " + data + " -> " + connection);\r
+ return connection;\r
+ }\r
+\r
+ class DataElementMapImpl implements DataElementMap {\r
+ @Override\r
+ public Object getData(IDiagram d, IElement element) {\r
+ if (d == null)\r
+ throw new NullPointerException("null diagram");\r
+ if (element == null)\r
+ throw new NullPointerException("null element");\r
+\r
+ assert ElementUtils.getDiagram(element) == d;\r
+ return element.getHint(ElementHints.KEY_OBJECT);\r
+ }\r
+\r
+ @Override\r
+ public IElement getElement(IDiagram d, Object data) {\r
+ if (d == null)\r
+ throw new NullPointerException("null diagram");\r
+ if (data == null)\r
+ throw new NullPointerException("null data");\r
+\r
+ GraphToDiagramUpdater updater = currentUpdater;\r
+ if (updater != null) {\r
+ // This HACK is for allowing GraphElementFactory implementations\r
+ // to find the IElements they are related to.\r
+ IElement e = updater.addedElementMap.get(data);\r
+ if (e != null)\r
+ return e;\r
+ }\r
+\r
+ IElement e = getMappedElement(data);\r
+ if (e != null)\r
+ return e;\r
+ return null;\r
+ }\r
+ }\r
+\r
+ class SubstituteElementClassImpl implements SubstituteElementClass {\r
+ @Override\r
+ public ElementClass substitute(IDiagram d, ElementClass ec) {\r
+ if (d != diagram)\r
+ throw new IllegalArgumentException("specified diagram does not have this SubstituteElementClass handler");\r
+\r
+ // If the element class is our own, there's no point in creating\r
+ // a copy of it.\r
+ if (ec.contains(elementLayerListener))\r
+ return ec;\r
+\r
+ List<ElementHandler> all = ec.getAll();\r
+ List<ElementHandler> result = new ArrayList<ElementHandler>(all.size());\r
+ for (ElementHandler eh : all) {\r
+ if (eh instanceof ElementLayerListenerImpl)\r
+ result.add(elementLayerListener);\r
+ else\r
+ result.add(eh);\r
+ }\r
+ return ElementClass.compile(result).setId(ec.getId());\r
+ }\r
+ }\r
+\r
+ final DataElementMapImpl dataElementMap = new DataElementMapImpl();\r
+\r
+ final SubstituteElementClassImpl substituteElementClass = new SubstituteElementClassImpl();\r
+\r
+ // ------------------------------------------------------------------------\r
+ // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING END\r
+ // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
+\r
+ void warning(String message, Exception e) {\r
+ errorHandler.warning(message, e);\r
+ }\r
+\r
+ void warning(Exception e) {\r
+ errorHandler.warning(e.getMessage(), e);\r
+ }\r
+\r
+ void error(String message, Throwable e) {\r
+ errorHandler.error(message, e);\r
+ }\r
+\r
+ void error(Throwable e) {\r
+ errorHandler.error(e.getMessage(), e);\r
+ }\r
+\r
+ // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
+ // GRAPH MODIFICATION QUEUE BEGIN\r
+ // ------------------------------------------------------------------------\r
+\r
+ ModificationQueue modificationQueue;\r
+ IModifiableSynchronizationContext synchronizationContext;\r
+\r
+ @Override\r
+ public <T> T set(Key key, Object value) {\r
+ if (synchronizationContext == null)\r
+ return null;\r
+ return synchronizationContext.set(key, value);\r
+ }\r
+\r
+ @Override\r
+ public <T> T get(Key key) {\r
+ if (synchronizationContext == null)\r
+ return null;\r
+ return synchronizationContext.get(key);\r
+ }\r
+\r
+ // ------------------------------------------------------------------------\r
+ // GRAPH MODIFICATION QUEUE END\r
+ // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
+\r
+ /**\r
+ * The previously loaded version of the diagram content. This is needed to\r
+ * calculate the difference between new and old content on each\r
+ * {@link #diagramGraphUpdater(DiagramContents)} invocation.\r
+ */\r
+ DiagramContents previousContent;\r
+\r
+ /**\r
+ * The diagram instance that this synchronizer is synchronizing with the\r
+ * graph.\r
+ */\r
+ IDiagram diagram;\r
+\r
+ /**\r
+ * An observer for diagram profile entries. Has a life-cycle that must be\r
+ * bound to the life-cycle of this GraphToDiagramSynchronizer instance.\r
+ * Disposed if synchronizer is detached in {@link #doDispose()} or finally\r
+ * when the canvas is disposed.\r
+ */\r
+ ProfileObserver profileObserver;\r
+\r
+ IElementClassProvider elementClassProvider;\r
+\r
+ BasicResources br;\r
+\r
+ // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
+ // Internal state machine handling BEGIN\r
+ // ------------------------------------------------------------------------\r
+\r
+ /**\r
+ * An indicator for the current state of this synchronizer. This is a simple\r
+ * state machine with the following possible state transitions:\r
+ *\r
+ * <ul>\r
+ * <li>INITIAL -> LOADING, DISPOSED</li>\r
+ * <li>LOADING -> IDLE</li>\r
+ * <li>IDLE -> UPDATING_DIAGRAM, DISPOSED</li>\r
+ * <li>UPDATING_DIAGRAM -> IDLE</li>\r
+ * </ul>\r
+ * \r
+ * Start states: INITIAL\r
+ * End states: DISPOSED\r
+ */\r
+ static enum State {\r
+ /**\r
+ * The initial state of the synchronizer.\r
+ */\r
+ INITIAL,\r
+ /**\r
+ * The synchronizer is performing load-time initialization. During this\r
+ * time no canvas refreshes should be forced.\r
+ */\r
+ LOADING,\r
+ /**\r
+ * The synchronizer is performing updates to the diagram model. This\r
+ * process goes on in the canvas context thread.\r
+ */\r
+ UPDATING_DIAGRAM,\r
+ /**\r
+ * The synchronizer is doing nothing.\r
+ */\r
+ IDLE,\r
+ /**\r
+ * The synchronized diagram is being disposed, which means that this\r
+ * synchronizer should not accept any further actions.\r
+ */\r
+ DISPOSED,\r
+ }\r
+\r
+ public static final EnumSet<State> FROM_INITIAL = EnumSet.of(State.LOADING, State.DISPOSED);\r
+ public static final EnumSet<State> FROM_LOADING = EnumSet.of(State.IDLE);\r
+ public static final EnumSet<State> FROM_UPDATING_DIAGRAM = EnumSet.of(State.IDLE);\r
+ public static final EnumSet<State> FROM_IDLE = EnumSet.of(State.UPDATING_DIAGRAM, State.DISPOSED);\r
+ public static final EnumSet<State> NO_STATES = EnumSet.noneOf(State.class);\r
+\r
+ private EnumSet<State> validTargetStates(State start) {\r
+ switch (start) {\r
+ case INITIAL: return FROM_INITIAL;\r
+ case LOADING: return FROM_LOADING;\r
+ case UPDATING_DIAGRAM: return FROM_UPDATING_DIAGRAM;\r
+ case IDLE: return FROM_IDLE;\r
+ case DISPOSED: return NO_STATES;\r
+ }\r
+ throw new IllegalArgumentException("unrecognized state " + start);\r
+ }\r
+\r
+ private String validateStateChange(State start, State end) {\r
+ EnumSet<State> validTargets = validTargetStates(start);\r
+ if (!validTargets.contains(end))\r
+ return "Cannot transition from " + start + " state to " + end + ".";\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * The current state of the synchronizer. At start it is\r
+ * {@link State#INITIAL} and after loading it is {@link State#IDLE}.\r
+ */\r
+ State synchronizerState = State.INITIAL;\r
+\r
+ /**\r
+ * A condition variable used to synchronize synchronizer state changes.\r
+ */\r
+ ReentrantLock stateLock = new ReentrantLock();\r
+\r
+ /**\r
+ * A condition that is signaled when the synchronizer state changes to IDLE.\r
+ */\r
+ Condition idleCondition = stateLock.newCondition();\r
+\r
+ State getState() {\r
+ return synchronizerState;\r
+ }\r
+\r
+ /**\r
+ * Activates the desired state after making sure that the synchronizer has\r
+ * been IDLE in between its current state and this invocation.\r
+ *\r
+ * @param newState the new state to activate\r
+ * @throws InterruptedException if waiting for IDLE state gets interrupted\r
+ * @throws IllegalStateException if the requested transition from the\r
+ * current state to the desired state would be illegal.\r
+ */\r
+ void activateState(State newState, boolean waitForIdle) throws InterruptedException {\r
+ stateLock.lock();\r
+ try {\r
+ // Wait until the state of the synchronizer IDLEs if necessary.\r
+ if (waitForIdle && synchronizerState != State.IDLE) {\r
+ String error = validateStateChange(synchronizerState, State.IDLE);\r
+ if (error != null)\r
+ throw new IllegalStateException(error);\r
+\r
+ while (synchronizerState != State.IDLE) {\r
+ if (DebugPolicy.DEBUG_STATE)\r
+ System.out.println(Thread.currentThread() + " waiting for IDLE state, current="\r
+ + synchronizerState);\r
+ idleCondition.await();\r
+ }\r
+ }\r
+\r
+ String error = validateStateChange(synchronizerState, newState);\r
+ if (error != null)\r
+ throw new IllegalStateException(error);\r
+\r
+ if (DebugPolicy.DEBUG_STATE)\r
+ System.out.println(Thread.currentThread() + " activated state " + newState);\r
+ this.synchronizerState = newState;\r
+\r
+ if (newState == State.IDLE)\r
+ idleCondition.signalAll();\r
+ } finally {\r
+ stateLock.unlock();\r
+ }\r
+ }\r
+\r
+ void idle() throws IllegalStateException, InterruptedException {\r
+ activateState(State.IDLE, false);\r
+ }\r
+\r
+ static interface StateRunnable extends Runnable {\r
+ void execute() throws InvocationTargetException;\r
+\r
+ public abstract class Stub implements StateRunnable {\r
+ @Override\r
+ public void run() {\r
+ }\r
+\r
+ @Override\r
+ public final void execute() throws InvocationTargetException {\r
+ try {\r
+ perform();\r
+ } catch (Exception e) {\r
+ throw new InvocationTargetException(e);\r
+ } catch (LinkageError e) {\r
+ throw new InvocationTargetException(e);\r
+ }\r
+ }\r
+\r
+ protected abstract void perform() throws Exception;\r
+ }\r
+ }\r
+\r
+ protected void runInState(State state, StateRunnable runnable) throws InvocationTargetException {\r
+ try {\r
+ activateState(state, true);\r
+ try {\r
+ runnable.execute();\r
+ } finally {\r
+ idle();\r
+ }\r
+ } catch (IllegalStateException e) {\r
+ throw new InvocationTargetException(e);\r
+ } catch (InterruptedException e) {\r
+ throw new InvocationTargetException(e);\r
+ }\r
+ }\r
+\r
+ protected void safeRunInState(State state, StateRunnable runnable) {\r
+ try {\r
+ runInState(state, runnable);\r
+ } catch (InvocationTargetException e) {\r
+ error("Failed to run runnable " + runnable + " in state " + state + ". See exception for details.", e\r
+ .getCause());\r
+ }\r
+ }\r
+\r
+ // ------------------------------------------------------------------------\r
+ // Internal state machine handling END\r
+ // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
+\r
+ /**\r
+ * @param processor\r
+ * @param canvas\r
+ * @param elementClassProvider\r
+ * @throws DatabaseException\r
+ */\r
+ public GraphToDiagramSynchronizer(RequestProcessor processor, ICanvasContext canvas, IElementClassProvider elementClassProvider) throws DatabaseException {\r
+ if (processor == null)\r
+ throw new IllegalArgumentException("null processor");\r
+ if (canvas == null)\r
+ throw new IllegalArgumentException("null canvas");\r
+ if (elementClassProvider == null)\r
+ throw new IllegalArgumentException("null element class provider");\r
+\r
+ this.session = processor.getSession();\r
+ this.canvas = canvas;\r
+ this.modificationQueue = new ModificationQueue(session, errorHandler);\r
+\r
+ processor.syncRequest(new ReadRequest() {\r
+ @Override\r
+ public void run(ReadGraph graph) throws DatabaseException {\r
+ initializeResources(graph);\r
+ }\r
+ });\r
+\r
+ this.elementClassProvider = elementClassProvider;\r
+ synchronizationContext.set(SynchronizationHints.ELEMENT_CLASS_PROVIDER, elementClassProvider);\r
+\r
+ attachSessionListener(processor.getSession());\r
+ }\r
+\r
+ /**\r
+ * @return\r
+ */\r
+ public IElementClassProvider getElementClassProvider() {\r
+ return elementClassProvider;\r
+ }\r
+\r
+ public Session getSession() {\r
+ return session;\r
+ }\r
+\r
+ public ICanvasContext getCanvasContext() {\r
+ return canvas;\r
+ }\r
+\r
+ public IDiagram getDiagram() {\r
+ return diagram;\r
+ }\r
+\r
+ void setCanvasDirty() {\r
+ ICanvasContext c = canvas;\r
+ if (synchronizerState != State.LOADING && c != null && !c.isDisposed()) {\r
+ // TODO: Consider adding an invocation limiter here, to prevent\r
+ // calling setDirty too often if enough time hasn't passed yet since\r
+ // the last invocation.\r
+ c.getContentContext().setDirty();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * @param elementType\r
+ * @return\r
+ * @throws DatabaseException if ElementClass cannot be retrieved\r
+ */\r
+ public ElementClass getNodeClass(Resource elementType) throws DatabaseException {\r
+ return getNodeClass(session, elementType);\r
+ }\r
+\r
+ public ElementClass getNodeClass(RequestProcessor processor, Resource elementType) throws DatabaseException {\r
+ ElementClass ec = processor.syncRequest(new NodeClassRequest(canvas, diagram, elementType, true));\r
+ return ec;\r
+ }\r
+\r
+ @Override\r
+ protected void doDispose() {\r
+ try {\r
+ try {\r
+ stateLock.lock();\r
+ boolean isInitial = getState() == State.INITIAL;\r
+ activateState(State.DISPOSED, !isInitial);\r
+ } finally {\r
+ stateLock.unlock();\r
+ }\r
+ } catch (InterruptedException e) {\r
+ // Shouldn't happen.\r
+ e.printStackTrace();\r
+ } finally {\r
+ detachSessionListener();\r
+\r
+ if (profileObserver != null) {\r
+ profileObserver.dispose();\r
+ profileObserver = null;\r
+ }\r
+\r
+ if (diagram != null) {\r
+ diagram.removeCompositionListener(diagramListener);\r
+ diagram.removeCompositionVetoListener(diagramListener);\r
+ }\r
+\r
+ // TODO: we should probably leave the dataElement map as is since DataElementMap needs it even after the synchronizer has been disposed.\r
+ // Currently the diagram's DataElementMap will be broken after disposal.\r
+// dataElement.clear();\r
+// dataConnection.clear();\r
+\r
+ if (layerManager != null) {\r
+ layerManager.dispose();\r
+ }\r
+\r
+ // Let GC work.\r
+ modificationQueue.dispose();\r
+ }\r
+ }\r
+\r
+ void initializeResources(ReadGraph graph) {\r
+ this.br = new BasicResources(graph);\r
+\r
+ // Initialize synchronization context\r
+ synchronizationContext = new GraphSynchronizationContext(graph, modificationQueue);\r
+ synchronizationContext.set(SynchronizationHints.ERROR_HANDLER, errorHandler);\r
+ }\r
+\r
+ // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
+ // LAYERS BEGIN\r
+ // ------------------------------------------------------------------------\r
+\r
+ GraphLayerManager layerManager;\r
+\r
+ /**\r
+ * A common handler for all elements that is used to listen to changes in\r
+ * element visibility and focusability on diagram layers.\r
+ */\r
+ class ElementLayerListenerImpl implements ElementLayerListener {\r
+ private static final long serialVersionUID = -3410052116598828129L;\r
+\r
+ @Override\r
+ public void visibilityChanged(IElement e, ILayer layer, boolean visible) {\r
+ if (!isAlive())\r
+ return;\r
+ if (DebugPolicy.DEBUG_LAYERS)\r
+ System.out.println("visibility changed: " + e + ", " + layer + ", " + visible);\r
+ GraphLayer gl = layerManager.getGraphLayer(layer.getName());\r
+ if (gl != null) {\r
+ changeTag(e, gl.getVisible(), visible);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void focusabilityChanged(IElement e, ILayer layer, boolean focusable) {\r
+ if (!isAlive())\r
+ return;\r
+ if (DebugPolicy.DEBUG_LAYERS)\r
+ System.out.println("focusability changed: " + e + ", " + layer + ", " + focusable);\r
+ GraphLayer gl = layerManager.getGraphLayer(layer.getName());\r
+ if (gl != null) {\r
+ changeTag(e, gl.getFocusable(), focusable);\r
+ }\r
+ }\r
+\r
+ void changeTag(IElement e, Resource tag, boolean set) {\r
+ Object object = e.getHint(ElementHints.KEY_OBJECT);\r
+ Resource tagged = null;\r
+ if (object instanceof Resource) {\r
+ tagged = (Resource) object;\r
+ } else if (object instanceof EdgeResource) {\r
+ ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce instanceof ConnectionEntityImpl) {\r
+ tagged = ((ConnectionEntityImpl) ce).connection;\r
+ }\r
+ }\r
+ if (tagged == null)\r
+ return;\r
+\r
+ modificationQueue.async(new TagChange(tagged, tag, set), null);\r
+ }\r
+ };\r
+\r
+ ElementLayerListenerImpl elementLayerListener = new ElementLayerListenerImpl();\r
+\r
+ // ------------------------------------------------------------------------\r
+ // LAYERS END\r
+ // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
+\r
+ @Override\r
+ public IDiagram loadDiagram(IProgressMonitor progressMonitor, ReadGraph g, final String modelURI, final Resource diagram, final Resource runtime, final ResourceArray structuralPath,\r
+ IHintObservable initialHints) throws DatabaseException {\r
+ if (DebugPolicy.DEBUG_LOAD)\r
+ System.out.println(Thread.currentThread() + " loadDiagram: " + NameUtils.getSafeName(g, diagram));\r
+\r
+ SubMonitor monitor = SubMonitor.convert(progressMonitor, "Load Diagram", 100);\r
+\r
+ Object loadTask = Timing.BEGIN("GDS.loadDiagram");\r
+ try {\r
+ try {\r
+ activateState(State.LOADING, false);\r
+ } catch (IllegalStateException e) {\r
+ // Disposed already before loading even began.\r
+ this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);\r
+ return this.diagram;\r
+ }\r
+ try {\r
+ // Query for diagram class\r
+ Resource diagramClassResource = g.getPossibleType(diagram, br.DIA.Composite);\r
+ if (diagramClassResource != null) {\r
+ // Spawn new diagram\r
+ Object task = Timing.BEGIN("GDS.DiagramClassRequest");\r
+ final DiagramClass diagramClass = g.syncRequest(new DiagramClassRequest(diagram));\r
+ Timing.END(task);\r
+ final IDiagram d = Diagram.spawnNew(diagramClass);\r
+ {\r
+ d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, diagram);\r
+ if (runtime != null)\r
+ d.setHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE, runtime);\r
+ if (modelURI != null)\r
+ d.setHint(DiagramModelHints.KEY_DIAGRAM_MODEL_URI, modelURI);\r
+ d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE_ARRAY, structuralPath);\r
+\r
+ // Set dumb default routing when DiagramClass does not\r
+ // predefine the default connection routing for the diagram.\r
+ if (!d.containsHint(DiagramHints.ROUTE_ALGORITHM))\r
+ d.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, false));\r
+\r
+ d.setHint(SynchronizationHints.CONTEXT, this);\r
+\r
+ // Initialize hints with hints from initialHints if given\r
+ if (initialHints != null) {\r
+ d.setHints(initialHints.getHints());\r
+ }\r
+ }\r
+\r
+ // ITask task2 = ThreadLogger.getInstance().begin("loadLayers");\r
+ monitor.subTask("Layers");\r
+ {\r
+ this.layerManager = new GraphLayerManager(g, modificationQueue, diagram);\r
+ synchronizationContext.set(GraphSynchronizationHints.GRAPH_LAYER_MANAGER, this.layerManager);\r
+ ILayersEditor layers = layerManager.loadLayers(d, g, diagram);\r
+ // task2.finish();\r
+\r
+ d.setHint(DiagramHints.KEY_LAYERS, layers);\r
+ d.setHint(DiagramHints.KEY_LAYERS_EDITOR, layers);\r
+\r
+ d.addCompositionVetoListener(diagramListener);\r
+ d.addCompositionListener(diagramListener);\r
+\r
+ this.diagram = d;\r
+\r
+ d.setHint(DiagramHints.KEY_MUTATOR, new DefaultDiagramMutator(d, diagram, synchronizationContext));\r
+\r
+ // Add default layer if no layers exist.\r
+ // NOTE: this must be done after this.diagram has been set\r
+ // as it will trigger a graph modification which needs the\r
+ // diagram resource.\r
+ // ITask task3 = ThreadLogger.getInstance().begin("addDefaultLayer");\r
+// if (layers.getLayers().isEmpty()) {\r
+// if (DebugPolicy.DEBUG_LAYERS)\r
+// System.out.println("No layers, creating default layer '"\r
+// + DiagramConstants.DEFAULT_LAYER_NAME + "'");\r
+// SimpleLayer defaultLayer = new SimpleLayer(DiagramConstants.DEFAULT_LAYER_NAME);\r
+// layers.addLayer(defaultLayer);\r
+// layers.activate(defaultLayer);\r
+// }\r
+// // task3.finish();\r
+ }\r
+ monitor.worked(10);\r
+\r
+ monitor.subTask("Contents");\r
+ // Discover the plain resources that form the content of the\r
+ // diagram through a separate query. This allows us to\r
+ // separately\r
+ // track changes to the diagram structure itself, not the\r
+ // substructures contained by the structure elements.\r
+ ITask task4 = ThreadLogger.getInstance().begin("DiagramContentRequest1");\r
+ DiagramContentRequest query = new DiagramContentRequest(canvas, diagram, errorHandler);\r
+ g.syncRequest(query, new DiagramContentListener(diagram));\r
+ task4.finish();\r
+ // ITask task5 = ThreadLogger.getInstance().begin("DiagramContentRequest2");\r
+ ITask task42 = ThreadLogger.getInstance().begin("DiagramContentRequest2");\r
+ DiagramContents contents = g.syncRequest(query);\r
+ task42.finish();\r
+ // task5.finish();\r
+ monitor.worked(10);\r
+\r
+ monitor.subTask("Graphical elements");\r
+ {\r
+ Object applyDiagramContents = Timing.BEGIN("GDS.applyDiagramContents");\r
+ ITask task6 = ThreadLogger.getInstance().begin("applyDiagramContents");\r
+ processGraphUpdates(g, Collections.singleton(diagramGraphUpdater(contents)));\r
+ task6.finish();\r
+ Timing.END(applyDiagramContents);\r
+ }\r
+ monitor.worked(80);\r
+\r
+ DataNodeMap dn = new DataNodeMap() {\r
+ @Override\r
+ public INode getNode(Object data) {\r
+ if (DataNodeConstants.CANVAS_ROOT == data)\r
+ return canvas.getCanvasNode();\r
+ if (DataNodeConstants.DIAGRAM_ELEMENT_PARENT == data) {\r
+ ElementPainter ep = canvas.getAtMostOneItemOfClass(ElementPainter.class);\r
+ return ep != null ? ep.getDiagramElementParentNode() : null;\r
+ }\r
+\r
+ DataElementMap emap = GraphToDiagramSynchronizer.this.diagram.getDiagramClass().getSingleItem(DataElementMap.class);\r
+ IElement element = emap.getElement(GraphToDiagramSynchronizer.this.diagram, data);\r
+ if(element == null) return null;\r
+ return element.getHint(ElementHints.KEY_SG_NODE);\r
+ }\r
+ };\r
+\r
+ profileObserver = new ProfileObserver(g.getSession(), runtime,\r
+ canvas.getThreadAccess(), canvas, canvas.getSceneGraph(), diagram, \r
+ ArrayMap.keys(ProfileKeys.DIAGRAM, ProfileKeys.CANVAS, ProfileKeys.NODE_MAP).values(GraphToDiagramSynchronizer.this.diagram, canvas, dn),\r
+ new CanvasNotification(canvas));\r
+\r
+ profileObserver.listen(g, GraphToDiagramSynchronizer.this);\r
+\r
+ return d;\r
+\r
+ }\r
+\r
+ this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);\r
+ return this.diagram;\r
+\r
+ } finally {\r
+ idle();\r
+ }\r
+ } catch (InterruptedException e) {\r
+ throw new RuntimeException(e);\r
+ } catch (IllegalStateException e) {\r
+ // If the synchronizer was disposed ahead of time, it was done\r
+ // for a reason, such as the user having closed the owner editor.\r
+ if (!isAlive())\r
+ throw new CancelTransactionException(e);\r
+ throw new RuntimeException(e);\r
+ } finally {\r
+ Timing.END(loadTask);\r
+ }\r
+ }\r
+\r
+ static class CanvasNotification implements Runnable {\r
+\r
+ final private ICanvasContext canvas;\r
+\r
+ public CanvasNotification(ICanvasContext canvas) {\r
+ this.canvas = canvas;\r
+ }\r
+\r
+ public void run() {\r
+ canvas.getContentContext().setDirty();\r
+ }\r
+\r
+ }\r
+\r
+ ArrayList<IModification> pendingModifications = new ArrayList<IModification>();\r
+ MapSet<IElement, IModification> modificationIndex = new MapSet.Hash<IElement, IModification>();\r
+\r
+ void addModification(IElement element, IModification modification) {\r
+ pendingModifications.add(modification);\r
+ if (element != null)\r
+ modificationIndex.add(element, modification);\r
+\r
+ }\r
+ class DefaultDiagramMutator implements DiagramMutator {\r
+\r
+ Map<IElement, Resource> creation = new HashMap<IElement, Resource>();\r
+\r
+ IDiagram d;\r
+ Resource diagram;\r
+\r
+ IModifiableSynchronizationContext synchronizationContext;\r
+\r
+ public DefaultDiagramMutator(IDiagram d, Resource diagram, IModifiableSynchronizationContext synchronizationContext) {\r
+ this.d = d;\r
+ this.diagram = diagram;\r
+ this.synchronizationContext = synchronizationContext;\r
+\r
+ if (synchronizationContext.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER) == null)\r
+ throw new IllegalArgumentException("SynchronizationHints.ELEMENT_CLASS_PROVIDER not available");\r
+ }\r
+\r
+ void assertNotDisposed() {\r
+ if (!isAlive())\r
+ throw new IllegalStateException(getClass().getSimpleName() + " is disposed");\r
+ }\r
+\r
+ @Override\r
+ public IElement newElement(ElementClass clazz) {\r
+ assertNotDisposed();\r
+ ElementFactory ef = d.getDiagramClass().getAtMostOneItemOfClass(ElementFactory.class);\r
+ IElement element = null;\r
+ if (ef != null)\r
+ element = ef.spawnNew(clazz);\r
+ else\r
+ element = Element.spawnNew(clazz);\r
+\r
+ element.setHint(ElementHints.KEY_OBJECT, new TransientElementObject());\r
+\r
+ addModification(element, new AddElement(synchronizationContext, d, element));\r
+\r
+ return element;\r
+ }\r
+\r
+ @Override\r
+ public void commit() {\r
+ assertNotDisposed();\r
+ if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {\r
+ System.out.println("DiagramMutator is about to commit changes:");\r
+ for (IModification mod : pendingModifications)\r
+ System.out.println("\t- " + mod);\r
+ }\r
+\r
+ Collections.sort(pendingModifications);\r
+\r
+ if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {\r
+ if (pendingModifications.size() > 1) {\r
+ System.out.println("* changes were re-ordered to:");\r
+ for (IModification mod : pendingModifications)\r
+ System.out.println("\t" + mod);\r
+ }\r
+ }\r
+\r
+ Timing.safeTimed(errorHandler, "QUEUE AND WAIT FOR MODIFICATIONS TO FINISH", new GTask() {\r
+ @Override\r
+ public void run() throws DatabaseException {\r
+ // Performs a separate write request and query result update\r
+ // for each modification\r
+// for (IModification mod : pendingModifications) {\r
+// try {\r
+// modificationQueue.sync(mod);\r
+// } catch (InterruptedException e) {\r
+// error("Pending diagram modification " + mod\r
+// + " was interrupted. See exception for details.", e);\r
+// }\r
+// }\r
+\r
+ // NOTE: this is still under testing, the author is not\r
+ // truly certain that it should work in all cases ATM.\r
+\r
+ // Performs all modifications with in a single write request\r
+ for (IModification mod : pendingModifications) {\r
+ modificationQueue.offer(mod, null);\r
+ }\r
+ try {\r
+ // Perform the modifications in a single request.\r
+ modificationQueue.finish();\r
+ } catch (InterruptedException e) {\r
+ errorHandler.error("Diagram modification finishing was interrupted. See exception for details.", e);\r
+ }\r
+ }\r
+ });\r
+ pendingModifications.clear();\r
+ modificationIndex.clear();\r
+ creation.clear();\r
+ if (DebugPolicy.DEBUG_MUTATOR_COMMIT)\r
+ System.out.println("DiagramMutator has committed");\r
+ }\r
+\r
+ @Override\r
+ public void clear() {\r
+ assertNotDisposed();\r
+ pendingModifications.clear();\r
+ modificationIndex.clear();\r
+ creation.clear();\r
+ if (DebugPolicy.DEBUG_MUTATOR)\r
+ System.out.println("DiagramMutator has been cleared");\r
+ }\r
+\r
+ @Override\r
+ public void modifyTransform(IElement element) {\r
+ assertNotDisposed();\r
+ Resource resource = backendObject(element);\r
+ AffineTransform tr = element.getHint(ElementHints.KEY_TRANSFORM);\r
+ if (resource != null && tr != null) {\r
+ addModification(element, new TransformElement(resource, tr));\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void synchronizeHintsToBackend(IElement element) {\r
+ assertNotDisposed();\r
+ IHintSynchronizer synchronizer = element.getHint(SynchronizationHints.HINT_SYNCHRONIZER);\r
+ if (synchronizer != null) {\r
+ CollectingModificationQueue queue = new CollectingModificationQueue();\r
+ synchronizer.synchronize(synchronizationContext, element);\r
+ addModification(element, new CompositeModification(ModificationAdapter.LOW_PRIORITY, queue.getQueue()));\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void synchronizeElementOrder() {\r
+ assertNotDisposed();\r
+ List<IElement> snapshot = d.getSnapshot();\r
+ addModification(null, new ElementReorder(d, snapshot));\r
+ }\r
+\r
+ @Override\r
+ public void register(IElement element, Object object) {\r
+ creation.put(element, (Resource) object);\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ @Override\r
+ public <T> T backendObject(IElement element) {\r
+ Object object = ElementUtils.getObject(element);\r
+ if (object instanceof Resource)\r
+ return (T) object;\r
+ else\r
+ return (T) creation.get(element);\r
+ }\r
+\r
+ }\r
+\r
+ // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
+ // GRAPH TO DIAGRAM SYCHRONIZATION LOGIC BEGIN\r
+ // ------------------------------------------------------------------------\r
+\r
+ static class ConnectionData {\r
+ ConnectionEntityImpl impl;\r
+ List<Resource> branchPoints = new ArrayList<Resource>();\r
+ List<EdgeResource> segments = new ArrayList<EdgeResource>();\r
+\r
+ ConnectionData(ConnectionEntityImpl ce) {\r
+ this.impl = ce;\r
+ }\r
+\r
+ void addBranchPoint(Resource bp) {\r
+ branchPoints.add(bp);\r
+ }\r
+\r
+ void addSegment(EdgeResource seg) {\r
+ segments.add(seg);\r
+ }\r
+ }\r
+\r
+ class GraphToDiagramUpdater {\r
+ DiagramContents lastContent;\r
+ DiagramContents content;\r
+ DiagramContentChanges changes;\r
+\r
+ final List<IElement> addedElements;\r
+ final List<IElement> removedElements;\r
+\r
+ final List<IElement> addedConnectionSegments;\r
+ final List<IElement> removedConnectionSegments;\r
+\r
+ final List<IElement> addedBranchPoints;\r
+ final List<IElement> removedBranchPoints;\r
+\r
+ final Map<Object, IElement> addedElementMap;\r
+ final Map<Resource, IElement> addedConnectionMap;\r
+ final Map<Resource, ConnectionEntityImpl> addedConnectionEntities;\r
+ final List<Resource> removedConnectionEntities;\r
+ final Map<ConnectionEntityImpl, ConnectionData> changedConnectionEntities;\r
+\r
+ final Map<Resource, IElement> addedRouteGraphConnectionMap;\r
+ final List<IElement> removedRouteGraphConnections;\r
+\r
+\r
+ GraphToDiagramUpdater(DiagramContents lastContent, DiagramContents content, DiagramContentChanges changes) {\r
+ this.lastContent = lastContent;\r
+ this.content = content;\r
+ this.changes = changes;\r
+\r
+ this.addedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());\r
+ this.removedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());\r
+ this.addedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());\r
+ this.removedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());\r
+ this.addedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());\r
+ this.removedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());\r
+ this.addedElementMap = new HashMap<Object, IElement>();\r
+ this.addedConnectionMap = new HashMap<Resource, IElement>();\r
+ this.addedConnectionEntities = new HashMap<Resource, ConnectionEntityImpl>();\r
+ this.removedConnectionEntities = new ArrayList<Resource>(changes.connections.size());\r
+ this.changedConnectionEntities = new HashMap<ConnectionEntityImpl, ConnectionData>();\r
+ this.addedRouteGraphConnectionMap = new HashMap<Resource, IElement>();\r
+ this.removedRouteGraphConnections = new ArrayList<IElement>(changes.routeGraphConnections.size());\r
+ }\r
+\r
+ public void clear() {\r
+ // Prevent DiagramContents leakage through DisposableListeners.\r
+ lastContent = null;\r
+ content = null;\r
+ changes = null;\r
+ \r
+ this.addedElements.clear();\r
+ this.removedElements.clear();\r
+ this.addedConnectionSegments.clear();\r
+ this.removedConnectionSegments.clear();\r
+ this.addedBranchPoints.clear();\r
+ this.removedBranchPoints.clear();\r
+ this.addedElementMap.clear();\r
+ this.addedConnectionMap.clear();\r
+ this.addedConnectionEntities.clear();\r
+ this.removedConnectionEntities.clear();\r
+ this.changedConnectionEntities.clear();\r
+ this.addedRouteGraphConnectionMap.clear();\r
+ this.removedRouteGraphConnections.clear();\r
+ }\r
+\r
+ void processNodes(AsyncReadGraph graph) {\r
+\r
+ for (Map.Entry<Resource, Change> entry : changes.elements.entrySet()) {\r
+\r
+ final Resource element = entry.getKey();\r
+ Change change = entry.getValue();\r
+\r
+ switch (change) {\r
+ case ADDED: {\r
+ IElement mappedElement = getMappedElement(element);\r
+ if (mappedElement == null) {\r
+ if (DebugPolicy.DEBUG_NODE_LOAD)\r
+ graph.asyncRequest(new ReadRequest() {\r
+ @Override\r
+ public void run(ReadGraph graph) throws DatabaseException {\r
+ System.out.println(" EXTERNALLY ADDED ELEMENT: "\r
+ + NameUtils.getSafeName(graph, element) + " ("\r
+ + element.getResourceId() + ")");\r
+ }\r
+ });\r
+\r
+ if (content.connectionSet.contains(element)) {\r
+\r
+ // TODO: Connection loading has no listening, changes :Connection will not be noticed by this code!\r
+ Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {\r
+ @Override\r
+ public String toString() {\r
+ return "Connection load listener for " + element;\r
+ }\r
+ @Override\r
+ public void execute(IElement loaded) {\r
+ // Invoked when the element has been loaded.\r
+ if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
+ System.out.println("CONNECTION LoadListener for " + loaded);\r
+\r
+ if (loaded == null) {\r
+ disposeListener();\r
+ return;\r
+ }\r
+\r
+ Object data = loaded.getHint(ElementHints.KEY_OBJECT);\r
+\r
+ // Logic for disposing listener\r
+ if (!previousContent.connectionSet.contains(data)) {\r
+ if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
+ System.out.println("CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");\r
+ disposeListener();\r
+ return;\r
+ }\r
+\r
+ if (addedElementMap.containsKey(data)) {\r
+ // This element was just loaded, in\r
+ // which case its hints need to\r
+ // uploaded to the real mapped\r
+ // element immediately.\r
+ IElement mappedElement = getMappedElement(data);\r
+ if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
+ System.out.println("LOADED ADDED CONNECTION, currently mapped connection: " + mappedElement);\r
+ if (mappedElement != null && (mappedElement instanceof Element)) {\r
+ if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {\r
+ System.out.println(" mapped hints: " + mappedElement.getHints());\r
+ System.out.println(" loaded hints: " + loaded.getHints());\r
+ }\r
+ updateMappedElement((Element) mappedElement, loaded);\r
+ }\r
+ } else {\r
+ // This element was already loaded.\r
+ // Just schedule an update some time\r
+ // in the future.\r
+ if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
+ System.out.println("PREVIOUSLY LOADED CONNECTION UPDATED, scheduling update into the future");\r
+ offerGraphUpdate( connectionUpdater(element, loaded) );\r
+ }\r
+ }\r
+ };\r
+\r
+ graph.asyncRequest(new ConnectionRequest(canvas, diagram, element, errorHandler, loadListener), new AsyncProcedure<IElement>() {\r
+ @Override\r
+ public void execute(AsyncReadGraph graph, final IElement e) {\r
+ if (e == null)\r
+ return;\r
+\r
+ //System.out.println("ConnectionRequestProcedure " + e);\r
+ mapElement(element, e);\r
+ synchronized (GraphToDiagramUpdater.this) {\r
+ addedElements.add(e);\r
+ addedElementMap.put(element, e);\r
+ addedConnectionMap.put(element, e);\r
+ }\r
+\r
+ // Read connection type\r
+ graph.forSingleType(element, br.DIA.Connection, new Procedure<Resource>() {\r
+ @Override\r
+ public void exception(Throwable t) {\r
+ error(t);\r
+ }\r
+\r
+ @Override\r
+ public void execute(Resource connectionType) {\r
+ synchronized (GraphToDiagramUpdater.this) {\r
+ //System.out.println("new connection entity " + e);\r
+ ConnectionEntityImpl entity = new ConnectionEntityImpl(element, connectionType, e);\r
+ e.setHint(ElementHints.KEY_CONNECTION_ENTITY, entity);\r
+ addedConnectionEntities.put(element, entity);\r
+ }\r
+ }\r
+ });\r
+ }\r
+\r
+ @Override\r
+ public void exception(AsyncReadGraph graph, Throwable throwable) {\r
+ error(throwable);\r
+ }\r
+ });\r
+ } else if (content.nodeSet.contains(element)) {\r
+\r
+ Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {\r
+ @Override\r
+ public String toString() {\r
+ return "Node load listener for " + element;\r
+ }\r
+ @Override\r
+ public void execute(IElement loaded) {\r
+ // Invoked when the element has been loaded.\r
+ if (DebugPolicy.DEBUG_NODE_LISTENER)\r
+ System.out.println("NODE LoadListener for " + loaded);\r
+\r
+ if (loaded == null) {\r
+ disposeListener();\r
+ return;\r
+ }\r
+\r
+ Object data = loaded.getHint(ElementHints.KEY_OBJECT);\r
+\r
+ // Logic for disposing listener\r
+ if (!previousContent.nodeSet.contains(data)) {\r
+ if (DebugPolicy.DEBUG_NODE_LISTENER)\r
+ System.out.println("NODE LoadListener, node not in current content: " + data + ". Disposing.");\r
+ disposeListener();\r
+ return;\r
+ }\r
+\r
+ if (addedElementMap.containsKey(data)) {\r
+ // This element was just loaded, in\r
+ // which case its hints need to\r
+ // uploaded to the real mapped\r
+ // element immediately.\r
+ IElement mappedElement = getMappedElement(data);\r
+ if (DebugPolicy.DEBUG_NODE_LISTENER)\r
+ System.out.println("LOADED ADDED ELEMENT, currently mapped element: " + mappedElement);\r
+ if (mappedElement != null && (mappedElement instanceof Element)) {\r
+ if (DebugPolicy.DEBUG_NODE_LISTENER) {\r
+ System.out.println(" mapped hints: " + mappedElement.getHints());\r
+ System.out.println(" loaded hints: " + loaded.getHints());\r
+ }\r
+ updateMappedElement((Element) mappedElement, loaded);\r
+ }\r
+ } else {\r
+ // This element was already loaded.\r
+ // Just schedule an update some time\r
+ // in the future.\r
+ if (DebugPolicy.DEBUG_NODE_LISTENER)\r
+ System.out.println("PREVIOUSLY LOADED NODE UPDATED, scheduling update into the future");\r
+ offerGraphUpdate( nodeUpdater(element, loaded) );\r
+ }\r
+ }\r
+ };\r
+\r
+ //System.out.println("NODE REQUEST: " + element);\r
+ graph.asyncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {\r
+ @Override\r
+ public void execute(AsyncReadGraph graph, IElement e) {\r
+ if (e == null)\r
+ return;\r
+\r
+ // This is invoked before the element is actually loaded.\r
+ //System.out.println("NodeRequestProcedure " + e);\r
+ if (DebugPolicy.DEBUG_NODE_LOAD)\r
+ System.out.println("MAPPING ADDED NODE: " + element + " -> " + e);\r
+ mapElement(element, e);\r
+ synchronized (GraphToDiagramUpdater.this) {\r
+ addedElements.add(e);\r
+ addedElementMap.put(element, e);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void exception(AsyncReadGraph graph, Throwable throwable) {\r
+ error(throwable);\r
+ }\r
+ });\r
+\r
+ } else {\r
+// warning("Diagram elements must be either elements or connections, "\r
+// + NameUtils.getSafeName(g, element) + " is neither",\r
+// new AssumptionException(""));\r
+ }\r
+ }\r
+ break;\r
+ }\r
+ case REMOVED: {\r
+ IElement e = getMappedElement(element);\r
+ if (DebugPolicy.DEBUG_NODE_LOAD)\r
+ graph.asyncRequest(new ReadRequest() {\r
+ @Override\r
+ public void run(ReadGraph graph) throws DatabaseException {\r
+ System.out.println(" EXTERNALLY REMOVED ELEMENT: "\r
+ + NameUtils.getSafeName(graph, element) + " ("\r
+ + element.getResourceId() + ")");\r
+ }\r
+ });\r
+ if (e != null) {\r
+ removedElements.add(e);\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ void gatherChangedConnectionParts(Map<?, Change> changes) {\r
+ for (Map.Entry<?, Change> entry : changes.entrySet()) {\r
+ Object part = entry.getKey();\r
+ Change change = entry.getValue();\r
+\r
+ switch (change) {\r
+ case ADDED: {\r
+ synchronized (GraphToDiagramUpdater.this) {\r
+ Resource connection = content.partToConnection.get(part);\r
+ assert connection != null;\r
+\r
+ IElement ce = getMappedElement(connection);\r
+ if (ce == null)\r
+ ce = addedElementMap.get(connection);\r
+\r
+ if (ce != null)\r
+ markConnectionChanged(ce);\r
+ break;\r
+ }\r
+ }\r
+ case REMOVED: {\r
+ if (lastContent == null)\r
+ break;\r
+ Resource connection = lastContent.partToConnection.get(part);\r
+ if (connection != null && content.connectionSet.contains(connection)) {\r
+ markConnectionChanged(connection);\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ void markConnectionChanged(Resource connection) {\r
+// System.out.println("markConnectionChanged");\r
+ ConnectionEntityImpl ce = getMappedConnection(connection);\r
+ if (ce != null) {\r
+ markConnectionChanged(ce);\r
+ return;\r
+ }\r
+ error("WARNING: marking connection entity " + connection\r
+ + " changed, but the connection was not previously mapped",\r
+ new Exception("created exception to get a stack trace"));\r
+ }\r
+\r
+ void markConnectionChanged(IElement connection) {\r
+ ConnectionEntityImpl entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (entity != null)\r
+ markConnectionChanged(entity);\r
+ }\r
+\r
+ void markConnectionChanged(ConnectionEntityImpl ce) {\r
+ if (!changedConnectionEntities.containsKey(ce)) {\r
+ changedConnectionEntities.put(ce, new ConnectionData(ce));\r
+ }\r
+ }\r
+\r
+ void processConnections() {\r
+ // Find added/removed connection segments/branch points\r
+ // in order to find all changed connection entities.\r
+ gatherChangedConnectionParts(changes.connectionSegments);\r
+ gatherChangedConnectionParts(changes.branchPoints);\r
+\r
+ // Find removed connection entities\r
+ for (Map.Entry<Resource, Change> entry : changes.connections.entrySet()) {\r
+ Resource ce = entry.getKey();\r
+ Change change = entry.getValue();\r
+\r
+ switch (change) {\r
+ case REMOVED: {\r
+ removedConnectionEntities.add(ce);\r
+ }\r
+ }\r
+ }\r
+\r
+ // Generate update data of changed connection entities.\r
+ // This ConnectionData will be applied in the canvas thread\r
+ // diagram updater.\r
+ for (ConnectionData cd : changedConnectionEntities.values()) {\r
+ for (Object part : content.connectionToParts.getValuesUnsafe(cd.impl.connection)) {\r
+ if (part instanceof Resource) {\r
+ cd.branchPoints.add((Resource) part);\r
+ } else if (part instanceof EdgeResource) {\r
+ cd.segments.add((EdgeResource) part);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ void processRouteGraphConnections(AsyncReadGraph graph) {\r
+ for (Map.Entry<Resource, Change> entry : changes.routeGraphConnections.entrySet()) {\r
+ final Resource connection = entry.getKey();\r
+\r
+ Change change = entry.getValue();\r
+ switch (change) {\r
+ case ADDED: {\r
+ IElement mappedElement = getMappedElement(connection);\r
+ if (mappedElement != null)\r
+ continue;\r
+\r
+ Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {\r
+ @Override\r
+ public String toString() {\r
+ return "processRouteGraphConnections " + connection;\r
+ }\r
+ @Override\r
+ public void execute(IElement loaded) {\r
+ // Invoked when the element has been loaded.\r
+ if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
+ System.out.println("ROUTE GRAPH CONNECTION LoadListener for " + loaded);\r
+\r
+ if (loaded == null) {\r
+ disposeListener();\r
+ return;\r
+ }\r
+\r
+ Object data = loaded.getHint(ElementHints.KEY_OBJECT);\r
+\r
+ // Logic for disposing listener\r
+ if (!previousContent.routeGraphConnectionSet.contains(data)) {\r
+ if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
+ System.out.println("ROUTE GRAPH CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");\r
+ disposeListener();\r
+ return;\r
+ }\r
+\r
+ if (addedElementMap.containsKey(data)) {\r
+ // This element was just loaded, in\r
+ // which case its hints need to\r
+ // uploaded to the real mapped\r
+ // element immediately.\r
+ IElement mappedElement = getMappedElement(data);\r
+ if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
+ System.out.println("LOADED ADDED ROUTE GRAPH CONNECTION, currently mapped connection: " + mappedElement);\r
+ if (mappedElement instanceof Element) {\r
+ if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {\r
+ System.out.println(" mapped hints: " + mappedElement.getHints());\r
+ System.out.println(" loaded hints: " + loaded.getHints());\r
+ }\r
+ updateMappedElement((Element) mappedElement, loaded);\r
+ }\r
+ } else {\r
+ // This element was already loaded.\r
+ // Just schedule an update some time\r
+ // in the future.\r
+ if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
+ System.out.println("PREVIOUSLY LOADED ROUTE GRAPH CONNECTION UPDATED, scheduling update into the future: " + connection);\r
+\r
+ Set<Object> dirtyNodes = new THashSet<Object>(4);\r
+ IElement mappedElement = getMappedElement(connection);\r
+ ConnectionEntity ce = mappedElement.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce != null) {\r
+ for (Connection conn : ce.getTerminalConnections(null)) {\r
+ Object o = conn.node.getHint(ElementHints.KEY_OBJECT);\r
+ if (o != null) {\r
+ dirtyNodes.add(o);\r
+ if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
+ System.out.println("Marked connectivity dirty for node: " + conn.node);\r
+ }\r
+ }\r
+ }\r
+\r
+ offerGraphUpdate( routeGraphConnectionUpdater(connection, loaded, dirtyNodes) );\r
+ }\r
+ }\r
+ };\r
+\r
+ graph.asyncRequest(new ConnectionRequest(canvas, diagram, connection, errorHandler, loadListener), new Procedure<IElement>() {\r
+ @Override\r
+ public void execute(final IElement e) {\r
+ if (e == null)\r
+ return;\r
+\r
+ //System.out.println("ConnectionRequestProcedure " + e);\r
+ if (DebugPolicy.DEBUG_NODE_LOAD)\r
+ System.out.println("MAPPING ADDED ROUTE GRAPH CONNECTION: " + connection + " -> " + e);\r
+ mapElement(connection, e);\r
+ synchronized (GraphToDiagramUpdater.this) {\r
+ addedElements.add(e);\r
+ addedElementMap.put(connection, e);\r
+ addedRouteGraphConnectionMap.put(connection, e);\r
+ }\r
+ }\r
+ @Override\r
+ public void exception(Throwable throwable) {\r
+ error(throwable);\r
+ }\r
+ });\r
+ break;\r
+ }\r
+ case REMOVED: {\r
+ IElement e = getMappedElement(connection);\r
+ if (e != null)\r
+ removedRouteGraphConnections.add(e);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ ConnectionEntityImpl getConnectionEntity(Object connectionPart) {\r
+ Resource connection = content.partToConnection.get(connectionPart);\r
+ assert connection != null;\r
+ ConnectionEntityImpl ce = addedConnectionEntities.get(connection);\r
+ if (ce != null)\r
+ return ce;\r
+ return assertMappedConnection(connection);\r
+ }\r
+\r
+ void processBranchPoints(AsyncReadGraph graph) {\r
+ for (Map.Entry<Resource, Change> entry : changes.branchPoints.entrySet()) {\r
+\r
+ final Resource element = entry.getKey();\r
+ Change change = entry.getValue();\r
+\r
+ switch (change) {\r
+ case ADDED: {\r
+ IElement mappedElement = getMappedElement(element);\r
+ if (mappedElement == null) {\r
+ if (DebugPolicy.DEBUG_NODE_LOAD)\r
+ graph.asyncRequest(new ReadRequest() {\r
+ @Override\r
+ public void run(ReadGraph graph) throws DatabaseException {\r
+ System.out.println(" EXTERNALLY ADDED BRANCH POINT: "\r
+ + NameUtils.getSafeName(graph, element) + " ("\r
+ + element.getResourceId() + ")");\r
+ }\r
+ });\r
+\r
+ Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {\r
+ @Override\r
+ public String toString() {\r
+ return "processBranchPoints for " + element;\r
+ }\r
+ @Override\r
+ public void execute(IElement loaded) {\r
+ // Invoked when the element has been loaded.\r
+ if (DebugPolicy.DEBUG_NODE_LISTENER)\r
+ System.out.println("BRANCH POINT LoadListener for " + loaded);\r
+\r
+ if (loaded == null) {\r
+ disposeListener();\r
+ return;\r
+ }\r
+\r
+ Object data = loaded.getHint(ElementHints.KEY_OBJECT);\r
+ if (addedElementMap.containsKey(data)) {\r
+ // This element was just loaded, in\r
+ // which case its hints need to\r
+ // uploaded to the real mapped\r
+ // element immediately.\r
+ IElement mappedElement = getMappedElement(data);\r
+ if (DebugPolicy.DEBUG_NODE_LISTENER)\r
+ System.out.println("LOADED ADDED BRANCH POINT, currently mapped element: " + mappedElement);\r
+ if (mappedElement != null && (mappedElement instanceof Element)) {\r
+ if (DebugPolicy.DEBUG_NODE_LISTENER) {\r
+ System.out.println(" mapped hints: " + mappedElement.getHints());\r
+ System.out.println(" loaded hints: " + loaded.getHints());\r
+ }\r
+ updateMappedElement((Element) mappedElement, loaded);\r
+ }\r
+ } else {\r
+ // This element was already loaded.\r
+ // Just schedule an update some time\r
+ // in the future.\r
+ if (DebugPolicy.DEBUG_NODE_LISTENER)\r
+ System.out.println("PREVIOUSLY LOADED BRANCH POINT UPDATED, scheduling update into the future");\r
+ offerGraphUpdate( nodeUpdater(element, loaded) );\r
+ }\r
+ }\r
+ };\r
+\r
+ graph.asyncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {\r
+ @Override\r
+ public void execute(AsyncReadGraph graph, IElement e) {\r
+ if (e != null) {\r
+ mapElement(element, e);\r
+ synchronized (GraphToDiagramUpdater.this) {\r
+ addedBranchPoints.add(e);\r
+ addedElementMap.put(element, e);\r
+ ConnectionEntityImpl ce = getConnectionEntity(element);\r
+ e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);\r
+ e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void exception(AsyncReadGraph graph, Throwable throwable) {\r
+ error(throwable);\r
+ }\r
+ });\r
+ }\r
+ break;\r
+ }\r
+ case REMOVED: {\r
+ IElement e = getMappedElement(element);\r
+ if (DebugPolicy.DEBUG_NODE_LOAD)\r
+ graph.asyncRequest(new ReadRequest() {\r
+ @Override\r
+ public void run(ReadGraph graph) throws DatabaseException {\r
+ System.out.println(" EXTERNALLY REMOVED BRANCH POINT: "\r
+ + NameUtils.getSafeName(graph, element) + " ("\r
+ + element.getResourceId() + ")");\r
+ }\r
+ });\r
+ if (e != null) {\r
+ removedBranchPoints.add(e);\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ void processConnectionSegments(AsyncReadGraph graph) {\r
+ ConnectionSegmentAdapter adapter = connectionSegmentAdapter;\r
+\r
+ for (Map.Entry<EdgeResource, Change> entry : changes.connectionSegments.entrySet()) {\r
+ final EdgeResource seg = entry.getKey();\r
+ Change change = entry.getValue();\r
+\r
+ switch (change) {\r
+ case ADDED: {\r
+ IElement mappedElement = getMappedElement(seg);\r
+ if (mappedElement == null) {\r
+ if (DebugPolicy.DEBUG_EDGE_LOAD)\r
+ graph.asyncRequest(new ReadRequest() {\r
+ @Override\r
+ public void run(ReadGraph graph) throws DatabaseException {\r
+ System.out.println(" EXTERNALLY ADDED CONNECTION SEGMENT: " + seg.toString()\r
+ + " - " + seg.toString(graph));\r
+ }\r
+ });\r
+\r
+ graph.asyncRequest(new EdgeRequest(canvas, errorHandler, canvasListenerSupport, diagram, adapter, seg), new AsyncProcedure<IElement>() {\r
+ @Override\r
+ public void execute(AsyncReadGraph graph, IElement e) {\r
+ if (DebugPolicy.DEBUG_EDGE_LOAD)\r
+ System.out.println("ADDED EDGE LOADED: " + e);\r
+ if (e != null) {\r
+ mapElement(seg, e);\r
+ synchronized (GraphToDiagramUpdater.this) {\r
+ addedConnectionSegments.add(e);\r
+ addedElementMap.put(seg, e);\r
+ ConnectionEntityImpl ce = getConnectionEntity(seg);\r
+ e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);\r
+ e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void exception(AsyncReadGraph graph, Throwable throwable) {\r
+ error(throwable);\r
+ }\r
+ });\r
+ }\r
+ break;\r
+ }\r
+ case REMOVED: {\r
+ final IElement e = getMappedElement(seg);\r
+ if (DebugPolicy.DEBUG_EDGE_LOAD)\r
+ graph.asyncRequest(new ReadRequest() {\r
+ @Override\r
+ public void run(ReadGraph graph) throws DatabaseException {\r
+ System.out.println(" EXTERNALLY REMOVED CONNECTION SEGMENT: " + seg.toString() + " - "\r
+ + seg.toString(graph) + " -> " + e);\r
+ }\r
+ });\r
+ if (e != null) {\r
+ removedConnectionSegments.add(e);\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ void executeDeferredLoaders(ReadGraph graph) throws DatabaseException {\r
+ // The rest of the diagram loading passes\r
+ Deque<IElement> q1 = new ArrayDeque<IElement>();\r
+ Deque<IElement> q2 = new ArrayDeque<IElement>();\r
+ collectElementLoaders(q1, addedElements);\r
+ while (!q1.isEmpty()) {\r
+ //System.out.println("DEFFERED LOADERS: " + q1);\r
+ for (IElement e : q1) {\r
+ ElementLoader loader = e.removeHint(DiagramModelHints.KEY_ELEMENT_LOADER);\r
+ //System.out.println("EXECUTING DEFFERED LOADER: " + loader);\r
+ loader.load(graph, diagram, e);\r
+ }\r
+\r
+ collectElementLoaders(q2, q1);\r
+ Deque<IElement> qt = q1;\r
+ q1 = q2;\r
+ q2 = qt;\r
+ q2.clear();\r
+ }\r
+ }\r
+\r
+ private void collectElementLoaders(Queue<IElement> queue, Collection<IElement> cs) {\r
+ for (IElement e : cs) {\r
+ ElementLoader loader = e.getHint(DiagramModelHints.KEY_ELEMENT_LOADER);\r
+ if (loader != null)\r
+ queue.add(e);\r
+ }\r
+ }\r
+\r
+ public void process(ReadGraph graph) throws DatabaseException {\r
+ // No changes? Do nothing.\r
+ if (changes.isEmpty())\r
+ return;\r
+\r
+ // NOTE: This order is important.\r
+ Object task = Timing.BEGIN("processNodesConnections");\r
+ //System.out.println("---- PROCESS NODES & CONNECTIONS BEGIN");\r
+ if (!changes.elements.isEmpty()) {\r
+ graph.syncRequest(new AsyncReadRequest() {\r
+ @Override\r
+ public void run(AsyncReadGraph graph) {\r
+ processNodes(graph);\r
+ }\r
+ @Override\r
+ public String toString() {\r
+ return "processNodes";\r
+ }\r
+ });\r
+ }\r
+ //System.out.println("---- PROCESS NODES & CONNECTIONS END");\r
+\r
+ processConnections();\r
+\r
+ //System.out.println("---- PROCESS BRANCH POINTS BEGIN");\r
+ if (!changes.branchPoints.isEmpty()) {\r
+ graph.syncRequest(new AsyncReadRequest() {\r
+ @Override\r
+ public void run(AsyncReadGraph graph) {\r
+ processBranchPoints(graph);\r
+ }\r
+ @Override\r
+ public String toString() {\r
+ return "processBranchPoints";\r
+ }\r
+ });\r
+ }\r
+ //System.out.println("---- PROCESS BRANCH POINTS END");\r
+\r
+ Timing.END(task);\r
+ task = Timing.BEGIN("processConnectionSegments");\r
+\r
+ //System.out.println("---- PROCESS CONNECTION SEGMENTS BEGIN");\r
+ if (!changes.connectionSegments.isEmpty()) {\r
+ graph.syncRequest(new AsyncReadRequest() {\r
+ @Override\r
+ public void run(AsyncReadGraph graph) {\r
+ processConnectionSegments(graph);\r
+ }\r
+ @Override\r
+ public String toString() {\r
+ return "processConnectionSegments";\r
+ }\r
+ });\r
+ }\r
+ //System.out.println("---- PROCESS CONNECTION SEGMENTS END");\r
+\r
+ Timing.END(task);\r
+\r
+ task = Timing.BEGIN("processRouteGraphConnections");\r
+ if (!changes.routeGraphConnections.isEmpty()) {\r
+ graph.syncRequest(new AsyncReadRequest() {\r
+ @Override\r
+ public void run(AsyncReadGraph graph) {\r
+ processRouteGraphConnections(graph);\r
+ }\r
+ @Override\r
+ public String toString() {\r
+ return "processRouteGraphConnections";\r
+ }\r
+ });\r
+ }\r
+ Timing.END(task);\r
+\r
+ //System.out.println("---- AFTER LOADING");\r
+ //for (IElement e : addedElements)\r
+ // System.out.println(" ADDED ELEMENT: " + e);\r
+ //for (IElement e : addedBranchPoints)\r
+ // System.out.println(" ADDED BRANCH POINTS: " + e);\r
+\r
+ task = Timing.BEGIN("executeDeferredLoaders");\r
+ executeDeferredLoaders(graph);\r
+ Timing.END(task);\r
+ }\r
+\r
+ public boolean isEmpty() {\r
+ return addedElements.isEmpty() && removedElements.isEmpty()\r
+ && addedConnectionSegments.isEmpty() && removedConnectionSegments.isEmpty()\r
+ && addedBranchPoints.isEmpty() && removedBranchPoints.isEmpty()\r
+ && addedConnectionEntities.isEmpty() && removedConnectionEntities.isEmpty()\r
+ && addedRouteGraphConnectionMap.isEmpty() && removedRouteGraphConnections.isEmpty()\r
+ && !changes.elementOrderChanged;\r
+ }\r
+\r
+ class DefaultConnectionSegmentAdapter implements ConnectionSegmentAdapter {\r
+\r
+ @Override\r
+ public void getClass(AsyncReadGraph graph, EdgeResource edge, ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, IDiagram diagram, final AsyncProcedure<ElementClass> procedure) {\r
+ if (info.connectionType != null) {\r
+ NodeClassRequest request = new NodeClassRequest(canvas, diagram, info.connectionType, true);\r
+ graph.asyncRequest(request, new CacheListener<ElementClass>(listenerSupport));\r
+ graph.asyncRequest(request, procedure);\r
+ } else {\r
+ procedure.execute(graph, null);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void load(AsyncReadGraph graph, final EdgeResource edge, final ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, final IDiagram diagram, final IElement element) {\r
+ graph.asyncRequest(new Read<IElement>() {\r
+ @Override\r
+ public IElement perform(ReadGraph graph) throws DatabaseException {\r
+ //ITask task = ThreadLogger.getInstance().begin("LoadSegment");\r
+ syncLoad(graph, edge, info, diagram, element);\r
+ //task.finish();\r
+ return element;\r
+ }\r
+ @Override\r
+ public String toString() {\r
+ return "defaultConnectionSegmentAdapter";\r
+ }\r
+ }, new DisposableListener<IElement>(listenerSupport) {\r
+ \r
+ @Override\r
+ public String toString() {\r
+ return "DefaultConnectionSegmentAdapter listener for " + edge;\r
+ }\r
+ \r
+ @Override\r
+ public void execute(IElement loaded) {\r
+ // Invoked when the element has been loaded.\r
+ if (DebugPolicy.DEBUG_EDGE_LISTENER)\r
+ System.out.println("EDGE LoadListener for " + loaded);\r
+\r
+ if (loaded == null) {\r
+ disposeListener();\r
+ return;\r
+ }\r
+\r
+ Object data = loaded.getHint(ElementHints.KEY_OBJECT);\r
+ if (addedElementMap.containsKey(data)) {\r
+ // This element was just loaded, in\r
+ // which case its hints need to\r
+ // uploaded to the real mapped\r
+ // element immediately.\r
+ IElement mappedElement = getMappedElement(data);\r
+ if (DebugPolicy.DEBUG_EDGE_LISTENER)\r
+ System.out.println("LOADED ADDED EDGE, currently mapped element: " + mappedElement);\r
+ if (mappedElement != null && (mappedElement instanceof Element)) {\r
+ if (DebugPolicy.DEBUG_EDGE_LISTENER) {\r
+ System.out.println(" mapped hints: " + mappedElement.getHints());\r
+ System.out.println(" loaded hints: " + loaded.getHints());\r
+ }\r
+ updateMappedElement((Element) mappedElement, loaded);\r
+ }\r
+ } else {\r
+ // This element was already loaded.\r
+ // Just schedule an update some time\r
+ // in the future.\r
+ if (DebugPolicy.DEBUG_EDGE_LISTENER)\r
+ System.out.println("PREVIOUSLY LOADED EDGE UPDATED, scheduling update into the future");\r
+ offerGraphUpdate( edgeUpdater(element, loaded) );\r
+ }\r
+ }\r
+ });\r
+ }\r
+\r
+ void syncLoad(ReadGraph graph, EdgeResource connectionSegment, ConnectionInfo info, IDiagram diagram, IElement element) throws DatabaseException {\r
+ // Check that at least some data exists before continuing further.\r
+ if (!graph.hasStatement(connectionSegment.first()) && !graph.hasStatement(connectionSegment.second())) {\r
+ return;\r
+ }\r
+\r
+ // Validate that both ends of the segment are\r
+ // part of the same connection before loading.\r
+ // This will happen for connections that are\r
+ // modified through splitting and joining of\r
+ // connection segments.\r
+ Resource c = ConnectionUtil.tryGetConnection(graph, connectionSegment);\r
+ if (c == null) {\r
+ // Ok, this segment is somehow invalid. Just don't load it.\r
+ if (DebugPolicy.DEBUG_CONNECTION_LOAD)\r
+ System.out.println("Skipping edge " + connectionSegment + ". Both segment ends are not part of the same connection.");\r
+ return;\r
+ }\r
+\r
+ if (!info.isValid()) {\r
+ // This edge must be somehow invalid, don't proceed with loading.\r
+ if (DebugPolicy.DEBUG_CONNECTION_LOAD)\r
+ warning("Cannot load edge " + connectionSegment + ". ConnectionInfo " + info + " is invalid.", new DebugException("execution trace"));\r
+ return;\r
+ }\r
+\r
+ Element edge = (Element) element;\r
+ edge.setHint(ElementHints.KEY_OBJECT, connectionSegment);\r
+\r
+ // connectionSegment resources may currently be in a different\r
+ // order than ConnectionInfo.firstEnd/secondEnd. Therefore the\r
+ // segment ends must be resolved here.\r
+ ConnectionSegmentEnd firstEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.first());\r
+ ConnectionSegmentEnd secondEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.second());\r
+ if (firstEnd == null || secondEnd == null) {\r
+ if (DebugPolicy.DEBUG_CONNECTION_LOAD)\r
+ warning("End attachments for edge " + connectionSegment + " are unresolved: (" + firstEnd + "," + secondEnd + ")", new DebugException("execution trace"));\r
+ return;\r
+ }\r
+\r
+ if (DebugPolicy.DEBUG_CONNECTION_LOAD)\r
+ System.out.println("CONNECTION INFO: " + connectionSegment + " - " + info);\r
+ DesignatedTerminal firstTerminal = DiagramGraphUtil.findDesignatedTerminal(\r
+ graph, diagram, connectionSegment.first(), firstEnd);\r
+ DesignatedTerminal secondTerminal = DiagramGraphUtil.findDesignatedTerminal(\r
+ graph, diagram, connectionSegment.second(), secondEnd);\r
+\r
+ // Edges must be connected at both ends in order for edge loading to succeed.\r
+ String err = validateConnectivity(graph, connectionSegment, firstTerminal, secondTerminal);\r
+ if (err != null) {\r
+ // Stop loading edge if the connectivity cannot be completely resolved.\r
+ if (DebugPolicy.DEBUG_CONNECTION_LOAD)\r
+ warning(err, null);\r
+ return;\r
+ }\r
+\r
+ // NOTICE: Layer information is loaded from the connection entity resource\r
+ // that is shared by all segments of the same connection.\r
+ ElementFactoryUtil.loadLayersForElement(graph, layerManager, diagram, edge, info.connection,\r
+ new AsyncProcedureAdapter<IElement>() {\r
+ @Override\r
+ public void exception(AsyncReadGraph graph, Throwable t) {\r
+ error("failed to load layers for connection segment", t);\r
+ }\r
+ });\r
+\r
+ edge.setHintWithoutNotification(KEY_CONNECTION_BEGIN_PLACEHOLDER, new PlaceholderConnection(\r
+ EdgeEnd.Begin,\r
+ firstTerminal.element.getHint(ElementHints.KEY_OBJECT),\r
+ firstTerminal.terminal));\r
+ edge.setHintWithoutNotification(KEY_CONNECTION_END_PLACEHOLDER, new PlaceholderConnection(\r
+ EdgeEnd.End,\r
+ secondTerminal.element.getHint(ElementHints.KEY_OBJECT),\r
+ secondTerminal.terminal));\r
+\r
+ IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);\r
+ if (modelingRules != null) {\r
+ ConnectionVisualsLoader loader = diagram.getHint(DiagramModelHints.KEY_CONNECTION_VISUALS_LOADER);\r
+ if (loader != null)\r
+ loader.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);\r
+ else\r
+ DiagramGraphUtil.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);\r
+ }\r
+ }\r
+\r
+ private String validateConnectivity(ReadGraph graph, EdgeResource edge,\r
+ DesignatedTerminal firstTerminal,\r
+ DesignatedTerminal secondTerminal)\r
+ throws DatabaseException {\r
+ boolean firstLoose = firstTerminal == null;\r
+ boolean secondLoose = secondTerminal == null;\r
+ boolean stray = firstLoose && secondLoose;\r
+ if (firstTerminal == null || secondTerminal == null) {\r
+ StringBuilder sb = new StringBuilder();\r
+ sb.append("encountered ");\r
+ sb.append(stray ? "stray" : "loose");\r
+ sb.append(" connection segment, ");\r
+ if (firstLoose)\r
+ sb.append("first ");\r
+ if (stray)\r
+ sb.append("and ");\r
+ if (secondLoose)\r
+ sb.append("second ");\r
+ sb.append("end disconnected: ");\r
+ sb.append(edge.toString(graph));\r
+ sb.append(" - ");\r
+ sb.append(edge.toString());\r
+ return sb.toString();\r
+ }\r
+ return null;\r
+ }\r
+\r
+ }\r
+\r
+ ConnectionSegmentAdapter connectionSegmentAdapter = new DefaultConnectionSegmentAdapter();\r
+\r
+ }\r
+\r
+ private static final Double DIAGRAM_UPDATE_DIAGRAM_PRIORITY = 1d;\r
+ private static final Double DIAGRAM_UPDATE_NODE_PRIORITY = 2d;\r
+ private static final Double DIAGRAM_UPDATE_CONNECTION_PRIORITY = 3d;\r
+ private static final Double DIAGRAM_UPDATE_EDGE_PRIORITY = 4d;\r
+\r
+ interface DiagramUpdater extends Runnable {\r
+ Double getPriority();\r
+\r
+ Comparator<DiagramUpdater> DIAGRAM_UPDATER_COMPARATOR = new Comparator<DiagramUpdater>() {\r
+ @Override\r
+ public int compare(DiagramUpdater o1, DiagramUpdater o2) {\r
+ return o1.getPriority().compareTo(o2.getPriority());\r
+ }\r
+ };\r
+ }\r
+\r
+ interface GraphUpdateReactor {\r
+ DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException;\r
+ }\r
+\r
+ static abstract class AbstractDiagramUpdater implements DiagramUpdater, GraphUpdateReactor {\r
+ protected final Double priority;\r
+ protected final String runnerName;\r
+\r
+ public AbstractDiagramUpdater(Double priority, String runnerName) {\r
+ if (priority == null)\r
+ throw new NullPointerException("null priority");\r
+ if (runnerName == null)\r
+ throw new NullPointerException("null runner name");\r
+ this.priority = priority;\r
+ this.runnerName = runnerName;\r
+ }\r
+\r
+ @Override\r
+ public Double getPriority() {\r
+ return priority;\r
+ }\r
+\r
+ @Override\r
+ public AbstractDiagramUpdater graphUpdate(ReadGraph graph) {\r
+ return this;\r
+ }\r
+\r
+ @Override\r
+ public void run() {\r
+ Object task = Timing.BEGIN(runnerName);\r
+ forDiagram();\r
+ Timing.END(task);\r
+ }\r
+\r
+ protected void forDiagram() {\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return runnerName + "@" + System.identityHashCode(this) + " [" + priority + "]";\r
+ }\r
+ }\r
+\r
+ /**\r
+ * @param content the new contents of the diagram, must not be\r
+ * <code>null</code>.\r
+ */\r
+ private GraphUpdateReactor diagramGraphUpdater(final DiagramContents content) {\r
+ if (content == null)\r
+ throw new NullPointerException("null diagram content");\r
+\r
+ return new GraphUpdateReactor() {\r
+ @Override\r
+ public String toString() {\r
+ return "DiagramGraphUpdater@" + System.identityHashCode(this);\r
+ }\r
+\r
+ @Override\r
+ public DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException {\r
+ // Never do anything here if the canvas has already been disposed.\r
+ if (!GraphToDiagramSynchronizer.this.isAlive())\r
+ return null;\r
+\r
+ // We must be prepared for the following changes in the diagram graph\r
+ // model:\r
+ // - the diagram has been completely removed\r
+ // - elements have been added\r
+ // - elements have been removed\r
+ //\r
+ // Element-specific changes are handled by the element query listeners:\r
+ // - elements have been modified\r
+ // - element position has changed\r
+ // - element class (e.g. image) has changed\r
+\r
+ diagramUpdateLock.lock();\r
+ try {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
+ System.out.println("In diagramGraphUpdater:");\r
+\r
+ // Find out what has changed since the last query.\r
+ Object task = Timing.BEGIN("diagramContentDifference");\r
+ DiagramContents lastContent = previousContent;\r
+ DiagramContentChanges changes = content.differenceFrom(previousContent);\r
+ previousContent = content;\r
+ Timing.END(task);\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
+ System.out.println(" changes: " + changes);\r
+\r
+ // Bail out if there are no changes to react to.\r
+ if (changes.isEmpty())\r
+ return null;\r
+\r
+ // Load everything that needs to be loaded from the graph,\r
+ // but don't update the UI model in this thread yet.\r
+ task = Timing.BEGIN("updater.process");\r
+ GraphToDiagramUpdater updater = new GraphToDiagramUpdater(lastContent, content, changes);\r
+ GraphToDiagramSynchronizer.this.currentUpdater = updater;\r
+ try {\r
+ updater.process(graph);\r
+ } finally {\r
+ GraphToDiagramSynchronizer.this.currentUpdater = null;\r
+ }\r
+ Timing.END(task);\r
+\r
+ if (updater.isEmpty())\r
+ return null;\r
+\r
+ // Return an updater that will update the UI run-time model.\r
+ return diagramUpdater(updater);\r
+ } finally {\r
+ diagramUpdateLock.unlock();\r
+ }\r
+ }\r
+ };\r
+ }\r
+\r
+ DiagramUpdater diagramUpdater(final GraphToDiagramUpdater updater) {\r
+ return new AbstractDiagramUpdater(DIAGRAM_UPDATE_DIAGRAM_PRIORITY, "updateDiagram") {\r
+ @Override\r
+ protected void forDiagram() {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
+ System.out.println("running diagram updater: " + this);\r
+\r
+ // DiagramUtils.testDiagram(diagram);\r
+ Set<IElement> dirty = new HashSet<IElement>();\r
+\r
+ Object task2 = Timing.BEGIN("Preprocess connection changes");\r
+ Map<ConnectionEntity, ConnectionChildren> connectionChangeData = new HashMap<ConnectionEntity, ConnectionChildren>(updater.changedConnectionEntities.size());\r
+ for (ConnectionData cd : updater.changedConnectionEntities.values()) {\r
+ connectionChangeData.put(cd.impl, cd.impl.getConnectionChildren());\r
+ }\r
+ Timing.END(task2);\r
+\r
+ task2 = Timing.BEGIN("removeRouteGraphConnections");\r
+ for (IElement removedRouteGraphConnection : updater.removedRouteGraphConnections) {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
+ System.out.println("removing route graph connection: " + removedRouteGraphConnection);\r
+\r
+ ConnectionEntity ce = removedRouteGraphConnection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ continue;\r
+ Object connectionData = ElementUtils.getObject(removedRouteGraphConnection);\r
+ tempConnections.clear();\r
+ for (Connection conn : ce.getTerminalConnections(tempConnections)) {\r
+ ((Element) conn.node).removeHintWithoutNotification(new TerminalKeyOf(conn.terminal, connectionData, Connection.class));\r
+ // To be sure the view will be up-to-date, mark the node\r
+ // connected to the removed connection dirty.\r
+ dirty.add(conn.node);\r
+ }\r
+ }\r
+ Timing.END(task2);\r
+\r
+ task2 = Timing.BEGIN("removeBranchPoints");\r
+ for (IElement removed : updater.removedBranchPoints) {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
+ System.out.println("removing branch point: " + removed);\r
+\r
+ unmapElement(removed.getHint(ElementHints.KEY_OBJECT));\r
+ removeNodeTopologyHints((Element) removed);\r
+\r
+ IElement connection = ElementUtils.getParent(removed);\r
+ if (connection != null) {\r
+ dirty.add(connection);\r
+ }\r
+ }\r
+ Timing.END(task2);\r
+\r
+ task2 = Timing.BEGIN("removeConnectionSegments");\r
+ for (IElement removed : updater.removedConnectionSegments) {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
+ System.out.println("removing segment: " + removed);\r
+\r
+ unmapElement(removed.getHint(ElementHints.KEY_OBJECT));\r
+ removeEdgeTopologyHints((Element) removed);\r
+\r
+ IElement connection = ElementUtils.getParent(removed);\r
+ if (connection != null) {\r
+ dirty.add(connection);\r
+ }\r
+ }\r
+ Timing.END(task2);\r
+\r
+ task2 = Timing.BEGIN("removeElements");\r
+ for (IElement removed : updater.removedElements) {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
+ System.out.println("removing element: " + removed);\r
+\r
+ removed.setHint(KEY_REMOVE_RELATIONSHIPS, Boolean.TRUE);\r
+ if (diagram.containsElement(removed)) {\r
+ diagram.removeElement(removed);\r
+ }\r
+ unmapElement(removed.getHint(ElementHints.KEY_OBJECT));\r
+ removeNodeTopologyHints((Element) removed);\r
+\r
+ // No use marking removed elements dirty.\r
+ dirty.remove(removed);\r
+ }\r
+ Timing.END(task2);\r
+\r
+ // TODO: get rid of this\r
+ task2 = Timing.BEGIN("removeConnectionEntities");\r
+ for (Resource ce : updater.removedConnectionEntities) {\r
+ unmapConnection(ce);\r
+ }\r
+ Timing.END(task2);\r
+\r
+ task2 = Timing.BEGIN("setConnectionData");\r
+ for (ConnectionData cd : updater.changedConnectionEntities.values()) {\r
+ cd.impl.setData(cd.segments, cd.branchPoints);\r
+ }\r
+ Timing.END(task2);\r
+\r
+ // TODO: get rid of this\r
+ task2 = Timing.BEGIN("addConnectionEntities");\r
+ for (Map.Entry<Resource, ConnectionEntityImpl> entry : updater.addedConnectionEntities\r
+ .entrySet()) {\r
+ mapConnection(entry.getKey(), entry.getValue());\r
+ }\r
+ Timing.END(task2);\r
+\r
+ task2 = Timing.BEGIN("addBranchPoints");\r
+ for (IElement added : updater.addedBranchPoints) {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
+ System.out.println("adding branch point: " + added);\r
+\r
+ mapElement(ElementUtils.getObject(added), added);\r
+\r
+ IElement connection = ElementUtils.getParent(added);\r
+ if (connection != null) {\r
+ dirty.add(connection);\r
+ }\r
+ }\r
+ Timing.END(task2);\r
+\r
+ // Add new elements at end of diagram, element order will be synchronized later.\r
+ task2 = Timing.BEGIN("addElements");\r
+ for(Resource r : updater.content.elements) {\r
+ IElement added = updater.addedElementMap.get(r);\r
+ if(added != null) {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
+ System.out.println("adding element: " + added);\r
+\r
+ //Object task3 = BEGIN("mapElement " + added);\r
+ Object task3 = Timing.BEGIN("mapElement");\r
+ mapElement(added.getHint(ElementHints.KEY_OBJECT), added);\r
+ Timing.END(task3);\r
+\r
+ //task3 = BEGIN("addElement " + added);\r
+ task3 = Timing.BEGIN("addElement");\r
+ //System.out.println("diagram.addElement: " + added + " - " + diagram);\r
+ diagram.addElement(added);\r
+ dirty.add(added);\r
+ Timing.END(task3);\r
+ }\r
+ }\r
+ Timing.END(task2);\r
+\r
+ // We've ensured that all nodes must have been and\r
+ // mapped before reaching this.\r
+ task2 = Timing.BEGIN("addConnectionSegments");\r
+ for (IElement added : updater.addedConnectionSegments) {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
+ System.out.println("adding segment: " + added);\r
+\r
+ PlaceholderConnection cb = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_BEGIN_PLACEHOLDER);\r
+ PlaceholderConnection ce = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_END_PLACEHOLDER);\r
+ if (cb == null || ce == null) {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
+ warning("ignoring connection segment " + added + ", connectivity was not resolved (begin=" + cb + ", end=" + ce +")", null);\r
+ continue;\r
+ }\r
+\r
+ mapElement(ElementUtils.getObject(added), added);\r
+\r
+ IElement beginNode = assertMappedElement(cb.node);\r
+ IElement endNode = assertMappedElement(ce.node);\r
+\r
+ if (cb != null)\r
+ connect(added, cb.end, beginNode, cb.terminal);\r
+ if (ce != null)\r
+ connect(added, ce.end, endNode, ce.terminal);\r
+\r
+ IElement connection = ElementUtils.getParent(added);\r
+ if (connection != null) {\r
+ dirty.add(connection);\r
+ }\r
+ }\r
+ Timing.END(task2);\r
+\r
+ // We've ensured that all nodes must have been and\r
+ // mapped before reaching this.\r
+\r
+ task2 = Timing.BEGIN("handle dirty RouteGraph connections");\r
+ for (IElement addedRouteGraphConnection : updater.addedRouteGraphConnectionMap.values()) {\r
+ updateDirtyRouteGraphConnection(addedRouteGraphConnection, dirty);\r
+ }\r
+ Timing.END(task2);\r
+\r
+ // Prevent memory leaks\r
+ tempConnections.clear();\r
+\r
+ // Make sure that the diagram element order matches that of the database.\r
+ final TObjectIntHashMap<IElement> orderMap = new TObjectIntHashMap<IElement>(2 * updater.content.elements.size());\r
+ int i = 1;\r
+ for (Resource r : updater.content.elements) {\r
+ IElement e = getMappedElement(r);\r
+ if (e != null)\r
+ orderMap.put(e, i);\r
+ ++i;\r
+ }\r
+ diagram.sort(new Comparator<IElement>() {\r
+ @Override\r
+ public int compare(IElement e1, IElement e2) {\r
+ int o1 = orderMap.get(e1);\r
+ int o2 = orderMap.get(e2);\r
+ return o1 - o2;\r
+ }\r
+ });\r
+\r
+ // TODO: consider removing this. The whole thing should work without it and\r
+ // this "fix" will only be hiding the real problems.\r
+ task2 = Timing.BEGIN("fixChangedConnections");\r
+ for (ConnectionData cd : updater.changedConnectionEntities.values()) {\r
+ cd.impl.fix();\r
+ }\r
+ Timing.END(task2);\r
+\r
+ task2 = Timing.BEGIN("validateAndFix");\r
+ DiagramUtils.validateAndFix(diagram, dirty);\r
+ Timing.END(task2);\r
+\r
+ // This will fire connection entity change listeners\r
+ task2 = Timing.BEGIN("Postprocess connection changes");\r
+ for (ConnectionData cd : updater.changedConnectionEntities.values()) {\r
+ ConnectionChildren oldChildren = connectionChangeData.get(cd.impl);\r
+ if (oldChildren != null) {\r
+ ConnectionChildren currentChildren = cd.impl.getConnectionChildren();\r
+ cd.impl.fireListener(oldChildren, currentChildren);\r
+ }\r
+ }\r
+ Timing.END(task2);\r
+\r
+ task2 = Timing.BEGIN("setDirty");\r
+ for (IElement e : dirty) {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
+ System.out.println("MARKING ELEMENT DIRTY: " + e);\r
+ e.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);\r
+ }\r
+ Timing.END(task2);\r
+\r
+ // Signal about possible changes in the z-order of diagram elements.\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
+ System.out.println("MARKING DIAGRAM DIRTY: " + diagram);\r
+ diagram.setHint(Hints.KEY_DIRTY, Hints.VALUE_Z_ORDER_CHANGED);\r
+\r
+ // Mark the updater as "processed".\r
+ updater.clear();\r
+\r
+ // Inform listeners that the diagram has been updated.\r
+ diagram.setHint(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, Boolean.TRUE);\r
+ }\r
+ };\r
+ }\r
+\r
+ /**\r
+ * @param connection\r
+ * @param dirtySet\r
+ */\r
+ private void updateDirtyRouteGraphConnection(IElement connection, Set<IElement> dirtySet) {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
+ System.out.println("updating dirty route graph connection: " + connection);\r
+\r
+ ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ return;\r
+\r
+ tempConnections.clear();\r
+ Object connectionData = ElementUtils.getObject(connection);\r
+ for (Connection conn : ce.getTerminalConnections(tempConnections)) {\r
+ ((Element) conn.node).setHintWithoutNotification(\r
+ new TerminalKeyOf(conn.terminal, connectionData, Connection.class),\r
+ conn);\r
+ if (dirtySet != null)\r
+ dirtySet.add(conn.node);\r
+ }\r
+\r
+ // Prevent memory leaks.\r
+ tempConnections.clear();\r
+ }\r
+\r
+ abstract class ElementUpdater extends AbstractDiagramUpdater {\r
+ private final IElement newElement;\r
+\r
+ public ElementUpdater(Double priority, String runnerName, IElement newElement) {\r
+ super(priority, runnerName);\r
+ if (newElement == null)\r
+ throw new NullPointerException("null element");\r
+ this.newElement = newElement;\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return super.toString() + "[" + newElement + "]";\r
+ }\r
+\r
+ @Override\r
+ public void run() {\r
+// System.out.println("ElementUpdateRunner new=" + newElement);\r
+ Object elementResource = newElement.getHint(ElementHints.KEY_OBJECT);\r
+// System.out.println("ElementUpdateRunner res=" + elementResource);\r
+ final Element mappedElement = (Element) getMappedElement(elementResource);\r
+// System.out.println("ElementUpdateRunner mapped=" + mappedElement);\r
+ if (mappedElement == null) {\r
+ if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE) {\r
+ System.out.println("SKIP DIAGRAM UPDATE " + this + " for element resource " + elementResource + ", no mapped element (newElement=" + newElement + ")");\r
+ }\r
+ // Indicates the element has been removed from the graph.\r
+ return;\r
+ }\r
+\r
+ Object task = Timing.BEGIN(runnerName);\r
+ forMappedElement(mappedElement);\r
+ Timing.END(task);\r
+ }\r
+\r
+ protected abstract void forMappedElement(Element mappedElement);\r
+ }\r
+\r
+ ElementUpdater nodeUpdater(final Resource resource, final IElement newElement) {\r
+\r
+ return new ElementUpdater(DIAGRAM_UPDATE_NODE_PRIORITY, "updateNode", newElement) {\r
+\r
+ Collection<Terminal> getTerminals(IElement e) {\r
+ Collection<Terminal> ts = Collections.emptyList();\r
+ TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);\r
+ if (tt != null) {\r
+ ts = new ArrayList<Terminal>();\r
+ tt.getTerminals(newElement, ts);\r
+ }\r
+ return ts;\r
+ }\r
+\r
+ @Override\r
+ protected void forMappedElement(final Element mappedElement) {\r
+ if (DebugPolicy.DEBUG_NODE_UPDATE)\r
+ System.out.println("running node updater: " + this + " - new element: " + newElement);\r
+\r
+ // Newly loaded node elements NEVER contain topology-related\r
+ // hints, i.e. TerminalKeyOf hints. Instead all connections are\r
+ // actually set into element hints when connection edges are\r
+ // loaded.\r
+\r
+ Collection<Terminal> oldTerminals = getTerminals(mappedElement);\r
+ Collection<Terminal> newTerminals = getTerminals(newElement);\r
+ if (!oldTerminals.equals(newTerminals)) {\r
+ // Okay, there are differences in the terminals. Need to fix\r
+ // the TerminalKeyOf hint values to use the new terminal\r
+ // instances when correspondences exist.\r
+\r
+ // If there is no correspondence for an old terminal, we\r
+ // are simply forced to remove the hints related to this\r
+ // connection.\r
+\r
+ Map<Terminal, Terminal> newTerminalMap = new HashMap<Terminal, Terminal>(newTerminals.size());\r
+ for (Terminal t : newTerminals) {\r
+ newTerminalMap.put(t, t);\r
+ }\r
+\r
+ for (Map.Entry<TerminalKeyOf, Object> entry : mappedElement.getHintsOfClass(TerminalKeyOf.class).entrySet()) {\r
+ TerminalKeyOf key = entry.getKey();\r
+ Connection c = (Connection) entry.getValue();\r
+ if (c.node == mappedElement) {\r
+ Terminal newTerminal = newTerminalMap.get(c.terminal);\r
+ if (newTerminal != null) {\r
+ c = new Connection(c.edge, c.end, c.node, newTerminal);\r
+ ((Element) c.edge).setHintWithoutNotification(EndKeyOf.get(c.end), c);\r
+ } else {\r
+ mappedElement.removeHintWithoutNotification(key);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ updateMappedElement(mappedElement, newElement);\r
+ mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);\r
+ }\r
+ };\r
+ }\r
+\r
+ ElementUpdater connectionUpdater(final Object data, final IElement newElement) {\r
+ return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateConnection", newElement) {\r
+ @Override\r
+ public void forMappedElement(Element mappedElement) {\r
+ if (DebugPolicy.DEBUG_CONNECTION_UPDATE)\r
+ System.out.println("running connection updater: " + this + " - new element: " + newElement\r
+ + " with data " + data);\r
+\r
+ // This is kept up-to-date by GDS, make sure not to overwrite it\r
+ // from the mapped element.\r
+ newElement.removeHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+\r
+ updateMappedElement(mappedElement, newElement);\r
+ mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);\r
+ }\r
+ };\r
+ }\r
+\r
+ ElementUpdater edgeUpdater(final Object data, final IElement newElement) {\r
+ return new ElementUpdater(DIAGRAM_UPDATE_EDGE_PRIORITY, "updateEdge", newElement) {\r
+ @Override\r
+ public void forMappedElement(Element mappedElement) {\r
+ if (DebugPolicy.DEBUG_EDGE_UPDATE)\r
+ System.out.println("running edge updater: " + this + " - new element: " + newElement\r
+ + " with data " + data);\r
+\r
+ updateMappedElement(mappedElement, newElement);\r
+ mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);\r
+ }\r
+ };\r
+ }\r
+\r
+ ElementUpdater routeGraphConnectionUpdater(final Object data, final IElement newElement, final Set<Object> dirtyNodes) {\r
+ return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateRouteGraphConnection", newElement) {\r
+ @Override\r
+ public void forMappedElement(Element mappedElement) {\r
+ if (DebugPolicy.DEBUG_CONNECTION_UPDATE)\r
+ System.out.println("running route graph connection updater: " + this + " - new element: " + newElement\r
+ + " with data " + data);\r
+\r
+ // Remove all TerminalKeyOf hints from nodes that were\r
+ // previously connected to the connection (mappedElement)\r
+ // before updating mappedElement with new topology information.\r
+\r
+ for (Object dirtyNodeObject : dirtyNodes) {\r
+ Element dirtyNode = (Element) getMappedElement(dirtyNodeObject);\r
+ if (dirtyNode == null)\r
+ continue;\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
+ System.out.println("preparing node with dirty connectivity: " + dirtyNode);\r
+\r
+ for (Map.Entry<TerminalKeyOf, Object> entry : dirtyNode.getHintsOfClass(TerminalKeyOf.class).entrySet()) {\r
+ Connection conn = (Connection) entry.getValue();\r
+ Object connectionNode = conn.edge.getHint(ElementHints.KEY_OBJECT);\r
+ if (data.equals(connectionNode)) {\r
+ dirtyNode.removeHintWithoutNotification(entry.getKey());\r
+ }\r
+ }\r
+ }\r
+\r
+ // Update connection information, including topology\r
+ updateMappedElement(mappedElement, newElement);\r
+\r
+ // Reinstall TerminalKeyOf hints into nodes that are now connected\r
+ // to mappedElement to keep diagram run-time model properly in sync\r
+ // with the database.\r
+ updateDirtyRouteGraphConnection(mappedElement, null);\r
+\r
+ // TODO: should mark dirty nodes' scene graph dirty ?\r
+\r
+ mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);\r
+ }\r
+ };\r
+ }\r
+\r
+ /**\r
+ * Copies hints from <code>newElement</code> to <code>mappedElement</code>\r
+ * asserting some validity conditions at the same time.\r
+ * \r
+ * @param mappedElement\r
+ * @param newElement\r
+ */\r
+ static void updateMappedElement(Element mappedElement, IElement newElement) {\r
+ if (mappedElement == newElement)\r
+ // Can't update anything if the two elements are the same.\r
+ return;\r
+\r
+ ElementClass oldClass = mappedElement.getElementClass();\r
+ ElementClass newClass = newElement.getElementClass();\r
+\r
+ Object mappedData = mappedElement.getHint(ElementHints.KEY_OBJECT);\r
+ Object newData = newElement.getHint(ElementHints.KEY_OBJECT);\r
+\r
+ assert mappedData != null;\r
+ assert newData != null;\r
+ assert mappedData.equals(newData);\r
+\r
+ if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {\r
+ System.out.println("Updating mapped element, setting hints\n from: " + newElement + "\n into: " + mappedElement);\r
+ }\r
+\r
+ // TODO: consider if this equals check is a waste of time or does it pay\r
+ // off due to having to reinitialize per-class caches for the new\r
+ // ElementClass that are constructed on the fly?\r
+ if (!newClass.equals(oldClass)) {\r
+ if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {\r
+ System.out.println(" old element class: " + oldClass);\r
+ System.out.println(" new element class: " + newClass);\r
+ }\r
+ mappedElement.setElementClass(newClass);\r
+ }\r
+\r
+ // Tuukka@2010-02-19: replaced with notifications for making\r
+ // the graph synchronizer more transparent to the client.\r
+\r
+ // Hint notifications will not work when this is used.\r
+ //mappedElement.setHintsWithoutNotification(newElement.getHints());\r
+\r
+ Map<DiscardableKey, Object> discardableHints = mappedElement.getHintsOfClass(DiscardableKey.class);\r
+\r
+ // Set all hints from newElement to mappedElement.\r
+ // Leave any hints in mappedElement but not in newElement as is.\r
+ Map<Key, Object> hints = newElement.getHints();\r
+ Map<Key, Object> newHints = new HashMap<Key, Object>();\r
+ for (Map.Entry<Key, Object> entry : hints.entrySet()) {\r
+ Key key = entry.getKey();\r
+ Object newValue = entry.getValue();\r
+ Object oldValue = mappedElement.getHint(key);\r
+ if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE_DETAIL) {\r
+ System.out.println(" hint " + key + " compare values: " + oldValue + " -> " + newValue);\r
+ }\r
+ if (!newValue.equals(oldValue)) {\r
+ newHints.put(key, newValue);\r
+ if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {\r
+ System.out.format(" %-42s : %64s -> %-64s\n", key, oldValue, newValue);\r
+ }\r
+ } else {\r
+ // If the hint value has not changed but the hint still exists\r
+ // we don't need to discard it even if it is considered\r
+ // discardable.\r
+ discardableHints.remove(key);\r
+ }\r
+ }\r
+\r
+ // Set all hints at once and send notifications after setting the values.\r
+ if (!discardableHints.isEmpty()) {\r
+ if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE)\r
+ System.out.println("Discarding " + discardableHints.size() + " discardable hints:\n " + discardableHints);\r
+ mappedElement.removeHints(discardableHints.keySet());\r
+ }\r
+ if (!newHints.isEmpty()) {\r
+ if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {\r
+ System.out.println("Updating mapped element, setting new hints:\n\t"\r
+ + EString.implode(newHints.entrySet(), "\n\t") + "\nto replace old hints\n\t"\r
+ + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));\r
+ }\r
+ mappedElement.setHints(newHints);\r
+ }\r
+ if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {\r
+ System.out.println("All hints after update:\n\t"\r
+ + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));\r
+ }\r
+ }\r
+\r
+ class TransactionListener extends SessionEventListenerAdapter {\r
+ long startTime;\r
+ @Override\r
+ public void writeTransactionStarted() {\r
+ startTime = System.nanoTime();\r
+ if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)\r
+ System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionStarted");\r
+ inWriteTransaction.set(true);\r
+ }\r
+ @Override\r
+ public void writeTransactionFinished() {\r
+ long endTime = System.nanoTime();\r
+ if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)\r
+ System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionFinished: " + (endTime - startTime)*1e-6 + " ms");\r
+ inWriteTransaction.set(false);\r
+ scheduleGraphUpdates();\r
+ }\r
+ };\r
+\r
+ Object graphUpdateLock = new Object();\r
+ TransactionListener sessionListener = null;\r
+ AtomicBoolean inWriteTransaction = new AtomicBoolean(false);\r
+ AtomicBoolean graphUpdateRequestScheduled = new AtomicBoolean(false);\r
+ List<GraphUpdateReactor> queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();\r
+\r
+ private void offerGraphUpdate(GraphUpdateReactor update) {\r
+ if (DebugPolicy.DEBUG_GRAPH_UPDATE)\r
+ System.out.println("offerGraphUpdate: " + update);\r
+ boolean inWrite = inWriteTransaction.get();\r
+ synchronized (graphUpdateLock) {\r
+ if (DebugPolicy.DEBUG_GRAPH_UPDATE)\r
+ System.out.println("queueing graph update: " + update);\r
+ queuedGraphUpdates.add(update);\r
+ }\r
+ if (!inWrite) {\r
+ if (DebugPolicy.DEBUG_GRAPH_UPDATE)\r
+ System.out.println("scheduling queued graph update immediately: " + update);\r
+ scheduleGraphUpdates();\r
+ }\r
+ }\r
+\r
+ private Collection<GraphUpdateReactor> scrubGraphUpdates() {\r
+ synchronized (graphUpdateLock) {\r
+ if (queuedGraphUpdates.isEmpty())\r
+ return Collections.emptyList();\r
+ final List<GraphUpdateReactor> updates = queuedGraphUpdates;\r
+ queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();\r
+ return updates;\r
+ }\r
+ }\r
+\r
+ private void scheduleGraphUpdates() {\r
+ synchronized (graphUpdateLock) {\r
+ if (queuedGraphUpdates.isEmpty())\r
+ return;\r
+ if (!graphUpdateRequestScheduled.compareAndSet(false, true))\r
+ return;\r
+ }\r
+\r
+ if (DebugPolicy.DEBUG_GRAPH_UPDATE)\r
+ System.out.println("scheduling " + queuedGraphUpdates.size() + " queued graph updates with ");\r
+\r
+ session.asyncRequest(new ReadRequest() {\r
+ @Override\r
+ public void run(final ReadGraph graph) throws DatabaseException {\r
+ Collection<GraphUpdateReactor> updates;\r
+ synchronized (graphUpdateLock) {\r
+ graphUpdateRequestScheduled.set(false);\r
+ updates = scrubGraphUpdates();\r
+ }\r
+\r
+ if (!GraphToDiagramSynchronizer.this.isAlive())\r
+ return;\r
+\r
+ processGraphUpdates(graph, updates);\r
+ }\r
+ }, new ProcedureAdapter<Object>() {\r
+ @Override\r
+ public void exception(Throwable t) {\r
+ error(t);\r
+ }\r
+ });\r
+ }\r
+\r
+ private void processGraphUpdates(ReadGraph graph, final Collection<GraphUpdateReactor> graphUpdates)\r
+ throws DatabaseException {\r
+ final List<DiagramUpdater> diagramUpdates = new ArrayList<DiagramUpdater>(graphUpdates.size());\r
+\r
+ // Run GraphUpdaters and gather DiagramUpdaters.\r
+ if (DebugPolicy.DEBUG_GRAPH_UPDATE)\r
+ System.out.println("Running GRAPH updates: " + graphUpdates);\r
+ for (GraphUpdateReactor graphUpdate : graphUpdates) {\r
+ DiagramUpdater diagramUpdate = graphUpdate.graphUpdate(graph);\r
+ if (diagramUpdate != null) {\r
+ if (DebugPolicy.DEBUG_GRAPH_UPDATE)\r
+ System.out.println(graphUpdate + " => " + diagramUpdate);\r
+ diagramUpdates.add(diagramUpdate);\r
+ }\r
+ }\r
+\r
+ if (diagramUpdates.isEmpty())\r
+ return;\r
+\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
+ System.out.println("Diagram updates: " + diagramUpdates);\r
+ Collections.sort(diagramUpdates, DiagramUpdater.DIAGRAM_UPDATER_COMPARATOR);\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
+ System.out.println("Sorted diagram updates: " + diagramUpdates);\r
+\r
+ ThreadUtils.asyncExec(canvas.getThreadAccess(), new StateRunnable() {\r
+ @Override\r
+ public void run() {\r
+ if (GraphToDiagramSynchronizer.this.isAlive() && getState() != State.DISPOSED)\r
+ safeRunInState(State.UPDATING_DIAGRAM, this);\r
+ }\r
+\r
+ @Override\r
+ public void execute() throws InvocationTargetException {\r
+ // Block out diagram write transactions.\r
+ DiagramUtils.inDiagramTransaction(diagram, TransactionType.READ, new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
+ System.out.println("Running DIAGRAM updates: " + diagramUpdates);\r
+ for (DiagramUpdater update : diagramUpdates) {\r
+ if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
+ System.out.println("Running DIAGRAM update: " + update);\r
+ update.run();\r
+ }\r
+ }\r
+ });\r
+\r
+ setCanvasDirty();\r
+ }\r
+ });\r
+ }\r
+\r
+ private void attachSessionListener(Session session) {\r
+ SessionEventSupport support = session.peekService(SessionEventSupport.class);\r
+ if (support != null) {\r
+ sessionListener = new TransactionListener();\r
+ support.addListener(sessionListener);\r
+ }\r
+ }\r
+\r
+ private void detachSessionListener() {\r
+ if (sessionListener != null) {\r
+ session.getService(SessionEventSupport.class).removeListener(sessionListener);\r
+ sessionListener = null;\r
+ }\r
+ }\r
+\r
+\r
+ // ------------------------------------------------------------------------\r
+ // GRAPH TO DIAGRAM SYNCHRONIZATION LOGIC END\r
+ // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
+\r
+ // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
+ // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.\r
+ // This does not try to synchronize anything back into the graph.\r
+ // ------------------------------------------------------------------------\r
+\r
+ IHintListener elementHintValidator = new HintListenerAdapter() {\r
+ @Override\r
+ public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
+ if (!(sender instanceof Element))\r
+ throw new IllegalStateException("invalid sender: " + sender);\r
+ Element e = (Element) sender;\r
+ if (newValue != null) {\r
+ if (key instanceof TerminalKeyOf) {\r
+ Connection c = (Connection) newValue;\r
+ if (e != c.node)\r
+ throw new IllegalStateException("TerminalKeyOf hint of node " + e + " refers to a different node " + c.node + ". Should be the same.");\r
+ Object edgeObject = ElementUtils.getObject(c.edge);\r
+ if (!(edgeObject instanceof EdgeResource))\r
+ throw new IllegalStateException("EndKeyOf hint of edge " + c.edge + " refers contains an invalid object: " + edgeObject);\r
+ } else if (key instanceof EndKeyOf) {\r
+ Connection c = (Connection) newValue;\r
+ if (e != c.edge)\r
+ throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers to a different edge " + c.edge + ". Should be the same.");\r
+ Object edgeObject = ElementUtils.getObject(c.edge);\r
+ if (!(edgeObject instanceof EdgeResource))\r
+ throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers contains an invalid object: " + edgeObject);\r
+ }\r
+ }\r
+ }\r
+ };\r
+\r
+ class DiagramListener implements CompositionListener, CompositionVetoListener {\r
+ @Override\r
+ public boolean beforeElementAdded(IDiagram d, IElement e) {\r
+ // Make sure that MutatedElements NEVER get added to the diagram.\r
+ if (d == diagram) {\r
+ if (!(e instanceof Element)) {\r
+ // THIS IS NOT GOOD!\r
+ 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
+ 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
+ return false;\r
+ }\r
+\r
+ // Perform sanity checks that might veto the element addition.\r
+ boolean pass = true;\r
+\r
+ // Check that all elements added to the diagram are adaptable to Resource\r
+ ElementClass ec = e.getElementClass();\r
+ Resource resource = ElementUtils.adapt(ec, Resource.class);\r
+ if (resource == null) {\r
+ pass = false;\r
+ new Exception("Attempted to add an element to the diagram that is not adaptable to Resource: " + e + ", class: " + ec).printStackTrace();\r
+ }\r
+\r
+ // Sanity check connection hints\r
+ for (Map.Entry<TerminalKeyOf, Object> entry : e.getHintsOfClass(TerminalKeyOf.class).entrySet()) {\r
+ Connection c = (Connection) entry.getValue();\r
+ Object edgeObject = ElementUtils.getObject(c.edge);\r
+ if (e != c.node) {\r
+ System.err.println("Invalid node in TerminalKeyOf hint: " + entry.getKey() + "=" + entry.getValue());\r
+ System.err.println("\tconnection.edge=" + c.edge);\r
+ System.err.println("\tconnection.node=" + c.node);\r
+ System.err.println("\tconnection.end=" + c.end);\r
+ System.err.println("\telement=" + e);\r
+ System.err.println("\telement class=" + e.getElementClass());\r
+ pass = false;\r
+ }\r
+ if (!(edgeObject instanceof EdgeResource)) {\r
+ System.err.println("Invalid object in TerminalKeyOf hint edge: " + entry.getKey() + "=" + entry.getValue());\r
+ System.err.println("\tconnection.edge=" + c.edge);\r
+ System.err.println("\tconnection.node=" + c.node);\r
+ System.err.println("\tconnection.end=" + c.end);\r
+ System.err.println("\telement=" + e);\r
+ System.err.println("\telement class=" + e.getElementClass());\r
+ pass = false;\r
+ }\r
+ }\r
+\r
+ return pass;\r
+ }\r
+ return true;\r
+ }\r
+\r
+ @Override\r
+ public boolean beforeElementRemoved(IDiagram d, IElement e) {\r
+ // Never veto diagram changes.\r
+ return true;\r
+ }\r
+\r
+ @Override\r
+ public void onElementAdded(IDiagram d, IElement e) {\r
+ if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)\r
+ System.out.println("[" + d + "] element added: " + e);\r
+\r
+ if (USE_ELEMENT_VALIDATING_LISTENERS)\r
+ e.addHintListener(elementHintValidator);\r
+ }\r
+\r
+ @Override\r
+ public void onElementRemoved(IDiagram d, IElement e) {\r
+ if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)\r
+ System.out.println("[" + d + "] element removed: " + e);\r
+\r
+ if (USE_ELEMENT_VALIDATING_LISTENERS)\r
+ e.removeHintListener(elementHintValidator);\r
+\r
+ if (e.containsHint(KEY_REMOVE_RELATIONSHIPS))\r
+ relationshipHandler.denyAll(diagram, e);\r
+ }\r
+ }\r
+\r
+ DiagramListener diagramListener = new DiagramListener();\r
+\r
+ static void removeNodeTopologyHints(Element node) {\r
+ Set<TerminalKeyOf> terminalKeys = node.getHintsOfClass(TerminalKeyOf.class).keySet();\r
+ if (!terminalKeys.isEmpty()) {\r
+ removeNodeTopologyHints(node, terminalKeys);\r
+ }\r
+ }\r
+\r
+ static void removeNodeTopologyHints(Element node, Collection<TerminalKeyOf> terminalKeys) {\r
+ for (TerminalKeyOf key : terminalKeys) {\r
+ Connection c = node.removeHintWithoutNotification(key);\r
+ if (c != null) {\r
+ removeEdgeTopologyHints((Element) c.edge);\r
+ }\r
+ }\r
+ }\r
+\r
+ static void removeEdgeTopologyHints(Element edge) {\r
+ Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);\r
+ for (EndKeyOf key : EndKeyOf.KEYS) {\r
+ Connection c = edge.removeHintWithoutNotification(key);\r
+ if (c != null) {\r
+ ((Element) c.node).removeHintWithoutNotification(new TerminalKeyOf(c.terminal, edgeData, Connection.class));\r
+ }\r
+ }\r
+ }\r
+\r
+ // ------------------------------------------------------------------------\r
+ // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.\r
+ // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
+\r
+ // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
+ // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC BEGIN\r
+ // ------------------------------------------------------------------------\r
+\r
+ void adaptDiagramClass(AsyncReadGraph graph, Resource diagram, final AsyncProcedure<DiagramClass> procedure) {\r
+ graph.forAdapted(diagram, DiagramClass.class, new AsyncProcedure<DiagramClass>() {\r
+ @Override\r
+ public void exception(AsyncReadGraph graph, Throwable throwable) {\r
+ procedure.exception(graph, throwable);\r
+ }\r
+\r
+ @Override\r
+ public void execute(AsyncReadGraph graph, DiagramClass dc) {\r
+ // To move TopologyImpl out of here, we need a separate\r
+ // DiagramClassFactory that takes a canvas context as an argument.\r
+ // DataElementMapImpl, ElementFactoryImpl and diagramLifeCycle can\r
+ // safely stay here.\r
+ procedure.execute(graph, dc.newClassWith(\r
+ // This handler takes care of the topology of the diagram model.\r
+ // It sets and fixes element hints related to describing the\r
+ // connectivity of elements.\r
+ diagramTopology,\r
+ // TODO: not quite sure whether this can prove itself useful or not.\r
+ elementFactory,\r
+ // This map provides a bidirectional mapping between\r
+ // IElement and back-end objects.\r
+ dataElementMap,\r
+ // This handler provides a facility to adapt an element class\r
+ // to work properly with a diagram synchronized using this\r
+ // GraphToDiagramSynchronizer.\r
+ substituteElementClass,\r
+ // These handlers provide a way to create simple identified\r
+ // uni- and bidirectional relationships between any diagram\r
+ // objects/elements.\r
+ relationshipHandler));\r
+ }\r
+ });\r
+ }\r
+\r
+ static Connection connect(IElement edge, EdgeEnd end, IElement element, Terminal terminal) {\r
+ Connection c = new Connection(edge, end, element, terminal);\r
+\r
+ Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);\r
+ if (DebugPolicy.DEBUG_CONNECTION) {\r
+ System.out.println("[connect](edge=" + edge + ", edgeData=" + edgeData + ", end=" + end + ", element="\r
+ + element + ", terminal=" + terminal + ")");\r
+ }\r
+\r
+ TerminalKeyOf key = new TerminalKeyOf(terminal, edgeData, Connection.class);\r
+ element.setHint(key, c);\r
+\r
+ EndKeyOf key2 = EndKeyOf.get(end);\r
+ edge.setHint(key2, c);\r
+\r
+ return c;\r
+ }\r
+\r
+ static class ElementFactoryImpl implements ElementFactory {\r
+ @Override\r
+ public IElement spawnNew(ElementClass clazz) {\r
+ IElement e = Element.spawnNew(clazz);\r
+ return e;\r
+ }\r
+ }\r
+\r
+ ElementFactoryImpl elementFactory = new ElementFactoryImpl();\r
+\r
+ public static final Object FIRST_TIME = new Object() {\r
+ @Override\r
+ public String toString() {\r
+ return "FIRST_TIME";\r
+ }\r
+ };\r
+\r
+\r
+ /**\r
+ * A base for all listeners of graph requests performed internally by\r
+ * GraphToDiagramSynchronizer.\r
+ *\r
+ * @param <T> type of stored data element\r
+ * @param <Result> query result type\r
+ */\r
+ abstract class BaseListener<T, Result> implements AsyncListener<Result> {\r
+\r
+ protected final T data;\r
+\r
+ private Object oldResult = FIRST_TIME;\r
+\r
+ protected boolean disposed = false;\r
+\r
+ final ICanvasContext canvas;\r
+\r
+ public BaseListener(T data) {\r
+ this.canvas = GraphToDiagramSynchronizer.this.canvas;\r
+ this.data = data;\r
+ }\r
+\r
+ @Override\r
+ public void exception(AsyncReadGraph graph, Throwable throwable) {\r
+ // Exceptions are always expected to mean that the listener should\r
+ // be considered disposed once a query fails.\r
+ disposed = true;\r
+ }\r
+\r
+ abstract void execute(AsyncReadGraph graph, Object oldResult, Object newResult);\r
+\r
+ @Override\r
+ public void execute(AsyncReadGraph graph, Result result) {\r
+ if (DebugPolicy.DEBUG_LISTENER_BASE)\r
+ System.out.println("BaseListener: " + result);\r
+\r
+ if (disposed) {\r
+ if (DebugPolicy.DEBUG_LISTENER_BASE)\r
+ System.out.println("BaseListener: execute invoked although listener is disposed!");\r
+ return;\r
+ }\r
+\r
+ // A null result will permanently mark this listener disposed!\r
+ if (result == null) {\r
+ disposed = true;\r
+ if (DebugPolicy.DEBUG_LISTENER_BASE)\r
+ System.out.println(this + " null result, listener marked disposed");\r
+ }\r
+\r
+ if (oldResult == FIRST_TIME) {\r
+ oldResult = result;\r
+ if (DebugPolicy.DEBUG_LISTENER_BASE)\r
+ System.out.println(this + " first result computed: " + result);\r
+ } else {\r
+ if (DebugPolicy.DEBUG_LISTENER_BASE)\r
+ System.out.println(this + " result changed from '" + oldResult + "' to '" + result + "'");\r
+ try {\r
+ execute(graph, oldResult, result);\r
+ } finally {\r
+ oldResult = result;\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public boolean isDisposed() {\r
+ if (disposed)\r
+ return true;\r
+\r
+ boolean alive = isAlive();\r
+ //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", isAlive=" + alive);\r
+ if (!alive)\r
+ return true;\r
+ // If a mapping no longer exists for this element, dispose of this\r
+ // listener.\r
+ //IElement e = getMappedElement(resource);\r
+ //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", element=" + e);\r
+ //return e == null;\r
+ return false;\r
+ }\r
+\r
+// @Override\r
+// protected void finalize() throws Throwable {\r
+// System.out.println("finalize listener: " + this);\r
+// super.finalize();\r
+// }\r
+ }\r
+\r
+ class DiagramClassRequest extends BaseRequest2<Resource, DiagramClass> {\r
+ public DiagramClassRequest(Resource resource) {\r
+ super(GraphToDiagramSynchronizer.this.canvas, resource);\r
+ }\r
+\r
+ @Override\r
+ public void perform(AsyncReadGraph graph, AsyncProcedure<DiagramClass> procedure) {\r
+ adaptDiagramClass(graph, data, procedure);\r
+ }\r
+ }\r
+\r
+ public class DiagramContentListener extends BaseListener<Resource, DiagramContents> {\r
+\r
+ public DiagramContentListener(Resource resource) {\r
+ super(resource);\r
+ }\r
+\r
+ @Override\r
+ public void execute(final AsyncReadGraph graph, Object oldResult, Object newResult) {\r
+ final DiagramContents newContent = (newResult == null) ? new DiagramContents()\r
+ : (DiagramContents) newResult;\r
+\r
+ // diagramGraphUpdater is called synchronously during\r
+ // loading. The first result will not get updated through\r
+ // this listener but through loadDiagram.\r
+\r
+ if (DebugPolicy.DISABLE_DIAGRAM_UPDATES) {\r
+ System.out.println("Skipped diagram content update: " + newResult);\r
+ return;\r
+ }\r
+\r
+ if (DebugPolicy.DEBUG_DIAGRAM_LISTENER)\r
+ System.out.println("diagram contents changed: " + oldResult + " => " + newResult);\r
+\r
+ offerGraphUpdate( diagramGraphUpdater(newContent) );\r
+ }\r
+\r
+ @Override\r
+ public boolean isDisposed() {\r
+ return !isAlive();\r
+ }\r
+\r
+ @Override\r
+ public void exception(AsyncReadGraph graph, Throwable t) {\r
+ super.exception(graph, t);\r
+ error("DiagramContentRequest failed", t);\r
+ }\r
+ }\r
+\r
+ // ------------------------------------------------------------------------\r
+ // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC END\r
+ // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
+\r
+ // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
+ // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER BEGIN\r
+ // ------------------------------------------------------------------------\r
+\r
+ static class TopologyImpl implements Topology {\r
+\r
+ @Override\r
+ public Connection getConnection(IElement edge, EdgeEnd end) {\r
+ Key key = EndKeyOf.get(end);\r
+ Connection c = edge.getHint(key);\r
+ if (c == null)\r
+ return null;\r
+ return c;\r
+ }\r
+\r
+ @Override\r
+ public void getConnections(IElement node, Terminal terminal, Collection<Connection> connections) {\r
+// IDiagram d = ElementUtils.getDiagram(node);\r
+ for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {\r
+ // First check that the terminal matches.\r
+ TerminalKeyOf key = entry.getKey();\r
+ if (!key.getTerminal().equals(terminal))\r
+ continue;\r
+\r
+ Connection c = (Connection) entry.getValue();\r
+ if (c != null) {\r
+ connections.add(c);\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void connect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {\r
+ if (node != null && terminal != null)\r
+ GraphToDiagramSynchronizer.connect(edge, end, node, terminal);\r
+\r
+ if (DebugPolicy.DEBUG_CONNECTION) {\r
+ if (end == EdgeEnd.Begin)\r
+ System.out.println("Connection started from: " + edge + ", " + end + ", " + node + ", " + terminal);\r
+ else\r
+ System.out.println("Creating connection to: " + edge + ", " + end + ", " + node + ", " + terminal);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void disconnect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {\r
+ EndKeyOf edgeKey = EndKeyOf.get(end);\r
+ Connection c = edge.getHint(edgeKey);\r
+ if (c == null)\r
+ throw new UnsupportedOperationException("cannot disconnect, no Connection in edge " + edge);\r
+\r
+ for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {\r
+ Connection cc = (Connection) entry.getValue();\r
+ if (c == cc) {\r
+ node.removeHint(entry.getKey());\r
+ edge.removeHint(edgeKey);\r
+ return;\r
+ }\r
+ }\r
+\r
+ throw new UnsupportedOperationException("cannot disconnect, no connection between found between edge "\r
+ + edge + " and node " + node);\r
+ }\r
+ }\r
+\r
+ Topology diagramTopology = new TopologyImpl();\r
+\r
+ // ------------------------------------------------------------------------\r
+ // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER END\r
+ // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
+\r
+ // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
+ // DIAGRAM OBJECT RELATIONSHIP HANDLER BEGIN\r
+ // ------------------------------------------------------------------------\r
+\r
+ RelationshipHandler relationshipHandler = new RelationshipHandler() {\r
+\r
+ AssociativeMap map = new AssociativeMap(Associativity.of(true, false, false));\r
+\r
+ Object getPossibleObjectOrElement(Object o) {\r
+ if (o instanceof IElement) {\r
+ IElement e = (IElement) o;\r
+ Object oo = e.getHint(ElementHints.KEY_OBJECT);\r
+ return oo != null ? oo : e;\r
+ }\r
+ return o;\r
+ }\r
+\r
+ IElement getElement(Object o) {\r
+ if (o instanceof IElement)\r
+ return (IElement) o;\r
+ return getMappedElement(o);\r
+ }\r
+\r
+ @Override\r
+ public void claim(IDiagram diagram, Object subject,\r
+ Relationship predicate, Object object) {\r
+ Object sd = getPossibleObjectOrElement(subject);\r
+ Object od = getPossibleObjectOrElement(object);\r
+\r
+ Collection<Tuple> ts = null;\r
+ Relationship inverse = predicate.getInverse();\r
+ if (inverse != null)\r
+ ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od, inverse, sd));\r
+ else\r
+ ts = Collections.singletonList(new Tuple(sd, predicate, od));\r
+\r
+ synchronized (this) {\r
+ map.add(ts);\r
+ }\r
+\r
+ if (DebugPolicy.DEBUG_RELATIONSHIP) {\r
+ new Exception().printStackTrace();\r
+ System.out.println("Claimed relationships:");\r
+ for (Tuple t : ts)\r
+ System.out.println("\t" + t);\r
+ }\r
+ }\r
+\r
+ private void doDeny(IDiagram diagram, Object subject,\r
+ Relationship predicate, Object object) {\r
+ Object sd = getPossibleObjectOrElement(subject);\r
+ Object od = getPossibleObjectOrElement(object);\r
+ if (sd == subject || od == object) {\r
+ System.out\r
+ .println("WARNING: denying relationship '"\r
+ + predicate\r
+ + "' between diagram element(s), not back-end object(s): "\r
+ + sd + " -> " + od);\r
+ }\r
+\r
+ Collection<Tuple> ts = null;\r
+ Relationship inverse = predicate.getInverse();\r
+ if (inverse != null)\r
+ ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od,\r
+ inverse, sd));\r
+ else\r
+ ts = Collections.singleton(new Tuple(sd, predicate, od));\r
+\r
+ synchronized (this) {\r
+ map.remove(ts);\r
+ }\r
+\r
+ if (DebugPolicy.DEBUG_RELATIONSHIP) {\r
+ new Exception().printStackTrace();\r
+ System.out.println("Denied relationships:");\r
+ for (Tuple t : ts)\r
+ System.out.println("\t" + t);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void deny(IDiagram diagram, Object subject,\r
+ Relationship predicate, Object object) {\r
+ synchronized (this) {\r
+ doDeny(diagram, subject, predicate, object);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void deny(IDiagram diagram, Relation relation) {\r
+ synchronized (this) {\r
+ doDeny(diagram, relation.getSubject(), relation\r
+ .getRelationship(), relation.getObject());\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void denyAll(IDiagram diagram, Object element) {\r
+ synchronized (this) {\r
+ for (Relation relation : getRelations(diagram, element, null)) {\r
+ doDeny(diagram, relation.getSubject(), relation\r
+ .getRelationship(), relation.getObject());\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public Collection<Relation> getRelations(IDiagram diagram,\r
+ Object element, Collection<Relation> result) {\r
+ if (DebugPolicy.DEBUG_GET_RELATIONSHIP)\r
+ System.out.println("getRelations(" + element + ")");\r
+ Object e = getPossibleObjectOrElement(element);\r
+\r
+ Collection<Tuple> tuples = null;\r
+ synchronized (this) {\r
+ tuples = map.get(new Tuple(e, null, null), null);\r
+ }\r
+\r
+ if (DebugPolicy.DEBUG_GET_RELATIONSHIP) {\r
+ System.out.println("Result size: " + tuples.size());\r
+ for (Tuple t : tuples)\r
+ System.out.println("\t" + t);\r
+ }\r
+\r
+ if (tuples.isEmpty())\r
+ return Collections.emptyList();\r
+ if (result == null)\r
+ result = new ArrayList<Relation>(tuples.size());\r
+ for (Tuple t : tuples) {\r
+ Object obj = t.getField(2);\r
+ IElement el = getElement(obj);\r
+ Relationship r = (Relationship) t.getField(1);\r
+ result.add(new Relation(element, r, el != null ? el : obj));\r
+ }\r
+ return result;\r
+ }\r
+\r
+ };\r
+\r
+ // ------------------------------------------------------------------------\r
+ // DIAGRAM ELEMENT RELATIONSHIP HANDLER END\r
+ // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
+\r
+}\r