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

Last change on this file since 15013 was 12769, checked in by Don-vip, 8 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
RevLine 
[6380]1// License: GPL. For details, see LICENSE file.
[626]2package org.openstreetmap.josm.command;
3
[1990]4import static org.openstreetmap.josm.tools.I18n.marktr;
[626]5import static org.openstreetmap.josm.tools.I18n.tr;
[6507]6import static org.openstreetmap.josm.tools.I18n.trn;
[626]7
[4302]8import java.util.Arrays;
[626]9import java.util.Collection;
[3262]10import java.util.Collections;
[4773]11import java.util.HashMap;
[626]12import java.util.LinkedList;
13import java.util.List;
[4773]14import java.util.Map;
[12726]15import java.util.NoSuchElementException;
[9371]16import java.util.Objects;
[11348]17import java.util.stream.Collectors;
[5077]18
[4918]19import javax.swing.Icon;
[626]20
[8941]21import org.openstreetmap.josm.data.osm.DataSet;
[12663]22import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
[626]23import org.openstreetmap.josm.data.osm.OsmPrimitive;
[1814]24import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
[8975]25import org.openstreetmap.josm.tools.I18n;
[626]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.
[1169]31 *
[626]32 * @author imi
[8945]33 * @since 24
[626]34 */
35public class ChangePropertyCommand extends Command {
[11348]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
[1169]60 /**
61 * All primitives that are affected with this command.
62 */
[8945]63 private final List<OsmPrimitive> objects = new LinkedList<>();
64
[1169]65 /**
[4773]66 * Key and value pairs. If value is <code>null</code>, delete all key references with the given
[6324]67 * key. Otherwise, change the tags of all objects to the given value or create keys of
[1169]68 * those objects that do not have the key yet.
69 */
[8338]70 private final Map<String, String> tags;
[628]71
[4773]72 /**
[6324]73 * Creates a command to change multiple tags of multiple objects
[4773]74 *
[12726]75 * @param ds The target data set. Must not be {@code null}
76 * @param objects the objects to modify. Must not be empty
[6324]77 * @param tags the tags to set
[12726]78 * @since 12726
[4773]79 */
[12726]80 public ChangePropertyCommand(DataSet ds, Collection<? extends OsmPrimitive> objects, Map<String, String> tags) {
81 super(ds);
[4773]82 this.tags = tags;
83 init(objects);
[1169]84 }
[626]85
[4773]86 /**
[12726]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 /**
[6324]99 * Creates a command to change one tag of multiple objects
[4773]100 *
[12726]101 * @param objects the objects to modify. Must not be empty, and objects must belong to a data set
[6324]102 * @param key the key of the tag to set
[4773]103 * @param value the value of the key to set
[12726]104 * @throws NullPointerException if objects is null or contain null item
105 * @throws NoSuchElementException if objects is empty
[4773]106 */
107 public ChangePropertyCommand(Collection<? extends OsmPrimitive> objects, String key, String value) {
[12726]108 super(objects.iterator().next().getDataSet());
[7005]109 this.tags = new HashMap<>(1);
[4773]110 this.tags.put(key, value);
111 init(objects);
112 }
113
114 /**
[6324]115 * Creates a command to change one tag of one object
[4773]116 *
[12726]117 * @param object the object to modify. Must belong to a data set
[6324]118 * @param key the key of the tag to set
[4773]119 * @param value the value of the key to set
[12726]120 * @throws NullPointerException if object is null
[4773]121 */
[1169]122 public ChangePropertyCommand(OsmPrimitive object, String key, String value) {
[4302]123 this(Arrays.asList(object), key, value);
[1169]124 }
[626]125
[4773]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()) {
[12769]142 if (oldVal != null) {
[4773]143 // new value is null and tag exists (will delete tag)
144 modified = true;
[12769]145 break;
146 }
147 } else if (oldVal == null || !newVal.equals(oldVal)) {
[4773]148 // new value is not null and is different from current value
149 modified = true;
[12769]150 break;
151 }
[4773]152 }
153 if (modified)
154 this.objects.add(osm);
155 }
156 }
157
[8941]158 @Override
159 public boolean executeCommand() {
[12769]160 if (objects.isEmpty())
161 return true;
[8941]162 final DataSet dataSet = objects.get(0).getDataSet();
[8961]163 if (dataSet != null) {
164 dataSet.beginUpdate();
165 }
[3910]166 try {
167 super.executeCommand(); // save old
[4773]168
169 for (OsmPrimitive osm : objects) {
[5143]170 // loop over all tags
[4773]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()) {
[5143]176 if (oldVal != null)
[4773]177 osm.remove(tag.getKey());
[8342]178 } else if (oldVal == null || !newVal.equals(oldVal))
[4773]179 osm.put(tag.getKey(), newVal);
[3910]180 }
[5143]181 // init() only keeps modified primitives. Therefore the modified
182 // bit can be set without further checks.
183 osm.setModified(true);
[1169]184 }
[3910]185 return true;
[8342]186 } finally {
[8961]187 if (dataSet != null) {
188 dataSet.endUpdate();
189 }
[3910]190 }
[626]191 }
[1169]192
[8941]193 @Override
194 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
[1169]195 modified.addAll(objects);
196 }
197
[4918]198 @Override
199 public String getDescriptionText() {
[8975]200 @I18n.QuirkyPluralString
201 final String text;
[4773]202 if (objects.size() == 1 && tags.size() == 1) {
[8846]203 OsmPrimitive primitive = objects.get(0);
[10308]204 String msg;
[4773]205 Map.Entry<String, String> entry = tags.entrySet().iterator().next();
[10663]206 if (entry.getValue() == null || entry.getValue().isEmpty()) {
[1989]207 switch(OsmPrimitiveType.from(primitive)) {
[2990]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;
[10216]211 default: throw new AssertionError();
[1989]212 }
[4773]213 text = tr(msg, entry.getKey(), primitive.getDisplayName(DefaultNameFormatter.getInstance()));
[1989]214 } else {
215 switch(OsmPrimitiveType.from(primitive)) {
[2990]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;
[10216]219 default: throw new AssertionError();
[1989]220 }
[4773]221 text = tr(msg, entry.getKey(), entry.getValue(), primitive.getDisplayName(DefaultNameFormatter.getInstance()));
[1989]222 }
[4773]223 } else if (objects.size() > 1 && tags.size() == 1) {
224 Map.Entry<String, String> entry = tags.entrySet().iterator().next();
[10663]225 if (entry.getValue() == null || entry.getValue().isEmpty()) {
[6679]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());
[6507]228 } else {
[6679]229 /* I18n: plural form for objects, but value < 2 not possible! */
[8509]230 text = trn("Set {0}={1} for {2} object", "Set {0}={1} for {2} objects",
231 objects.size(), entry.getKey(), entry.getValue(), objects.size());
[6507]232 }
[8342]233 } else {
[4773]234 boolean allnull = true;
235 for (Map.Entry<String, String> tag : this.tags.entrySet()) {
[10663]236 if (tag.getValue() != null && !tag.getValue().isEmpty()) {
[4773]237 allnull = false;
238 break;
239 }
240 }
[5077]241
[4773]242 if (allnull) {
[6881]243 /* I18n: plural form detected for objects only (but value < 2 not possible!), try to do your best for tags */
[6679]244 text = trn("Deleted {0} tags for {1} object", "Deleted {0} tags for {1} objects", objects.size(), tags.size(), objects.size());
[6507]245 } else {
[6679]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());
[6507]248 }
[4773]249 }
[4918]250 return text;
[3262]251 }
252
[4918]253 @Override
254 public Icon getDescriptionIcon() {
255 return ImageProvider.get("data", "key");
256 }
257
[8945]258 @Override
259 public Collection<PseudoCommand> getChildren() {
[1169]260 if (objects.size() == 1)
[3262]261 return null;
[11348]262 return objects.stream().map(OsmPseudoCommand::new).collect(Collectors.toList());
[1169]263 }
[6538]264
[6881]265 /**
[8945]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 /**
[6881]276 * Returns the tags to set (key/value pairs).
277 * @return the tags to set (key/value pairs)
278 */
[6538]279 public Map<String, String> getTags() {
280 return Collections.unmodifiableMap(tags);
281 }
[8456]282
283 @Override
284 public int hashCode() {
[9371]285 return Objects.hash(super.hashCode(), objects, tags);
[8456]286 }
287
288 @Override
289 public boolean equals(Object obj) {
[9371]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);
[8456]296 }
[626]297}
Note: See TracBrowser for help on using the repository browser.