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.OsmPrimitive;
015import org.openstreetmap.josm.tools.CheckParameterUtil;
016
017/**
018 * This is a collection of {@link Conflict}s. This collection is {@link Iterable}, i.e.
019 * it can be used in <code>for</code>-loops as follows:
020 * <pre>
021 *    ConflictCollection conflictCollection = ....
022 *
023 *    for(Conflict c : conflictCollection) {
024 *      // do something
025 *    }
026 * </pre>
027 *
028 * This collection emits an event when the content of the collection changes. You can register
029 * and unregister for these events using:
030 * <ul>
031 *   <li>{@link #addConflictListener(IConflictListener)}</li>
032 *   <li>{@link #removeConflictListener(IConflictListener)}</li>
033 * </ul>
034 */
035public class ConflictCollection implements Iterable<Conflict<? extends OsmPrimitive>>{
036    private final List<Conflict<? extends OsmPrimitive>> conflicts;
037    private CopyOnWriteArrayList<IConflictListener> listeners;
038
039    /**
040     * Constructs a new {@code ConflictCollection}.
041     */
042    public ConflictCollection() {
043        conflicts = new ArrayList<Conflict<? extends OsmPrimitive>>();
044        listeners = new CopyOnWriteArrayList<IConflictListener>();
045    }
046
047    /**
048     * Adds the specified conflict listener, if not already present.
049     * @param listener The conflict listener to add
050     */
051    public void addConflictListener(IConflictListener listener) {
052        if (listener != null) {
053            listeners.addIfAbsent(listener);
054        }
055    }
056
057    /**
058     * Removes the specified conflict listener.
059     * @param listener The conflict listener to remove
060     */
061    public void removeConflictListener(IConflictListener listener) {
062        listeners.remove(listener);
063    }
064
065    protected void fireConflictAdded() {
066        for (IConflictListener listener : listeners) {
067            listener.onConflictsAdded(this);
068        }
069    }
070
071    protected void fireConflictRemoved() {
072        for (IConflictListener listener : listeners) {
073            listener.onConflictsRemoved(this);
074        }
075    }
076
077    /**
078     * Adds a conflict to the collection
079     *
080     * @param conflict the conflict
081     * @exception IllegalStateException thrown, if this collection already includes a
082     * conflict for conflict.getMy()
083     */
084    protected void addConflict(Conflict<?> conflict) throws IllegalStateException {
085        if (hasConflictForMy(conflict.getMy()))
086            throw new IllegalStateException(tr("Already registered a conflict for primitive ''{0}''.", conflict.getMy().toString()));
087        if (!conflicts.contains(conflict)) {
088            conflicts.add(conflict);
089        }
090    }
091
092    /**
093     * Adds a conflict to the collection of conflicts.
094     *
095     * @param conflict the conflict to add. Must not be null.
096     * @throws IllegalArgumentException thrown, if conflict is null
097     * @throws IllegalStateException thrown if this collection already includes a conflict for conflict.getMy()
098     *
099     */
100    public void add(Conflict<?> conflict) throws IllegalStateException {
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        for(Conflict<?> c : otherConflicts) {
114            addConflict(c);
115        }
116        fireConflictAdded();
117    }
118
119    /**
120     * Adds a conflict for the pair of {@link OsmPrimitive}s given by <code>my</code> and
121     * <code>their</code>.
122     *
123     * @param my  my primitive
124     * @param their their primitive
125     */
126    public void add(OsmPrimitive my, OsmPrimitive their) {
127        addConflict(new Conflict<OsmPrimitive>(my, their));
128        fireConflictAdded();
129    }
130
131    /**
132     * removes a conflict from this collection
133     *
134     * @param conflict the conflict
135     */
136    public void remove(Conflict<?> conflict) {
137        conflicts.remove(conflict);
138        fireConflictRemoved();
139    }
140
141    /**
142     * removes the conflict registered for {@link OsmPrimitive} <code>my</code> if any
143     *
144     * @param my  the primitive
145     */
146    public void remove(OsmPrimitive my) {
147        Iterator<Conflict<?>> it = iterator();
148        while(it.hasNext()) {
149            if (it.next().isMatchingMy(my)) {
150                it.remove();
151            }
152        }
153        fireConflictRemoved();
154    }
155
156    /**
157     * Replies the conflict for the {@link OsmPrimitive} <code>my</code>, null
158     * if no such conflict exists.
159     *
160     * @param my  my primitive
161     * @return the conflict for the {@link OsmPrimitive} <code>my</code>, null
162     * if no such conflict exists.
163     */
164    public Conflict<?> getConflictForMy(OsmPrimitive my) {
165        for(Conflict<?> c : conflicts) {
166            if (c.isMatchingMy(my))
167                return c;
168        }
169        return null;
170    }
171    /**
172     * Replies the conflict for the {@link OsmPrimitive} <code>their</code>, null
173     * if no such conflict exists.
174     *
175     * @param their their primitive
176     * @return the conflict for the {@link OsmPrimitive} <code>their</code>, null
177     * if no such conflict exists.
178     */
179    public Conflict<?> getConflictForTheir(OsmPrimitive their) {
180        for(Conflict<?> c : conflicts) {
181            if (c.isMatchingTheir(their))
182                return c;
183        }
184        return null;
185    }
186
187    /**
188     * Replies true, if this collection includes a conflict for <code>my</code>.
189     *
190     * @param my my primitive
191     * @return true, if this collection includes a conflict for <code>my</code>; false, otherwise
192     */
193    public boolean hasConflictForMy(OsmPrimitive my) {
194        return getConflictForMy(my) != null;
195    }
196
197    /**
198     * Replies true, if this collection includes a given conflict
199     *
200     * @param c the conflict
201     * @return true, if this collection includes the conflict; false, otherwise
202     */
203    public boolean hasConflict(Conflict<?> c) {
204        return hasConflictForMy(c.getMy());
205    }
206
207    /**
208     * Replies true, if this collection includes a conflict for <code>their</code>.
209     *
210     * @param their their primitive
211     * @return true, if this collection includes a conflict for <code>their</code>; false, otherwise
212     */
213    public boolean hasConflictForTheir(OsmPrimitive their) {
214        return getConflictForTheir(their)  != null;
215    }
216
217    /**
218     * Removes any conflicts for the {@link OsmPrimitive} <code>my</code>.
219     *
220     * @param my the primitive
221     */
222    public void removeForMy(OsmPrimitive my) {
223        Iterator<Conflict<?>> it = iterator();
224        while(it.hasNext()) {
225            if (it.next().isMatchingMy(my)) {
226                it.remove();
227            }
228        }
229    }
230
231    /**
232     * Removes any conflicts for the {@link OsmPrimitive} <code>their</code>.
233     *
234     * @param their the primitive
235     */
236    public void removeForTheir(OsmPrimitive their) {
237        Iterator<Conflict<?>> it = iterator();
238        while(it.hasNext()) {
239            if (it.next().isMatchingTheir(their)) {
240                it.remove();
241            }
242        }
243    }
244
245    /**
246     * Replies the conflicts as list.
247     *
248     * @return the list of conflicts
249     */
250    public List<Conflict<?>> get() {
251        return conflicts;
252    }
253
254    /**
255     * Replies the size of the collection
256     *
257     * @return the size of the collection
258     */
259    public int size() {
260        return conflicts.size();
261    }
262
263    /**
264     * Replies the conflict at position <code>idx</code>
265     *
266     * @param idx  the index
267     * @return the conflict at position <code>idx</code>
268     */
269    public Conflict<?> get(int idx) {
270        return conflicts.get(idx);
271    }
272
273    /**
274     * Replies the iterator for this collection.
275     *
276     * @return the iterator
277     */
278    @Override
279    public Iterator<Conflict<?>> iterator() {
280        return conflicts.iterator();
281    }
282
283    public void add(ConflictCollection other) {
284        for (Conflict<?> c : other) {
285            add(c);
286        }
287    }
288
289    /**
290     * Replies the set of  {@link OsmPrimitive} which participate in the role
291     * of "my" in the conflicts managed by this collection.
292     *
293     * @return the set of  {@link OsmPrimitive} which participate in the role
294     * of "my" in the conflicts managed by this collection.
295     */
296    public Set<OsmPrimitive> getMyConflictParties() {
297        HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
298        for (Conflict<?> c: conflicts) {
299            ret.add(c.getMy());
300        }
301        return ret;
302    }
303    /**
304     * Replies the set of  {@link OsmPrimitive} which participate in the role
305     * of "their" in the conflicts managed by this collection.
306     *
307     * @return the set of  {@link OsmPrimitive} which participate in the role
308     * of "their" in the conflicts managed by this collection.
309     */
310    public Set<OsmPrimitive> getTheirConflictParties() {
311        HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
312        for (Conflict<?> c: conflicts) {
313            ret.add(c.getTheir());
314        }
315        return ret;
316    }
317
318    /**
319     * Replies true if this collection is empty
320     *
321     * @return true, if this collection is empty; false, otherwise
322     */
323    public boolean isEmpty() {
324        return size() == 0;
325    }
326
327    @Override
328    public String toString() {
329        return conflicts.toString();
330    }
331}