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

Last change on this file since 11921 was 11243, checked in by Don-vip, 7 years ago

see #10387 - fix unit tests

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