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

Last change on this file since 8961 was 8961, checked in by Don-vip, 8 years ago

fix #12019 - NPE with ChangePropertyCommand called on objects without dataset - regression from r8941

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