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 public 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 * @exception IllegalStateException thrown, if this collection already includes a 105 * conflict for conflict.getMy() 106 */ 107 protected void addConflict(Conflict<?> conflict) throws IllegalStateException { 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 thrown, if conflict is null 120 * @throws IllegalStateException thrown if this collection already includes a conflict for conflict.getMy() 121 * 122 */ 123 public void add(Conflict<?> conflict) throws IllegalStateException { 124 CheckParameterUtil.ensureParameterNotNull(conflict, "conflict"); 125 addConflict(conflict); 126 fireConflictAdded(); 127 } 128 129 /** 130 * Add the conflicts in <code>otherConflicts</code> to this collection of conflicts 131 * 132 * @param otherConflicts the collection of conflicts. Does nothing is conflicts is null. 133 */ 134 public void add(Collection<Conflict<?>> otherConflicts) { 135 if (otherConflicts == null) return; 136 for(Conflict<?> c : otherConflicts) { 137 addConflict(c); 138 } 139 fireConflictAdded(); 140 } 141 142 /** 143 * Adds a conflict for the pair of {@link OsmPrimitive}s given by <code>my</code> and 144 * <code>their</code>. 145 * 146 * @param my my primitive 147 * @param their their primitive 148 */ 149 public void add(OsmPrimitive my, OsmPrimitive their) { 150 addConflict(new Conflict<>(my, their)); 151 fireConflictAdded(); 152 } 153 154 /** 155 * removes a conflict from this collection 156 * 157 * @param conflict the conflict 158 */ 159 public void remove(Conflict<?> conflict) { 160 conflicts.remove(conflict); 161 fireConflictRemoved(); 162 } 163 164 /** 165 * removes the conflict registered for {@link OsmPrimitive} <code>my</code> if any 166 * 167 * @param my the primitive 168 */ 169 public void remove(OsmPrimitive my) { 170 Iterator<Conflict<?>> it = iterator(); 171 while(it.hasNext()) { 172 if (it.next().isMatchingMy(my)) { 173 it.remove(); 174 } 175 } 176 fireConflictRemoved(); 177 } 178 179 /** 180 * Replies the conflict for the {@link OsmPrimitive} <code>my</code>, null 181 * if no such conflict exists. 182 * 183 * @param my my primitive 184 * @return the conflict for the {@link OsmPrimitive} <code>my</code>, null 185 * if no such conflict exists. 186 */ 187 public Conflict<?> getConflictForMy(OsmPrimitive my) { 188 for(Conflict<?> c : conflicts) { 189 if (c.isMatchingMy(my)) 190 return c; 191 } 192 return null; 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 HashSet<OsmPrimitive> ret = new HashSet<>(); 325 for (Conflict<?> c: conflicts) { 326 ret.add(c.getMy()); 327 } 328 return ret; 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 HashSet<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}