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