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}