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 }