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}