001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.conflict.tags;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.text.MessageFormat;
007    import java.util.ArrayList;
008    import java.util.Collection;
009    import java.util.Collections;
010    import java.util.List;
011    
012    import org.openstreetmap.josm.command.ChangePropertyCommand;
013    import org.openstreetmap.josm.command.Command;
014    import org.openstreetmap.josm.data.osm.OsmPrimitive;
015    import org.openstreetmap.josm.data.osm.Tag;
016    import org.openstreetmap.josm.data.osm.TagCollection;
017    import org.openstreetmap.josm.tools.CheckParameterUtil;
018    /**
019     * Represents a decision for a conflict due to multiple possible value for a tag.
020     *
021     *
022     */
023    public 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 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            ArrayList<String> ret = new ArrayList<String>(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            String value = getChosenValue();
252            ChangePropertyCommand cmd = new ChangePropertyCommand(primitive, key,value);
253            return cmd;
254        }
255    
256        /**
257         * Builds a change command for applying this resolution to a collection of primitives
258         *
259         * @param primitives  the collection of primitives
260         * @return the change command
261         * @throws IllegalArgumentException thrown if primitives is null
262         * @throws IllegalStateException thrown if this resolution is not resolved yet
263         */
264        public Command buildChangeCommand(Collection<? extends OsmPrimitive> primitives) {
265            CheckParameterUtil.ensureParameterNotNull(primitives, "primitives");
266            if (!isDecided())
267                throw new IllegalStateException(tr("Not decided yet."));
268            String key = tags.getKeys().iterator().next();
269            String value = getChosenValue();
270            ChangePropertyCommand cmd = new ChangePropertyCommand(primitives, key,value);
271            return cmd;
272        }
273    
274        /**
275         * Replies a tag representing the current resolution. Null, if this resolution is not resolved
276         * yet.
277         *
278         * @return a tag representing the current resolution. Null, if this resolution is not resolved
279         * yet
280         */
281        public Tag getResolution() {
282            switch(type) {
283            case KEEP_ALL: return new Tag(getKey(), tags.getJoinedValues(getKey()));
284            case KEEP_ONE: return new Tag(getKey(),value);
285            case KEEP_NONE: return new Tag(getKey(), "");
286            case UNDECIDED: return null;
287            }
288            return null;
289        }
290    }