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

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

see #11390 - sonar - squid:S1610 - Java 8: Abstract classes without fields should be converted to interfaces

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