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/** 020 * Represents a decision for a conflict due to multiple possible value for a tag. 021 * @since 2008 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 final TagCollection tags; 029 /** the selected value if {@link #type} is {@link MultiValueDecisionType#KEEP_ONE} */ 030 private String value; 031 032 private static final String[] SUMMABLE_KEYS = new String[] { 033 "capacity(:.+)?", "step_count" 034 }; 035 036 /** 037 * constructor 038 */ 039 public MultiValueResolutionDecision() { 040 type = MultiValueDecisionType.UNDECIDED; 041 tags = new TagCollection(); 042 autoDecide(); 043 } 044 045 /** 046 * Creates a new decision for the tag collection <code>tags</code>. 047 * All tags must have the same key. 048 * 049 * @param tags the tags. Must not be null. 050 * @throws IllegalArgumentException if tags is null 051 * @throws IllegalArgumentException if there are more than one keys 052 * @throws IllegalArgumentException if tags is empty 053 */ 054 public MultiValueResolutionDecision(TagCollection tags) { 055 CheckParameterUtil.ensureParameterNotNull(tags, "tags"); 056 if (tags.isEmpty()) 057 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' must not be empty.", "tags")); 058 if (tags.getKeys().size() != 1) 059 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' with tags for exactly one key expected. Got {1}.", 060 "tags", tags.getKeys().size())); 061 this.tags = tags; 062 autoDecide(); 063 } 064 065 /** 066 * Tries to find the best decision based on the current values. 067 */ 068 protected final void autoDecide() { 069 this.type = MultiValueDecisionType.UNDECIDED; 070 // exactly one empty value ? -> delete the tag 071 if (tags.size() == 1 && tags.getValues().contains("")) { 072 this.type = MultiValueDecisionType.KEEP_NONE; 073 074 // exactly one non empty value? -> keep this value 075 } else if (tags.size() == 1) { 076 this.type = MultiValueDecisionType.KEEP_ONE; 077 this.value = tags.getValues().iterator().next(); 078 } 079 } 080 081 /** 082 * Apply the decision to keep no value 083 */ 084 public void keepNone() { 085 this.type = MultiValueDecisionType.KEEP_NONE; 086 } 087 088 /** 089 * Apply the decision to keep all values 090 */ 091 public void keepAll() { 092 this.type = MultiValueDecisionType.KEEP_ALL; 093 } 094 095 /** 096 * Apply the decision to sum all numeric values 097 * @since 7743 098 */ 099 public void sumAllNumeric() { 100 this.type = MultiValueDecisionType.SUM_ALL_NUMERIC; 101 } 102 103 /** 104 * Apply the decision to keep exactly one value 105 * 106 * @param value the value to keep 107 * @throws IllegalArgumentException if value is null 108 * @throws IllegalStateException if value is not in the list of known values for this tag 109 */ 110 public void keepOne(String value) { 111 CheckParameterUtil.ensureParameterNotNull(value, "value"); 112 if (!tags.getValues().contains(value)) 113 throw new IllegalStateException(tr("Tag collection does not include the selected value ''{0}''.", value)); 114 this.value = value; 115 this.type = MultiValueDecisionType.KEEP_ONE; 116 } 117 118 /** 119 * sets a new value for this 120 * 121 * @param value the new vlaue 122 */ 123 public void setNew(String value) { 124 if (value == null) { 125 value = ""; 126 } 127 this.value = value; 128 this.type = MultiValueDecisionType.KEEP_ONE; 129 130 } 131 132 /** 133 * marks this as undecided 134 * 135 */ 136 public void undecide() { 137 this.type = MultiValueDecisionType.UNDECIDED; 138 } 139 140 /** 141 * Replies the chosen value 142 * 143 * @return the chosen value 144 * @throws IllegalStateException if this resolution is not yet decided 145 */ 146 public String getChosenValue() { 147 switch(type) { 148 case UNDECIDED: throw new IllegalStateException(tr("Not decided yet.")); 149 case KEEP_ONE: return value; 150 case SUM_ALL_NUMERIC: return tags.getSummedValues(getKey()); 151 case KEEP_ALL: return tags.getJoinedValues(getKey()); 152 case KEEP_NONE: 153 default: return null; 154 } 155 } 156 157 /** 158 * Replies the list of possible, non empty values 159 * 160 * @return the list of possible, non empty values 161 */ 162 public List<String> getValues() { 163 List<String> ret = new ArrayList<>(tags.getValues()); 164 ret.remove(""); 165 ret.remove(null); 166 Collections.sort(ret); 167 return ret; 168 } 169 170 /** 171 * Replies the key of the tag to be resolved by this resolution 172 * 173 * @return the key of the tag to be resolved by this resolution 174 */ 175 public String getKey() { 176 return tags.getKeys().iterator().next(); 177 } 178 179 /** 180 * Replies true if the empty value is a possible value in this resolution 181 * 182 * @return true if the empty value is a possible value in this resolution 183 */ 184 public boolean canKeepNone() { 185 return tags.getValues().contains(""); 186 } 187 188 /** 189 * Replies true, if this resolution has more than 1 possible non-empty values 190 * 191 * @return true, if this resolution has more than 1 possible non-empty values 192 */ 193 public boolean canKeepAll() { 194 return getValues().size() > 1; 195 } 196 197 /** 198 * Replies true, if summing all numeric values is a possible value in this resolution 199 * 200 * @return true, if summing all numeric values is a possible value in this resolution 201 * @since 7743 202 */ 203 public boolean canSumAllNumeric() { 204 if (!canKeepAll()) { 205 return false; 206 } 207 for (String key : SUMMABLE_KEYS) { 208 if (getKey().matches(key)) { 209 return true; 210 } 211 } 212 return false; 213 } 214 215 /** 216 * Replies true if this resolution is decided 217 * 218 * @return true if this resolution is decided 219 */ 220 public boolean isDecided() { 221 return !type.equals(MultiValueDecisionType.UNDECIDED); 222 } 223 224 /** 225 * Replies the type of the resolution 226 * 227 * @return the type of the resolution 228 */ 229 public MultiValueDecisionType getDecisionType() { 230 return type; 231 } 232 233 /** 234 * Applies the resolution to an {@link OsmPrimitive} 235 * 236 * @param primitive the primitive 237 * @throws IllegalStateException if this resolution is not resolved yet 238 * 239 */ 240 public void applyTo(OsmPrimitive primitive) { 241 if (primitive == null) return; 242 if (!isDecided()) 243 throw new IllegalStateException(tr("Not decided yet.")); 244 String key = tags.getKeys().iterator().next(); 245 if (type.equals(MultiValueDecisionType.KEEP_NONE)) { 246 primitive.remove(key); 247 } else { 248 primitive.put(key, getChosenValue()); 249 } 250 } 251 252 /** 253 * Applies this resolution to a collection of primitives 254 * 255 * @param primitives the collection of primitives 256 * @throws IllegalStateException if this resolution is not resolved yet 257 */ 258 public void applyTo(Collection<? extends OsmPrimitive> primitives) { 259 if (primitives == null) return; 260 for (OsmPrimitive primitive: primitives) { 261 if (primitive == null) { 262 continue; 263 } 264 applyTo(primitive); 265 } 266 } 267 268 /** 269 * Builds a change command for applying this resolution to a primitive 270 * 271 * @param primitive the primitive 272 * @return the change command 273 * @throws IllegalArgumentException if primitive is null 274 * @throws IllegalStateException if this resolution is not resolved yet 275 */ 276 public Command buildChangeCommand(OsmPrimitive primitive) { 277 CheckParameterUtil.ensureParameterNotNull(primitive, "primitive"); 278 if (!isDecided()) 279 throw new IllegalStateException(tr("Not decided yet.")); 280 String key = tags.getKeys().iterator().next(); 281 return new ChangePropertyCommand(primitive, key, getChosenValue()); 282 } 283 284 /** 285 * Builds a change command for applying this resolution to a collection of primitives 286 * 287 * @param primitives the collection of primitives 288 * @return the change command 289 * @throws IllegalArgumentException if primitives is null 290 * @throws IllegalStateException if this resolution is not resolved yet 291 */ 292 public Command buildChangeCommand(Collection<? extends OsmPrimitive> primitives) { 293 CheckParameterUtil.ensureParameterNotNull(primitives, "primitives"); 294 if (!isDecided()) 295 throw new IllegalStateException(tr("Not decided yet.")); 296 String key = tags.getKeys().iterator().next(); 297 return new ChangePropertyCommand(primitives, key, getChosenValue()); 298 } 299 300 /** 301 * Replies a tag representing the current resolution. Null, if this resolution is not resolved yet. 302 * 303 * @return a tag representing the current resolution. Null, if this resolution is not resolved yet 304 */ 305 public Tag getResolution() { 306 switch(type) { 307 case SUM_ALL_NUMERIC: return new Tag(getKey(), tags.getSummedValues(getKey())); 308 case KEEP_ALL: return new Tag(getKey(), tags.getJoinedValues(getKey())); 309 case KEEP_ONE: return new Tag(getKey(), value); 310 case KEEP_NONE: return new Tag(getKey(), ""); 311 case UNDECIDED: 312 default: return null; 313 } 314 } 315}