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        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     * @throws IllegalStateException if this collection already includes a conflict for conflict.getMy()
105     */
106    protected void addConflict(Conflict<?> conflict) {
107        if (hasConflictForMy(conflict.getMy()))
108            throw new IllegalStateException(tr("Already registered a conflict for primitive ''{0}''.", conflict.getMy().toString()));
109        if (!conflicts.contains(conflict)) {
110            conflicts.add(conflict);
111        }
112    }
113
114    /**
115     * Adds a conflict to the collection of conflicts.
116     *
117     * @param conflict the conflict to add. Must not be null.
118     * @throws IllegalArgumentException if conflict is null
119     * @throws IllegalStateException if this collection already includes a conflict for conflict.getMy()
120     */
121    public void add(Conflict<?> conflict) {
122        CheckParameterUtil.ensureParameterNotNull(conflict, "conflict");
123        addConflict(conflict);
124        fireConflictAdded();
125    }
126
127    /**
128     * Add the conflicts in <code>otherConflicts</code> to this collection of conflicts
129     *
130     * @param otherConflicts the collection of conflicts. Does nothing is conflicts is null.
131     */
132    public void add(Collection<Conflict<?>> otherConflicts) {
133        if (otherConflicts == null) return;
134        for (Conflict<?> c : otherConflicts) {
135            addConflict(c);
136        }
137        fireConflictAdded();
138    }
139
140    /**
141     * Adds a conflict for the pair of {@link OsmPrimitive}s given by <code>my</code> and
142     * <code>their</code>.
143     *
144     * @param my  my primitive
145     * @param their their primitive
146     */
147    public void add(OsmPrimitive my, OsmPrimitive their) {
148        addConflict(new Conflict<>(my, their));
149        fireConflictAdded();
150    }
151
152    /**
153     * removes a conflict from this collection
154     *
155     * @param conflict the conflict
156     */
157    public void remove(Conflict<?> conflict) {
158        conflicts.remove(conflict);
159        fireConflictRemoved();
160    }
161
162    /**
163     * removes the conflict registered for {@link OsmPrimitive} <code>my</code> if any
164     *
165     * @param my  the primitive
166     */
167    public void remove(OsmPrimitive my) {
168        Iterator<Conflict<?>> it = iterator();
169        while (it.hasNext()) {
170            if (it.next().isMatchingMy(my)) {
171                it.remove();
172            }
173        }
174        fireConflictRemoved();
175    }
176
177    /**
178     * Replies the conflict for the {@link OsmPrimitive} <code>my</code>, null
179     * if no such conflict exists.
180     *
181     * @param my  my primitive
182     * @return the conflict for the {@link OsmPrimitive} <code>my</code>, null
183     * if no such conflict exists.
184     */
185    public Conflict<?> getConflictForMy(OsmPrimitive my) {
186        for (Conflict<?> c : conflicts) {
187            if (c.isMatchingMy(my))
188                return c;
189        }
190        return null;
191    }
192
193    /**
194     * Replies the conflict for the {@link OsmPrimitive} <code>their</code>, null
195     * if no such conflict exists.
196     *
197     * @param their their primitive
198     * @return the conflict for the {@link OsmPrimitive} <code>their</code>, null
199     * if no such conflict exists.
200     */
201    public Conflict<?> getConflictForTheir(OsmPrimitive their) {
202        for (Conflict<?> c : conflicts) {
203            if (c.isMatchingTheir(their))
204                return c;
205        }
206        return null;
207    }
208
209    /**
210     * Replies true, if this collection includes a conflict for <code>my</code>.
211     *
212     * @param my my primitive
213     * @return true, if this collection includes a conflict for <code>my</code>; false, otherwise
214     */
215    public boolean hasConflictForMy(OsmPrimitive my) {
216        return getConflictForMy(my) != null;
217    }
218
219    /**
220     * Replies true, if this collection includes a given conflict
221     *
222     * @param c the conflict
223     * @return true, if this collection includes the conflict; false, otherwise
224     */
225    public boolean hasConflict(Conflict<?> c) {
226        return hasConflictForMy(c.getMy());
227    }
228
229    /**
230     * Replies true, if this collection includes a conflict for <code>their</code>.
231     *
232     * @param their their primitive
233     * @return true, if this collection includes a conflict for <code>their</code>; false, otherwise
234     */
235    public boolean hasConflictForTheir(OsmPrimitive their) {
236        return getConflictForTheir(their)  != null;
237    }
238
239    /**
240     * Removes any conflicts for the {@link OsmPrimitive} <code>my</code>.
241     *
242     * @param my the primitive
243     */
244    public void removeForMy(OsmPrimitive my) {
245        Iterator<Conflict<?>> it = iterator();
246        while (it.hasNext()) {
247            if (it.next().isMatchingMy(my)) {
248                it.remove();
249            }
250        }
251    }
252
253    /**
254     * Removes any conflicts for the {@link OsmPrimitive} <code>their</code>.
255     *
256     * @param their the primitive
257     */
258    public void removeForTheir(OsmPrimitive their) {
259        Iterator<Conflict<?>> it = iterator();
260        while (it.hasNext()) {
261            if (it.next().isMatchingTheir(their)) {
262                it.remove();
263            }
264        }
265    }
266
267    /**
268     * Replies the conflicts as list.
269     *
270     * @return the list of conflicts
271     */
272    public List<Conflict<?>> get() {
273        return conflicts;
274    }
275
276    /**
277     * Replies the size of the collection
278     *
279     * @return the size of the collection
280     */
281    public int size() {
282        return conflicts.size();
283    }
284
285    /**
286     * Replies the conflict at position <code>idx</code>
287     *
288     * @param idx  the index
289     * @return the conflict at position <code>idx</code>
290     */
291    public Conflict<?> get(int idx) {
292        return conflicts.get(idx);
293    }
294
295    /**
296     * Replies the iterator for this collection.
297     *
298     * @return the iterator
299     */
300    @Override
301    public Iterator<Conflict<?>> iterator() {
302        return conflicts.iterator();
303    }
304
305    /**
306     * Adds all conflicts from another collection.
307     * @param other The other collection of conflicts to add
308     */
309    public void add(ConflictCollection other) {
310        for (Conflict<?> c : other) {
311            add(c);
312        }
313    }
314
315    /**
316     * Replies the set of  {@link OsmPrimitive} which participate in the role
317     * of "my" in the conflicts managed by this collection.
318     *
319     * @return the set of  {@link OsmPrimitive} which participate in the role
320     * of "my" in the conflicts managed by this collection.
321     */
322    public Set<OsmPrimitive> getMyConflictParties() {
323        Set<OsmPrimitive> ret = new HashSet<>();
324        for (Conflict<?> c: conflicts) {
325            ret.add(c.getMy());
326        }
327        return ret;
328    }
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        Set<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
386    @Override
387    public int hashCode() {
388        final int prime = 31;
389        int result = 1;
390        result = prime * result + ((conflicts == null) ? 0 : conflicts.hashCode());
391        result = prime * result + ((listeners == null) ? 0 : listeners.hashCode());
392        return result;
393    }
394
395    @Override
396    public boolean equals(Object obj) {
397        if (this == obj)
398            return true;
399        if (obj == null)
400            return false;
401        if (getClass() != obj.getClass())
402            return false;
403        ConflictCollection other = (ConflictCollection) obj;
404        if (conflicts == null) {
405            if (other.conflicts != null)
406                return false;
407        } else if (!conflicts.equals(other.conflicts))
408            return false;
409        if (listeners == null) {
410            if (other.listeners != null)
411                return false;
412        } else if (!listeners.equals(other.listeners))
413            return false;
414        return true;
415    }
416}