source: josm/trunk/src/org/openstreetmap/josm/actions/PurgeAction.java@ 8509

Last change on this file since 8509 was 8365, checked in by Don-vip, 9 years ago

fix Findbugs performance issues

  • Property svn:eol-style set to native
File size: 12.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.Dimension;
8import java.awt.GridBagLayout;
9import java.awt.Insets;
10import java.awt.event.ActionEvent;
11import java.awt.event.KeyEvent;
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.Comparator;
16import java.util.HashSet;
17import java.util.List;
18import java.util.Set;
19
20import javax.swing.AbstractAction;
21import javax.swing.BorderFactory;
22import javax.swing.Box;
23import javax.swing.JButton;
24import javax.swing.JCheckBox;
25import javax.swing.JLabel;
26import javax.swing.JList;
27import javax.swing.JPanel;
28import javax.swing.JScrollPane;
29import javax.swing.JSeparator;
30
31import org.openstreetmap.josm.Main;
32import org.openstreetmap.josm.command.PurgeCommand;
33import org.openstreetmap.josm.data.osm.Node;
34import org.openstreetmap.josm.data.osm.OsmPrimitive;
35import org.openstreetmap.josm.data.osm.Relation;
36import org.openstreetmap.josm.data.osm.RelationMember;
37import org.openstreetmap.josm.data.osm.Way;
38import org.openstreetmap.josm.gui.ExtendedDialog;
39import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
40import org.openstreetmap.josm.gui.help.HelpUtil;
41import org.openstreetmap.josm.gui.layer.OsmDataLayer;
42import org.openstreetmap.josm.tools.GBC;
43import org.openstreetmap.josm.tools.ImageProvider;
44import org.openstreetmap.josm.tools.Shortcut;
45
46/**
47 * The action to purge the selected primitives, i.e. remove them from the
48 * data layer, or remove their content and make them incomplete.
49 *
50 * This means, the deleted flag is not affected and JOSM simply forgets
51 * about these primitives.
52 *
53 * This action is undo-able. In order not to break previous commands in the
54 * undo buffer, we must re-add the identical object (and not semantically
55 * equal ones).
56 */
57public class PurgeAction extends JosmAction {
58
59 /**
60 * Constructs a new {@code PurgeAction}.
61 */
62 public PurgeAction() {
63 /* translator note: other expressions for "purge" might be "forget", "clean", "obliterate", "prune" */
64 super(tr("Purge..."), "purge", tr("Forget objects but do not delete them on server when uploading."),
65 Shortcut.registerShortcut("system:purge", tr("Edit: {0}", tr("Purge")),
66 KeyEvent.VK_P, Shortcut.CTRL_SHIFT),
67 true);
68 putValue("help", HelpUtil.ht("/Action/Purge"));
69 }
70
71 protected transient OsmDataLayer layer;
72 protected JCheckBox cbClearUndoRedo;
73
74 protected transient Set<OsmPrimitive> toPurge;
75 /**
76 * finally, contains all objects that are purged
77 */
78 protected transient Set<OsmPrimitive> toPurgeChecked;
79 /**
80 * Subset of toPurgeChecked. Marks primitives that remain in the
81 * dataset, but incomplete.
82 */
83 protected transient Set<OsmPrimitive> makeIncomplete;
84 /**
85 * Subset of toPurgeChecked. Those that have not been in the selection.
86 */
87 protected transient List<OsmPrimitive> toPurgeAdditionally;
88
89 @Override
90 public void actionPerformed(ActionEvent e) {
91 if (!isEnabled())
92 return;
93
94 Collection<OsmPrimitive> sel = getCurrentDataSet().getAllSelected();
95 layer = Main.main.getEditLayer();
96
97 toPurge = new HashSet<>(sel);
98 toPurgeAdditionally = new ArrayList<>();
99 toPurgeChecked = new HashSet<>();
100
101 // Add referrer, unless the object to purge is not new
102 // and the parent is a relation
103 Set<OsmPrimitive> toPurgeRecursive = new HashSet<>();
104 while (!toPurge.isEmpty()) {
105
106 for (OsmPrimitive osm: toPurge) {
107 for (OsmPrimitive parent: osm.getReferrers()) {
108 if (toPurge.contains(parent) || toPurgeChecked.contains(parent) || toPurgeRecursive.contains(parent)) {
109 continue;
110 }
111 if (parent instanceof Way || (parent instanceof Relation && osm.isNew())) {
112 toPurgeAdditionally.add(parent);
113 toPurgeRecursive.add(parent);
114 }
115 }
116 toPurgeChecked.add(osm);
117 }
118 toPurge = toPurgeRecursive;
119 toPurgeRecursive = new HashSet<>();
120 }
121
122 makeIncomplete = new HashSet<>();
123
124 // Find the objects that will be incomplete after purging.
125 // At this point, all parents of new to-be-purged primitives are
126 // also to-be-purged and
127 // all parents of not-new to-be-purged primitives are either
128 // to-be-purged or of type relation.
129 TOP:
130 for (OsmPrimitive child : toPurgeChecked) {
131 if (child.isNew()) {
132 continue;
133 }
134 for (OsmPrimitive parent : child.getReferrers()) {
135 if (parent instanceof Relation && !toPurgeChecked.contains(parent)) {
136 makeIncomplete.add(child);
137 continue TOP;
138 }
139 }
140 }
141
142 // Add untagged way nodes. Do not add nodes that have other
143 // referrers not yet to-be-purged.
144 if (Main.pref.getBoolean("purge.add_untagged_waynodes", true)) {
145 Set<OsmPrimitive> wayNodes = new HashSet<>();
146 for (OsmPrimitive osm : toPurgeChecked) {
147 if (osm instanceof Way) {
148 Way w = (Way) osm;
149 NODE:
150 for (Node n : w.getNodes()) {
151 if (n.isTagged() || toPurgeChecked.contains(n)) {
152 continue;
153 }
154 for (OsmPrimitive ref : n.getReferrers()) {
155 if (ref != w && !toPurgeChecked.contains(ref)) {
156 continue NODE;
157 }
158 }
159 wayNodes.add(n);
160 }
161 }
162 }
163 toPurgeChecked.addAll(wayNodes);
164 toPurgeAdditionally.addAll(wayNodes);
165 }
166
167 if (Main.pref.getBoolean("purge.add_relations_with_only_incomplete_members", true)) {
168 Set<Relation> relSet = new HashSet<>();
169 for (OsmPrimitive osm : toPurgeChecked) {
170 for (OsmPrimitive parent : osm.getReferrers()) {
171 if (parent instanceof Relation
172 && !(toPurgeChecked.contains(parent))
173 && hasOnlyIncompleteMembers((Relation) parent, toPurgeChecked, relSet)) {
174 relSet.add((Relation) parent);
175 }
176 }
177 }
178
179 /**
180 * Add higher level relations (list gets extended while looping over it)
181 */
182 List<Relation> relLst = new ArrayList<>(relSet);
183 for (int i=0; i<relLst.size(); ++i) {
184 for (OsmPrimitive parent : relLst.get(i).getReferrers()) {
185 if (!(toPurgeChecked.contains(parent))
186 && hasOnlyIncompleteMembers((Relation) parent, toPurgeChecked, relLst)) {
187 relLst.add((Relation) parent);
188 }
189 }
190 }
191 relSet = new HashSet<>(relLst);
192 toPurgeChecked.addAll(relSet);
193 toPurgeAdditionally.addAll(relSet);
194 }
195
196 boolean modified = false;
197 for (OsmPrimitive osm : toPurgeChecked) {
198 if (osm.isModified()) {
199 modified = true;
200 break;
201 }
202 }
203
204 ExtendedDialog confirmDlg = new ExtendedDialog(Main.parent, tr("Confirm Purging"), new String[] {tr("Purge"), tr("Cancel")});
205 confirmDlg.setContent(buildPanel(modified), false);
206 confirmDlg.setButtonIcons(new String[] {"ok", "cancel"});
207
208 int answer = confirmDlg.showDialog().getValue();
209 if (answer != 1)
210 return;
211
212 Main.pref.put("purge.clear_undo_redo", cbClearUndoRedo.isSelected());
213
214 Main.main.undoRedo.add(new PurgeCommand(Main.main.getEditLayer(), toPurgeChecked, makeIncomplete));
215
216 if (cbClearUndoRedo.isSelected()) {
217 Main.main.undoRedo.clean();
218 getCurrentDataSet().clearSelectionHistory();
219 }
220 }
221
222 private JPanel buildPanel(boolean modified) {
223 JPanel pnl = new JPanel(new GridBagLayout());
224
225 pnl.add(Box.createRigidArea(new Dimension(400,0)), GBC.eol().fill(GBC.HORIZONTAL));
226
227 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
228 pnl.add(new JLabel("<html>"+
229 tr("This operation makes JOSM forget the selected objects.<br> " +
230 "They will be removed from the layer, but <i>not</i> deleted<br> " +
231 "on the server when uploading.")+"</html>",
232 ImageProvider.get("purge"), JLabel.LEFT), GBC.eol().fill(GBC.HORIZONTAL));
233
234 if (!toPurgeAdditionally.isEmpty()) {
235 pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0,5,0,5));
236 pnl.add(new JLabel("<html>"+
237 tr("The following dependent objects will be purged<br> " +
238 "in addition to the selected objects:")+"</html>",
239 ImageProvider.get("warning-small"), JLabel.LEFT), GBC.eol().fill(GBC.HORIZONTAL));
240
241 Collections.sort(toPurgeAdditionally, new Comparator<OsmPrimitive>() {
242 @Override
243 public int compare(OsmPrimitive o1, OsmPrimitive o2) {
244 int type = o2.getType().compareTo(o1.getType());
245 if (type != 0)
246 return type;
247 return Long.compare(o1.getUniqueId(), o2.getUniqueId());
248 }
249 });
250 JList<OsmPrimitive> list = new JList<>(toPurgeAdditionally.toArray(new OsmPrimitive[toPurgeAdditionally.size()]));
251 /* force selection to be active for all entries */
252 list.setCellRenderer(new OsmPrimitivRenderer() {
253 @Override
254 public Component getListCellRendererComponent(JList<? extends OsmPrimitive> list,
255 OsmPrimitive value,
256 int index,
257 boolean isSelected,
258 boolean cellHasFocus) {
259 return super.getListCellRendererComponent(list, value, index, true, false);
260 }
261 });
262 JScrollPane scroll = new JScrollPane(list);
263 scroll.setPreferredSize(new Dimension(250, 300));
264 scroll.setMinimumSize(new Dimension(250, 300));
265 pnl.add(scroll, GBC.std().fill(GBC.BOTH).weight(1.0, 1.0));
266
267 JButton addToSelection = new JButton(new AbstractAction() {
268 {
269 putValue(SHORT_DESCRIPTION, tr("Add to selection"));
270 putValue(SMALL_ICON, ImageProvider.get("dialogs","select"));
271 }
272
273 @Override
274 public void actionPerformed(ActionEvent e) {
275 layer.data.addSelected(toPurgeAdditionally);
276 }
277 });
278 addToSelection.setMargin(new Insets(0,0,0,0));
279 pnl.add(addToSelection, GBC.eol().anchor(GBC.SOUTHWEST).weight(0.0, 1.0).insets(2,0,0,3));
280 }
281
282 if (modified) {
283 pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0,5,0,5));
284 pnl.add(new JLabel("<html>"+tr("Some of the objects are modified.<br> " +
285 "Proceed, if these changes should be discarded."+"</html>"),
286 ImageProvider.get("warning-small"), JLabel.LEFT),
287 GBC.eol().fill(GBC.HORIZONTAL));
288 }
289
290 cbClearUndoRedo = new JCheckBox(tr("Clear Undo/Redo buffer"));
291 cbClearUndoRedo.setSelected(Main.pref.getBoolean("purge.clear_undo_redo", false));
292
293 pnl.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0,5,0,5));
294 pnl.add(cbClearUndoRedo, GBC.eol());
295 return pnl;
296 }
297
298 @Override
299 protected void updateEnabledState() {
300 if (getCurrentDataSet() == null) {
301 setEnabled(false);
302 } else {
303 setEnabled(!(getCurrentDataSet().selectionEmpty()));
304 }
305 }
306
307 @Override
308 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
309 setEnabled(selection != null && !selection.isEmpty());
310 }
311
312 private boolean hasOnlyIncompleteMembers(Relation r, Collection<OsmPrimitive> toPurge, Collection<? extends OsmPrimitive> moreToPurge) {
313 for (RelationMember m : r.getMembers()) {
314 if (!m.getMember().isIncomplete() && !toPurge.contains(m.getMember()) && !moreToPurge.contains(m.getMember()))
315 return false;
316 }
317 return true;
318 }
319}
Note: See TracBrowser for help on using the repository browser.