source: josm/trunk/src/org/openstreetmap/josm/command/DeleteCommand.java@ 1938

Last change on this file since 1938 was 1937, checked in by jttt, 15 years ago

Replace some occurrences of RelationMember.member with getters

  • Property svn:eol-style set to native
File size: 17.9 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.command;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.GridBagLayout;
8import java.awt.geom.Area;
9import java.util.ArrayList;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.Iterator;
15import java.util.LinkedList;
16import java.util.List;
17
18import javax.swing.JLabel;
19import javax.swing.JOptionPane;
20import javax.swing.JPanel;
21import javax.swing.tree.DefaultMutableTreeNode;
22import javax.swing.tree.MutableTreeNode;
23
24import org.openstreetmap.josm.Main;
25import org.openstreetmap.josm.data.osm.Node;
26import org.openstreetmap.josm.data.osm.OsmPrimitive;
27import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
28import org.openstreetmap.josm.data.osm.Relation;
29import org.openstreetmap.josm.data.osm.RelationMember;
30import org.openstreetmap.josm.data.osm.Way;
31import org.openstreetmap.josm.data.osm.WaySegment;
32import org.openstreetmap.josm.data.osm.visitor.CollectBackReferencesVisitor;
33import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
34import org.openstreetmap.josm.gui.ExtendedDialog;
35import org.openstreetmap.josm.gui.PrimitiveNameFormatter;
36import org.openstreetmap.josm.gui.layer.OsmDataLayer;
37import org.openstreetmap.josm.tools.ImageProvider;
38
39/**
40 * A command to delete a number of primitives from the dataset.
41 * @author imi
42 */
43public class DeleteCommand extends Command {
44
45 /**
46 * The primitives that get deleted.
47 */
48 private final Collection<? extends OsmPrimitive> toDelete;
49
50 /**
51 * Constructor for a collection of data
52 */
53 public DeleteCommand(Collection<? extends OsmPrimitive> data) {
54 super();
55 this.toDelete = data;
56 }
57
58 /**
59 * Constructor for a single data item. Use the collection constructor to delete multiple
60 * objects.
61 */
62 public DeleteCommand(OsmPrimitive data) {
63 this.toDelete = Collections.singleton(data);
64 }
65
66 /**
67 * Constructor for a single data item. Use the collection constructor to delete multiple
68 * objects.
69 *
70 * @param layer the layer context for deleting this primitive
71 * @param data the primitive to delete
72 */
73 public DeleteCommand(OsmDataLayer layer, OsmPrimitive data) {
74 super(layer);
75 this.toDelete = Collections.singleton(data);
76 }
77
78 /**
79 * Constructor for a collection of data to be deleted in the context of
80 * a specific layer
81 *
82 * @param layer the layer context for deleting these primitives
83 * @param data the primitives to delete
84 */
85 public DeleteCommand(OsmDataLayer layer, Collection<? extends OsmPrimitive> data) {
86 super(layer);
87 this.toDelete = data;
88 }
89
90 @Override
91 public boolean executeCommand() {
92 super.executeCommand();
93 for (OsmPrimitive osm : toDelete) {
94 osm.delete(true);
95 }
96 return true;
97 }
98
99 @Override
100 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted,
101 Collection<OsmPrimitive> added) {
102 deleted.addAll(toDelete);
103 }
104
105 @Override
106 public MutableTreeNode description() {
107 if (toDelete.size() == 1) {
108 OsmPrimitive primitive = toDelete.iterator().next();
109 return new DefaultMutableTreeNode(new JLabel(tr("Delete {1} {0}", new PrimitiveNameFormatter()
110 .getName(primitive), OsmPrimitiveType.from(primitive).getLocalizedDisplayNameSingular()),
111 ImageProvider.get(OsmPrimitiveType.from(primitive)), JLabel.HORIZONTAL));
112 }
113
114 String cname = null;
115 String apiname = null;
116 String cnamem = null;
117 for (OsmPrimitive osm : toDelete) {
118 if (cname == null) {
119 apiname = OsmPrimitiveType.from(osm).getAPIName();
120 cname = OsmPrimitiveType.from(osm).getLocalizedDisplayNameSingular();
121 cnamem = OsmPrimitiveType.from(osm).getLocalizedDisplayNamePlural();
122 } else if (!cname.equals(OsmPrimitiveType.from(osm).getLocalizedDisplayNameSingular())) {
123 apiname = "object";
124 cname = trn("object", "objects", 1);
125 cnamem = trn("object", "objects", 2);
126 }
127 }
128 DefaultMutableTreeNode root = new DefaultMutableTreeNode(new JLabel(tr("Delete {0} {1}", toDelete.size(), trn(
129 cname, cnamem, toDelete.size())), ImageProvider.get("data", apiname), JLabel.HORIZONTAL));
130 for (OsmPrimitive osm : toDelete) {
131 root.add(new DefaultMutableTreeNode(new JLabel(new PrimitiveNameFormatter().getName(osm), ImageProvider
132 .get(OsmPrimitiveType.from(osm)), JLabel.HORIZONTAL)));
133 }
134 return root;
135 }
136
137 /**
138 * Delete the primitives and everything they reference.
139 *
140 * If a node is deleted, the node and all ways and relations the node is part of are deleted as
141 * well.
142 *
143 * If a way is deleted, all relations the way is member of are also deleted.
144 *
145 * If a way is deleted, only the way and no nodes are deleted.
146 *
147 * @param selection The list of all object to be deleted.
148 * @return command A command to perform the deletions, or null of there is nothing to delete.
149 */
150 public static Command deleteWithReferences(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection) {
151 CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data);
152 for (OsmPrimitive osm : selection) {
153 osm.visit(v);
154 }
155 v.data.addAll(selection);
156 if (v.data.isEmpty())
157 return null;
158 if (!checkAndConfirmOutlyingDeletes(layer,v.data))
159 return null;
160 return new DeleteCommand(layer,v.data);
161 }
162
163 private static int testRelation(Relation ref, OsmPrimitive osm) {
164 PrimitiveNameFormatter formatter = new PrimitiveNameFormatter();
165 String role = new String();
166 for (RelationMember m : ref.getMembers()) {
167 if (m.getMember() == osm) {
168 role = m.getRole();
169 break;
170 }
171 }
172 if (role.length() > 0)
173 return new ExtendedDialog(Main.parent, tr("Conflicting relation"), tr(
174 "Selection \"{0}\" is used by relation \"{1}\" with role {2}.\nDelete from relation?", formatter
175 .getName(osm), formatter.getName(ref), role), new String[] { tr("Delete from relation"),
176 tr("Cancel") }, new String[] { "dialogs/delete.png", "cancel.png" }).getValue();
177 else
178 return new ExtendedDialog(Main.parent, tr("Conflicting relation"), tr(
179 "Selection \"{0}\" is used by relation \"{1}\".\nDelete from relation?", formatter.getName(osm),
180 formatter.getName(ref)), new String[] { tr("Delete from relation"), tr("Cancel") }, new String[] {
181 "dialogs/delete.png", "cancel.png" }).getValue();
182 }
183
184 public static Command delete(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection) {
185 return delete(layer, selection, true);
186 }
187
188 /**
189 * Replies the collection of nodes referred to by primitives in <code>primitivesToDelete</code> which
190 * can be deleted too. A node can be deleted if
191 * <ul>
192 * <li>it is untagged (see {@see Node#isTagged()}</li>
193 * <li>it is not referred to by other primitives outside of <code>primitivesToDelete</code></li>
194 * <ul>
195 * @param layer the layer in whose context primitives are deleted
196 * @param primitivesToDelete the primitives to delete
197 * @return the collection of nodes referred to by primitives in <code>primitivesToDelete</code> which
198 * can be deleted too
199 */
200 protected static Collection<Node> computeNodesToDelete(OsmDataLayer layer, Collection<OsmPrimitive> primitivesToDelete) {
201 Collection<Node> nodesToDelete = new HashSet<Node>();
202 for (OsmPrimitive osm : primitivesToDelete) {
203 if (! (osm instanceof Way) ) {
204 continue;
205 }
206 for (Node n : ((Way) osm).getNodes()) {
207 if (n.isTagged()) {
208 continue;
209 }
210 CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data, false);
211 n.visit(v);
212 v.data.removeAll(primitivesToDelete);
213 if (v.data.isEmpty()) {
214 nodesToDelete.add(n);
215 }
216 }
217 }
218 return nodesToDelete;
219 }
220
221 /**
222 * Try to delete all given primitives.
223 *
224 * If a node is used by a way, it's removed from that way. If a node or a way is used by a
225 * relation, inform the user and do not delete.
226 *
227 * If this would cause ways with less than 2 nodes to be created, delete these ways instead. If
228 * they are part of a relation, inform the user and do not delete.
229 *
230 * @param layer the {@see OsmDataLayer} in whose context a primitive the primitives are deleted
231 * @param selection The objects to delete.
232 * @param alsoDeleteNodesInWay <code>true</code> if nodes should be deleted as well
233 * @return command a command to perform the deletions, or null if there is nothing to delete.
234 */
235 public static Command delete(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection, boolean alsoDeleteNodesInWay) {
236 if (selection.isEmpty())
237 return null;
238
239 Collection<OsmPrimitive> primitivesToDelete = new HashSet<OsmPrimitive>(selection);
240 Collection<Way> waysToBeChanged = new HashSet<Way>();
241 HashMap<OsmPrimitive, Collection<OsmPrimitive>> relationsToBeChanged = new HashMap<OsmPrimitive, Collection<OsmPrimitive>>();
242
243 if (alsoDeleteNodesInWay) {
244 // delete untagged nodes only referenced by primitives in primitivesToDelete,
245 // too
246 Collection<Node> nodesToDelete = computeNodesToDelete(layer, primitivesToDelete);
247 primitivesToDelete.addAll(nodesToDelete);
248 }
249
250 if (!checkAndConfirmOutlyingDeletes(layer,primitivesToDelete))
251 return null;
252
253 for (OsmPrimitive osm : primitivesToDelete) {
254 CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data, false);
255 osm.visit(v);
256 for (OsmPrimitive ref : v.data) {
257 if (primitivesToDelete.contains(ref)) {
258 continue;
259 }
260 if (ref instanceof Way) {
261 waysToBeChanged.add((Way) ref);
262 } else if (ref instanceof Relation) {
263 if (testRelation((Relation) ref, osm) == 1) {
264 Collection<OsmPrimitive> relset = relationsToBeChanged.get(ref);
265 if (relset == null) {
266 relset = new HashSet<OsmPrimitive>();
267 }
268 relset.add(osm);
269 relationsToBeChanged.put(ref, relset);
270 } else
271 return null;
272 } else
273 return null;
274 }
275 }
276
277 Collection<Command> cmds = new LinkedList<Command>();
278 for (Way w : waysToBeChanged) {
279 Way wnew = new Way(w);
280 wnew.removeNodes(primitivesToDelete);
281 if (wnew.getNodesCount() < 2) {
282 primitivesToDelete.add(w);
283
284 CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data, false);
285 w.visit(v);
286 for (OsmPrimitive ref : v.data) {
287 if (primitivesToDelete.contains(ref)) {
288 continue;
289 }
290 if (ref instanceof Relation) {
291 Boolean found = false;
292 Collection<OsmPrimitive> relset = relationsToBeChanged.get(ref);
293 if (relset == null) {
294 relset = new HashSet<OsmPrimitive>();
295 } else {
296 for (OsmPrimitive m : relset) {
297 if (m == w) {
298 found = true;
299 break;
300 }
301 }
302 }
303 if (!found) {
304 if (testRelation((Relation) ref, w) == 1) {
305 relset.add(w);
306 relationsToBeChanged.put(ref, relset);
307 } else
308 return null;
309 }
310 } else
311 return null;
312 }
313 } else {
314 cmds.add(new ChangeCommand(w, wnew));
315 }
316 }
317
318 Iterator<OsmPrimitive> iterator = relationsToBeChanged.keySet().iterator();
319 while (iterator.hasNext()) {
320 Relation cur = (Relation) iterator.next();
321 Relation rel = new Relation(cur);
322 for (OsmPrimitive osm : relationsToBeChanged.get(cur)) {
323 rel.removeMembersFor(osm);
324 }
325 cmds.add(new ChangeCommand(cur, rel));
326 }
327
328 // #2707: ways to be deleted can include new nodes (with node.id == 0).
329 // Remove them from the way before the way is deleted. Otherwise the
330 // deleted way is saved (or sent to the API) with a dangling reference to a node
331 // Example:
332 // <node id='2' action='delete' visible='true' version='1' ... />
333 // <node id='1' action='delete' visible='true' version='1' ... />
334 // <!-- missing node with id -1 because new deleted nodes are not persisted -->
335 // <way id='3' action='delete' visible='true' version='1'>
336 // <nd ref='1' />
337 // <nd ref='-1' /> <!-- heres the problem -->
338 // <nd ref='2' />
339 // </way>
340 for (OsmPrimitive primitive : primitivesToDelete) {
341 if (!(primitive instanceof Way)) {
342 continue;
343 }
344 Way w = (Way) primitive;
345 if (w.id == 0) { // new ways with id == 0 are fine,
346 continue; // process existing ways only
347 }
348 Way wnew = new Way(w);
349 List<Node> nodesToKeep = new ArrayList<Node>();
350 // lookup new nodes which have been added to the set of deleted
351 // nodes ...
352 for (Node n : wnew.getNodes()) {
353 if (n.id != 0 || !primitivesToDelete.contains(n)) {
354 nodesToKeep.add(n);
355 }
356 }
357 // .. and remove them from the way
358 //
359 wnew.setNodes(nodesToKeep);
360 if (nodesToKeep.size() < w.getNodesCount()) {
361 cmds.add(new ChangeCommand(w, wnew));
362 }
363 }
364
365 if (!primitivesToDelete.isEmpty()) {
366 cmds.add(new DeleteCommand(layer,primitivesToDelete));
367 }
368
369 return new SequenceCommand(tr("Delete"), cmds);
370 }
371
372 public static Command deleteWaySegment(OsmDataLayer layer, WaySegment ws) {
373 List<Node> n1 = new ArrayList<Node>(), n2 = new ArrayList<Node>();
374
375 n1.addAll(ws.way.getNodes().subList(0, ws.lowerIndex + 1));
376 n2.addAll(ws.way.getNodes().subList(ws.lowerIndex + 1, ws.way.getNodesCount()));
377
378 if (n1.size() < 2 && n2.size() < 2)
379 return new DeleteCommand(layer, Collections.singleton(ws.way));
380
381 Way wnew = new Way(ws.way);
382
383 if (n1.size() < 2) {
384 wnew.setNodes(n2);
385 return new ChangeCommand(ws.way, wnew);
386 } else if (n2.size() < 2) {
387 wnew.setNodes(n1);
388 return new ChangeCommand(ws.way, wnew);
389 } else {
390 Collection<Command> cmds = new LinkedList<Command>();
391
392 wnew.setNodes(n1);
393 cmds.add(new ChangeCommand(ws.way, wnew));
394
395 Way wnew2 = new Way();
396 wnew2.setKeys(wnew.getKeys());
397 wnew2.setNodes(n2);
398 cmds.add(new AddCommand(wnew2));
399
400 return new SequenceCommand(tr("Split way segment"), cmds);
401 }
402 }
403
404 /**
405 * Check whether user is about to delete data outside of the download area. Request confirmation
406 * if he is.
407 *
408 * @param layer the layer in whose context data is deleted
409 * @param primitivesToDelete the primitives to delete
410 * @return true, if deleting outlying primitives is OK; false, otherwise
411 */
412 private static boolean checkAndConfirmOutlyingDeletes(OsmDataLayer layer, Collection<OsmPrimitive> primitivesToDelete) {
413 Area a = layer.data.getDataSourceArea();
414 if (a != null) {
415 for (OsmPrimitive osm : primitivesToDelete) {
416 if (osm instanceof Node && osm.id != 0) {
417 Node n = (Node) osm;
418 if (!a.contains(n.getCoor())) {
419 JPanel msg = new JPanel(new GridBagLayout());
420 msg.add(new JLabel(
421 "<html>" +
422 // leave message in one tr() as there is a grammatical
423 // connection.
424 tr("You are about to delete nodes outside of the area you have downloaded."
425 + "<br>"
426 + "This can cause problems because other objects (that you don't see) might use them."
427 + "<br>" + "Do you really want to delete?") + "</html>"));
428 return ConditionalOptionPaneUtil.showConfirmationDialog(
429 "delete_outside_nodes",
430 Main.parent,
431 msg,
432 tr("Delete confirmation"),
433 JOptionPane.YES_NO_OPTION,
434 JOptionPane.QUESTION_MESSAGE,
435 JOptionPane.YES_OPTION
436 );
437 }
438 }
439 }
440 }
441 return true;
442 }
443}
Note: See TracBrowser for help on using the repository browser.