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