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

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

see #13491 - remove unused parameter

  • Property svn:eol-style set to native
File size: 11.7 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 /**
140 * Creates a new command in the context of the current edit layer, if any
141 */
142 public Command() {
143 this.layer = Main.getLayerManager().getEditLayer();
144 }
145
146 /**
147 * Creates a new command in the context of a specific data layer
148 *
149 * @param layer the data layer. Must not be null.
150 * @throws IllegalArgumentException if layer is null
151 */
152 public Command(OsmDataLayer layer) {
153 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
154 this.layer = layer;
155 }
156
157 /**
158 * Executes the command on the dataset. This implementation will remember all
159 * primitives returned by fillModifiedData for restoring them on undo.
160 * <p>
161 * The layer should be invalidated after execution so that it can be re-painted.
162 * @return true
163 * @see #invalidateAffectedLayers()
164 */
165 public boolean executeCommand() {
166 CloneVisitor visitor = new CloneVisitor();
167 Collection<OsmPrimitive> all = new ArrayList<>();
168 fillModifiedData(all, all, all);
169 for (OsmPrimitive osm : all) {
170 osm.accept(visitor);
171 }
172 cloneMap = visitor.orig;
173 return true;
174 }
175
176 /**
177 * Undoes the command.
178 * It can be assumed that all objects are in the same state they were before.
179 * It can also be assumed that executeCommand was called exactly once before.
180 *
181 * This implementation undoes all objects stored by a former call to executeCommand.
182 */
183 public void undoCommand() {
184 for (Entry<OsmPrimitive, PrimitiveData> e : cloneMap.entrySet()) {
185 OsmPrimitive primitive = e.getKey();
186 if (primitive.getDataSet() != null) {
187 e.getKey().load(e.getValue());
188 }
189 }
190 }
191
192 /**
193 * Called when a layer has been removed to have the command remove itself from
194 * any buffer if it is not longer applicable to the dataset (e.g. it was part of
195 * the removed layer)
196 *
197 * @param oldLayer the old layer that was removed
198 * @return true if this command is invalid after that layer is removed.
199 */
200 public boolean invalidBecauselayerRemoved(Layer oldLayer) {
201 return layer == oldLayer;
202 }
203
204 /**
205 * Lets other commands access the original version
206 * of the object. Usually for undoing.
207 * @param osm The requested OSM object
208 * @return The original version of the requested object, if any
209 */
210 public PrimitiveData getOrig(OsmPrimitive osm) {
211 return cloneMap.get(osm);
212 }
213
214 /**
215 * Replies the layer this command is (or was) applied to.
216 * @return the layer this command is (or was) applied to
217 */
218 protected OsmDataLayer getLayer() {
219 return layer;
220 }
221
222 /**
223 * Gets the data set this command affects.
224 * @return The data set. May be <code>null</code> if no layer was set and no edit layer was found.
225 * @since 10467
226 */
227 public DataSet getAffectedDataSet() {
228 return layer == null ? null : layer.data;
229 }
230
231 /**
232 * Fill in the changed data this command operates on.
233 * Add to the lists, don't clear them.
234 *
235 * @param modified The modified primitives
236 * @param deleted The deleted primitives
237 * @param added The added primitives
238 */
239 public abstract void fillModifiedData(Collection<OsmPrimitive> modified,
240 Collection<OsmPrimitive> deleted,
241 Collection<OsmPrimitive> added);
242
243 /**
244 * Return the primitives that take part in this command.
245 * The collection is computed during execution.
246 */
247 @Override
248 public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
249 return cloneMap.keySet();
250 }
251
252 /**
253 * Check whether user is about to operate on data outside of the download area.
254 *
255 * @param primitives the primitives to operate on
256 * @param ignore {@code null} or a primitive to be ignored
257 * @return true, if operating on outlying primitives is OK; false, otherwise
258 */
259 public static int checkOutlyingOrIncompleteOperation(
260 Collection<? extends OsmPrimitive> primitives,
261 Collection<? extends OsmPrimitive> ignore) {
262 int res = 0;
263 for (OsmPrimitive osm : primitives) {
264 if (osm.isIncomplete()) {
265 res |= IS_INCOMPLETE;
266 } else if (osm.isOutsideDownloadArea()
267 && (ignore == null || !ignore.contains(osm))) {
268 res |= IS_OUTSIDE;
269 }
270 }
271 return res;
272 }
273
274 /**
275 * Check whether user is about to operate on data outside of the download area.
276 * Request confirmation if he is.
277 *
278 * @param operation the operation name which is used for setting some preferences
279 * @param dialogTitle the title of the dialog being displayed
280 * @param outsideDialogMessage the message text to be displayed when data is outside of the download area
281 * @param incompleteDialogMessage the message text to be displayed when data is incomplete
282 * @param primitives the primitives to operate on
283 * @param ignore {@code null} or a primitive to be ignored
284 * @return true, if operating on outlying primitives is OK; false, otherwise
285 */
286 public static boolean checkAndConfirmOutlyingOperation(String operation,
287 String dialogTitle, String outsideDialogMessage, String incompleteDialogMessage,
288 Collection<? extends OsmPrimitive> primitives,
289 Collection<? extends OsmPrimitive> ignore) {
290 int checkRes = checkOutlyingOrIncompleteOperation(primitives, ignore);
291 if ((checkRes & IS_OUTSIDE) != 0) {
292 JPanel msg = new JPanel(new GridBagLayout());
293 msg.add(new JMultilineLabel("<html>" + outsideDialogMessage + "</html>"));
294 boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
295 operation + "_outside_nodes",
296 Main.parent,
297 msg,
298 dialogTitle,
299 JOptionPane.YES_NO_OPTION,
300 JOptionPane.QUESTION_MESSAGE,
301 JOptionPane.YES_OPTION);
302 if (!answer)
303 return false;
304 }
305 if ((checkRes & IS_INCOMPLETE) != 0) {
306 JPanel msg = new JPanel(new GridBagLayout());
307 msg.add(new JMultilineLabel("<html>" + incompleteDialogMessage + "</html>"));
308 boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
309 operation + "_incomplete",
310 Main.parent,
311 msg,
312 dialogTitle,
313 JOptionPane.YES_NO_OPTION,
314 JOptionPane.QUESTION_MESSAGE,
315 JOptionPane.YES_OPTION);
316 if (!answer)
317 return false;
318 }
319 return true;
320 }
321
322 @Override
323 public int hashCode() {
324 return Objects.hash(cloneMap, layer);
325 }
326
327 @Override
328 public boolean equals(Object obj) {
329 if (this == obj) return true;
330 if (obj == null || getClass() != obj.getClass()) return false;
331 Command command = (Command) obj;
332 return Objects.equals(cloneMap, command.cloneMap) &&
333 Objects.equals(layer, command.layer);
334 }
335
336 /**
337 * Invalidate all layers that were affected by this command.
338 * @see Layer#invalidate()
339 */
340 public void invalidateAffectedLayers() {
341 OsmDataLayer layer = getLayer();
342 if (layer != null) {
343 layer.invalidate();
344 }
345 }
346}
Note: See TracBrowser for help on using the repository browser.