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
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.Collection;
9import java.util.Collections;
10import java.util.LinkedList;
11import java.util.List;
12import java.util.Map;
13import java.util.NoSuchElementException;
14import java.util.Objects;
15import java.util.stream.Collectors;
16
17import javax.swing.Icon;
18
19import org.openstreetmap.josm.data.osm.DataSet;
20import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
22import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
23import org.openstreetmap.josm.tools.I18n;
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.
29 *
30 * @author imi
31 * @since 24
32 */
33public class ChangePropertyCommand extends Command {
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
58 /**
59 * All primitives that are affected with this command.
60 */
61 private final List<OsmPrimitive> objects = new LinkedList<>();
62
63 /**
64 * Key and value pairs. If value is <code>null</code>, delete all key references with the given
65 * key. Otherwise, change the tags of all objects to the given value or create keys of
66 * those objects that do not have the key yet.
67 */
68 private final Map<String, String> tags;
69
70 /**
71 * Creates a command to change multiple tags of multiple objects
72 *
73 * @param ds The target data set. Must not be {@code null}
74 * @param objects the objects to modify. Must not be empty
75 * @param tags the tags to set
76 * @since 12726
77 */
78 public ChangePropertyCommand(DataSet ds, Collection<? extends OsmPrimitive> objects, Map<String, String> tags) {
79 super(ds);
80 this.tags = tags;
81 init(objects);
82 }
83
84 /**
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 /**
97 * Creates a command to change one tag of multiple objects
98 *
99 * @param objects the objects to modify. Must not be empty, and objects must belong to a data set
100 * @param key the key of the tag to set
101 * @param value the value of the key to set
102 * @throws NullPointerException if objects is null or contain null item
103 * @throws NoSuchElementException if objects is empty
104 */
105 public ChangePropertyCommand(Collection<? extends OsmPrimitive> objects, String key, String value) {
106 super(objects.iterator().next().getDataSet());
107 this.tags = Collections.singletonMap(key, value);
108 init(objects);
109 }
110
111 /**
112 * Creates a command to change one tag of one object
113 *
114 * @param object the object to modify. Must belong to a data set
115 * @param key the key of the tag to set
116 * @param value the value of the key to set
117 * @throws NullPointerException if object is null
118 */
119 public ChangePropertyCommand(OsmPrimitive object, String key, String value) {
120 this(Collections.singleton(object), key, value);
121 }
122
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()) {
139 if (oldVal != null) {
140 // new value is null and tag exists (will delete tag)
141 modified = true;
142 break;
143 }
144 } else if (oldVal == null || !newVal.equals(oldVal)) {
145 // new value is not null and is different from current value
146 modified = true;
147 break;
148 }
149 }
150 if (modified)
151 this.objects.add(osm);
152 }
153 }
154
155 @Override
156 public boolean executeCommand() {
157 if (objects.isEmpty())
158 return true;
159 final DataSet dataSet = objects.get(0).getDataSet();
160 if (dataSet != null) {
161 dataSet.beginUpdate();
162 }
163 try {
164 super.executeCommand(); // save old
165
166 for (OsmPrimitive osm : objects) {
167 // loop over all tags
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()) {
173 if (oldVal != null)
174 osm.remove(tag.getKey());
175 } else if (oldVal == null || !newVal.equals(oldVal))
176 osm.put(tag.getKey(), newVal);
177 }
178 // init() only keeps modified primitives. Therefore the modified
179 // bit can be set without further checks.
180 osm.setModified(true);
181 }
182 return true;
183 } finally {
184 if (dataSet != null) {
185 dataSet.endUpdate();
186 }
187 }
188 }
189
190 @Override
191 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
192 modified.addAll(objects);
193 }
194
195 @Override
196 public String getDescriptionText() {
197 @I18n.QuirkyPluralString
198 final String text;
199 if (objects.size() == 1 && tags.size() == 1) {
200 OsmPrimitive primitive = objects.get(0);
201 String msg;
202 Map.Entry<String, String> entry = tags.entrySet().iterator().next();
203 if (entry.getValue() == null || entry.getValue().isEmpty()) {
204 switch(OsmPrimitiveType.from(primitive)) {
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;
208 default: throw new AssertionError();
209 }
210 text = tr(msg, entry.getKey(), primitive.getDisplayName(DefaultNameFormatter.getInstance()));
211 } else {
212 switch(OsmPrimitiveType.from(primitive)) {
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;
216 default: throw new AssertionError();
217 }
218 text = tr(msg, entry.getKey(), entry.getValue(), primitive.getDisplayName(DefaultNameFormatter.getInstance()));
219 }
220 } else if (objects.size() > 1 && tags.size() == 1) {
221 Map.Entry<String, String> entry = tags.entrySet().iterator().next();
222 if (entry.getValue() == null || entry.getValue().isEmpty()) {
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());
225 } else {
226 /* I18n: plural form for objects, but value < 2 not possible! */
227 text = trn("Set {0}={1} for {2} object", "Set {0}={1} for {2} objects",
228 objects.size(), entry.getKey(), entry.getValue(), objects.size());
229 }
230 } else {
231 boolean allNull = this.tags.entrySet().stream()
232 .allMatch(tag -> tag.getValue() == null || tag.getValue().isEmpty());
233
234 if (allNull) {
235 /* I18n: plural form detected for objects only (but value < 2 not possible!), try to do your best for tags */
236 text = trn("Deleted {0} tags for {1} object", "Deleted {0} tags for {1} objects", objects.size(), tags.size(), objects.size());
237 } else {
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());
240 }
241 }
242 return text;
243 }
244
245 @Override
246 public Icon getDescriptionIcon() {
247 return ImageProvider.get("data", "key");
248 }
249
250 @Override
251 public Collection<PseudoCommand> getChildren() {
252 if (objects.size() == 1)
253 return null;
254 return objects.stream().map(OsmPseudoCommand::new).collect(Collectors.toList());
255 }
256
257 /**
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 /**
268 * Returns the tags to set (key/value pairs).
269 * @return the tags to set (key/value pairs)
270 */
271 public Map<String, String> getTags() {
272 return Collections.unmodifiableMap(tags);
273 }
274
275 @Override
276 public int hashCode() {
277 return Objects.hash(super.hashCode(), objects, tags);
278 }
279
280 @Override
281 public boolean equals(Object obj) {
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);
288 }
289}
Note: See TracBrowser for help on using the repository browser.