001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.data.osm; 003 004 import java.util.ArrayList; 005 import java.util.Arrays; 006 import java.util.Collection; 007 import java.util.HashSet; 008 import java.util.List; 009 import java.util.Set; 010 011 import org.openstreetmap.josm.Main; 012 import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor; 013 import org.openstreetmap.josm.data.osm.visitor.Visitor; 014 import org.openstreetmap.josm.tools.CopyList; 015 016 /** 017 * An relation, having a set of tags and any number (0...n) of members. 018 * 019 * @author Frederik Ramm <frederik@remote.org> 020 */ 021 public final class Relation extends OsmPrimitive implements IRelation { 022 023 private RelationMember[] members = new RelationMember[0]; 024 025 private BBox bbox; 026 027 /** 028 * @return Members of the relation. Changes made in returned list are not mapped 029 * back to the primitive, use setMembers() to modify the members 030 * @since 1925 031 */ 032 public List<RelationMember> getMembers() { 033 return new CopyList<RelationMember>(members); 034 } 035 036 /** 037 * 038 * @param members Can be null, in that case all members are removed 039 * @since 1925 040 */ 041 public void setMembers(List<RelationMember> members) { 042 boolean locked = writeLock(); 043 try { 044 for (RelationMember rm : this.members) { 045 rm.getMember().removeReferrer(this); 046 rm.getMember().clearCachedStyle(); 047 } 048 049 if (members != null) { 050 this.members = members.toArray(new RelationMember[members.size()]); 051 } else { 052 this.members = new RelationMember[0]; 053 } 054 for (RelationMember rm : this.members) { 055 rm.getMember().addReferrer(this); 056 rm.getMember().clearCachedStyle(); 057 } 058 059 fireMembersChanged(); 060 } finally { 061 writeUnlock(locked); 062 } 063 } 064 065 /** 066 * @return number of members 067 */ 068 @Override 069 public int getMembersCount() { 070 return members.length; 071 } 072 073 public RelationMember getMember(int index) { 074 return members[index]; 075 } 076 077 public void addMember(RelationMember member) { 078 boolean locked = writeLock(); 079 try { 080 RelationMember[] newMembers = new RelationMember[members.length + 1]; 081 System.arraycopy(members, 0, newMembers, 0, members.length); 082 newMembers[members.length] = member; 083 members = newMembers; 084 member.getMember().addReferrer(this); 085 member.getMember().clearCachedStyle(); 086 fireMembersChanged(); 087 } finally { 088 writeUnlock(locked); 089 } 090 } 091 092 public void addMember(int index, RelationMember member) { 093 boolean locked = writeLock(); 094 try { 095 RelationMember[] newMembers = new RelationMember[members.length + 1]; 096 System.arraycopy(members, 0, newMembers, 0, index); 097 System.arraycopy(members, index, newMembers, index + 1, members.length - index); 098 newMembers[index] = member; 099 members = newMembers; 100 member.getMember().addReferrer(this); 101 member.getMember().clearCachedStyle(); 102 fireMembersChanged(); 103 } finally { 104 writeUnlock(locked); 105 } 106 } 107 108 /** 109 * Replace member at position specified by index. 110 * @param index 111 * @param member 112 * @return Member that was at the position 113 */ 114 public RelationMember setMember(int index, RelationMember member) { 115 boolean locked = writeLock(); 116 try { 117 RelationMember originalMember = members[index]; 118 members[index] = member; 119 if (originalMember.getMember() != member.getMember()) { 120 member.getMember().addReferrer(this); 121 member.getMember().clearCachedStyle(); 122 originalMember.getMember().removeReferrer(this); 123 originalMember.getMember().clearCachedStyle(); 124 fireMembersChanged(); 125 } 126 return originalMember; 127 } finally { 128 writeUnlock(locked); 129 } 130 } 131 132 /** 133 * Removes member at specified position. 134 * @param index 135 * @return Member that was at the position 136 */ 137 public RelationMember removeMember(int index) { 138 boolean locked = writeLock(); 139 try { 140 List<RelationMember> members = getMembers(); 141 RelationMember result = members.remove(index); 142 setMembers(members); 143 return result; 144 } finally { 145 writeUnlock(locked); 146 } 147 } 148 149 @Override 150 public long getMemberId(int idx) { 151 return members[idx].getUniqueId(); 152 } 153 154 @Override 155 public String getRole(int idx) { 156 return members[idx].getRole(); 157 } 158 159 @Override 160 public OsmPrimitiveType getMemberType(int idx) { 161 return members[idx].getType(); 162 } 163 164 @Override public void visit(Visitor visitor) { 165 visitor.visit(this); 166 } 167 168 @Override public void visit(PrimitiveVisitor visitor) { 169 visitor.visit(this); 170 } 171 172 protected Relation(long id, boolean allowNegative) { 173 super(id, allowNegative); 174 } 175 176 /** 177 * Create a new relation with id 0 178 */ 179 public Relation() { 180 super(0, false); 181 } 182 183 public Relation(Relation clone, boolean clearId) { 184 super(clone.getUniqueId(), true); 185 cloneFrom(clone); 186 if (clearId) { 187 clearOsmId(); 188 } 189 } 190 191 /** 192 * Create an identical clone of the argument (including the id) 193 */ 194 public Relation(Relation clone) { 195 this(clone, false); 196 } 197 198 /** 199 * Creates a new relation for the given id. If the id > 0, the way is marked 200 * as incomplete. 201 * 202 * @param id the id. > 0 required 203 * @throws IllegalArgumentException thrown if id < 0 204 */ 205 public Relation(long id) throws IllegalArgumentException { 206 super(id, false); 207 } 208 209 /** 210 * Creates new relation 211 * @param id 212 * @param version 213 */ 214 public Relation(long id, int version) { 215 super(id, version, false); 216 } 217 218 @Override public void cloneFrom(OsmPrimitive osm) { 219 boolean locked = writeLock(); 220 try { 221 super.cloneFrom(osm); 222 // It's not necessary to clone members as RelationMember class is immutable 223 setMembers(((Relation)osm).getMembers()); 224 } finally { 225 writeUnlock(locked); 226 } 227 } 228 229 @Override public void load(PrimitiveData data) { 230 boolean locked = writeLock(); 231 try { 232 super.load(data); 233 234 RelationData relationData = (RelationData) data; 235 236 List<RelationMember> newMembers = new ArrayList<RelationMember>(); 237 for (RelationMemberData member : relationData.getMembers()) { 238 OsmPrimitive primitive = getDataSet().getPrimitiveById(member); 239 if (primitive == null) 240 throw new AssertionError("Data consistency problem - relation with missing member detected"); 241 newMembers.add(new RelationMember(member.getRole(), primitive)); 242 } 243 setMembers(newMembers); 244 } finally { 245 writeUnlock(locked); 246 } 247 } 248 249 @Override public RelationData save() { 250 RelationData data = new RelationData(); 251 saveCommonAttributes(data); 252 for (RelationMember member:getMembers()) { 253 data.getMembers().add(new RelationMemberData(member.getRole(), member.getMember())); 254 } 255 return data; 256 } 257 258 @Override public String toString() { 259 StringBuilder result = new StringBuilder(); 260 result.append("{Relation id="); 261 result.append(getUniqueId()); 262 result.append(" version="); 263 result.append(getVersion()); 264 result.append(" "); 265 result.append(getFlagsAsString()); 266 result.append(" ["); 267 for (RelationMember rm:getMembers()) { 268 result.append(OsmPrimitiveType.from(rm.getMember())); 269 result.append(" "); 270 result.append(rm.getMember().getUniqueId()); 271 result.append(", "); 272 } 273 result.delete(result.length()-2, result.length()); 274 result.append("]"); 275 result.append("}"); 276 return result.toString(); 277 } 278 279 @Override 280 public boolean hasEqualSemanticAttributes(OsmPrimitive other) { 281 if (other == null || ! (other instanceof Relation) ) 282 return false; 283 if (! super.hasEqualSemanticAttributes(other)) 284 return false; 285 Relation r = (Relation)other; 286 return Arrays.equals(members, r.members); 287 } 288 289 @Override 290 public int compareTo(OsmPrimitive o) { 291 return o instanceof Relation ? Long.valueOf(getUniqueId()).compareTo(o.getUniqueId()) : -1; 292 } 293 294 public RelationMember firstMember() { 295 if (isIncomplete()) return null; 296 297 RelationMember[] members = this.members; 298 return (members.length == 0) ? null : members[0]; 299 } 300 public RelationMember lastMember() { 301 if (isIncomplete()) return null; 302 303 RelationMember[] members = this.members; 304 return (members.length == 0) ? null : members[members.length - 1]; 305 } 306 307 /** 308 * removes all members with member.member == primitive 309 * 310 * @param primitive the primitive to check for 311 */ 312 public void removeMembersFor(OsmPrimitive primitive) { 313 if (primitive == null) 314 return; 315 316 boolean locked = writeLock(); 317 try { 318 List<RelationMember> todelete = new ArrayList<RelationMember>(); 319 for (RelationMember member: members) { 320 if (member.getMember() == primitive) { 321 todelete.add(member); 322 } 323 } 324 List<RelationMember> members = getMembers(); 325 members.removeAll(todelete); 326 setMembers(members); 327 } finally { 328 writeUnlock(locked); 329 } 330 } 331 332 @Override 333 public void setDeleted(boolean deleted) { 334 boolean locked = writeLock(); 335 try { 336 for (RelationMember rm:members) { 337 if (deleted) { 338 rm.getMember().removeReferrer(this); 339 } else { 340 rm.getMember().addReferrer(this); 341 } 342 } 343 super.setDeleted(deleted); 344 } finally { 345 writeUnlock(locked); 346 } 347 } 348 349 /** 350 * removes all members with member.member == primitive 351 * 352 * @param primitives the primitives to check for 353 */ 354 public void removeMembersFor(Collection<OsmPrimitive> primitives) { 355 if (primitives == null || primitives.isEmpty()) 356 return; 357 358 boolean locked = writeLock(); 359 try { 360 ArrayList<RelationMember> todelete = new ArrayList<RelationMember>(); 361 for (RelationMember member: members) { 362 if (primitives.contains(member.getMember())) { 363 todelete.add(member); 364 } 365 } 366 List<RelationMember> members = getMembers(); 367 members.removeAll(todelete); 368 setMembers(members); 369 } finally { 370 writeUnlock(locked); 371 } 372 } 373 374 @Override 375 public String getDisplayName(NameFormatter formatter) { 376 return formatter.format(this); 377 } 378 379 /** 380 * Replies the set of {@link OsmPrimitive}s referred to by at least one 381 * member of this relation 382 * 383 * @return the set of {@link OsmPrimitive}s referred to by at least one 384 * member of this relation 385 */ 386 public Set<OsmPrimitive> getMemberPrimitives() { 387 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>(); 388 RelationMember[] members = this.members; 389 for (RelationMember m: members) { 390 if (m.getMember() != null) { 391 ret.add(m.getMember()); 392 } 393 } 394 return ret; 395 } 396 397 @Override 398 public OsmPrimitiveType getType() { 399 return OsmPrimitiveType.RELATION; 400 } 401 402 @Override 403 public OsmPrimitiveType getDisplayType() { 404 return isMultipolygon() ? OsmPrimitiveType.MULTIPOLYGON 405 : OsmPrimitiveType.RELATION; 406 } 407 408 public boolean isMultipolygon() { 409 return "multipolygon".equals(get("type")) || "boundary".equals(get("type")); 410 } 411 412 @Override 413 public BBox getBBox() { 414 RelationMember[] members = this.members; 415 416 if (members.length == 0) 417 return new BBox(0, 0, 0, 0); 418 if (getDataSet() == null) 419 return calculateBBox(new HashSet<PrimitiveId>()); 420 else { 421 if (bbox == null) { 422 bbox = calculateBBox(new HashSet<PrimitiveId>()); 423 } 424 if (bbox == null) 425 return new BBox(0, 0, 0, 0); // No real members 426 else 427 return new BBox(bbox); 428 } 429 } 430 431 private BBox calculateBBox(Set<PrimitiveId> visitedRelations) { 432 if (visitedRelations.contains(this)) 433 return null; 434 visitedRelations.add(this); 435 436 RelationMember[] members = this.members; 437 if (members.length == 0) 438 return null; 439 else { 440 BBox result = null; 441 for (RelationMember rm:members) { 442 BBox box = rm.isRelation()?rm.getRelation().calculateBBox(visitedRelations):rm.getMember().getBBox(); 443 if (box != null) { 444 if (result == null) { 445 result = box; 446 } else { 447 result.add(box); 448 } 449 } 450 } 451 return result; 452 } 453 } 454 455 @Override 456 public void updatePosition() { 457 bbox = calculateBBox(new HashSet<PrimitiveId>()); 458 } 459 460 @Override 461 public void setDataset(DataSet dataSet) { 462 super.setDataset(dataSet); 463 checkMembers(); 464 bbox = null; // bbox might have changed if relation was in ds, was removed, modified, added back to dataset 465 } 466 467 private void checkMembers() { 468 DataSet dataSet = getDataSet(); 469 if (dataSet != null) { 470 RelationMember[] members = this.members; 471 for (RelationMember rm: members) { 472 if (rm.getMember().getDataSet() != dataSet) 473 throw new DataIntegrityProblemException(String.format("Relation member must be part of the same dataset as relation(%s, %s)", getPrimitiveId(), rm.getMember().getPrimitiveId())); 474 } 475 if (Main.pref.getBoolean("debug.checkDeleteReferenced", true)) { 476 for (RelationMember rm: members) { 477 if (rm.getMember().isDeleted()) 478 throw new DataIntegrityProblemException("Deleted member referenced: " + toString()); 479 } 480 } 481 } 482 } 483 484 private void fireMembersChanged() { 485 checkMembers(); 486 if (getDataSet() != null) { 487 getDataSet().fireRelationMembersChanged(this); 488 } 489 } 490 491 /** 492 * Replies true if at least one child primitive is incomplete 493 * 494 * @return true if at least one child primitive is incomplete 495 */ 496 public boolean hasIncompleteMembers() { 497 RelationMember[] members = this.members; 498 for (RelationMember rm: members) { 499 if (rm.getMember().isIncomplete()) return true; 500 } 501 return false; 502 } 503 504 /** 505 * Replies a collection with the incomplete children this relation 506 * refers to 507 * 508 * @return the incomplete children. Empty collection if no children are incomplete. 509 */ 510 public Collection<OsmPrimitive> getIncompleteMembers() { 511 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>(); 512 RelationMember[] members = this.members; 513 for (RelationMember rm: members) { 514 if (!rm.getMember().isIncomplete()) { 515 continue; 516 } 517 ret.add(rm.getMember()); 518 } 519 return ret; 520 } 521 }