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

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

see #13036 - deprecate Command() default constructor, fix unit tests and java warnings

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