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    }