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

Last change on this file since 16436 was 16436, checked in by simon04, 5 years ago

see #19251 - Java 8: use Stream

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