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

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

findbugs - NM_CONFUSING

  • Property svn:eol-style set to native
File size: 10.1 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.Node;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.PrimitiveData;
22import org.openstreetmap.josm.data.osm.Relation;
23import org.openstreetmap.josm.data.osm.Way;
24import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
25import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
26import org.openstreetmap.josm.gui.layer.Layer;
27import org.openstreetmap.josm.gui.layer.OsmDataLayer;
28import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
29import org.openstreetmap.josm.tools.CheckParameterUtil;
30
31/**
32 * Classes implementing Command modify a dataset in a specific way. A command is
33 * one atomic action on a specific dataset, such as move or delete.
34 *
35 * The command remembers the {@link OsmDataLayer} it is operating on.
36 *
37 * @author imi
38 */
39public abstract class Command extends PseudoCommand {
40
41 private static final class CloneVisitor extends AbstractVisitor {
42 public final Map<OsmPrimitive, PrimitiveData> orig = new LinkedHashMap<>();
43
44 @Override
45 public void visit(Node n) {
46 orig.put(n, n.save());
47 }
48
49 @Override
50 public void visit(Way w) {
51 orig.put(w, w.save());
52 }
53
54 @Override
55 public void visit(Relation e) {
56 orig.put(e, e.save());
57 }
58 }
59
60 /**
61 * Small helper for holding the interesting part of the old data state of the objects.
62 */
63 public static class OldNodeState {
64
65 private final LatLon latLon;
66 private final EastNorth eastNorth; // cached EastNorth to be used for applying exact displacement
67 private final boolean modified;
68
69 /**
70 * Constructs a new {@code OldNodeState} for the given node.
71 * @param node The node whose state has to be remembered
72 */
73 public OldNodeState(Node node) {
74 latLon = node.getCoor();
75 eastNorth = node.getEastNorth();
76 modified = node.isModified();
77 }
78
79 /**
80 * Returns old lat/lon.
81 * @return old lat/lon
82 * @see Node#getCoor()
83 * @since 10248
84 */
85 public final LatLon getLatLon() {
86 return latLon;
87 }
88
89 /**
90 * Returns old east/north.
91 * @return old east/north
92 * @see Node#getEastNorth()
93 */
94 public final EastNorth getEastNorth() {
95 return eastNorth;
96 }
97
98 /**
99 * Returns old modified state.
100 * @return old modified state
101 * @see Node #isModified()
102 */
103 public final boolean isModified() {
104 return modified;
105 }
106
107 @Override
108 public int hashCode() {
109 return Objects.hash(latLon, eastNorth, modified);
110 }
111
112 @Override
113 public boolean equals(Object obj) {
114 if (this == obj) return true;
115 if (obj == null || getClass() != obj.getClass()) return false;
116 OldNodeState that = (OldNodeState) obj;
117 return modified == that.modified &&
118 Objects.equals(latLon, that.latLon) &&
119 Objects.equals(eastNorth, that.eastNorth);
120 }
121 }
122
123 /** the map of OsmPrimitives in the original state to OsmPrimitives in cloned state */
124 private Map<OsmPrimitive, PrimitiveData> cloneMap = new HashMap<>();
125
126 /** the layer which this command is applied to */
127 private final OsmDataLayer layer;
128
129 /**
130 * Creates a new command in the context of the current edit layer, if any
131 */
132 public Command() {
133 this.layer = Main.main == null ? null : Main.main.getEditLayer();
134 }
135
136 /**
137 * Creates a new command in the context of a specific data layer
138 *
139 * @param layer the data layer. Must not be null.
140 * @throws IllegalArgumentException if layer is null
141 */
142 public Command(OsmDataLayer layer) {
143 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
144 this.layer = layer;
145 }
146
147 /**
148 * Executes the command on the dataset. This implementation will remember all
149 * primitives returned by fillModifiedData for restoring them on undo.
150 * @return true
151 */
152 public boolean executeCommand() {
153 CloneVisitor visitor = new CloneVisitor();
154 Collection<OsmPrimitive> all = new ArrayList<>();
155 fillModifiedData(all, all, all);
156 for (OsmPrimitive osm : all) {
157 osm.accept(visitor);
158 }
159 cloneMap = visitor.orig;
160 return true;
161 }
162
163 /**
164 * Undoes the command.
165 * It can be assumed that all objects are in the same state they were before.
166 * It can also be assumed that executeCommand was called exactly once before.
167 *
168 * This implementation undoes all objects stored by a former call to executeCommand.
169 */
170 public void undoCommand() {
171 for (Entry<OsmPrimitive, PrimitiveData> e : cloneMap.entrySet()) {
172 OsmPrimitive primitive = e.getKey();
173 if (primitive.getDataSet() != null) {
174 e.getKey().load(e.getValue());
175 }
176 }
177 }
178
179 /**
180 * Called when a layer has been removed to have the command remove itself from
181 * any buffer if it is not longer applicable to the dataset (e.g. it was part of
182 * the removed layer)
183 *
184 * @param oldLayer the old layer
185 * @return true if this command
186 */
187 public boolean invalidBecauselayerRemoved(Layer oldLayer) {
188 if (!(oldLayer instanceof OsmDataLayer))
189 return false;
190 return layer == oldLayer;
191 }
192
193 /**
194 * Lets other commands access the original version
195 * of the object. Usually for undoing.
196 * @param osm The requested OSM object
197 * @return The original version of the requested object, if any
198 */
199 public PrimitiveData getOrig(OsmPrimitive osm) {
200 return cloneMap.get(osm);
201 }
202
203 /**
204 * Replies the layer this command is (or was) applied to.
205 * @return the layer this command is (or was) applied to
206 */
207 protected OsmDataLayer getLayer() {
208 return layer;
209 }
210
211 /**
212 * Fill in the changed data this command operates on.
213 * Add to the lists, don't clear them.
214 *
215 * @param modified The modified primitives
216 * @param deleted The deleted primitives
217 * @param added The added primitives
218 */
219 public abstract void fillModifiedData(Collection<OsmPrimitive> modified,
220 Collection<OsmPrimitive> deleted,
221 Collection<OsmPrimitive> added);
222
223 /**
224 * Return the primitives that take part in this command.
225 * The collection is computed during execution.
226 */
227 @Override
228 public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
229 return cloneMap.keySet();
230 }
231
232 /**
233 * Check whether user is about to operate on data outside of the download area.
234 * Request confirmation if he is.
235 *
236 * @param operation the operation name which is used for setting some preferences
237 * @param dialogTitle the title of the dialog being displayed
238 * @param outsideDialogMessage the message text to be displayed when data is outside of the download area
239 * @param incompleteDialogMessage the message text to be displayed when data is incomplete
240 * @param primitives the primitives to operate on
241 * @param ignore {@code null} or a primitive to be ignored
242 * @return true, if operating on outlying primitives is OK; false, otherwise
243 */
244 public static boolean checkAndConfirmOutlyingOperation(String operation,
245 String dialogTitle, String outsideDialogMessage, String incompleteDialogMessage,
246 Collection<? extends OsmPrimitive> primitives,
247 Collection<? extends OsmPrimitive> ignore) {
248 boolean outside = false;
249 boolean incomplete = false;
250 for (OsmPrimitive osm : primitives) {
251 if (osm.isIncomplete()) {
252 incomplete = true;
253 } else if (osm.isOutsideDownloadArea()
254 && (ignore == null || !ignore.contains(osm))) {
255 outside = true;
256 }
257 }
258 if (outside) {
259 JPanel msg = new JPanel(new GridBagLayout());
260 msg.add(new JMultilineLabel("<html>" + outsideDialogMessage + "</html>"));
261 boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
262 operation + "_outside_nodes",
263 Main.parent,
264 msg,
265 dialogTitle,
266 JOptionPane.YES_NO_OPTION,
267 JOptionPane.QUESTION_MESSAGE,
268 JOptionPane.YES_OPTION);
269 if (!answer)
270 return false;
271 }
272 if (incomplete) {
273 JPanel msg = new JPanel(new GridBagLayout());
274 msg.add(new JMultilineLabel("<html>" + incompleteDialogMessage + "</html>"));
275 boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
276 operation + "_incomplete",
277 Main.parent,
278 msg,
279 dialogTitle,
280 JOptionPane.YES_NO_OPTION,
281 JOptionPane.QUESTION_MESSAGE,
282 JOptionPane.YES_OPTION);
283 if (!answer)
284 return false;
285 }
286 return true;
287 }
288
289 @Override
290 public int hashCode() {
291 return Objects.hash(cloneMap, layer);
292 }
293
294 @Override
295 public boolean equals(Object obj) {
296 if (this == obj) return true;
297 if (obj == null || getClass() != obj.getClass()) return false;
298 Command command = (Command) obj;
299 return Objects.equals(cloneMap, command.cloneMap) &&
300 Objects.equals(layer, command.layer);
301 }
302}
Note: See TracBrowser for help on using the repository browser.