source: josm/trunk/src/org/openstreetmap/josm/command/ChangePropertyCommand.java@ 14165

Last change on this file since 14165 was 12769, checked in by Don-vip, 7 years ago

fix #15262 - see #13036 - fix regression in ChangePropertyCommand + optimize commands creation when pasting tags

  • 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 static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.HashMap;
12import java.util.LinkedList;
13import java.util.List;
14import java.util.Map;
15import java.util.NoSuchElementException;
16import java.util.Objects;
17import java.util.stream.Collectors;
18
19import javax.swing.Icon;
20
21import org.openstreetmap.josm.data.osm.DataSet;
22import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
23import org.openstreetmap.josm.data.osm.OsmPrimitive;
24import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
25import org.openstreetmap.josm.tools.I18n;
26import org.openstreetmap.josm.tools.ImageProvider;
27
28/**
29 * Command that manipulate the key/value structure of several objects. Manages deletion,
30 * adding and modify of values and keys.
31 *
32 * @author imi
33 * @since 24
34 */
35public class ChangePropertyCommand extends Command {
36
37 static final class OsmPseudoCommand implements PseudoCommand {
38 private final OsmPrimitive osm;
39
40 OsmPseudoCommand(OsmPrimitive osm) {
41 this.osm = osm;
42 }
43
44 @Override
45 public String getDescriptionText() {
46 return osm.getDisplayName(DefaultNameFormatter.getInstance());
47 }
48
49 @Override
50 public Icon getDescriptionIcon() {
51 return ImageProvider.get(osm.getDisplayType());
52 }
53
54 @Override
55 public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
56 return Collections.singleton(osm);
57 }
58 }
59
60 /**
61 * All primitives that are affected with this command.
62 */
63 private final List<OsmPrimitive> objects = new LinkedList<>();
64
65 /**
66 * Key and value pairs. If value is <code>null</code>, delete all key references with the given
67 * key. Otherwise, change the tags of all objects to the given value or create keys of
68 * those objects that do not have the key yet.
69 */
70 private final Map<String, String> tags;
71
72 /**
73 * Creates a command to change multiple tags of multiple objects
74 *
75 * @param ds The target data set. Must not be {@code null}
76 * @param objects the objects to modify. Must not be empty
77 * @param tags the tags to set
78 * @since 12726
79 */
80 public ChangePropertyCommand(DataSet ds, Collection<? extends OsmPrimitive> objects, Map<String, String> tags) {
81 super(ds);
82 this.tags = tags;
83 init(objects);
84 }
85
86 /**
87 * Creates a command to change multiple tags of multiple objects
88 *
89 * @param objects the objects to modify. Must not be empty, and objects must belong to a data set
90 * @param tags the tags to set
91 * @throws NullPointerException if objects is null or contain null item
92 * @throws NoSuchElementException if objects is empty
93 */
94 public ChangePropertyCommand(Collection<? extends OsmPrimitive> objects, Map<String, String> tags) {
95 this(objects.iterator().next().getDataSet(), objects, tags);
96 }
97
98 /**
99 * Creates a command to change one tag of multiple objects
100 *
101 * @param objects the objects to modify. Must not be empty, and objects must belong to a data set
102 * @param key the key of the tag to set
103 * @param value the value of the key to set
104 * @throws NullPointerException if objects is null or contain null item
105 * @throws NoSuchElementException if objects is empty
106 */
107 public ChangePropertyCommand(Collection<? extends OsmPrimitive> objects, String key, String value) {
108 super(objects.iterator().next().getDataSet());
109 this.tags = new HashMap<>(1);
110 this.tags.put(key, value);
111 init(objects);
112 }
113
114 /**
115 * Creates a command to change one tag of one object
116 *
117 * @param object the object to modify. Must belong to a data set
118 * @param key the key of the tag to set
119 * @param value the value of the key to set
120 * @throws NullPointerException if object is null
121 */
122 public ChangePropertyCommand(OsmPrimitive object, String key, String value) {
123 this(Arrays.asList(object), key, value);
124 }
125
126 /**
127 * Initialize the instance by finding what objects will be modified
128 *
129 * @param objects the objects to (possibly) modify
130 */
131 private void init(Collection<? extends OsmPrimitive> objects) {
132 // determine what objects will be modified
133 for (OsmPrimitive osm : objects) {
134 boolean modified = false;
135
136 // loop over all tags
137 for (Map.Entry<String, String> tag : this.tags.entrySet()) {
138 String oldVal = osm.get(tag.getKey());
139 String newVal = tag.getValue();
140
141 if (newVal == null || newVal.isEmpty()) {
142 if (oldVal != null) {
143 // new value is null and tag exists (will delete tag)
144 modified = true;
145 break;
146 }
147 } else if (oldVal == null || !newVal.equals(oldVal)) {
148 // new value is not null and is different from current value
149 modified = true;
150 break;
151 }
152 }
153 if (modified)
154 this.objects.add(osm);
155 }
156 }
157
158 @Override
159 public boolean executeCommand() {
160 if (objects.isEmpty())
161 return true;
162 final DataSet dataSet = objects.get(0).getDataSet();
163 if (dataSet != null) {
164 dataSet.beginUpdate();
165 }
166 try {
167 super.executeCommand(); // save old
168
169 for (OsmPrimitive osm : objects) {
170 // loop over all tags
171 for (Map.Entry<String, String> tag : this.tags.entrySet()) {
172 String oldVal = osm.get(tag.getKey());
173 String newVal = tag.getValue();
174
175 if (newVal == null || newVal.isEmpty()) {
176 if (oldVal != null)
177 osm.remove(tag.getKey());
178 } else if (oldVal == null || !newVal.equals(oldVal))
179 osm.put(tag.getKey(), newVal);
180 }
181 // init() only keeps modified primitives. Therefore the modified
182 // bit can be set without further checks.
183 osm.setModified(true);
184 }
185 return true;
186 } finally {
187 if (dataSet != null) {
188 dataSet.endUpdate();
189 }
190 }
191 }
192
193 @Override
194 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
195 modified.addAll(objects);
196 }
197
198 @Override
199 public String getDescriptionText() {
200 @I18n.QuirkyPluralString
201 final String text;
202 if (objects.size() == 1 && tags.size() == 1) {
203 OsmPrimitive primitive = objects.get(0);
204 String msg;
205 Map.Entry<String, String> entry = tags.entrySet().iterator().next();
206 if (entry.getValue() == null || entry.getValue().isEmpty()) {
207 switch(OsmPrimitiveType.from(primitive)) {
208 case NODE: msg = marktr("Remove \"{0}\" for node ''{1}''"); break;
209 case WAY: msg = marktr("Remove \"{0}\" for way ''{1}''"); break;
210 case RELATION: msg = marktr("Remove \"{0}\" for relation ''{1}''"); break;
211 default: throw new AssertionError();
212 }
213 text = tr(msg, entry.getKey(), primitive.getDisplayName(DefaultNameFormatter.getInstance()));
214 } else {
215 switch(OsmPrimitiveType.from(primitive)) {
216 case NODE: msg = marktr("Set {0}={1} for node ''{2}''"); break;
217 case WAY: msg = marktr("Set {0}={1} for way ''{2}''"); break;
218 case RELATION: msg = marktr("Set {0}={1} for relation ''{2}''"); break;
219 default: throw new AssertionError();
220 }
221 text = tr(msg, entry.getKey(), entry.getValue(), primitive.getDisplayName(DefaultNameFormatter.getInstance()));
222 }
223 } else if (objects.size() > 1 && tags.size() == 1) {
224 Map.Entry<String, String> entry = tags.entrySet().iterator().next();
225 if (entry.getValue() == null || entry.getValue().isEmpty()) {
226 /* I18n: plural form for objects, but value < 2 not possible! */
227 text = trn("Remove \"{0}\" for {1} object", "Remove \"{0}\" for {1} objects", objects.size(), entry.getKey(), objects.size());
228 } else {
229 /* I18n: plural form for objects, but value < 2 not possible! */
230 text = trn("Set {0}={1} for {2} object", "Set {0}={1} for {2} objects",
231 objects.size(), entry.getKey(), entry.getValue(), objects.size());
232 }
233 } else {
234 boolean allnull = true;
235 for (Map.Entry<String, String> tag : this.tags.entrySet()) {
236 if (tag.getValue() != null && !tag.getValue().isEmpty()) {
237 allnull = false;
238 break;
239 }
240 }
241
242 if (allnull) {
243 /* I18n: plural form detected for objects only (but value < 2 not possible!), try to do your best for tags */
244 text = trn("Deleted {0} tags for {1} object", "Deleted {0} tags for {1} objects", objects.size(), tags.size(), objects.size());
245 } else {
246 /* I18n: plural form detected for objects only (but value < 2 not possible!), try to do your best for tags */
247 text = trn("Set {0} tags for {1} object", "Set {0} tags for {1} objects", objects.size(), tags.size(), objects.size());
248 }
249 }
250 return text;
251 }
252
253 @Override
254 public Icon getDescriptionIcon() {
255 return ImageProvider.get("data", "key");
256 }
257
258 @Override
259 public Collection<PseudoCommand> getChildren() {
260 if (objects.size() == 1)
261 return null;
262 return objects.stream().map(OsmPseudoCommand::new).collect(Collectors.toList());
263 }
264
265 /**
266 * Returns the number of objects that will effectively be modified, before the command is executed.
267 * @return the number of objects that will effectively be modified (can be 0)
268 * @see Command#getParticipatingPrimitives()
269 * @since 8945
270 */
271 public final int getObjectsNumber() {
272 return objects.size();
273 }
274
275 /**
276 * Returns the tags to set (key/value pairs).
277 * @return the tags to set (key/value pairs)
278 */
279 public Map<String, String> getTags() {
280 return Collections.unmodifiableMap(tags);
281 }
282
283 @Override
284 public int hashCode() {
285 return Objects.hash(super.hashCode(), objects, tags);
286 }
287
288 @Override
289 public boolean equals(Object obj) {
290 if (this == obj) return true;
291 if (obj == null || getClass() != obj.getClass()) return false;
292 if (!super.equals(obj)) return false;
293 ChangePropertyCommand that = (ChangePropertyCommand) obj;
294 return Objects.equals(objects, that.objects) &&
295 Objects.equals(tags, that.tags);
296 }
297}
Note: See TracBrowser for help on using the repository browser.