001 // License: GPL. Copyright 2007 by Immanuel Scholz and others
002 package org.openstreetmap.josm.command;
003
004 import static org.openstreetmap.josm.tools.I18n.marktr;
005 import static org.openstreetmap.josm.tools.I18n.tr;
006
007 import java.util.AbstractMap;
008 import java.util.ArrayList;
009 import java.util.Arrays;
010 import java.util.Collection;
011 import java.util.Collections;
012 import java.util.HashMap;
013 import java.util.LinkedList;
014 import java.util.List;
015 import java.util.Map;
016
017 import javax.swing.Icon;
018
019 import org.openstreetmap.josm.Main;
020 import org.openstreetmap.josm.data.osm.OsmPrimitive;
021 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
022 import org.openstreetmap.josm.gui.DefaultNameFormatter;
023 import org.openstreetmap.josm.tools.ImageProvider;
024
025 /**
026 * Command that manipulate the key/value structure of several objects. Manages deletion,
027 * adding and modify of values and keys.
028 *
029 * @author imi
030 */
031 public class ChangePropertyCommand extends Command {
032 /**
033 * All primitives that are affected with this command.
034 */
035 private final List<OsmPrimitive> objects;
036 /**
037 * Key and value pairs. If value is <code>null</code>, delete all key references with the given
038 * key. Otherwise, change the properties of all objects to the given value or create keys of
039 * those objects that do not have the key yet.
040 */
041 private final AbstractMap<String, String> tags;
042
043 /**
044 * Creates a command to change multiple properties of multiple objects
045 *
046 * @param objects the objects to modify
047 * @param tags the properties to set
048 */
049 public ChangePropertyCommand(Collection<? extends OsmPrimitive> objects, AbstractMap<String, String> tags) {
050 super();
051 this.objects = new LinkedList<OsmPrimitive>();
052 this.tags = tags;
053 init(objects);
054 }
055
056 /**
057 * Creates a command to change one property of multiple objects
058 *
059 * @param objects the objects to modify
060 * @param key the key of the property to set
061 * @param value the value of the key to set
062 */
063 public ChangePropertyCommand(Collection<? extends OsmPrimitive> objects, String key, String value) {
064 this.objects = new LinkedList<OsmPrimitive>();
065 this.tags = new HashMap<String, String>(1);
066 this.tags.put(key, value);
067 init(objects);
068 }
069
070 /**
071 * Creates a command to change on property of one object
072 *
073 * @param object the object to modify
074 * @param key the key of the property to set
075 * @param value the value of the key to set
076 */
077 public ChangePropertyCommand(OsmPrimitive object, String key, String value) {
078 this(Arrays.asList(object), key, value);
079 }
080
081 /**
082 * Initialize the instance by finding what objects will be modified
083 *
084 * @param objects the objects to (possibly) modify
085 */
086 private void init(Collection<? extends OsmPrimitive> objects) {
087 // determine what objects will be modified
088 for (OsmPrimitive osm : objects) {
089 boolean modified = false;
090
091 // loop over all tags
092 for (Map.Entry<String, String> tag : this.tags.entrySet()) {
093 String oldVal = osm.get(tag.getKey());
094 String newVal = tag.getValue();
095
096 if (newVal == null || newVal.isEmpty()) {
097 if (oldVal != null)
098 // new value is null and tag exists (will delete tag)
099 modified = true;
100 }
101 else if (oldVal == null || !newVal.equals(oldVal))
102 // new value is not null and is different from current value
103 modified = true;
104 }
105 if (modified)
106 this.objects.add(osm);
107 }
108 }
109
110 @Override public boolean executeCommand() {
111 Main.main.getCurrentDataSet().beginUpdate();
112 try {
113 super.executeCommand(); // save old
114
115 for (OsmPrimitive osm : objects) {
116 // loop over all tags
117 for (Map.Entry<String, String> tag : this.tags.entrySet()) {
118 String oldVal = osm.get(tag.getKey());
119 String newVal = tag.getValue();
120
121 if (newVal == null || newVal.isEmpty()) {
122 if (oldVal != null)
123 osm.remove(tag.getKey());
124 }
125 else if (oldVal == null || !newVal.equals(oldVal))
126 osm.put(tag.getKey(), newVal);
127 }
128 // init() only keeps modified primitives. Therefore the modified
129 // bit can be set without further checks.
130 osm.setModified(true);
131 }
132 return true;
133 }
134 finally {
135 Main.main.getCurrentDataSet().endUpdate();
136 }
137 }
138
139 @Override public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
140 modified.addAll(objects);
141 }
142
143 @Override
144 public String getDescriptionText() {
145 String text;
146 if (objects.size() == 1 && tags.size() == 1) {
147 OsmPrimitive primitive = objects.iterator().next();
148 String msg = "";
149 Map.Entry<String, String> entry = tags.entrySet().iterator().next();
150 if (entry.getValue() == null) {
151 switch(OsmPrimitiveType.from(primitive)) {
152 case NODE: msg = marktr("Remove \"{0}\" for node ''{1}''"); break;
153 case WAY: msg = marktr("Remove \"{0}\" for way ''{1}''"); break;
154 case RELATION: msg = marktr("Remove \"{0}\" for relation ''{1}''"); break;
155 }
156 text = tr(msg, entry.getKey(), primitive.getDisplayName(DefaultNameFormatter.getInstance()));
157 } else {
158 switch(OsmPrimitiveType.from(primitive)) {
159 case NODE: msg = marktr("Set {0}={1} for node ''{2}''"); break;
160 case WAY: msg = marktr("Set {0}={1} for way ''{2}''"); break;
161 case RELATION: msg = marktr("Set {0}={1} for relation ''{2}''"); break;
162 }
163 text = tr(msg, entry.getKey(), entry.getValue(), primitive.getDisplayName(DefaultNameFormatter.getInstance()));
164 }
165 } else if (objects.size() > 1 && tags.size() == 1) {
166 Map.Entry<String, String> entry = tags.entrySet().iterator().next();
167 if (entry.getValue() == null)
168 text = tr("Remove \"{0}\" for {1} objects", entry.getKey(), objects.size());
169 else
170 text = tr("Set {0}={1} for {2} objects", entry.getKey(), entry.getValue(), objects.size());
171 }
172 else {
173 boolean allnull = true;
174 for (Map.Entry<String, String> tag : this.tags.entrySet()) {
175 if (tag.getValue() != null) {
176 allnull = false;
177 break;
178 }
179 }
180
181 if (allnull) {
182 text = tr("Deleted {0} properties for {1} objects", tags.size(), objects.size());
183 } else
184 text = tr("Set {0} properties for {1} objects", tags.size(), objects.size());
185 }
186 return text;
187 }
188
189 @Override
190 public Icon getDescriptionIcon() {
191 return ImageProvider.get("data", "key");
192 }
193
194 @Override public Collection<PseudoCommand> getChildren() {
195 if (objects.size() == 1)
196 return null;
197 List<PseudoCommand> children = new ArrayList<PseudoCommand>();
198 for (final OsmPrimitive osm : objects) {
199 children.add(new PseudoCommand() {
200 @Override public String getDescriptionText() {
201 return osm.getDisplayName(DefaultNameFormatter.getInstance());
202 }
203
204 @Override public Icon getDescriptionIcon() {
205 return ImageProvider.get(osm.getDisplayType());
206 }
207
208 @Override public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
209 return Collections.singleton(osm);
210 }
211
212 });
213 }
214 return children;
215 }
216 }