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