[8378] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[21] | 2 | package org.openstreetmap.josm.command;
|
---|
| 3 |
|
---|
[3034] | 4 | import java.util.ArrayList;
|
---|
[22] | 5 | import java.util.Collection;
|
---|
[146] | 6 | import java.util.HashMap;
|
---|
[3034] | 7 | import java.util.LinkedHashMap;
|
---|
[146] | 8 | import java.util.Map;
|
---|
[86] | 9 | import java.util.Map.Entry;
|
---|
[9371] | 10 | import java.util.Objects;
|
---|
[21] | 11 |
|
---|
[630] | 12 | import org.openstreetmap.josm.Main;
|
---|
[6173] | 13 | import org.openstreetmap.josm.data.coor.EastNorth;
|
---|
| 14 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
[10467] | 15 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
[146] | 16 | import org.openstreetmap.josm.data.osm.Node;
|
---|
[22] | 17 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
[2284] | 18 | import org.openstreetmap.josm.data.osm.PrimitiveData;
|
---|
[1523] | 19 | import org.openstreetmap.josm.data.osm.Relation;
|
---|
[146] | 20 | import org.openstreetmap.josm.data.osm.Way;
|
---|
[12809] | 21 | import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
|
---|
[12636] | 22 | import org.openstreetmap.josm.gui.MainApplication;
|
---|
[304] | 23 | import org.openstreetmap.josm.gui.layer.Layer;
|
---|
| 24 | import org.openstreetmap.josm.gui.layer.OsmDataLayer;
|
---|
[2844] | 25 | import org.openstreetmap.josm.tools.CheckParameterUtil;
|
---|
[21] | 26 |
|
---|
| 27 | /**
|
---|
| 28 | * Classes implementing Command modify a dataset in a specific way. A command is
|
---|
[22] | 29 | * one atomic action on a specific dataset, such as move or delete.
|
---|
[21] | 30 | *
|
---|
[12718] | 31 | * The command remembers the {@link DataSet} it is operating on.
|
---|
[2284] | 32 | *
|
---|
[21] | 33 | * @author imi
|
---|
[10599] | 34 | * @since 21 (creation)
|
---|
| 35 | * @since 10599 (signature)
|
---|
[21] | 36 | */
|
---|
[10599] | 37 | public abstract class Command implements PseudoCommand {
|
---|
[21] | 38 |
|
---|
[10948] | 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 |
|
---|
[12809] | 46 | private static final class CloneVisitor implements OsmPrimitiveVisitor {
|
---|
[7005] | 47 | public final Map<OsmPrimitive, PrimitiveData> orig = new LinkedHashMap<>();
|
---|
[304] | 48 |
|
---|
[6084] | 49 | @Override
|
---|
[1750] | 50 | public void visit(Node n) {
|
---|
[2284] | 51 | orig.put(n, n.save());
|
---|
[1750] | 52 | }
|
---|
[8510] | 53 |
|
---|
[6084] | 54 | @Override
|
---|
[1750] | 55 | public void visit(Way w) {
|
---|
[2284] | 56 | orig.put(w, w.save());
|
---|
[1750] | 57 | }
|
---|
[8510] | 58 |
|
---|
[6084] | 59 | @Override
|
---|
[1750] | 60 | public void visit(Relation e) {
|
---|
[2284] | 61 | orig.put(e, e.save());
|
---|
[1750] | 62 | }
|
---|
| 63 | }
|
---|
[304] | 64 |
|
---|
[6173] | 65 | /**
|
---|
| 66 | * Small helper for holding the interesting part of the old data state of the objects.
|
---|
| 67 | */
|
---|
| 68 | public static class OldNodeState {
|
---|
| 69 |
|
---|
[10248] | 70 | private final LatLon latLon;
|
---|
[8285] | 71 | private final EastNorth eastNorth; // cached EastNorth to be used for applying exact displacement
|
---|
| 72 | private final boolean modified;
|
---|
[6173] | 73 |
|
---|
| 74 | /**
|
---|
| 75 | * Constructs a new {@code OldNodeState} for the given node.
|
---|
| 76 | * @param node The node whose state has to be remembered
|
---|
| 77 | */
|
---|
[8510] | 78 | public OldNodeState(Node node) {
|
---|
[10248] | 79 | latLon = node.getCoor();
|
---|
[6173] | 80 | eastNorth = node.getEastNorth();
|
---|
| 81 | modified = node.isModified();
|
---|
| 82 | }
|
---|
[8285] | 83 |
|
---|
| 84 | /**
|
---|
| 85 | * Returns old lat/lon.
|
---|
| 86 | * @return old lat/lon
|
---|
| 87 | * @see Node#getCoor()
|
---|
[10248] | 88 | * @since 10248
|
---|
[8285] | 89 | */
|
---|
[10248] | 90 | public final LatLon getLatLon() {
|
---|
| 91 | return latLon;
|
---|
[8285] | 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 | }
|
---|
[8447] | 111 |
|
---|
| 112 | @Override
|
---|
| 113 | public int hashCode() {
|
---|
[10248] | 114 | return Objects.hash(latLon, eastNorth, modified);
|
---|
[8447] | 115 | }
|
---|
| 116 |
|
---|
| 117 | @Override
|
---|
| 118 | public boolean equals(Object obj) {
|
---|
[9371] | 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 &&
|
---|
[10248] | 123 | Objects.equals(latLon, that.latLon) &&
|
---|
[9371] | 124 | Objects.equals(eastNorth, that.eastNorth);
|
---|
[8447] | 125 | }
|
---|
[6173] | 126 | }
|
---|
| 127 |
|
---|
[1750] | 128 | /** the map of OsmPrimitives in the original state to OsmPrimitives in cloned state */
|
---|
[7005] | 129 | private Map<OsmPrimitive, PrimitiveData> cloneMap = new HashMap<>();
|
---|
[304] | 130 |
|
---|
[12718] | 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
|
---|
[5759] | 136 | private final OsmDataLayer layer;
|
---|
[22] | 137 |
|
---|
[11240] | 138 | /** the dataset which this command is applied to */
|
---|
| 139 | private final DataSet data;
|
---|
| 140 |
|
---|
[5759] | 141 | /**
|
---|
| 142 | * Creates a new command in the context of the current edit layer, if any
|
---|
[12726] | 143 | * @deprecated to be removed end of 2017. Use {@link #Command(DataSet)} instead
|
---|
[5759] | 144 | */
|
---|
[12726] | 145 | @Deprecated
|
---|
[1750] | 146 | public Command() {
|
---|
[12636] | 147 | this.layer = MainApplication.getLayerManager().getEditLayer();
|
---|
[12718] | 148 | this.data = layer != null ? layer.data : Main.main.getEditDataSet();
|
---|
[1750] | 149 | }
|
---|
[1856] | 150 |
|
---|
[1750] | 151 | /**
|
---|
[1856] | 152 | * Creates a new command in the context of a specific data layer
|
---|
[2284] | 153 | *
|
---|
[2308] | 154 | * @param layer the data layer. Must not be null.
|
---|
[8291] | 155 | * @throws IllegalArgumentException if layer is null
|
---|
[12718] | 156 | * @deprecated to be removed end of 2017. Use {@link #Command(DataSet)} instead
|
---|
[1856] | 157 | */
|
---|
[12718] | 158 | @Deprecated
|
---|
[8291] | 159 | public Command(OsmDataLayer layer) {
|
---|
[2844] | 160 | CheckParameterUtil.ensureParameterNotNull(layer, "layer");
|
---|
[1856] | 161 | this.layer = layer;
|
---|
[11240] | 162 | this.data = layer.data;
|
---|
[1856] | 163 | }
|
---|
| 164 |
|
---|
| 165 | /**
|
---|
[11240] | 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 | /**
|
---|
[1750] | 179 | * Executes the command on the dataset. This implementation will remember all
|
---|
| 180 | * primitives returned by fillModifiedData for restoring them on undo.
|
---|
[10452] | 181 | * <p>
|
---|
| 182 | * The layer should be invalidated after execution so that it can be re-painted.
|
---|
[5759] | 183 | * @return true
|
---|
[10452] | 184 | * @see #invalidateAffectedLayers()
|
---|
[1750] | 185 | */
|
---|
| 186 | public boolean executeCommand() {
|
---|
| 187 | CloneVisitor visitor = new CloneVisitor();
|
---|
[7005] | 188 | Collection<OsmPrimitive> all = new ArrayList<>();
|
---|
[1750] | 189 | fillModifiedData(all, all, all);
|
---|
| 190 | for (OsmPrimitive osm : all) {
|
---|
[6009] | 191 | osm.accept(visitor);
|
---|
[1750] | 192 | }
|
---|
| 193 | cloneMap = visitor.orig;
|
---|
| 194 | return true;
|
---|
| 195 | }
|
---|
[86] | 196 |
|
---|
[1750] | 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() {
|
---|
[2284] | 205 | for (Entry<OsmPrimitive, PrimitiveData> e : cloneMap.entrySet()) {
|
---|
[2683] | 206 | OsmPrimitive primitive = e.getKey();
|
---|
| 207 | if (primitive.getDataSet() != null) {
|
---|
| 208 | e.getKey().load(e.getValue());
|
---|
| 209 | }
|
---|
[1750] | 210 | }
|
---|
| 211 | }
|
---|
[304] | 212 |
|
---|
[1750] | 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)
|
---|
[2284] | 217 | *
|
---|
[10663] | 218 | * @param oldLayer the old layer that was removed
|
---|
| 219 | * @return true if this command is invalid after that layer is removed.
|
---|
[12718] | 220 | * @deprecated to be removed end of 2017.
|
---|
[1750] | 221 | */
|
---|
[12718] | 222 | @Deprecated
|
---|
[1750] | 223 | public boolean invalidBecauselayerRemoved(Layer oldLayer) {
|
---|
| 224 | return layer == oldLayer;
|
---|
| 225 | }
|
---|
[304] | 226 |
|
---|
[630] | 227 | /**
|
---|
| 228 | * Lets other commands access the original version
|
---|
| 229 | * of the object. Usually for undoing.
|
---|
[5759] | 230 | * @param osm The requested OSM object
|
---|
| 231 | * @return The original version of the requested object, if any
|
---|
[630] | 232 | */
|
---|
[2284] | 233 | public PrimitiveData getOrig(OsmPrimitive osm) {
|
---|
[5759] | 234 | return cloneMap.get(osm);
|
---|
[630] | 235 | }
|
---|
[94] | 236 |
|
---|
[1750] | 237 | /**
|
---|
| 238 | * Replies the layer this command is (or was) applied to.
|
---|
[8931] | 239 | * @return the layer this command is (or was) applied to
|
---|
[12718] | 240 | * @deprecated to be removed end of 2017. Use {@link #getAffectedDataSet} instead
|
---|
[1750] | 241 | */
|
---|
[12718] | 242 | @Deprecated
|
---|
[6881] | 243 | protected OsmDataLayer getLayer() {
|
---|
[1750] | 244 | return layer;
|
---|
| 245 | }
|
---|
[630] | 246 |
|
---|
[1750] | 247 | /**
|
---|
[10467] | 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() {
|
---|
[11240] | 253 | return data;
|
---|
[10467] | 254 | }
|
---|
| 255 |
|
---|
| 256 | /**
|
---|
[1750] | 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 | */
|
---|
[6883] | 264 | public abstract void fillModifiedData(Collection<OsmPrimitive> modified,
|
---|
[1750] | 265 | Collection<OsmPrimitive> deleted,
|
---|
| 266 | Collection<OsmPrimitive> added);
|
---|
| 267 |
|
---|
[3262] | 268 | /**
|
---|
| 269 | * Return the primitives that take part in this command.
|
---|
[8945] | 270 | * The collection is computed during execution.
|
---|
[3262] | 271 | */
|
---|
[8931] | 272 | @Override
|
---|
| 273 | public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
|
---|
[3262] | 274 | return cloneMap.keySet();
|
---|
| 275 | }
|
---|
[1750] | 276 |
|
---|
[3262] | 277 | /**
|
---|
[4458] | 278 | * Check whether user is about to operate on data outside of the download area.
|
---|
[10948] | 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 | */
|
---|
[10970] | 284 | public static int checkOutlyingOrIncompleteOperation(
|
---|
[10948] | 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 | /**
|
---|
[12348] | 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 |
|
---|
[8447] | 313 | @Override
|
---|
| 314 | public int hashCode() {
|
---|
[11243] | 315 | return Objects.hash(cloneMap, layer, data);
|
---|
[8447] | 316 | }
|
---|
| 317 |
|
---|
| 318 | @Override
|
---|
| 319 | public boolean equals(Object obj) {
|
---|
[9371] | 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) &&
|
---|
[11240] | 324 | Objects.equals(layer, command.layer) &&
|
---|
| 325 | Objects.equals(data, command.data);
|
---|
[8447] | 326 | }
|
---|
[10452] | 327 |
|
---|
| 328 | /**
|
---|
| 329 | * Invalidate all layers that were affected by this command.
|
---|
| 330 | * @see Layer#invalidate()
|
---|
[12718] | 331 | * @deprecated to be removed end of 2017.
|
---|
[10452] | 332 | */
|
---|
[12718] | 333 | @Deprecated
|
---|
[10452] | 334 | public void invalidateAffectedLayers() {
|
---|
| 335 | OsmDataLayer layer = getLayer();
|
---|
| 336 | if (layer != null) {
|
---|
| 337 | layer.invalidate();
|
---|
| 338 | }
|
---|
| 339 | }
|
---|
[21] | 340 | }
|
---|