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