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

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

fix squid:RedundantThrowsDeclarationCheck + consistent Javadoc for exceptions

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