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

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

fix #11508 - fix bad behaviour of Move Node onto way with overlapping ways

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