]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/handler/DeleteHandler.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / handler / DeleteHandler.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.handler;
13
14 import java.util.ArrayDeque;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Deque;
18 import java.util.HashMap;
19 import java.util.HashSet;
20 import java.util.Set;
21 import java.util.function.Consumer;
22
23 import org.eclipse.core.runtime.IProgressMonitor;
24 import org.eclipse.core.runtime.IStatus;
25 import org.eclipse.core.runtime.Status;
26 import org.eclipse.core.runtime.SubMonitor;
27 import org.eclipse.jface.action.IStatusLineManager;
28 import org.eclipse.swt.widgets.Display;
29 import org.simantics.DatabaseJob;
30 import org.simantics.Simantics;
31 import org.simantics.db.ReadGraph;
32 import org.simantics.db.Resource;
33 import org.simantics.db.WriteGraph;
34 import org.simantics.db.common.request.WriteRequest;
35 import org.simantics.db.exception.DatabaseException;
36 import org.simantics.db.layer0.adapter.Remover;
37 import org.simantics.db.layer0.exception.CannotRemoveException;
38 import org.simantics.db.layer0.util.RemoverUtil;
39 import org.simantics.diagram.content.ConnectionUtil;
40 import org.simantics.diagram.content.EdgeResource;
41 import org.simantics.diagram.internal.Activator;
42 import org.simantics.diagram.synchronization.graph.RemoveBranchpoint;
43 import org.simantics.diagram.synchronization.graph.RemoveElement;
44 import org.simantics.diagram.ui.DiagramModelHints;
45 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
46 import org.simantics.g2d.connection.ConnectionEntity;
47 import org.simantics.g2d.connection.handler.ConnectionHandler;
48 import org.simantics.g2d.diagram.IDiagram;
49 import org.simantics.g2d.diagram.handler.PickRequest.PickFilter;
50 import org.simantics.g2d.diagram.handler.Relationship;
51 import org.simantics.g2d.diagram.handler.RelationshipHandler;
52 import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;
53 import org.simantics.g2d.diagram.handler.Topology;
54 import org.simantics.g2d.diagram.handler.Topology.Connection;
55 import org.simantics.g2d.diagram.handler.Topology.Terminal;
56 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
57 import org.simantics.g2d.diagram.participant.Selection;
58 import org.simantics.g2d.element.ElementClass;
59 import org.simantics.g2d.element.ElementHints;
60 import org.simantics.g2d.element.ElementUtils;
61 import org.simantics.g2d.element.IElement;
62 import org.simantics.g2d.element.handler.BendsHandler;
63 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
64 import org.simantics.g2d.element.handler.TerminalTopology;
65 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
66 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
67 import org.simantics.scenegraph.g2d.events.command.Commands;
68 import org.simantics.utils.logging.TimeLogger;
69 import org.simantics.utils.strings.EString;
70 import org.simantics.utils.threads.SWTThread;
71 import org.simantics.utils.threads.ThreadUtils;
72 import org.simantics.utils.ui.dialogs.ShowMessage;
73
74 /**
75  * DeleteHandler is a canvas handler for Commands.DELETE commands for an
76  * IDiagram.
77  * 
78  * <p>
79  * The handler attempts to delete the current selection for pointer 0, meaning
80  * {@link Selection#SELECTION0}.
81  * </p>
82  * 
83  * <p>
84  * The handler logic goes as follows:
85  * </p>
86  * <ol>
87  * <li>Separate nodes and edges form the the removed selection</li>
88  * <li>Find all edges attached to the removed nodes and remove them too</li>
89  * <li>Delete connections that contain less than 2 terminal connections</li>
90  * </ol>
91  * 
92  * @see Selection for the current diagram selection source
93  * 
94  * @author Tuukka Lehtonen
95  * 
96  * TODO: start using WorkbenchStatusLine participant
97  */
98 public class DeleteHandler extends AbstractDiagramParticipant {
99
100     public static final boolean DEBUG_DELETE = false;
101
102     @Dependency Selection sel;
103
104     private final IStatusLineManager statusLine;
105
106     public DeleteHandler(IStatusLineManager statusLine) {
107         this.statusLine = statusLine;
108     }
109
110     @EventHandler(priority = 0)
111     public boolean handleCommand(CommandEvent e) {
112         if (Commands.DELETE.equals( e.command )) {
113             IDiagram d = diagram;
114             if (d == null)
115                 return true;
116
117             Set<IElement> ss = sel.getSelection(0);
118             if (ss.isEmpty())
119                 return true;
120
121             if (delete(d, ss,
122                     ex -> {
123                         if (ex instanceof CannotRemoveException) {
124                             ShowMessage.showInformation("Delete Selection Was Denied", ex.getLocalizedMessage());
125                         }
126                     },
127                     error -> {
128                         error(error);
129                     }
130                     )) {
131                 sel.clear(0);
132             }
133
134             return true;
135         }
136         return false;
137     }
138
139     public static boolean delete(final IDiagram d, Collection<IElement> ss, Consumer<Exception> exceptionListener, Consumer<String> errorListener) {
140         TimeLogger.resetTimeAndLog(DeleteHandler.class, "delete");
141         
142         Topology topology = d.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
143         RelationshipHandler erh = d.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);
144
145         if (DEBUG_DELETE) {
146             System.out.println("diagram: " + d);
147             for (IElement e : d.getSnapshot()) {
148                 ElementClass ec = e.getElementClass();
149                 System.out.println("\t-element " + e);
150                 System.out.println("\t  -class " + e.getElementClass());
151                 if (ec.containsClass(ConnectionHandler.class)) {
152                     ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
153                     for (IElement child : ce.getBranchPoints(null)) {
154                         System.out.println("\t\t-branch " + child);
155                         System.out.println("\t\t  -class " + child.getElementClass());
156                     }
157                     for (IElement child : ce.getSegments(null)) {
158                         System.out.println("\t\t-segment " + child);
159                         System.out.println("\t\t  -class " + child.getElementClass());
160                     }
161                 }
162             }
163             System.out.println("delete requested for elements:");
164             for (IElement e : ss)
165                 System.out.println("\t-element " + e);
166         }
167
168         // Analyze removals:
169         //  - separate elements and connections
170         //  - find all connections attached to the elements and remove them too
171         Deque<IElement> elementsToProcess = new ArrayDeque<IElement>(ss);
172         Set<IElement> processedElements = new HashSet<IElement>();
173         Set<IElement> relationshipsProcessedForElement = new HashSet<IElement>();
174
175         final Collection<IElement> elements = new ArrayList<IElement>();
176         final Set<IElement> edges = new HashSet<IElement>();
177         Collection<Connection> connections = new ArrayList<Connection>();
178         Collection<Terminal> terminals = new ArrayList<Terminal>();
179         Collection<Relation> relations = new ArrayList<Relation>();
180         while (!elementsToProcess.isEmpty()) {
181             IElement el = elementsToProcess.pollFirst();
182
183             if (relationshipsProcessedForElement.add(el)) {
184                 // Check for relationships to other elements and mark child
185                 // elements to be removed before the parent element.
186                 relations.clear();
187                 erh.getRelations(d, el, relations);
188                 if (!relations.isEmpty()) {
189                     boolean restart = false;
190                     for (Relation r : relations) {
191                         //System.out.println("FOUND RELATION: " + r);
192                         if (r.getRelationship() == Relationship.PARENT_OF) {
193                             if ((r.getObject() instanceof IElement)) {
194                                 IElement ee = (IElement) r.getObject();
195                                 if (d.containsElement(ee)) {
196                                     //System.out.println("DIAGRAM CONTAINS OBJECT: " + r.getObject());
197
198                                     // Mark the object also to be processed for removal.
199                                     elementsToProcess.addFirst(ee);
200                                     restart = true;
201                                 }
202                             }
203                         }
204                     }
205                     if (restart) {
206                         // Only process this element after we're sure that
207                         // all its children have been processed.
208                         elementsToProcess.addLast(el);
209                         continue;
210                     }
211                 }
212             }
213
214             if (!processedElements.add(el))
215                 continue;
216
217             TerminalTopology tt = el.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
218             BendsHandler bh = el.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
219
220             if (bh != null) {
221                 // Verify that the edge is NOT between two branch points.
222                 // If it is, do not allow deletion because it is the only case
223                 // which can break a connection tree into a connection forest.
224                 // We do not want that to happen.
225                 Connection begin = topology.getConnection(el, EdgeEnd.Begin);
226                 Connection end = topology.getConnection(el, EdgeEnd.End);
227
228                 // Try to work with cases where the model is somewhat corrupt.
229                 if (begin != null && end != null) {
230                     if (PickFilter.FILTER_BRANCH_POINT.accept(begin.node) && PickFilter.FILTER_BRANCH_POINT.accept(end.node)) {
231                         if (errorListener != null) {
232                             errorListener.accept("Deletion of branch point connecting edges is not allowed. Must be connected to a node terminal.");
233                         }
234                         return false;
235                     }
236                 }
237
238                 if (DEBUG_DELETE)
239                     System.out.println("ADDED EDGE FOR REMOVAL: " + el);
240                 edges.add(el);
241             } else {
242                 if (DEBUG_DELETE)
243                     System.out.println("ADDED ELEMENT FOR REMOVAL: " + el);
244                 elements.add(el);
245
246                 if (tt != null) {
247                     terminals.clear();
248                     tt.getTerminals(el, terminals);
249                     connections.clear();
250                     for (Terminal terminal : terminals)
251                         topology.getConnections(el, terminal, connections);
252                     for (Connection c : connections) {
253                         if (c.edge != null) {
254                             if (c.edge.getElementClass().containsClass(BendsHandler.class))
255                                 edges.add(c.edge);
256                             if (DEBUG_DELETE)
257                                 System.out.println("TERMINAL CONNECTION WILL BE DISCONNECTED: " + c);
258                         }
259                     }
260                 }
261             }
262         }
263
264         if (elements.isEmpty() && edges.isEmpty())
265             return false;
266
267         if (DEBUG_DELETE) {
268             System.out.println("gathered elements to delete:");
269             System.out.println("\telements:");
270             if (!elements.isEmpty())
271                 for (IElement e : elements)
272                     System.out.println("\t\t" + e);
273             System.out.println("\tedges:");
274             if (!edges.isEmpty())
275                 for (IElement e : edges)
276                     System.out.println("\t\t" + e);
277         }
278
279         new DatabaseJob("Delete selection") {
280             @Override
281             protected IStatus run(IProgressMonitor monitor) {
282                 try {
283                     delete(monitor);
284                     return Status.OK_STATUS;
285                 } catch (CannotRemoveException e) {
286                     if (exceptionListener != null) exceptionListener.accept(e);
287                     return new Status(IStatus.CANCEL, Activator.PLUGIN_ID, e.getLocalizedMessage(), e);
288                 } catch (DatabaseException e) {
289                     if (exceptionListener != null) exceptionListener.accept(e);
290                     return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unexpected error in delete.", e);
291                 } finally {
292                     if (errorListener != null) {
293                         errorListener.accept(null);
294                     }
295                     monitor.done();
296                 }
297             }
298
299             private void delete(IProgressMonitor monitor) throws DatabaseException {
300                 SubMonitor subMonitor = SubMonitor.convert(monitor, 4);
301                 Simantics.getSession().syncRequest(new WriteRequest() {
302                     Set<Resource> connectionsToRemove = new HashSet<Resource>();
303                     Set<Resource> touchedConnections = new HashSet<Resource>();
304
305                     @Override
306                     public void perform(WriteGraph graph) throws DatabaseException {
307                         validateRemoval(graph);
308                         graph.markUndoPoint();
309
310                         ConnectionUtil cu = new ConnectionUtil(graph);
311                         SubMonitor edgeElementMonitor = subMonitor.split(2); // Consume 50 % of the monitor
312                         edgeElementMonitor.setWorkRemaining(edges.size() + elements.size()); // set work remaining to size of edges and elements
313                         edgeElementMonitor.setTaskName("Removing edges");
314                         // Remove edges
315                         for (IElement edge : edges) {
316                             ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
317                             touchConnection( ce.getConnection() );
318
319                             if (DEBUG_DELETE)
320                                 System.out.println("REMOVING EDGE: " + edge);
321                             Object obj = ElementUtils.getObject(edge);
322                             if (obj instanceof EdgeResource) {
323                                 cu.remove((EdgeResource) obj);
324                             }
325                             edgeElementMonitor.worked(1);
326                         }
327                         edgeElementMonitor.setTaskName("Removing elements");
328                         // Remove elements
329                         for (IElement element : elements) {
330                             ConnectionHandler ch = element.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);
331                             if (ch != null) {
332                                 if (DEBUG_DELETE)
333                                     System.out.println("MARKING CONNECTION TO BE REMOVED: " + element);
334                                 connectionsToRemove.add( (Resource) ElementUtils.getObject(element) );
335                             } else {
336                                 ConnectionEntity ce = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);
337                                 if(ce != null) {
338                                     if (DEBUG_DELETE)
339                                         System.out.println("REMOVING BRANCH POINT: " + element);
340                                     new RemoveBranchpoint(element).perform(graph);
341                                     touchConnection( ce.getConnection() );
342                                 } else {
343                                     if (DEBUG_DELETE)
344                                         System.out.println("REMOVING ELEMENT: " + element);
345
346                                     Object obj = ElementUtils.getObject(element);
347                                     if (obj instanceof Resource) {
348                                         // Get terminal connections for element
349                                         Collection<Resource> connectors = cu.getTerminalConnectors((Resource) obj, null);
350                                         for (Resource connector : connectors) {
351                                             Resource connection = ConnectionUtil.tryGetConnection(graph, connector);
352                                             if (connection != null)
353                                                 touchConnection( connection );
354                                             cu.disconnectFromAllRouteNodes(connector);
355                                         }
356
357                                         new RemoveElement((Resource)d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), (Resource)element.getHint(ElementHints.KEY_OBJECT)).perform(graph);
358                                     }
359                                 }
360                             }
361                             edgeElementMonitor.worked(1);
362                         }
363                         SubMonitor connectionsMonitor = subMonitor.split(1); // Consume 50 % of the monitor
364                         connectionsMonitor.setWorkRemaining(touchedConnections.size()); // set work remaining to size of edges and elements
365                         connectionsMonitor.setTaskName("Pruning connectors");
366                         // Check all touched connections to see if they are empty.
367                         for (Resource connection : touchedConnections) {
368                             int removedConnectors = cu.removeUnusedConnectors(connection);
369                             if (DEBUG_DELETE)
370                                 System.out.println("PRUNED " + removedConnectors + " CONNECTORS FROM TOUCHED CONNECTION " + connection);
371                             while (true) {
372                                 int removedInteriorRouteNodes = cu.removeExtraInteriorRouteNodes(connection);
373                                 if (DEBUG_DELETE)
374                                     System.out.println("PRUNED " + removedInteriorRouteNodes + " INTERIOR ROUTE NODES FROM TOUCHED CONNECTION " + connection);
375                                 if (removedInteriorRouteNodes == 0)
376                                     break;
377                             }
378                             int connectors = cu.getConnectedConnectors(connection, null).size();
379                             if (DEBUG_DELETE)
380                                 System.out.println("\t" + connectors + " CONNECTORS LEFT");
381                             if (connectors < 2) {
382                                 connectionsToRemove.add(connection);
383                             }
384                             connectionsMonitor.worked(1);
385                         }
386                         SubMonitor connectionsMonitor2 = subMonitor.split(1); // Consume 50 % of the monitor
387                         connectionsMonitor2.setWorkRemaining(connectionsToRemove.size()); // set work remaini
388                         connectionsMonitor2.setTaskName("Removing leftover connections");
389                         // Remove selected/left-over empty connections
390                         for (Resource connection : connectionsToRemove) {
391                             if (DEBUG_DELETE)
392                                 System.out.println("REMOVING CONNECTION: " + connection);
393                             RemoveElement.removeElement(graph, (Resource)d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), connection);
394                             connectionsMonitor2.worked(1);
395                         }
396                     }
397
398                     private void validateRemoval(ReadGraph graph) throws DatabaseException, CannotRemoveException {
399                         ArrayList<String> problems = new ArrayList<String>(elements.size());
400                         for (IElement element : elements) {
401                             Object obj = ElementUtils.getObject(element);
402                             if (obj instanceof Resource) {
403                                 Remover remover = RemoverUtil.getPossibleRemover(graph, (Resource) obj);
404                                 if (remover != null) {
405                                     String problem = remover.canRemove(graph, new HashMap<Object, Object>(4));
406                                     if (problem != null) {
407                                         problems.add(problem);
408                                     }
409                                 }
410                             }
411                         }
412                         if (!problems.isEmpty()) {
413                             throw new CannotRemoveException(EString.implode(problems));
414                         }
415                     }
416
417                     void touchConnection(Object c) {
418                         if (DEBUG_DELETE)
419                             System.out.println("TOUCHED CONNECTION: " + c);
420                         if (c instanceof IElement) {
421                             Object obj = ElementUtils.getObject((IElement) c);
422                             if (obj instanceof Resource)
423                                 touchedConnections.add((Resource) obj);
424                         } else if (c instanceof Resource) {
425                             touchedConnections.add((Resource) c);
426                         }
427                     }
428                 });
429             }
430         }.schedule();
431
432         return true;
433     }
434
435     void message(final String message) {
436         if (statusLine == null)
437             return;
438         swtExec(new Runnable() {
439             @Override
440             public void run() {
441                 statusLine.setMessage(message);
442                 statusLine.setErrorMessage(null);
443             }
444         });
445     }
446
447     void error(final String message) {
448         if (statusLine == null)
449             return;
450         swtExec(new Runnable() {
451             @Override
452             public void run() {
453                 statusLine.setErrorMessage(message);
454             }
455         });
456     }
457
458     void swtExec(Runnable r) {
459         ThreadUtils.asyncExec(SWTThread.getThreadAccess(Display.getDefault()), r);
460     }
461
462 }