source: josm/trunk/src/org/openstreetmap/josm/data/osm/Relation.java@ 6316

Last change on this file since 6316 was 6316, checked in by Don-vip, 11 years ago

Sonar/FindBugs - Loose coupling

  • Property svn:eol-style set to native
File size: 16.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import java.util.ArrayList;
5import java.util.Arrays;
6import java.util.Collection;
7import java.util.HashSet;
8import java.util.List;
9import java.util.Map;
10import java.util.Set;
11
12import org.openstreetmap.josm.Main;
13import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
14import org.openstreetmap.josm.data.osm.visitor.Visitor;
15import org.openstreetmap.josm.tools.CopyList;
16import org.openstreetmap.josm.tools.Utils;
17
18/**
19 * An relation, having a set of tags and any number (0...n) of members.
20 *
21 * @author Frederik Ramm <frederik@remote.org>
22 */
23public final class Relation extends OsmPrimitive implements IRelation {
24
25 private RelationMember[] members = new RelationMember[0];
26
27 private BBox bbox;
28
29 /**
30 * @return Members of the relation. Changes made in returned list are not mapped
31 * back to the primitive, use setMembers() to modify the members
32 * @since 1925
33 */
34 public List<RelationMember> getMembers() {
35 return new CopyList<RelationMember>(members);
36 }
37
38 /**
39 *
40 * @param members Can be null, in that case all members are removed
41 * @since 1925
42 */
43 public void setMembers(List<RelationMember> members) {
44 boolean locked = writeLock();
45 try {
46 for (RelationMember rm : this.members) {
47 rm.getMember().removeReferrer(this);
48 rm.getMember().clearCachedStyle();
49 }
50
51 if (members != null) {
52 this.members = members.toArray(new RelationMember[members.size()]);
53 } else {
54 this.members = new RelationMember[0];
55 }
56 for (RelationMember rm : this.members) {
57 rm.getMember().addReferrer(this);
58 rm.getMember().clearCachedStyle();
59 }
60
61 fireMembersChanged();
62 } finally {
63 writeUnlock(locked);
64 }
65 }
66
67 /**
68 * @return number of members
69 */
70 @Override
71 public int getMembersCount() {
72 return members.length;
73 }
74
75 public RelationMember getMember(int index) {
76 return members[index];
77 }
78
79 public void addMember(RelationMember member) {
80 boolean locked = writeLock();
81 try {
82 RelationMember[] newMembers = new RelationMember[members.length + 1];
83 System.arraycopy(members, 0, newMembers, 0, members.length);
84 newMembers[members.length] = member;
85 members = newMembers;
86 member.getMember().addReferrer(this);
87 member.getMember().clearCachedStyle();
88 fireMembersChanged();
89 } finally {
90 writeUnlock(locked);
91 }
92 }
93
94 public void addMember(int index, RelationMember member) {
95 boolean locked = writeLock();
96 try {
97 RelationMember[] newMembers = new RelationMember[members.length + 1];
98 System.arraycopy(members, 0, newMembers, 0, index);
99 System.arraycopy(members, index, newMembers, index + 1, members.length - index);
100 newMembers[index] = member;
101 members = newMembers;
102 member.getMember().addReferrer(this);
103 member.getMember().clearCachedStyle();
104 fireMembersChanged();
105 } finally {
106 writeUnlock(locked);
107 }
108 }
109
110 /**
111 * Replace member at position specified by index.
112 * @param index
113 * @param member
114 * @return Member that was at the position
115 */
116 public RelationMember setMember(int index, RelationMember member) {
117 boolean locked = writeLock();
118 try {
119 RelationMember originalMember = members[index];
120 members[index] = member;
121 if (originalMember.getMember() != member.getMember()) {
122 member.getMember().addReferrer(this);
123 member.getMember().clearCachedStyle();
124 originalMember.getMember().removeReferrer(this);
125 originalMember.getMember().clearCachedStyle();
126 fireMembersChanged();
127 }
128 return originalMember;
129 } finally {
130 writeUnlock(locked);
131 }
132 }
133
134 /**
135 * Removes member at specified position.
136 * @param index
137 * @return Member that was at the position
138 */
139 public RelationMember removeMember(int index) {
140 boolean locked = writeLock();
141 try {
142 List<RelationMember> members = getMembers();
143 RelationMember result = members.remove(index);
144 setMembers(members);
145 return result;
146 } finally {
147 writeUnlock(locked);
148 }
149 }
150
151 @Override
152 public long getMemberId(int idx) {
153 return members[idx].getUniqueId();
154 }
155
156 @Override
157 public String getRole(int idx) {
158 return members[idx].getRole();
159 }
160
161 @Override
162 public OsmPrimitiveType getMemberType(int idx) {
163 return members[idx].getType();
164 }
165
166 @Override public void accept(Visitor visitor) {
167 visitor.visit(this);
168 }
169
170 @Override public void accept(PrimitiveVisitor visitor) {
171 visitor.visit(this);
172 }
173
174 protected Relation(long id, boolean allowNegative) {
175 super(id, allowNegative);
176 }
177
178 /**
179 * Create a new relation with id 0
180 */
181 public Relation() {
182 super(0, false);
183 }
184
185 /**
186 * Constructs an identical clone of the argument.
187 * @param clone The relation to clone
188 * @param clearMetadata If {@code true}, clears the OSM id and other metadata as defined by {@link #clearOsmMetadata}. If {@code false}, does nothing
189 */
190 public Relation(Relation clone, boolean clearMetadata) {
191 super(clone.getUniqueId(), true);
192 cloneFrom(clone);
193 if (clearMetadata) {
194 clearOsmMetadata();
195 }
196 }
197
198 /**
199 * Create an identical clone of the argument (including the id)
200 * @param clone The relation to clone, including its id
201 */
202 public Relation(Relation clone) {
203 this(clone, false);
204 }
205
206 /**
207 * Creates a new relation for the given id. If the id > 0, the way is marked
208 * as incomplete.
209 *
210 * @param id the id. > 0 required
211 * @throws IllegalArgumentException thrown if id < 0
212 */
213 public Relation(long id) throws IllegalArgumentException {
214 super(id, false);
215 }
216
217 /**
218 * Creates new relation
219 * @param id
220 * @param version
221 */
222 public Relation(long id, int version) {
223 super(id, version, false);
224 }
225
226 @Override public void cloneFrom(OsmPrimitive osm) {
227 boolean locked = writeLock();
228 try {
229 super.cloneFrom(osm);
230 // It's not necessary to clone members as RelationMember class is immutable
231 setMembers(((Relation)osm).getMembers());
232 } finally {
233 writeUnlock(locked);
234 }
235 }
236
237 @Override public void load(PrimitiveData data) {
238 boolean locked = writeLock();
239 try {
240 super.load(data);
241
242 RelationData relationData = (RelationData) data;
243
244 List<RelationMember> newMembers = new ArrayList<RelationMember>();
245 for (RelationMemberData member : relationData.getMembers()) {
246 OsmPrimitive primitive = getDataSet().getPrimitiveById(member);
247 if (primitive == null)
248 throw new AssertionError("Data consistency problem - relation with missing member detected");
249 newMembers.add(new RelationMember(member.getRole(), primitive));
250 }
251 setMembers(newMembers);
252 } finally {
253 writeUnlock(locked);
254 }
255 }
256
257 @Override public RelationData save() {
258 RelationData data = new RelationData();
259 saveCommonAttributes(data);
260 for (RelationMember member:getMembers()) {
261 data.getMembers().add(new RelationMemberData(member.getRole(), member.getMember()));
262 }
263 return data;
264 }
265
266 @Override public String toString() {
267 StringBuilder result = new StringBuilder();
268 result.append("{Relation id=");
269 result.append(getUniqueId());
270 result.append(" version=");
271 result.append(getVersion());
272 result.append(" ");
273 result.append(getFlagsAsString());
274 result.append(" [");
275 for (RelationMember rm:getMembers()) {
276 result.append(OsmPrimitiveType.from(rm.getMember()));
277 result.append(" ");
278 result.append(rm.getMember().getUniqueId());
279 result.append(", ");
280 }
281 result.delete(result.length()-2, result.length());
282 result.append("]");
283 result.append("}");
284 return result.toString();
285 }
286
287 @Override
288 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
289 if (!(other instanceof Relation))
290 return false;
291 if (! super.hasEqualSemanticAttributes(other))
292 return false;
293 Relation r = (Relation)other;
294 return Arrays.equals(members, r.members);
295 }
296
297 @Override
298 public int compareTo(OsmPrimitive o) {
299 return o instanceof Relation ? Long.valueOf(getUniqueId()).compareTo(o.getUniqueId()) : -1;
300 }
301
302 public RelationMember firstMember() {
303 if (isIncomplete()) return null;
304
305 RelationMember[] members = this.members;
306 return (members.length == 0) ? null : members[0];
307 }
308 public RelationMember lastMember() {
309 if (isIncomplete()) return null;
310
311 RelationMember[] members = this.members;
312 return (members.length == 0) ? null : members[members.length - 1];
313 }
314
315 /**
316 * removes all members with member.member == primitive
317 *
318 * @param primitive the primitive to check for
319 */
320 public void removeMembersFor(OsmPrimitive primitive) {
321 if (primitive == null)
322 return;
323
324 boolean locked = writeLock();
325 try {
326 List<RelationMember> todelete = new ArrayList<RelationMember>();
327 for (RelationMember member: members) {
328 if (member.getMember() == primitive) {
329 todelete.add(member);
330 }
331 }
332 List<RelationMember> members = getMembers();
333 members.removeAll(todelete);
334 setMembers(members);
335 } finally {
336 writeUnlock(locked);
337 }
338 }
339
340 @Override
341 public void setDeleted(boolean deleted) {
342 boolean locked = writeLock();
343 try {
344 for (RelationMember rm:members) {
345 if (deleted) {
346 rm.getMember().removeReferrer(this);
347 } else {
348 rm.getMember().addReferrer(this);
349 }
350 }
351 super.setDeleted(deleted);
352 } finally {
353 writeUnlock(locked);
354 }
355 }
356
357 /**
358 * removes all members with member.member == primitive
359 *
360 * @param primitives the primitives to check for
361 * @since 5613
362 */
363 public void removeMembersFor(Collection<? extends OsmPrimitive> primitives) {
364 if (primitives == null || primitives.isEmpty())
365 return;
366
367 boolean locked = writeLock();
368 try {
369 List<RelationMember> todelete = new ArrayList<RelationMember>();
370 for (RelationMember member: members) {
371 if (primitives.contains(member.getMember())) {
372 todelete.add(member);
373 }
374 }
375 List<RelationMember> members = getMembers();
376 members.removeAll(todelete);
377 setMembers(members);
378 } finally {
379 writeUnlock(locked);
380 }
381 }
382
383 @Override
384 public String getDisplayName(NameFormatter formatter) {
385 return formatter.format(this);
386 }
387
388 /**
389 * Replies the set of {@link OsmPrimitive}s referred to by at least one
390 * member of this relation
391 *
392 * @return the set of {@link OsmPrimitive}s referred to by at least one
393 * member of this relation
394 */
395 public Set<OsmPrimitive> getMemberPrimitives() {
396 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
397 RelationMember[] members = this.members;
398 for (RelationMember m: members) {
399 if (m.getMember() != null) {
400 ret.add(m.getMember());
401 }
402 }
403 return ret;
404 }
405
406 public List<OsmPrimitive> getMemberPrimitivesList() {
407 return Utils.transform(getMembers(), new Utils.Function<RelationMember, OsmPrimitive>() {
408 @Override
409 public OsmPrimitive apply(RelationMember x) {
410 return x.getMember();
411 }
412 });
413 }
414
415 @Override
416 public OsmPrimitiveType getType() {
417 return OsmPrimitiveType.RELATION;
418 }
419
420 @Override
421 public OsmPrimitiveType getDisplayType() {
422 return isMultipolygon() ? OsmPrimitiveType.MULTIPOLYGON
423 : OsmPrimitiveType.RELATION;
424 }
425
426 public boolean isMultipolygon() {
427 return "multipolygon".equals(get("type")) || "boundary".equals(get("type"));
428 }
429
430 @Override
431 public BBox getBBox() {
432 RelationMember[] members = this.members;
433
434 if (members.length == 0)
435 return new BBox(0, 0, 0, 0);
436 if (getDataSet() == null)
437 return calculateBBox(new HashSet<PrimitiveId>());
438 else {
439 if (bbox == null) {
440 bbox = calculateBBox(new HashSet<PrimitiveId>());
441 }
442 if (bbox == null)
443 return new BBox(0, 0, 0, 0); // No real members
444 else
445 return new BBox(bbox);
446 }
447 }
448
449 private BBox calculateBBox(Set<PrimitiveId> visitedRelations) {
450 if (visitedRelations.contains(this))
451 return null;
452 visitedRelations.add(this);
453
454 RelationMember[] members = this.members;
455 if (members.length == 0)
456 return null;
457 else {
458 BBox result = null;
459 for (RelationMember rm:members) {
460 BBox box = rm.isRelation()?rm.getRelation().calculateBBox(visitedRelations):rm.getMember().getBBox();
461 if (box != null) {
462 if (result == null) {
463 result = box;
464 } else {
465 result.add(box);
466 }
467 }
468 }
469 return result;
470 }
471 }
472
473 @Override
474 public void updatePosition() {
475 bbox = calculateBBox(new HashSet<PrimitiveId>());
476 }
477
478 @Override
479 public void setDataset(DataSet dataSet) {
480 super.setDataset(dataSet);
481 checkMembers();
482 bbox = null; // bbox might have changed if relation was in ds, was removed, modified, added back to dataset
483 }
484
485 private void checkMembers() {
486 DataSet dataSet = getDataSet();
487 if (dataSet != null) {
488 RelationMember[] members = this.members;
489 for (RelationMember rm: members) {
490 if (rm.getMember().getDataSet() != dataSet)
491 throw new DataIntegrityProblemException(String.format("Relation member must be part of the same dataset as relation(%s, %s)", getPrimitiveId(), rm.getMember().getPrimitiveId()));
492 }
493 if (Main.pref.getBoolean("debug.checkDeleteReferenced", true)) {
494 for (RelationMember rm: members) {
495 if (rm.getMember().isDeleted())
496 throw new DataIntegrityProblemException("Deleted member referenced: " + toString());
497 }
498 }
499 }
500 }
501
502 private void fireMembersChanged() {
503 checkMembers();
504 if (getDataSet() != null) {
505 getDataSet().fireRelationMembersChanged(this);
506 }
507 }
508
509 /**
510 * Replies true if at least one child primitive is incomplete
511 *
512 * @return true if at least one child primitive is incomplete
513 */
514 public boolean hasIncompleteMembers() {
515 RelationMember[] members = this.members;
516 for (RelationMember rm: members) {
517 if (rm.getMember().isIncomplete()) return true;
518 }
519 return false;
520 }
521
522 /**
523 * Replies a collection with the incomplete children this relation
524 * refers to
525 *
526 * @return the incomplete children. Empty collection if no children are incomplete.
527 */
528 public Collection<OsmPrimitive> getIncompleteMembers() {
529 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
530 RelationMember[] members = this.members;
531 for (RelationMember rm: members) {
532 if (!rm.getMember().isIncomplete()) {
533 continue;
534 }
535 ret.add(rm.getMember());
536 }
537 return ret;
538 }
539
540 @Override
541 protected void keysChangedImpl(Map<String, String> originalKeys) {
542 super.keysChangedImpl(originalKeys);
543 // fix #8346 - Clear style cache for multipolygon members after a tag change
544 if (isMultipolygon()) {
545 for (OsmPrimitive member : getMemberPrimitives()) {
546 member.clearCachedStyle();
547 }
548 }
549 }
550}
Note: See TracBrowser for help on using the repository browser.