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