001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.conflict; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.util.ArrayList; 007import java.util.Collection; 008import java.util.HashSet; 009import java.util.Iterator; 010import java.util.List; 011import java.util.Set; 012import java.util.concurrent.CopyOnWriteArrayList; 013 014import org.openstreetmap.josm.data.osm.Node; 015import org.openstreetmap.josm.data.osm.OsmPrimitive; 016import org.openstreetmap.josm.data.osm.Relation; 017import org.openstreetmap.josm.data.osm.Way; 018import org.openstreetmap.josm.tools.CheckParameterUtil; 019import org.openstreetmap.josm.tools.Predicate; 020import org.openstreetmap.josm.tools.Utils; 021 022/** 023 * This is a collection of {@link Conflict}s. This collection is {@link Iterable}, i.e. 024 * it can be used in <code>for</code>-loops as follows: 025 * <pre> 026 * ConflictCollection conflictCollection = .... 027 * 028 * for (Conflict c : conflictCollection) { 029 * // do something 030 * } 031 * </pre> 032 * 033 * This collection emits an event when the content of the collection changes. You can register 034 * and unregister for these events using: 035 * <ul> 036 * <li>{@link #addConflictListener(IConflictListener)}</li> 037 * <li>{@link #removeConflictListener(IConflictListener)}</li> 038 * </ul> 039 */ 040public class ConflictCollection implements Iterable<Conflict<? extends OsmPrimitive>> { 041 private final List<Conflict<? extends OsmPrimitive>> conflicts; 042 private CopyOnWriteArrayList<IConflictListener> listeners; 043 044 private static class FilterPredicate implements Predicate<Conflict<? extends OsmPrimitive>> { 045 046 private final Class<? extends OsmPrimitive> c; 047 048 FilterPredicate(Class<? extends OsmPrimitive> c) { 049 this.c = c; 050 } 051 052 @Override 053 public boolean evaluate(Conflict<? extends OsmPrimitive> conflict) { 054 return conflict != null && c.isInstance(conflict.getMy()); 055 } 056 } 057 058 private static final FilterPredicate NODE_FILTER_PREDICATE = new FilterPredicate(Node.class); 059 private static final FilterPredicate WAY_FILTER_PREDICATE = new FilterPredicate(Way.class); 060 private static final FilterPredicate RELATION_FILTER_PREDICATE = new FilterPredicate(Relation.class); 061 062 /** 063 * Constructs a new {@code ConflictCollection}. 064 */ 065 public ConflictCollection() { 066 conflicts = new ArrayList<>(); 067 listeners = new CopyOnWriteArrayList<>(); 068 } 069 070 /** 071 * Adds the specified conflict listener, if not already present. 072 * @param listener The conflict listener to add 073 */ 074 public void addConflictListener(IConflictListener listener) { 075 if (listener != null) { 076 listeners.addIfAbsent(listener); 077 } 078 } 079 080 /** 081 * Removes the specified conflict listener. 082 * @param listener The conflict listener to remove 083 */ 084 public void removeConflictListener(IConflictListener listener) { 085 listeners.remove(listener); 086 } 087 088 protected void fireConflictAdded() { 089 for (IConflictListener listener : listeners) { 090 listener.onConflictsAdded(this); 091 } 092 } 093 094 protected void fireConflictRemoved() { 095 for (IConflictListener listener : listeners) { 096 listener.onConflictsRemoved(this); 097 } 098 } 099 100 /** 101 * Adds a conflict to the collection 102 * 103 * @param conflict the conflict 104 * @throws IllegalStateException if this collection already includes a conflict for conflict.getMy() 105 */ 106 protected void addConflict(Conflict<?> conflict) { 107 if (hasConflictForMy(conflict.getMy())) 108 throw new IllegalStateException(tr("Already registered a conflict for primitive ''{0}''.", conflict.getMy().toString())); 109 if (!conflicts.contains(conflict)) { 110 conflicts.add(conflict); 111 } 112 } 113 114 /** 115 * Adds a conflict to the collection of conflicts. 116 * 117 * @param conflict the conflict to add. Must not be null. 118 * @throws IllegalArgumentException if conflict is null 119 * @throws IllegalStateException if this collection already includes a conflict for conflict.getMy() 120 */ 121 public void add(Conflict<?> conflict) { 122 CheckParameterUtil.ensureParameterNotNull(conflict, "conflict"); 123 addConflict(conflict); 124 fireConflictAdded(); 125 } 126 127 /** 128 * Add the conflicts in <code>otherConflicts</code> to this collection of conflicts 129 * 130 * @param otherConflicts the collection of conflicts. Does nothing is conflicts is null. 131 */ 132 public void add(Collection<Conflict<?>> otherConflicts) { 133 if (otherConflicts == null) return; 134 for (Conflict<?> c : otherConflicts) { 135 addConflict(c); 136 } 137 fireConflictAdded(); 138 } 139 140 /** 141 * Adds a conflict for the pair of {@link OsmPrimitive}s given by <code>my</code> and 142 * <code>their</code>. 143 * 144 * @param my my primitive 145 * @param their their primitive 146 */ 147 public void add(OsmPrimitive my, OsmPrimitive their) { 148 addConflict(new Conflict<>(my, their)); 149 fireConflictAdded(); 150 } 151 152 /** 153 * removes a conflict from this collection 154 * 155 * @param conflict the conflict 156 */ 157 public void remove(Conflict<?> conflict) { 158 conflicts.remove(conflict); 159 fireConflictRemoved(); 160 } 161 162 /** 163 * removes the conflict registered for {@link OsmPrimitive} <code>my</code> if any 164 * 165 * @param my the primitive 166 */ 167 public void remove(OsmPrimitive my) { 168 Iterator<Conflict<?>> it = iterator(); 169 while (it.hasNext()) { 170 if (it.next().isMatchingMy(my)) { 171 it.remove(); 172 } 173 } 174 fireConflictRemoved(); 175 } 176 177 /** 178 * Replies the conflict for the {@link OsmPrimitive} <code>my</code>, null 179 * if no such conflict exists. 180 * 181 * @param my my primitive 182 * @return the conflict for the {@link OsmPrimitive} <code>my</code>, null 183 * if no such conflict exists. 184 */ 185 public Conflict<?> getConflictForMy(OsmPrimitive my) { 186 for (Conflict<?> c : conflicts) { 187 if (c.isMatchingMy(my)) 188 return c; 189 } 190 return null; 191 } 192 193 /** 194 * Replies the conflict for the {@link OsmPrimitive} <code>their</code>, null 195 * if no such conflict exists. 196 * 197 * @param their their primitive 198 * @return the conflict for the {@link OsmPrimitive} <code>their</code>, null 199 * if no such conflict exists. 200 */ 201 public Conflict<?> getConflictForTheir(OsmPrimitive their) { 202 for (Conflict<?> c : conflicts) { 203 if (c.isMatchingTheir(their)) 204 return c; 205 } 206 return null; 207 } 208 209 /** 210 * Replies true, if this collection includes a conflict for <code>my</code>. 211 * 212 * @param my my primitive 213 * @return true, if this collection includes a conflict for <code>my</code>; false, otherwise 214 */ 215 public boolean hasConflictForMy(OsmPrimitive my) { 216 return getConflictForMy(my) != null; 217 } 218 219 /** 220 * Replies true, if this collection includes a given conflict 221 * 222 * @param c the conflict 223 * @return true, if this collection includes the conflict; false, otherwise 224 */ 225 public boolean hasConflict(Conflict<?> c) { 226 return hasConflictForMy(c.getMy()); 227 } 228 229 /** 230 * Replies true, if this collection includes a conflict for <code>their</code>. 231 * 232 * @param their their primitive 233 * @return true, if this collection includes a conflict for <code>their</code>; false, otherwise 234 */ 235 public boolean hasConflictForTheir(OsmPrimitive their) { 236 return getConflictForTheir(their) != null; 237 } 238 239 /** 240 * Removes any conflicts for the {@link OsmPrimitive} <code>my</code>. 241 * 242 * @param my the primitive 243 */ 244 public void removeForMy(OsmPrimitive my) { 245 Iterator<Conflict<?>> it = iterator(); 246 while (it.hasNext()) { 247 if (it.next().isMatchingMy(my)) { 248 it.remove(); 249 } 250 } 251 } 252 253 /** 254 * Removes any conflicts for the {@link OsmPrimitive} <code>their</code>. 255 * 256 * @param their the primitive 257 */ 258 public void removeForTheir(OsmPrimitive their) { 259 Iterator<Conflict<?>> it = iterator(); 260 while (it.hasNext()) { 261 if (it.next().isMatchingTheir(their)) { 262 it.remove(); 263 } 264 } 265 } 266 267 /** 268 * Replies the conflicts as list. 269 * 270 * @return the list of conflicts 271 */ 272 public List<Conflict<?>> get() { 273 return conflicts; 274 } 275 276 /** 277 * Replies the size of the collection 278 * 279 * @return the size of the collection 280 */ 281 public int size() { 282 return conflicts.size(); 283 } 284 285 /** 286 * Replies the conflict at position <code>idx</code> 287 * 288 * @param idx the index 289 * @return the conflict at position <code>idx</code> 290 */ 291 public Conflict<?> get(int idx) { 292 return conflicts.get(idx); 293 } 294 295 /** 296 * Replies the iterator for this collection. 297 * 298 * @return the iterator 299 */ 300 @Override 301 public Iterator<Conflict<?>> iterator() { 302 return conflicts.iterator(); 303 } 304 305 /** 306 * Adds all conflicts from another collection. 307 * @param other The other collection of conflicts to add 308 */ 309 public void add(ConflictCollection other) { 310 for (Conflict<?> c : other) { 311 add(c); 312 } 313 } 314 315 /** 316 * Replies the set of {@link OsmPrimitive} which participate in the role 317 * of "my" in the conflicts managed by this collection. 318 * 319 * @return the set of {@link OsmPrimitive} which participate in the role 320 * of "my" in the conflicts managed by this collection. 321 */ 322 public Set<OsmPrimitive> getMyConflictParties() { 323 Set<OsmPrimitive> ret = new HashSet<>(); 324 for (Conflict<?> c: conflicts) { 325 ret.add(c.getMy()); 326 } 327 return ret; 328 } 329 330 /** 331 * Replies the set of {@link OsmPrimitive} which participate in the role 332 * of "their" in the conflicts managed by this collection. 333 * 334 * @return the set of {@link OsmPrimitive} which participate in the role 335 * of "their" in the conflicts managed by this collection. 336 */ 337 public Set<OsmPrimitive> getTheirConflictParties() { 338 Set<OsmPrimitive> ret = new HashSet<>(); 339 for (Conflict<?> c: conflicts) { 340 ret.add(c.getTheir()); 341 } 342 return ret; 343 } 344 345 /** 346 * Replies true if this collection is empty 347 * 348 * @return true, if this collection is empty; false, otherwise 349 */ 350 public boolean isEmpty() { 351 return size() == 0; 352 } 353 354 @Override 355 public String toString() { 356 return conflicts.toString(); 357 } 358 359 /** 360 * Returns the list of conflicts involving nodes. 361 * @return The list of conflicts involving nodes. 362 * @since 6555 363 */ 364 public final Collection<Conflict<? extends OsmPrimitive>> getNodeConflicts() { 365 return Utils.filter(conflicts, NODE_FILTER_PREDICATE); 366 } 367 368 /** 369 * Returns the list of conflicts involving nodes. 370 * @return The list of conflicts involving nodes. 371 * @since 6555 372 */ 373 public final Collection<Conflict<? extends OsmPrimitive>> getWayConflicts() { 374 return Utils.filter(conflicts, WAY_FILTER_PREDICATE); 375 } 376 377 /** 378 * Returns the list of conflicts involving nodes. 379 * @return The list of conflicts involving nodes. 380 * @since 6555 381 */ 382 public final Collection<Conflict<? extends OsmPrimitive>> getRelationConflicts() { 383 return Utils.filter(conflicts, RELATION_FILTER_PREDICATE); 384 } 385 386 @Override 387 public int hashCode() { 388 final int prime = 31; 389 int result = 1; 390 result = prime * result + ((conflicts == null) ? 0 : conflicts.hashCode()); 391 result = prime * result + ((listeners == null) ? 0 : listeners.hashCode()); 392 return result; 393 } 394 395 @Override 396 public boolean equals(Object obj) { 397 if (this == obj) 398 return true; 399 if (obj == null) 400 return false; 401 if (getClass() != obj.getClass()) 402 return false; 403 ConflictCollection other = (ConflictCollection) obj; 404 if (conflicts == null) { 405 if (other.conflicts != null) 406 return false; 407 } else if (!conflicts.equals(other.conflicts)) 408 return false; 409 if (listeners == null) { 410 if (other.listeners != null) 411 return false; 412 } else if (!listeners.equals(other.listeners)) 413 return false; 414 return true; 415 } 416}