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.Predicate;
021import org.openstreetmap.josm.tools.Utils;
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    private static class FilterPredicate implements Predicate<Conflict<? extends OsmPrimitive>> {
046
047        private final Class<? extends OsmPrimitive> c;
048
049        FilterPredicate(Class<? extends OsmPrimitive> c) {
050            this.c = c;
051        }
052
053        @Override
054        public boolean evaluate(Conflict<? extends OsmPrimitive> conflict) {
055            return conflict != null && c.isInstance(conflict.getMy());
056        }
057    }
058
059    private static final FilterPredicate NODE_FILTER_PREDICATE = new FilterPredicate(Node.class);
060    private static final FilterPredicate WAY_FILTER_PREDICATE = new FilterPredicate(Way.class);
061    private static final FilterPredicate RELATION_FILTER_PREDICATE = new FilterPredicate(Relation.class);
062
063    /**
064     * Constructs a new {@code ConflictCollection}.
065     */
066    public ConflictCollection() {
067        conflicts = new ArrayList<>();
068        listeners = new CopyOnWriteArrayList<>();
069    }
070
071    /**
072     * Adds the specified conflict listener, if not already present.
073     * @param listener The conflict listener to add
074     */
075    public void addConflictListener(IConflictListener listener) {
076        if (listener != null) {
077            listeners.addIfAbsent(listener);
078        }
079    }
080
081    /**
082     * Removes the specified conflict listener.
083     * @param listener The conflict listener to remove
084     */
085    public void removeConflictListener(IConflictListener listener) {
086        listeners.remove(listener);
087    }
088
089    protected void fireConflictAdded() {
090        for (IConflictListener listener : listeners) {
091            listener.onConflictsAdded(this);
092        }
093    }
094
095    protected void fireConflictRemoved() {
096        for (IConflictListener listener : listeners) {
097            listener.onConflictsRemoved(this);
098        }
099    }
100
101    /**
102     * Adds a conflict to the collection
103     *
104     * @param conflict the conflict
105     * @throws IllegalStateException if this collection already includes a conflict for conflict.getMy()
106     */
107    protected void addConflict(Conflict<?> conflict) {
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 if conflict is null
120     * @throws IllegalStateException if this collection already includes a conflict for conflict.getMy()
121     */
122    public void add(Conflict<?> conflict) {
123        CheckParameterUtil.ensureParameterNotNull(conflict, "conflict");
124        addConflict(conflict);
125        fireConflictAdded();
126    }
127
128    /**
129     * Add the conflicts in <code>otherConflicts</code> to this collection of conflicts
130     *
131     * @param otherConflicts the collection of conflicts. Does nothing is conflicts is null.
132     */
133    public void add(Collection<Conflict<?>> otherConflicts) {
134        if (otherConflicts == null) return;
135        for (Conflict<?> c : otherConflicts) {
136            addConflict(c);
137        }
138        fireConflictAdded();
139    }
140
141    /**
142     * Adds a conflict for the pair of {@link OsmPrimitive}s given by <code>my</code> and
143     * <code>their</code>.
144     *
145     * @param my  my primitive
146     * @param their their primitive
147     */
148    public void add(OsmPrimitive my, OsmPrimitive their) {
149        addConflict(new Conflict<>(my, their));
150        fireConflictAdded();
151    }
152
153    /**
154     * removes a conflict from this collection
155     *
156     * @param conflict the conflict
157     */
158    public void remove(Conflict<?> conflict) {
159        conflicts.remove(conflict);
160        fireConflictRemoved();
161    }
162
163    /**
164     * removes the conflict registered for {@link OsmPrimitive} <code>my</code> if any
165     *
166     * @param my  the primitive
167     */
168    public void remove(OsmPrimitive my) {
169        Iterator<Conflict<?>> it = iterator();
170        while (it.hasNext()) {
171            if (it.next().isMatchingMy(my)) {
172                it.remove();
173            }
174        }
175        fireConflictRemoved();
176    }
177
178    /**
179     * Replies the conflict for the {@link OsmPrimitive} <code>my</code>, null
180     * if no such conflict exists.
181     *
182     * @param my  my primitive
183     * @return the conflict for the {@link OsmPrimitive} <code>my</code>, null
184     * if no such conflict exists.
185     */
186    public Conflict<?> getConflictForMy(OsmPrimitive my) {
187        for (Conflict<?> c : conflicts) {
188            if (c.isMatchingMy(my))
189                return c;
190        }
191        return null;
192    }
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        Set<OsmPrimitive> ret = new HashSet<>();
325        for (Conflict<?> c: conflicts) {
326            ret.add(c.getMy());
327        }
328        return ret;
329    }
330
331    /**
332     * Replies the set of  {@link OsmPrimitive} which participate in the role
333     * of "their" in the conflicts managed by this collection.
334     *
335     * @return the set of  {@link OsmPrimitive} which participate in the role
336     * of "their" in the conflicts managed by this collection.
337     */
338    public Set<OsmPrimitive> getTheirConflictParties() {
339        Set<OsmPrimitive> ret = new HashSet<>();
340        for (Conflict<?> c: conflicts) {
341            ret.add(c.getTheir());
342        }
343        return ret;
344    }
345
346    /**
347     * Replies true if this collection is empty
348     *
349     * @return true, if this collection is empty; false, otherwise
350     */
351    public boolean isEmpty() {
352        return size() == 0;
353    }
354
355    @Override
356    public String toString() {
357        return conflicts.toString();
358    }
359
360    /**
361     * Returns the list of conflicts involving nodes.
362     * @return The list of conflicts involving nodes.
363     * @since 6555
364     */
365    public final Collection<Conflict<? extends OsmPrimitive>> getNodeConflicts() {
366        return Utils.filter(conflicts, NODE_FILTER_PREDICATE);
367    }
368
369    /**
370     * Returns the list of conflicts involving nodes.
371     * @return The list of conflicts involving nodes.
372     * @since 6555
373     */
374    public final Collection<Conflict<? extends OsmPrimitive>> getWayConflicts() {
375        return Utils.filter(conflicts, WAY_FILTER_PREDICATE);
376    }
377
378    /**
379     * Returns the list of conflicts involving nodes.
380     * @return The list of conflicts involving nodes.
381     * @since 6555
382     */
383    public final Collection<Conflict<? extends OsmPrimitive>> getRelationConflicts() {
384        return Utils.filter(conflicts, RELATION_FILTER_PREDICATE);
385    }
386
387    @Override
388    public int hashCode() {
389        return Objects.hash(conflicts, listeners);
390    }
391
392    @Override
393    public boolean equals(Object obj) {
394        if (this == obj) return true;
395        if (obj == null || getClass() != obj.getClass()) return false;
396        ConflictCollection conflicts1 = (ConflictCollection) obj;
397        return Objects.equals(conflicts, conflicts1.conflicts) &&
398                Objects.equals(listeners, conflicts1.listeners);
399    }
400}