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

Last change on this file since 14302 was 13174, checked in by Don-vip, 6 years ago

see #15310 - fix unit tests, warnings

  • Property svn:eol-style set to native
File size: 8.6 KB
RevLine 
[8378]1// License: GPL. For details, see LICENSE file.
[21]2package org.openstreetmap.josm.command;
3
[3034]4import java.util.ArrayList;
[22]5import java.util.Collection;
[146]6import java.util.HashMap;
[3034]7import java.util.LinkedHashMap;
[146]8import java.util.Map;
[86]9import java.util.Map.Entry;
[9371]10import java.util.Objects;
[21]11
[6173]12import org.openstreetmap.josm.data.coor.EastNorth;
13import org.openstreetmap.josm.data.coor.LatLon;
[10467]14import org.openstreetmap.josm.data.osm.DataSet;
[146]15import org.openstreetmap.josm.data.osm.Node;
[22]16import org.openstreetmap.josm.data.osm.OsmPrimitive;
[2284]17import org.openstreetmap.josm.data.osm.PrimitiveData;
[1523]18import org.openstreetmap.josm.data.osm.Relation;
[146]19import org.openstreetmap.josm.data.osm.Way;
[12809]20import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
[2844]21import org.openstreetmap.josm.tools.CheckParameterUtil;
[21]22
23/**
24 * Classes implementing Command modify a dataset in a specific way. A command is
[22]25 * one atomic action on a specific dataset, such as move or delete.
[21]26 *
[12718]27 * The command remembers the {@link DataSet} it is operating on.
[2284]28 *
[21]29 * @author imi
[10599]30 * @since 21 (creation)
31 * @since 10599 (signature)
[21]32 */
[10599]33public abstract class Command implements PseudoCommand {
[21]34
[10948]35 /** IS_OK : operation is okay */
36 public static final int IS_OK = 0;
37 /** IS_OUTSIDE : operation on element outside of download area */
38 public static final int IS_OUTSIDE = 1;
39 /** IS_INCOMPLETE: operation on incomplete target */
40 public static final int IS_INCOMPLETE = 2;
41
[12809]42 private static final class CloneVisitor implements OsmPrimitiveVisitor {
[13158]43 final Map<OsmPrimitive, PrimitiveData> orig = new LinkedHashMap<>();
[304]44
[6084]45 @Override
[1750]46 public void visit(Node n) {
[2284]47 orig.put(n, n.save());
[1750]48 }
[8510]49
[6084]50 @Override
[1750]51 public void visit(Way w) {
[2284]52 orig.put(w, w.save());
[1750]53 }
[8510]54
[6084]55 @Override
[1750]56 public void visit(Relation e) {
[2284]57 orig.put(e, e.save());
[1750]58 }
59 }
[304]60
[6173]61 /**
62 * Small helper for holding the interesting part of the old data state of the objects.
63 */
64 public static class OldNodeState {
65
[10248]66 private final LatLon latLon;
[8285]67 private final EastNorth eastNorth; // cached EastNorth to be used for applying exact displacement
68 private final boolean modified;
[6173]69
70 /**
71 * Constructs a new {@code OldNodeState} for the given node.
72 * @param node The node whose state has to be remembered
73 */
[8510]74 public OldNodeState(Node node) {
[10248]75 latLon = node.getCoor();
[6173]76 eastNorth = node.getEastNorth();
77 modified = node.isModified();
78 }
[8285]79
80 /**
81 * Returns old lat/lon.
82 * @return old lat/lon
83 * @see Node#getCoor()
[10248]84 * @since 10248
[8285]85 */
[10248]86 public final LatLon getLatLon() {
87 return latLon;
[8285]88 }
89
90 /**
91 * Returns old east/north.
92 * @return old east/north
93 * @see Node#getEastNorth()
94 */
95 public final EastNorth getEastNorth() {
96 return eastNorth;
97 }
98
99 /**
100 * Returns old modified state.
101 * @return old modified state
102 * @see Node #isModified()
103 */
104 public final boolean isModified() {
105 return modified;
106 }
[8447]107
108 @Override
109 public int hashCode() {
[10248]110 return Objects.hash(latLon, eastNorth, modified);
[8447]111 }
112
113 @Override
114 public boolean equals(Object obj) {
[9371]115 if (this == obj) return true;
116 if (obj == null || getClass() != obj.getClass()) return false;
117 OldNodeState that = (OldNodeState) obj;
118 return modified == that.modified &&
[10248]119 Objects.equals(latLon, that.latLon) &&
[9371]120 Objects.equals(eastNorth, that.eastNorth);
[8447]121 }
[6173]122 }
123
[1750]124 /** the map of OsmPrimitives in the original state to OsmPrimitives in cloned state */
[7005]125 private Map<OsmPrimitive, PrimitiveData> cloneMap = new HashMap<>();
[304]126
[11240]127 /** the dataset which this command is applied to */
128 private final DataSet data;
129
[5759]130 /**
[11240]131 * Creates a new command in the context of a specific data set, without data layer
132 *
133 * @param data the data set. Must not be null.
134 * @throws IllegalArgumentException if data is null
135 * @since 11240
136 */
137 public Command(DataSet data) {
138 CheckParameterUtil.ensureParameterNotNull(data, "data");
139 this.data = data;
140 }
141
142 /**
[1750]143 * Executes the command on the dataset. This implementation will remember all
144 * primitives returned by fillModifiedData for restoring them on undo.
[10452]145 * <p>
146 * The layer should be invalidated after execution so that it can be re-painted.
[5759]147 * @return true
[1750]148 */
149 public boolean executeCommand() {
150 CloneVisitor visitor = new CloneVisitor();
[7005]151 Collection<OsmPrimitive> all = new ArrayList<>();
[1750]152 fillModifiedData(all, all, all);
153 for (OsmPrimitive osm : all) {
[6009]154 osm.accept(visitor);
[1750]155 }
156 cloneMap = visitor.orig;
157 return true;
158 }
[86]159
[1750]160 /**
161 * Undoes the command.
162 * It can be assumed that all objects are in the same state they were before.
163 * It can also be assumed that executeCommand was called exactly once before.
164 *
165 * This implementation undoes all objects stored by a former call to executeCommand.
166 */
167 public void undoCommand() {
[2284]168 for (Entry<OsmPrimitive, PrimitiveData> e : cloneMap.entrySet()) {
[2683]169 OsmPrimitive primitive = e.getKey();
170 if (primitive.getDataSet() != null) {
171 e.getKey().load(e.getValue());
172 }
[1750]173 }
174 }
[304]175
[1750]176 /**
[630]177 * Lets other commands access the original version
178 * of the object. Usually for undoing.
[5759]179 * @param osm The requested OSM object
180 * @return The original version of the requested object, if any
[630]181 */
[2284]182 public PrimitiveData getOrig(OsmPrimitive osm) {
[5759]183 return cloneMap.get(osm);
[630]184 }
[94]185
[1750]186 /**
[10467]187 * Gets the data set this command affects.
188 * @return The data set. May be <code>null</code> if no layer was set and no edit layer was found.
189 * @since 10467
190 */
191 public DataSet getAffectedDataSet() {
[11240]192 return data;
[10467]193 }
194
195 /**
[1750]196 * Fill in the changed data this command operates on.
197 * Add to the lists, don't clear them.
198 *
199 * @param modified The modified primitives
200 * @param deleted The deleted primitives
201 * @param added The added primitives
202 */
[6883]203 public abstract void fillModifiedData(Collection<OsmPrimitive> modified,
[1750]204 Collection<OsmPrimitive> deleted,
205 Collection<OsmPrimitive> added);
206
[3262]207 /**
208 * Return the primitives that take part in this command.
[8945]209 * The collection is computed during execution.
[3262]210 */
[8931]211 @Override
212 public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
[3262]213 return cloneMap.keySet();
214 }
[1750]215
[3262]216 /**
[4458]217 * Check whether user is about to operate on data outside of the download area.
[10948]218 *
219 * @param primitives the primitives to operate on
220 * @param ignore {@code null} or a primitive to be ignored
221 * @return true, if operating on outlying primitives is OK; false, otherwise
222 */
[10970]223 public static int checkOutlyingOrIncompleteOperation(
[10948]224 Collection<? extends OsmPrimitive> primitives,
225 Collection<? extends OsmPrimitive> ignore) {
226 int res = 0;
227 for (OsmPrimitive osm : primitives) {
228 if (osm.isIncomplete()) {
229 res |= IS_INCOMPLETE;
230 } else if (osm.isOutsideDownloadArea()
231 && (ignore == null || !ignore.contains(osm))) {
232 res |= IS_OUTSIDE;
233 }
234 }
235 return res;
236 }
237
238 /**
[12348]239 * Ensures that all primitives that are participating in this command belong to the affected data set.
240 *
241 * Commands may use this in their update methods to check the consitency of the primitives they operate on.
242 * @throws AssertionError if no {@link DataSet} is set or if any primitive does not belong to that dataset.
243 */
244 protected void ensurePrimitivesAreInDataset() {
245 for (OsmPrimitive primitive : this.getParticipatingPrimitives()) {
246 if (primitive.getDataSet() != this.getAffectedDataSet()) {
247 throw new AssertionError("Primitive is of wrong data set for this command: " + primitive);
248 }
249 }
250 }
251
[8447]252 @Override
253 public int hashCode() {
[13173]254 return Objects.hash(cloneMap, data);
[8447]255 }
256
257 @Override
258 public boolean equals(Object obj) {
[9371]259 if (this == obj) return true;
260 if (obj == null || getClass() != obj.getClass()) return false;
261 Command command = (Command) obj;
262 return Objects.equals(cloneMap, command.cloneMap) &&
[11240]263 Objects.equals(data, command.data);
[8447]264 }
[21]265}
Note: See TracBrowser for help on using the repository browser.