package org.openstreetmap.josm.data.osm;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.osm.visitor.Visitor;
import org.openstreetmap.josm.tools.CopyList;
/**
* An relation, having a set of tags and any number (0...n) of members.
*
* @author Frederik Ramm <frederik@remote.org>
*/
public final class Relation extends OsmPrimitive {
private final List<RelationMember> members = new ArrayList<RelationMember>();
private BBox bbox;
/**
* @return Members of the relation. Changes made in returned list are not mapped
* back to the primitive, use setMembers() to modify the members
* @since 1925
*/
public List<RelationMember> getMembers() {
return new CopyList<RelationMember>(members.toArray(new RelationMember[members.size()]));
}
/**
*
* @param members Can be null, in that case all members are removed
* @since 1925
*/
public void setMembers(List<RelationMember> members) {
for (RelationMember rm:this.members) {
rm.getMember().removeReferrer(this);
}
this.members.clear();
if (members != null) {
this.members.addAll(members);
}
for (RelationMember rm:this.members) {
rm.getMember().addReferrer(this);
}
fireMembersChanged();
}
/**
*
* @since 1926
*/
public int getMembersCount() {
return members.size();
}
/**
*
* @param index
* @return
* @since 1926
*/
public RelationMember getMember(int index) {
return members.get(index);
}
/**
*
* @param member
* @since 1951
*/
public void addMember(RelationMember member) {
members.add(member);
member.getMember().addReferrer(this);
fireMembersChanged();
}
/**
*
* @param index
* @param member
* @since 1951
*/
public void addMember(int index, RelationMember member) {
members.add(index, member);
member.getMember().addReferrer(this);
fireMembersChanged();
}
/**
* Replace member at position specified by index.
* @param index
* @param member
* @return Member that was at the position
* @since 1951
*/
public RelationMember setMember(int index, RelationMember member) {
RelationMember result = members.set(index, member);
if (result.getMember() != member.getMember()) {
member.getMember().addReferrer(this);
result.getMember().removeReferrer(this);
fireMembersChanged();
}
return result;
}
/**
* Removes member at specified position.
* @param index
* @return Member that was at the position
* @since 1951
*/
public RelationMember removeMember(int index) {
RelationMember result = members.remove(index);
for (RelationMember rm:members) {
// Do not remove referrer if this primitive is used in relation twice
if (rm.getMember() == result.getMember())
return result;
}
result.getMember().removeReferrer(this);
fireMembersChanged();
return result;
}
@Override public void visit(Visitor visitor) {
visitor.visit(this);
}
protected Relation(long id, boolean allowNegative) {
super(id, allowNegative);
}
/**
* Create a new relation with id 0
*/
public Relation() {
super(0, false);
}
public Relation(Relation clone, boolean clearId) {
super(clone.getUniqueId(), true);
cloneFrom(clone);
if (clearId) {
clearOsmId();
}
}
/**
* Create an identical clone of the argument (including the id)
*/
public Relation(Relation clone) {
this(clone, false);
}
/**
* Creates a new relation for the given id. If the id > 0, the way is marked
* as incomplete.
*
* @param id the id. > 0 required
* @throws IllegalArgumentException thrown if id < 0
*/
public Relation(long id) throws IllegalArgumentException {
super(id, false);
}
/**
* Creates new relation
* @param id
* @param version
*/
public Relation(long id, int version) {
super(id, version, false);
}
@Override public void cloneFrom(OsmPrimitive osm) {
super.cloneFrom(osm);
// It's not necessary to clone members as RelationMember class is immutable
setMembers(((Relation)osm).getMembers());
}
@Override public void load(PrimitiveData data) {
super.load(data);
RelationData relationData = (RelationData) data;
List<RelationMember> newMembers = new ArrayList<RelationMember>();
for (RelationMemberData member : relationData.getMembers()) {
OsmPrimitive primitive = getDataSet().getPrimitiveById(member);
if (primitive == null)
throw new AssertionError("Data consistency problem - relation with missing member detected");
newMembers.add(new RelationMember(member.getRole(), primitive));
}
setMembers(newMembers);
}
@Override public RelationData save() {
RelationData data = new RelationData();
saveCommonAttributes(data);
for (RelationMember member:getMembers()) {
data.getMembers().add(new RelationMemberData(member.getRole(), member.getMember()));
}
return data;
}
@Override public String toString() {
StringBuilder result = new StringBuilder();
result.append("{Relation id=");
result.append(getUniqueId());
result.append(" version=");
result.append(getVersion());
result.append(" ");
result.append(getFlagsAsString());
result.append(" [");
for (RelationMember rm:getMembers()) {
result.append(OsmPrimitiveType.from(rm.getMember()));
result.append(" ");
result.append(rm.getMember().getUniqueId());
result.append(", ");
}
result.delete(result.length()-2, result.length());
result.append("]");
result.append("}");
return result.toString();
}
@Override
public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
if (other == null || ! (other instanceof Relation) )
return false;
if (! super.hasEqualSemanticAttributes(other))
return false;
Relation r = (Relation)other;
return members.equals(r.members);
}
public int compareTo(OsmPrimitive o) {
return o instanceof Relation ? Long.valueOf(getUniqueId()).compareTo(o.getUniqueId()) : -1;
}
public RelationMember firstMember() {
if (isIncomplete()) return null;
return (members.size() == 0) ? null : members.get(0);
}
public RelationMember lastMember() {
if (isIncomplete()) return null;
return (members.size() == 0) ? null : members.get(members.size() -1);
}
/**
* removes all members with member.member == primitive
*
* @param primitive the primitive to check for
*/
public void removeMembersFor(OsmPrimitive primitive) {
if (primitive == null)
return;
ArrayList<RelationMember> todelete = new ArrayList<RelationMember>();
for (RelationMember member: members) {
if (member.getMember() == primitive) {
todelete.add(member);
}
}
primitive.removeReferrer(this);
members.removeAll(todelete);
fireMembersChanged();
}
@Override
public void setDeleted(boolean deleted) {
for (RelationMember rm:members) {
if (deleted) {
rm.getMember().removeReferrer(this);
} else {
rm.getMember().addReferrer(this);
}
}
super.setDeleted(deleted);
}
/**
* removes all members with member.member == primitive
*
* @param primitives the primitives to check for
*/
public void removeMembersFor(Collection<OsmPrimitive> primitives) {
if (primitives == null || primitives.isEmpty())
return;
ArrayList<RelationMember> todelete = new ArrayList<RelationMember>();
for (RelationMember member: members) {
if (primitives.contains(member.getMember())) {
todelete.add(member);
}
}
members.removeAll(todelete);
for (OsmPrimitive primitive:primitives) {
primitive.removeReferrer(this);
}
fireMembersChanged();
}
@Override
public String getDisplayName(NameFormatter formatter) {
return formatter.format(this);
}
/**
* Replies the set of {@see OsmPrimitive}s referred to by at least one
* member of this relation
*
* @return the set of {@see OsmPrimitive}s referred to by at least one
* member of this relation
*/
public Set<OsmPrimitive> getMemberPrimitives() {
HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
for (RelationMember m: members) {
if (m.getMember() != null) {
ret.add(m.getMember());
}
}
return ret;
}
public OsmPrimitiveType getType() {
return OsmPrimitiveType.RELATION;
}
@Override
public BBox getBBox() {
if (members.isEmpty())
return new BBox(0, 0, 0, 0);
if (getDataSet() == null)
return calculateBBox(new HashSet<PrimitiveId>());
else {
if (bbox == null) {
bbox = calculateBBox(new HashSet<PrimitiveId>());
}
if (bbox == null)
return new BBox(0, 0, 0, 0); // No real members
else
return new BBox(bbox);
}
}
private BBox calculateBBox(Set<PrimitiveId> visitedRelations) {
if (visitedRelations.contains(this))
return null;
visitedRelations.add(this);
if (members.isEmpty())
return null;
else {
BBox result = null;
for (RelationMember rm:members) {
BBox box = rm.isRelation()?rm.getRelation().calculateBBox(visitedRelations):rm.getMember().getBBox();
if (box != null) {
if (result == null) {
result = box;
} else {
result.add(box);
}
}
}
return result;
}
}
@Override
public void updatePosition() {
bbox = calculateBBox(new HashSet<PrimitiveId>());
}
@Override
public void setDataset(DataSet dataSet) {
super.setDataset(dataSet);
checkMembers();
bbox = null; // bbox might have changed if relation was in ds, was removed, modified, added back to dataset
}
private void checkMembers() {
DataSet dataSet = getDataSet();
if (dataSet != null) {
for (RelationMember rm: members) {
if (rm.getMember().getDataSet() != dataSet)
throw new DataIntegrityProblemException(String.format("Relation member must be part of the same dataset as relation(%s, %s)", getPrimitiveId(), rm.getMember().getPrimitiveId()));
}
if (Main.pref.getBoolean("debug.checkDeleteReferenced", true)) {
for (RelationMember rm: members) {
if (rm.getMember().isDeleted())
throw new DataIntegrityProblemException("Deleted member referenced: " + toString());
}
}
}
}
private void fireMembersChanged() {
checkMembers();
if (getDataSet() != null) {
getDataSet().fireRelationMembersChanged(this);
}
}
/**
* Replies true if at least one child primitive is incomplete
*
* @return true if at least one child primitive is incomplete
*/
public boolean hasIncompleteMembers() {
for (RelationMember rm: members) {
if (rm.getMember().isIncomplete()) return true;
}
return false;
}
/**
* Replies a collection with the incomplete children this relation
* refers to
*
* @return the incomplete children. Empty collection if no children are incomplete.
*/
public Collection<OsmPrimitive> getIncompleteMembers() {
Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
for (RelationMember rm: members) {
if (!rm.getMember().isIncomplete()) {
continue;
}
ret.add(rm.getMember());
}
return ret;
}
}