source: josm/trunk/src/org/openstreetmap/josm/command/Command.java@ 9168

Last change on this file since 9168 was 8945, checked in by Don-vip, 8 years ago

fix #12008 - do not create empty commands from tagging preset dialog + add robustness

  • Property svn:eol-style set to native
File size: 11.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.command;
3
4import java.awt.GridBagLayout;
5import java.util.ArrayList;
6import java.util.Collection;
7import java.util.HashMap;
8import java.util.LinkedHashMap;
9import java.util.Map;
10import java.util.Map.Entry;
11
12import javax.swing.JOptionPane;
13import javax.swing.JPanel;
14
15import org.openstreetmap.josm.Main;
16import org.openstreetmap.josm.data.coor.EastNorth;
17import org.openstreetmap.josm.data.coor.LatLon;
18import org.openstreetmap.josm.data.osm.Node;
19import org.openstreetmap.josm.data.osm.OsmPrimitive;
20import org.openstreetmap.josm.data.osm.PrimitiveData;
21import org.openstreetmap.josm.data.osm.Relation;
22import org.openstreetmap.josm.data.osm.Way;
23import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
24import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
25import org.openstreetmap.josm.gui.layer.Layer;
26import org.openstreetmap.josm.gui.layer.OsmDataLayer;
27import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
28import org.openstreetmap.josm.tools.CheckParameterUtil;
29
30/**
31 * Classes implementing Command modify a dataset in a specific way. A command is
32 * one atomic action on a specific dataset, such as move or delete.
33 *
34 * The command remembers the {@link OsmDataLayer} it is operating on.
35 *
36 * @author imi
37 */
38public abstract class Command extends PseudoCommand {
39
40 private static final class CloneVisitor extends AbstractVisitor {
41 public final Map<OsmPrimitive, PrimitiveData> orig = new LinkedHashMap<>();
42
43 @Override
44 public void visit(Node n) {
45 orig.put(n, n.save());
46 }
47
48 @Override
49 public void visit(Way w) {
50 orig.put(w, w.save());
51 }
52
53 @Override
54 public void visit(Relation e) {
55 orig.put(e, e.save());
56 }
57 }
58
59 /**
60 * Small helper for holding the interesting part of the old data state of the objects.
61 */
62 public static class OldNodeState {
63
64 private final LatLon latlon;
65 private final EastNorth eastNorth; // cached EastNorth to be used for applying exact displacement
66 private final boolean modified;
67
68 /**
69 * Constructs a new {@code OldNodeState} for the given node.
70 * @param node The node whose state has to be remembered
71 */
72 public OldNodeState(Node node) {
73 latlon = node.getCoor();
74 eastNorth = node.getEastNorth();
75 modified = node.isModified();
76 }
77
78 /**
79 * Returns old lat/lon.
80 * @return old lat/lon
81 * @see Node#getCoor()
82 */
83 public final LatLon getLatlon() {
84 return latlon;
85 }
86
87 /**
88 * Returns old east/north.
89 * @return old east/north
90 * @see Node#getEastNorth()
91 */
92 public final EastNorth getEastNorth() {
93 return eastNorth;
94 }
95
96 /**
97 * Returns old modified state.
98 * @return old modified state
99 * @see Node #isModified()
100 */
101 public final boolean isModified() {
102 return modified;
103 }
104
105 @Override
106 public int hashCode() {
107 final int prime = 31;
108 int result = 1;
109 result = prime * result + ((eastNorth == null) ? 0 : eastNorth.hashCode());
110 result = prime * result + ((latlon == null) ? 0 : latlon.hashCode());
111 result = prime * result + (modified ? 1231 : 1237);
112 return result;
113 }
114
115 @Override
116 public boolean equals(Object obj) {
117 if (this == obj)
118 return true;
119 if (obj == null)
120 return false;
121 if (getClass() != obj.getClass())
122 return false;
123 OldNodeState other = (OldNodeState) obj;
124 if (eastNorth == null) {
125 if (other.eastNorth != null)
126 return false;
127 } else if (!eastNorth.equals(other.eastNorth))
128 return false;
129 if (latlon == null) {
130 if (other.latlon != null)
131 return false;
132 } else if (!latlon.equals(other.latlon))
133 return false;
134 if (modified != other.modified)
135 return false;
136 return true;
137 }
138 }
139
140 /** the map of OsmPrimitives in the original state to OsmPrimitives in cloned state */
141 private Map<OsmPrimitive, PrimitiveData> cloneMap = new HashMap<>();
142
143 /** the layer which this command is applied to */
144 private final OsmDataLayer layer;
145
146 /**
147 * Creates a new command in the context of the current edit layer, if any
148 */
149 public Command() {
150 this.layer = Main.main == null ? null : Main.main.getEditLayer();
151 }
152
153 /**
154 * Creates a new command in the context of a specific data layer
155 *
156 * @param layer the data layer. Must not be null.
157 * @throws IllegalArgumentException if layer is null
158 */
159 public Command(OsmDataLayer layer) {
160 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
161 this.layer = layer;
162 }
163
164 /**
165 * Executes the command on the dataset. This implementation will remember all
166 * primitives returned by fillModifiedData for restoring them on undo.
167 * @return true
168 */
169 public boolean executeCommand() {
170 CloneVisitor visitor = new CloneVisitor();
171 Collection<OsmPrimitive> all = new ArrayList<>();
172 fillModifiedData(all, all, all);
173 for (OsmPrimitive osm : all) {
174 osm.accept(visitor);
175 }
176 cloneMap = visitor.orig;
177 return true;
178 }
179
180 /**
181 * Undoes the command.
182 * It can be assumed that all objects are in the same state they were before.
183 * It can also be assumed that executeCommand was called exactly once before.
184 *
185 * This implementation undoes all objects stored by a former call to executeCommand.
186 */
187 public void undoCommand() {
188 for (Entry<OsmPrimitive, PrimitiveData> e : cloneMap.entrySet()) {
189 OsmPrimitive primitive = e.getKey();
190 if (primitive.getDataSet() != null) {
191 e.getKey().load(e.getValue());
192 }
193 }
194 }
195
196 /**
197 * Called when a layer has been removed to have the command remove itself from
198 * any buffer if it is not longer applicable to the dataset (e.g. it was part of
199 * the removed layer)
200 *
201 * @param oldLayer the old layer
202 * @return true if this command
203 */
204 public boolean invalidBecauselayerRemoved(Layer oldLayer) {
205 if (!(oldLayer instanceof OsmDataLayer))
206 return false;
207 return layer == oldLayer;
208 }
209
210 /**
211 * Lets other commands access the original version
212 * of the object. Usually for undoing.
213 * @param osm The requested OSM object
214 * @return The original version of the requested object, if any
215 */
216 public PrimitiveData getOrig(OsmPrimitive osm) {
217 return cloneMap.get(osm);
218 }
219
220 /**
221 * Replies the layer this command is (or was) applied to.
222 * @return the layer this command is (or was) applied to
223 */
224 protected OsmDataLayer getLayer() {
225 return layer;
226 }
227
228 /**
229 * Fill in the changed data this command operates on.
230 * Add to the lists, don't clear them.
231 *
232 * @param modified The modified primitives
233 * @param deleted The deleted primitives
234 * @param added The added primitives
235 */
236 public abstract void fillModifiedData(Collection<OsmPrimitive> modified,
237 Collection<OsmPrimitive> deleted,
238 Collection<OsmPrimitive> added);
239
240 /**
241 * Return the primitives that take part in this command.
242 * The collection is computed during execution.
243 */
244 @Override
245 public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
246 return cloneMap.keySet();
247 }
248
249 /**
250 * Check whether user is about to operate on data outside of the download area.
251 * Request confirmation if he is.
252 *
253 * @param operation the operation name which is used for setting some preferences
254 * @param dialogTitle the title of the dialog being displayed
255 * @param outsideDialogMessage the message text to be displayed when data is outside of the download area
256 * @param incompleteDialogMessage the message text to be displayed when data is incomplete
257 * @param primitives the primitives to operate on
258 * @param ignore {@code null} or a primitive to be ignored
259 * @return true, if operating on outlying primitives is OK; false, otherwise
260 */
261 public static boolean checkAndConfirmOutlyingOperation(String operation,
262 String dialogTitle, String outsideDialogMessage, String incompleteDialogMessage,
263 Collection<? extends OsmPrimitive> primitives,
264 Collection<? extends OsmPrimitive> ignore) {
265 boolean outside = false;
266 boolean incomplete = false;
267 for (OsmPrimitive osm : primitives) {
268 if (osm.isIncomplete()) {
269 incomplete = true;
270 } else if (osm.isOutsideDownloadArea()
271 && (ignore == null || !ignore.contains(osm))) {
272 outside = true;
273 }
274 }
275 if (outside) {
276 JPanel msg = new JPanel(new GridBagLayout());
277 msg.add(new JMultilineLabel("<html>" + outsideDialogMessage + "</html>"));
278 boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
279 operation + "_outside_nodes",
280 Main.parent,
281 msg,
282 dialogTitle,
283 JOptionPane.YES_NO_OPTION,
284 JOptionPane.QUESTION_MESSAGE,
285 JOptionPane.YES_OPTION);
286 if (!answer)
287 return false;
288 }
289 if (incomplete) {
290 JPanel msg = new JPanel(new GridBagLayout());
291 msg.add(new JMultilineLabel("<html>" + incompleteDialogMessage + "</html>"));
292 boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
293 operation + "_incomplete",
294 Main.parent,
295 msg,
296 dialogTitle,
297 JOptionPane.YES_NO_OPTION,
298 JOptionPane.QUESTION_MESSAGE,
299 JOptionPane.YES_OPTION);
300 if (!answer)
301 return false;
302 }
303 return true;
304 }
305
306 @Override
307 public int hashCode() {
308 final int prime = 31;
309 int result = 1;
310 result = prime * result + ((cloneMap == null) ? 0 : cloneMap.hashCode());
311 result = prime * result + ((layer == null) ? 0 : layer.hashCode());
312 return result;
313 }
314
315 @Override
316 public boolean equals(Object obj) {
317 if (this == obj)
318 return true;
319 if (obj == null)
320 return false;
321 if (getClass() != obj.getClass())
322 return false;
323 Command other = (Command) obj;
324 if (cloneMap == null) {
325 if (other.cloneMap != null)
326 return false;
327 } else if (!cloneMap.equals(other.cloneMap))
328 return false;
329 if (layer == null) {
330 if (other.layer != null)
331 return false;
332 } else if (!layer.equals(other.layer))
333 return false;
334 return true;
335 }
336}
Note: See TracBrowser for help on using the repository browser.