001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.conflict.tags;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.text.MessageFormat;
007import java.util.ArrayList;
008import java.util.Collection;
009import java.util.Collections;
010import java.util.List;
011
012import org.openstreetmap.josm.command.ChangePropertyCommand;
013import org.openstreetmap.josm.command.Command;
014import org.openstreetmap.josm.data.osm.OsmPrimitive;
015import org.openstreetmap.josm.data.osm.Tag;
016import org.openstreetmap.josm.data.osm.TagCollection;
017import org.openstreetmap.josm.tools.CheckParameterUtil;
018/**
019 * Represents a decision for a conflict due to multiple possible value for a tag.
020 *
021 *
022 */
023public class MultiValueResolutionDecision {
024
025    /** the type of decision */
026    private MultiValueDecisionType type;
027    /** the collection of tags for which a decision is needed */
028    private TagCollection  tags;
029    /** the selected value if {@link #type} is {@link MultiValueDecisionType#KEEP_ONE} */
030    private String value;
031
032    /**
033     * constuctor
034     */
035    public MultiValueResolutionDecision() {
036        type = MultiValueDecisionType.UNDECIDED;
037        tags = new TagCollection();
038        autoDecide();
039    }
040
041    /**
042     * Creates a new decision for the tag collection <code>tags</code>.
043     * All tags must have the same key.
044     *
045     * @param tags the tags. Must not be null.
046     * @exception IllegalArgumentException  thrown if tags is null
047     * @exception IllegalArgumentException thrown if there are more than one keys
048     * @exception IllegalArgumentException thrown if tags is empty
049     */
050    public MultiValueResolutionDecision(TagCollection tags) throws IllegalArgumentException {
051        CheckParameterUtil.ensureParameterNotNull(tags, "tags");
052        if (tags.isEmpty())
053            throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' must not be empty.", "tags"));
054        if (tags.getKeys().size() != 1)
055            throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' with tags for exactly one key expected. Got {1}.", "tags", tags.getKeys().size()));
056        this.tags = tags;
057        autoDecide();
058    }
059
060    /**
061     * Tries to find the best decision based on the current values.
062     */
063    protected final void autoDecide() {
064        this.type = MultiValueDecisionType.UNDECIDED;
065        // exactly one empty value ? -> delete the tag
066        if (tags.size() == 1 && tags.getValues().contains("")) {
067            this.type = MultiValueDecisionType.KEEP_NONE;
068
069            // exactly one non empty value? -> keep this value
070        } else if (tags.size() == 1) {
071            this.type = MultiValueDecisionType.KEEP_ONE;
072            this.value = tags.getValues().iterator().next();
073        }
074    }
075
076    /**
077     * Apply the decision to keep no value
078     */
079    public void keepNone() {
080        this.type = MultiValueDecisionType.KEEP_NONE;
081    }
082
083    /**
084     * Apply the decision to keep all values
085     */
086    public void keepAll() {
087        this.type = MultiValueDecisionType.KEEP_ALL;
088    }
089
090    /**
091     * Apply the decision to keep exactly one value
092     *
093     * @param value  the value to keep
094     * @throws IllegalArgumentException thrown if value is null
095     * @throws IllegalStateException thrown if value is not in the list of known values for this tag
096     */
097    public void keepOne(String value) throws IllegalArgumentException, IllegalStateException {
098        CheckParameterUtil.ensureParameterNotNull(value, "value");
099        if (!tags.getValues().contains(value))
100            throw new IllegalStateException(tr("Tag collection does not include the selected value ''{0}''.", value));
101        this.value = value;
102        this.type = MultiValueDecisionType.KEEP_ONE;
103    }
104
105    /**
106     * sets a new value for this
107     *
108     * @param value the new vlaue
109     */
110    public void setNew(String value) {
111        if (value == null) {
112            value = "";
113        }
114        this.value = value;
115        this.type = MultiValueDecisionType.KEEP_ONE;
116
117    }
118
119    /**
120     * marks this as undecided
121     *
122     */
123    public void undecide() {
124        this.type = MultiValueDecisionType.UNDECIDED;
125    }
126
127    /**
128     * Replies the chosen value
129     *
130     * @return the chosen value
131     * @throws IllegalStateException thrown if this resolution is not yet decided
132     */
133    public String getChosenValue() throws IllegalStateException {
134        switch(type) {
135        case UNDECIDED: throw new IllegalStateException(tr("Not decided yet."));
136        case KEEP_ONE: return value;
137        case KEEP_NONE: return null;
138        case KEEP_ALL: return tags.getJoinedValues(getKey());
139        }
140        // should not happen
141        return null;
142    }
143
144    /**
145     * Replies the list of possible, non empty values
146     *
147     * @return the list of possible, non empty values
148     */
149    public List<String> getValues() {
150        List<String> ret = new ArrayList<>(tags.getValues());
151        ret.remove("");
152        ret.remove(null);
153        Collections.sort(ret);
154        return ret;
155    }
156
157    /**
158     * Replies the key of the tag to be resolved by this resolution
159     *
160     * @return the key of the tag to be resolved by this resolution
161     */
162    public String getKey() {
163        return tags.getKeys().iterator().next();
164    }
165
166    /**
167     * Replies true if the empty value is a possible value in this resolution
168     *
169     * @return true if the empty value is a possible value in this resolution
170     */
171    public boolean canKeepNone() {
172        return tags.getValues().contains("");
173    }
174
175    /**
176     * Replies true, if this resolution has more than 1 possible non-empty values
177     *
178     * @return true, if this resolution has more than 1 possible non-empty values
179     */
180    public boolean canKeepAll() {
181        return getValues().size() > 1;
182    }
183
184    /**
185     * Replies  true if this resolution is decided
186     *
187     * @return true if this resolution is decided
188     */
189    public boolean isDecided() {
190        return !type.equals(MultiValueDecisionType.UNDECIDED);
191    }
192
193    /**
194     * Replies the type of the resolution
195     *
196     * @return the type of the resolution
197     */
198    public MultiValueDecisionType getDecisionType() {
199        return type;
200    }
201
202    /**
203     * Applies the resolution to an {@link OsmPrimitive}
204     *
205     * @param primitive the primitive
206     * @throws IllegalStateException thrown if this resolution is not resolved yet
207     *
208     */
209    public void applyTo(OsmPrimitive primitive) throws IllegalStateException{
210        if (primitive == null) return;
211        if (!isDecided())
212            throw new IllegalStateException(tr("Not decided yet."));
213        String key = tags.getKeys().iterator().next();
214        String value = getChosenValue();
215        if (type.equals(MultiValueDecisionType.KEEP_NONE)) {
216            primitive.remove(key);
217        } else {
218            primitive.put(key, value);
219        }
220    }
221
222    /**
223     * Applies this resolution to a collection of primitives
224     *
225     * @param primitives the collection of primitives
226     * @throws IllegalStateException thrown if this resolution is not resolved yet
227     */
228    public void applyTo(Collection<? extends OsmPrimitive> primitives) throws IllegalStateException {
229        if (primitives == null) return;
230        for (OsmPrimitive primitive: primitives) {
231            if (primitive == null) {
232                continue;
233            }
234            applyTo(primitive);
235        }
236    }
237
238    /**
239     * Builds a change command for applying this resolution to a primitive
240     *
241     * @param primitive  the primitive
242     * @return the change command
243     * @throws IllegalArgumentException thrown if primitive is null
244     * @throws IllegalStateException thrown if this resolution is not resolved yet
245     */
246    public Command buildChangeCommand(OsmPrimitive primitive) throws IllegalArgumentException, IllegalStateException {
247        CheckParameterUtil.ensureParameterNotNull(primitive, "primitive");
248        if (!isDecided())
249            throw new IllegalStateException(tr("Not decided yet."));
250        String key = tags.getKeys().iterator().next();
251        return new ChangePropertyCommand(primitive, key, getChosenValue());
252    }
253
254    /**
255     * Builds a change command for applying this resolution to a collection of primitives
256     *
257     * @param primitives  the collection of primitives
258     * @return the change command
259     * @throws IllegalArgumentException thrown if primitives is null
260     * @throws IllegalStateException thrown if this resolution is not resolved yet
261     */
262    public Command buildChangeCommand(Collection<? extends OsmPrimitive> primitives) {
263        CheckParameterUtil.ensureParameterNotNull(primitives, "primitives");
264        if (!isDecided())
265            throw new IllegalStateException(tr("Not decided yet."));
266        String key = tags.getKeys().iterator().next();
267        return new ChangePropertyCommand(primitives, key, getChosenValue());
268    }
269
270    /**
271     * Replies a tag representing the current resolution. Null, if this resolution is not resolved yet.
272     *
273     * @return a tag representing the current resolution. Null, if this resolution is not resolved yet
274     */
275    public Tag getResolution() {
276        switch(type) {
277        case KEEP_ALL: return new Tag(getKey(), tags.getJoinedValues(getKey()));
278        case KEEP_ONE: return new Tag(getKey(),value);
279        case KEEP_NONE: return new Tag(getKey(), "");
280        case UNDECIDED: return null;
281        }
282        return null;
283    }
284}