// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.data.conflict;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.tools.CheckParameterUtil;
import org.openstreetmap.josm.tools.SubclassFilteredCollection;
/**
* This is a collection of {@link Conflict}s. This collection is {@link Iterable}, i.e.
* it can be used in <code>for</code>-loops as follows:
* <pre>
* ConflictCollection conflictCollection = ....
*
* for (Conflict c : conflictCollection) {
* // do something
* }
* </pre>
*
* This collection emits an event when the content of the collection changes. You can register
* and unregister for these events using:
* <ul>
* <li>{@link #addConflictListener(IConflictListener)}</li>
* <li>{@link #removeConflictListener(IConflictListener)}</li>
* </ul>
*/
public class ConflictCollection implements Iterable<Conflict<? extends OsmPrimitive>> {
private final List<Conflict<? extends OsmPrimitive>> conflicts;
private final CopyOnWriteArrayList<IConflictListener> listeners;
/**
* Constructs a new {@code ConflictCollection}.
*/
public ConflictCollection() {
conflicts = new ArrayList<>();
listeners = new CopyOnWriteArrayList<>();
}
/**
* Adds the specified conflict listener, if not already present.
* @param listener The conflict listener to add
*/
public void addConflictListener(IConflictListener listener) {
if (listener != null) {
listeners.addIfAbsent(listener);
}
}
/**
* Removes the specified conflict listener.
* @param listener The conflict listener to remove
*/
public void removeConflictListener(IConflictListener listener) {
listeners.remove(listener);
}
protected void fireConflictAdded() {
for (IConflictListener listener : listeners) {
listener.onConflictsAdded(this);
}
}
protected void fireConflictRemoved() {
for (IConflictListener listener : listeners) {
listener.onConflictsRemoved(this);
}
}
/**
* Adds a conflict to the collection
*
* @param conflict the conflict
* @throws IllegalStateException if this collection already includes a conflict for conflict.getMy()
*/
protected void addConflict(Conflict<?> conflict) {
if (hasConflictForMy(conflict.getMy()))
throw new IllegalStateException(tr("Already registered a conflict for primitive ''{0}''.", conflict.getMy().toString()));
if (!conflicts.contains(conflict)) {
conflicts.add(conflict);
}
}
/**
* Adds a conflict to the collection of conflicts.
*
* @param conflict the conflict to add. Must not be null.
* @throws IllegalArgumentException if conflict is null
* @throws IllegalStateException if this collection already includes a conflict for conflict.getMy()
*/
public void add(Conflict<?> conflict) {
CheckParameterUtil.ensureParameterNotNull(conflict, "conflict");
addConflict(conflict);
fireConflictAdded();
}
/**
* Add the conflicts in <code>otherConflicts</code> to this collection of conflicts
*
* @param otherConflicts the collection of conflicts. Does nothing is conflicts is null.
*/
public void add(Collection<Conflict<?>> otherConflicts) {
if (otherConflicts == null) return;
for (Conflict<?> c : otherConflicts) {
addConflict(c);
}
fireConflictAdded();
}
/**
* Adds a conflict for the pair of {@link OsmPrimitive}s given by <code>my</code> and
* <code>their</code>.
*
* @param my my primitive
* @param their their primitive
*/
public void add(OsmPrimitive my, OsmPrimitive their) {
addConflict(new Conflict<>(my, their));
fireConflictAdded();
}
/**
* removes a conflict from this collection
*
* @param conflict the conflict
*/
public void remove(Conflict<?> conflict) {
conflicts.remove(conflict);
fireConflictRemoved();
}
/**
* removes the conflict registered for {@link OsmPrimitive} <code>my</code> if any
*
* @param my the primitive
*/
public void remove(OsmPrimitive my) {
Iterator<Conflict<?>> it = iterator();
while (it.hasNext()) {
if (it.next().isMatchingMy(my)) {
it.remove();
}
}
fireConflictRemoved();
}
/**
* Replies the conflict for the {@link OsmPrimitive} <code>my</code>, null
* if no such conflict exists.
*
* @param my my primitive
* @return the conflict for the {@link OsmPrimitive} <code>my</code>, null
* if no such conflict exists.
*/
public Conflict<?> getConflictForMy(OsmPrimitive my) {
for (Conflict<?> c : conflicts) {
if (c.isMatchingMy(my))
return c;
}
return null;
}
/**
* Replies the conflict for the {@link OsmPrimitive} <code>their</code>, null
* if no such conflict exists.
*
* @param their their primitive
* @return the conflict for the {@link OsmPrimitive} <code>their</code>, null
* if no such conflict exists.
*/
public Conflict<?> getConflictForTheir(OsmPrimitive their) {
for (Conflict<?> c : conflicts) {
if (c.isMatchingTheir(their))
return c;
}
return null;
}
/**
* Replies true, if this collection includes a conflict for <code>my</code>.
*
* @param my my primitive
* @return true, if this collection includes a conflict for <code>my</code>; false, otherwise
*/
public boolean hasConflictForMy(OsmPrimitive my) {
return getConflictForMy(my) != null;
}
/**
* Replies true, if this collection includes a given conflict
*
* @param c the conflict
* @return true, if this collection includes the conflict; false, otherwise
*/
public boolean hasConflict(Conflict<?> c) {
return hasConflictForMy(c.getMy());
}
/**
* Replies true, if this collection includes a conflict for <code>their</code>.
*
* @param their their primitive
* @return true, if this collection includes a conflict for <code>their</code>; false, otherwise
*/
public boolean hasConflictForTheir(OsmPrimitive their) {
return getConflictForTheir(their) != null;
}
/**
* Removes any conflicts for the {@link OsmPrimitive} <code>my</code>.
*
* @param my the primitive
*/
public void removeForMy(OsmPrimitive my) {
Iterator<Conflict<?>> it = iterator();
while (it.hasNext()) {
if (it.next().isMatchingMy(my)) {
it.remove();
}
}
}
/**
* Removes any conflicts for the {@link OsmPrimitive} <code>their</code>.
*
* @param their the primitive
*/
public void removeForTheir(OsmPrimitive their) {
Iterator<Conflict<?>> it = iterator();
while (it.hasNext()) {
if (it.next().isMatchingTheir(their)) {
it.remove();
}
}
}
/**
* Replies the conflicts as list.
*
* @return the list of conflicts
*/
public List<Conflict<?>> get() {
return conflicts;
}
/**
* Replies the size of the collection
*
* @return the size of the collection
*/
public int size() {
return conflicts.size();
}
/**
* Replies the conflict at position <code>idx</code>
*
* @param idx the index
* @return the conflict at position <code>idx</code>
*/
public Conflict<?> get(int idx) {
return conflicts.get(idx);
}
/**
* Replies the iterator for this collection.
*
* @return the iterator
*/
@Override
public Iterator<Conflict<?>> iterator() {
return conflicts.iterator();
}
/**
* Adds all conflicts from another collection.
* @param other The other collection of conflicts to add
*/
public void add(ConflictCollection other) {
for (Conflict<?> c : other) {
add(c);
}
}
/**
* Replies the set of {@link OsmPrimitive} which participate in the role
* of "my" in the conflicts managed by this collection.
*
* @return the set of {@link OsmPrimitive} which participate in the role
* of "my" in the conflicts managed by this collection.
*/
public Set<OsmPrimitive> getMyConflictParties() {
Set<OsmPrimitive> ret = new HashSet<>();
for (Conflict<?> c: conflicts) {
ret.add(c.getMy());
}
return ret;
}
/**
* Replies the set of {@link OsmPrimitive} which participate in the role
* of "their" in the conflicts managed by this collection.
*
* @return the set of {@link OsmPrimitive} which participate in the role
* of "their" in the conflicts managed by this collection.
*/
public Set<OsmPrimitive> getTheirConflictParties() {
Set<OsmPrimitive> ret = new HashSet<>();
for (Conflict<?> c: conflicts) {
ret.add(c.getTheir());
}
return ret;
}
/**
* Replies true if this collection is empty
*
* @return true, if this collection is empty; false, otherwise
*/
public boolean isEmpty() {
return size() == 0;
}
@Override
public String toString() {
return conflicts.toString();
}
/**
* Returns the list of conflicts involving nodes.
* @return The list of conflicts involving nodes.
* @since 6555
*/
public final Collection<Conflict<? extends OsmPrimitive>> getNodeConflicts() {
return SubclassFilteredCollection.filter(conflicts, c -> c != null && c.getMy() instanceof Node);
}
/**
* Returns the list of conflicts involving nodes.
* @return The list of conflicts involving nodes.
* @since 6555
*/
public final Collection<Conflict<? extends OsmPrimitive>> getWayConflicts() {
return SubclassFilteredCollection.filter(conflicts, c -> c != null && c.getMy() instanceof Way);
}
/**
* Returns the list of conflicts involving nodes.
* @return The list of conflicts involving nodes.
* @since 6555
*/
public final Collection<Conflict<? extends OsmPrimitive>> getRelationConflicts() {
return SubclassFilteredCollection.filter(conflicts, c -> c != null && c.getMy() instanceof Relation);
}
@Override
public int hashCode() {
return Objects.hash(conflicts, listeners);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
ConflictCollection conflicts1 = (ConflictCollection) obj;
return Objects.equals(conflicts, conflicts1.conflicts) &&
Objects.equals(listeners, conflicts1.listeners);
}
}