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

Last change on this file since 12901 was 12809, checked in by bastiK, 7 years ago

replace abstract class AbstractVisitor by interface OsmPrimitiveVisitor; deprecate Visitor

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