001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.command;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005import static org.openstreetmap.josm.tools.I18n.trn;
006
007import java.util.ArrayList;
008import java.util.Collection;
009import java.util.Collections;
010import java.util.LinkedList;
011import java.util.List;
012import java.util.NoSuchElementException;
013import java.util.Objects;
014
015import javax.swing.Icon;
016
017import org.openstreetmap.josm.data.osm.DataSet;
018import org.openstreetmap.josm.data.osm.OsmPrimitive;
019import org.openstreetmap.josm.data.validation.util.NameVisitor;
020import org.openstreetmap.josm.tools.ImageProvider;
021
022/**
023 * Command that replaces the key of one or several objects
024 * @since 3669
025 */
026public class ChangePropertyKeyCommand extends Command {
027    static final class SinglePrimitivePseudoCommand implements PseudoCommand {
028        private final String name;
029        private final OsmPrimitive osm;
030        private final Icon icon;
031
032        SinglePrimitivePseudoCommand(String name, OsmPrimitive osm, Icon icon) {
033            this.name = name;
034            this.osm = osm;
035            this.icon = icon;
036        }
037
038        @Override
039        public String getDescriptionText() {
040            return name;
041        }
042
043        @Override
044        public Icon getDescriptionIcon() {
045            return icon;
046        }
047
048        @Override
049        public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
050            return Collections.singleton(osm);
051        }
052    }
053
054    /**
055     * All primitives, that are affected with this command.
056     */
057    private final List<? extends OsmPrimitive> objects;
058    /**
059     * The key that is subject to change.
060     */
061    private final String key;
062    /**
063     * The mew key.
064     */
065    private final String newKey;
066
067    /**
068     * Constructs a new {@code ChangePropertyKeyCommand}.
069     *
070     * @param object the object subject to change replacement. Must not be null, and belong to a data set
071     * @param key The key to replace
072     * @param newKey the new value of the key
073     * @since 6329
074     */
075    public ChangePropertyKeyCommand(OsmPrimitive object, String key, String newKey) {
076        this(Collections.singleton(object), key, newKey);
077    }
078
079    /**
080     * Constructs a new {@code ChangePropertyKeyCommand}.
081     *
082     * @param objects all objects subject to change replacement. Must not be null or empty, and objects must belong to a data set
083     * @param key The key to replace
084     * @param newKey the new value of the key
085     * @throws NullPointerException if objects is null or contain null item
086     * @throws NoSuchElementException if objects is empty
087     */
088    public ChangePropertyKeyCommand(Collection<? extends OsmPrimitive> objects, String key, String newKey) {
089        this(objects.iterator().next().getDataSet(), objects, key, newKey);
090    }
091
092    /**
093     * Constructs a new {@code ChangePropertyKeyCommand}.
094     *
095     * @param ds The target data set. Must not be {@code null}
096     * @param objects all objects subject to change replacement.
097     * @param key The key to replace
098     * @param newKey the new value of the key
099     * @since 12726
100     */
101    public ChangePropertyKeyCommand(DataSet ds, Collection<? extends OsmPrimitive> objects, String key, String newKey) {
102        super(ds);
103        this.objects = new LinkedList<>(objects);
104        this.key = key;
105        this.newKey = newKey;
106    }
107
108    @Override
109    public boolean executeCommand() {
110        if (!super.executeCommand())
111            return false; // save old
112        for (OsmPrimitive osm : objects) {
113            String oldValue = osm.get(key);
114            if (oldValue != null || osm.hasKey(newKey)) {
115                osm.setModified(true);
116                osm.put(newKey, oldValue);
117                osm.remove(key);
118            }
119        }
120        return true;
121    }
122
123    @Override
124    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
125        modified.addAll(objects);
126    }
127
128    @Override
129    public String getDescriptionText() {
130        String text = tr("Replace \"{0}\" by \"{1}\" for", key, newKey);
131        if (objects.size() == 1) {
132            NameVisitor v = new NameVisitor();
133            objects.get(0).accept(v);
134            text += " "+tr(v.className)+" "+v.name;
135        } else {
136            text += " "+objects.size()+" "+trn("object", "objects", objects.size());
137        }
138        return text;
139    }
140
141    @Override
142    public Icon getDescriptionIcon() {
143        return ImageProvider.get("dialogs", "propertiesdialog", ImageProvider.ImageSizes.SMALLICON);
144    }
145
146    @Override
147    public Collection<PseudoCommand> getChildren() {
148        if (objects.size() == 1)
149            return null;
150        List<PseudoCommand> children = new ArrayList<>();
151
152        final NameVisitor v = new NameVisitor();
153        for (final OsmPrimitive osm : objects) {
154            osm.accept(v);
155            children.add(new SinglePrimitivePseudoCommand(v.name, osm, v.icon));
156        }
157        return children;
158    }
159
160    @Override
161    public int hashCode() {
162        return Objects.hash(super.hashCode(), objects, key, newKey);
163    }
164
165    @Override
166    public boolean equals(Object obj) {
167        if (this == obj) return true;
168        if (obj == null || getClass() != obj.getClass()) return false;
169        if (!super.equals(obj)) return false;
170        ChangePropertyKeyCommand that = (ChangePropertyKeyCommand) obj;
171        return Objects.equals(objects, that.objects) &&
172                Objects.equals(key, that.key) &&
173                Objects.equals(newKey, that.newKey);
174    }
175}