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}