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

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

see #13036 - deprecate Command() default constructor, fix unit tests and java warnings

  • Property svn:eol-style set to native
File size: 11.0 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 } else if (oldVal == null || !newVal.equals(oldVal))
146 // new value is not null and is different from current value
147 modified = true;
148 }
149 if (modified)
150 this.objects.add(osm);
151 }
152 }
153
154 @Override
155 public boolean executeCommand() {
156 final DataSet dataSet = objects.get(0).getDataSet();
157 if (dataSet != null) {
158 dataSet.beginUpdate();
159 }
160 try {
161 super.executeCommand(); // save old
162
163 for (OsmPrimitive osm : objects) {
164 // loop over all tags
165 for (Map.Entry<String, String> tag : this.tags.entrySet()) {
166 String oldVal = osm.get(tag.getKey());
167 String newVal = tag.getValue();
168
169 if (newVal == null || newVal.isEmpty()) {
170 if (oldVal != null)
171 osm.remove(tag.getKey());
172 } else if (oldVal == null || !newVal.equals(oldVal))
173 osm.put(tag.getKey(), newVal);
174 }
175 // init() only keeps modified primitives. Therefore the modified
176 // bit can be set without further checks.
177 osm.setModified(true);
178 }
179 return true;
180 } finally {
181 if (dataSet != null) {
182 dataSet.endUpdate();
183 }
184 }
185 }
186
187 @Override
188 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
189 modified.addAll(objects);
190 }
191
192 @Override
193 public String getDescriptionText() {
194 @I18n.QuirkyPluralString
195 final String text;
196 if (objects.size() == 1 && tags.size() == 1) {
197 OsmPrimitive primitive = objects.get(0);
198 String msg;
199 Map.Entry<String, String> entry = tags.entrySet().iterator().next();
200 if (entry.getValue() == null || entry.getValue().isEmpty()) {
201 switch(OsmPrimitiveType.from(primitive)) {
202 case NODE: msg = marktr("Remove \"{0}\" for node ''{1}''"); break;
203 case WAY: msg = marktr("Remove \"{0}\" for way ''{1}''"); break;
204 case RELATION: msg = marktr("Remove \"{0}\" for relation ''{1}''"); break;
205 default: throw new AssertionError();
206 }
207 text = tr(msg, entry.getKey(), primitive.getDisplayName(DefaultNameFormatter.getInstance()));
208 } else {
209 switch(OsmPrimitiveType.from(primitive)) {
210 case NODE: msg = marktr("Set {0}={1} for node ''{2}''"); break;
211 case WAY: msg = marktr("Set {0}={1} for way ''{2}''"); break;
212 case RELATION: msg = marktr("Set {0}={1} for relation ''{2}''"); break;
213 default: throw new AssertionError();
214 }
215 text = tr(msg, entry.getKey(), entry.getValue(), primitive.getDisplayName(DefaultNameFormatter.getInstance()));
216 }
217 } else if (objects.size() > 1 && tags.size() == 1) {
218 Map.Entry<String, String> entry = tags.entrySet().iterator().next();
219 if (entry.getValue() == null || entry.getValue().isEmpty()) {
220 /* I18n: plural form for objects, but value < 2 not possible! */
221 text = trn("Remove \"{0}\" for {1} object", "Remove \"{0}\" for {1} objects", objects.size(), entry.getKey(), objects.size());
222 } else {
223 /* I18n: plural form for objects, but value < 2 not possible! */
224 text = trn("Set {0}={1} for {2} object", "Set {0}={1} for {2} objects",
225 objects.size(), entry.getKey(), entry.getValue(), objects.size());
226 }
227 } else {
228 boolean allnull = true;
229 for (Map.Entry<String, String> tag : this.tags.entrySet()) {
230 if (tag.getValue() != null && !tag.getValue().isEmpty()) {
231 allnull = false;
232 break;
233 }
234 }
235
236 if (allnull) {
237 /* I18n: plural form detected for objects only (but value < 2 not possible!), try to do your best for tags */
238 text = trn("Deleted {0} tags for {1} object", "Deleted {0} tags for {1} objects", objects.size(), tags.size(), objects.size());
239 } else {
240 /* I18n: plural form detected for objects only (but value < 2 not possible!), try to do your best for tags */
241 text = trn("Set {0} tags for {1} object", "Set {0} tags for {1} objects", objects.size(), tags.size(), objects.size());
242 }
243 }
244 return text;
245 }
246
247 @Override
248 public Icon getDescriptionIcon() {
249 return ImageProvider.get("data", "key");
250 }
251
252 @Override
253 public Collection<PseudoCommand> getChildren() {
254 if (objects.size() == 1)
255 return null;
256 return objects.stream().map(OsmPseudoCommand::new).collect(Collectors.toList());
257 }
258
259 /**
260 * Returns the number of objects that will effectively be modified, before the command is executed.
261 * @return the number of objects that will effectively be modified (can be 0)
262 * @see Command#getParticipatingPrimitives()
263 * @since 8945
264 */
265 public final int getObjectsNumber() {
266 return objects.size();
267 }
268
269 /**
270 * Returns the tags to set (key/value pairs).
271 * @return the tags to set (key/value pairs)
272 */
273 public Map<String, String> getTags() {
274 return Collections.unmodifiableMap(tags);
275 }
276
277 @Override
278 public int hashCode() {
279 return Objects.hash(super.hashCode(), objects, tags);
280 }
281
282 @Override
283 public boolean equals(Object obj) {
284 if (this == obj) return true;
285 if (obj == null || getClass() != obj.getClass()) return false;
286 if (!super.equals(obj)) return false;
287 ChangePropertyCommand that = (ChangePropertyCommand) obj;
288 return Objects.equals(objects, that.objects) &&
289 Objects.equals(tags, that.tags);
290 }
291}
Note: See TracBrowser for help on using the repository browser.