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

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

checkstyle: enable relevant whitespace checks and fix them

  • Property svn:eol-style set to native
File size: 17.3 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 index (positive integer)
112 * @param member relation member to set
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 index (positive integer)
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}.
190 * If {@code false}, does nothing
191 */
192 public Relation(Relation clone, boolean clearMetadata) {
193 super(clone.getUniqueId(), true);
194 cloneFrom(clone);
195 if (clearMetadata) {
196 clearOsmMetadata();
197 }
198 }
199
200 /**
201 * Create an identical clone of the argument (including the id)
202 * @param clone The relation to clone, including its id
203 */
204 public Relation(Relation clone) {
205 this(clone, false);
206 }
207
208 /**
209 * Creates a new relation for the given id. If the id &gt; 0, the way is marked
210 * as incomplete.
211 *
212 * @param id the id. &gt; 0 required
213 * @throws IllegalArgumentException if id &lt; 0
214 */
215 public Relation(long id) {
216 super(id, false);
217 }
218
219 /**
220 * Creates new relation
221 * @param id the id
222 * @param version version number (positive integer)
223 */
224 public Relation(long id, int version) {
225 super(id, version, false);
226 }
227
228 @Override
229 public void cloneFrom(OsmPrimitive osm) {
230 boolean locked = writeLock();
231 try {
232 super.cloneFrom(osm);
233 // It's not necessary to clone members as RelationMember class is immutable
234 setMembers(((Relation) osm).getMembers());
235 } finally {
236 writeUnlock(locked);
237 }
238 }
239
240 @Override
241 public void load(PrimitiveData data) {
242 boolean locked = writeLock();
243 try {
244 super.load(data);
245
246 RelationData relationData = (RelationData) data;
247
248 List<RelationMember> newMembers = new ArrayList<>();
249 for (RelationMemberData member : relationData.getMembers()) {
250 OsmPrimitive primitive = getDataSet().getPrimitiveById(member);
251 if (primitive == null)
252 throw new AssertionError("Data consistency problem - relation with missing member detected");
253 newMembers.add(new RelationMember(member.getRole(), primitive));
254 }
255 setMembers(newMembers);
256 } finally {
257 writeUnlock(locked);
258 }
259 }
260
261 @Override public RelationData save() {
262 RelationData data = new RelationData();
263 saveCommonAttributes(data);
264 for (RelationMember member:getMembers()) {
265 data.getMembers().add(new RelationMemberData(member.getRole(), member.getMember()));
266 }
267 return data;
268 }
269
270 @Override
271 public String toString() {
272 StringBuilder result = new StringBuilder();
273 result.append("{Relation id=")
274 .append(getUniqueId())
275 .append(" version=")
276 .append(getVersion())
277 .append(' ')
278 .append(getFlagsAsString())
279 .append(" [");
280 for (RelationMember rm:getMembers()) {
281 result.append(OsmPrimitiveType.from(rm.getMember()))
282 .append(' ')
283 .append(rm.getMember().getUniqueId())
284 .append(", ");
285 }
286 result.delete(result.length()-2, result.length())
287 .append("]}");
288 return result.toString();
289 }
290
291 @Override
292 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
293 if (!(other instanceof Relation))
294 return false;
295 if (!super.hasEqualSemanticAttributes(other))
296 return false;
297 Relation r = (Relation) other;
298 return Arrays.equals(members, r.members);
299 }
300
301 @Override
302 public int compareTo(OsmPrimitive o) {
303 return o instanceof Relation ? Long.compare(getUniqueId(), o.getUniqueId()) : -1;
304 }
305
306 /**
307 * Returns the first member.
308 * @return first member, or {@code null}
309 */
310 public RelationMember firstMember() {
311 return (isIncomplete() || members.length == 0) ? null : members[0];
312 }
313
314 /**
315 * Returns the last member.
316 * @return last member, or {@code null}
317 */
318 public RelationMember lastMember() {
319 return (isIncomplete() || members.length == 0) ? null : members[members.length - 1];
320 }
321
322 /**
323 * removes all members with member.member == primitive
324 *
325 * @param primitive the primitive to check for
326 */
327 public void removeMembersFor(OsmPrimitive primitive) {
328 removeMembersFor(Collections.singleton(primitive));
329 }
330
331 @Override
332 public void setDeleted(boolean deleted) {
333 boolean locked = writeLock();
334 try {
335 for (RelationMember rm:members) {
336 if (deleted) {
337 rm.getMember().removeReferrer(this);
338 } else {
339 rm.getMember().addReferrer(this);
340 }
341 }
342 super.setDeleted(deleted);
343 } finally {
344 writeUnlock(locked);
345 }
346 }
347
348 /**
349 * Obtains all members with member.member == primitive
350 * @param primitives the primitives to check for
351 */
352 public Collection<RelationMember> getMembersFor(final Collection<? extends OsmPrimitive> primitives) {
353 return Utils.filter(getMembers(), new Predicate<RelationMember>() {
354 @Override
355 public boolean evaluate(RelationMember member) {
356 return primitives.contains(member.getMember());
357 }
358 });
359 }
360
361 /**
362 * removes all members with member.member == primitive
363 *
364 * @param primitives the primitives to check for
365 * @since 5613
366 */
367 public void removeMembersFor(Collection<? extends OsmPrimitive> primitives) {
368 if (primitives == null || primitives.isEmpty())
369 return;
370
371 boolean locked = writeLock();
372 try {
373 List<RelationMember> members = getMembers();
374 members.removeAll(getMembersFor(primitives));
375 setMembers(members);
376 } finally {
377 writeUnlock(locked);
378 }
379 }
380
381 @Override
382 public String getDisplayName(NameFormatter formatter) {
383 return formatter.format(this);
384 }
385
386 /**
387 * Replies the set of {@link OsmPrimitive}s referred to by at least one
388 * member of this relation
389 *
390 * @return the set of {@link OsmPrimitive}s referred to by at least one
391 * member of this relation
392 */
393 public Set<OsmPrimitive> getMemberPrimitives() {
394 Set<OsmPrimitive> ret = new HashSet<>();
395 RelationMember[] members = this.members;
396 for (RelationMember m: members) {
397 if (m.getMember() != null) {
398 ret.add(m.getMember());
399 }
400 }
401 return ret;
402 }
403
404 public <T extends OsmPrimitive> Collection<T> getMemberPrimitives(Class<T> tClass) {
405 return Utils.filteredCollection(getMemberPrimitives(), tClass);
406 }
407
408 public List<OsmPrimitive> getMemberPrimitivesList() {
409 return Utils.transform(getMembers(), new Utils.Function<RelationMember, OsmPrimitive>() {
410 @Override
411 public OsmPrimitive apply(RelationMember x) {
412 return x.getMember();
413 }
414 });
415 }
416
417 @Override
418 public OsmPrimitiveType getType() {
419 return OsmPrimitiveType.RELATION;
420 }
421
422 @Override
423 public OsmPrimitiveType getDisplayType() {
424 return isMultipolygon() ? OsmPrimitiveType.MULTIPOLYGON
425 : OsmPrimitiveType.RELATION;
426 }
427
428 public boolean isMultipolygon() {
429 return "multipolygon".equals(get("type")) || "boundary".equals(get("type"));
430 }
431
432 @Override
433 public BBox getBBox() {
434 RelationMember[] members = this.members;
435
436 if (members.length == 0)
437 return new BBox(0, 0, 0, 0);
438 if (getDataSet() == null)
439 return calculateBBox(new HashSet<PrimitiveId>());
440 else {
441 if (bbox == null) {
442 bbox = calculateBBox(new HashSet<PrimitiveId>());
443 }
444 if (bbox == null)
445 return new BBox(0, 0, 0, 0); // No real members
446 else
447 return new BBox(bbox);
448 }
449 }
450
451 private BBox calculateBBox(Set<PrimitiveId> visitedRelations) {
452 if (visitedRelations.contains(this))
453 return null;
454 visitedRelations.add(this);
455
456 RelationMember[] members = this.members;
457 if (members.length == 0)
458 return null;
459 else {
460 BBox result = null;
461 for (RelationMember rm:members) {
462 BBox box = rm.isRelation() ? rm.getRelation().calculateBBox(visitedRelations) : rm.getMember().getBBox();
463 if (box != null) {
464 if (result == null) {
465 result = box;
466 } else {
467 result.add(box);
468 }
469 }
470 }
471 return result;
472 }
473 }
474
475 @Override
476 public void updatePosition() {
477 bbox = calculateBBox(new HashSet<PrimitiveId>());
478 }
479
480 @Override
481 void setDataset(DataSet dataSet) {
482 super.setDataset(dataSet);
483 checkMembers();
484 bbox = null; // bbox might have changed if relation was in ds, was removed, modified, added back to dataset
485 }
486
487 private void checkMembers() throws DataIntegrityProblemException {
488 DataSet dataSet = getDataSet();
489 if (dataSet != null) {
490 RelationMember[] members = this.members;
491 for (RelationMember rm: members) {
492 if (rm.getMember().getDataSet() != dataSet)
493 throw new DataIntegrityProblemException(
494 String.format("Relation member must be part of the same dataset as relation(%s, %s)",
495 getPrimitiveId(), rm.getMember().getPrimitiveId()));
496 }
497 if (Main.pref.getBoolean("debug.checkDeleteReferenced", true)) {
498 for (RelationMember rm: members) {
499 if (rm.getMember().isDeleted())
500 throw new DataIntegrityProblemException("Deleted member referenced: " + toString());
501 }
502 }
503 }
504 }
505
506 private void fireMembersChanged() throws DataIntegrityProblemException {
507 checkMembers();
508 if (getDataSet() != null) {
509 getDataSet().fireRelationMembersChanged(this);
510 }
511 }
512
513 /**
514 * Determines if at least one child primitive is incomplete.
515 *
516 * @return true if at least one child primitive is incomplete
517 */
518 public boolean hasIncompleteMembers() {
519 RelationMember[] members = this.members;
520 for (RelationMember rm: members) {
521 if (rm.getMember().isIncomplete()) return true;
522 }
523 return false;
524 }
525
526 /**
527 * Replies a collection with the incomplete children this relation refers to.
528 *
529 * @return the incomplete children. Empty collection if no children are incomplete.
530 */
531 public Collection<OsmPrimitive> getIncompleteMembers() {
532 Set<OsmPrimitive> ret = new HashSet<>();
533 RelationMember[] members = this.members;
534 for (RelationMember rm: members) {
535 if (!rm.getMember().isIncomplete()) {
536 continue;
537 }
538 ret.add(rm.getMember());
539 }
540 return ret;
541 }
542
543 @Override
544 protected void keysChangedImpl(Map<String, String> originalKeys) {
545 super.keysChangedImpl(originalKeys);
546 for (OsmPrimitive member : getMemberPrimitives()) {
547 member.clearCachedStyle();
548 }
549 }
550
551 @Override
552 public boolean concernsArea() {
553 return isMultipolygon() && hasAreaTags();
554 }
555
556 @Override
557 public boolean isOutsideDownloadArea() {
558 return false;
559 }
560
561 /**
562 * Returns the set of roles used in this relation.
563 * @return the set of roles used in this relation. Can be empty but never null
564 * @since 7556
565 */
566 public Set<String> getMemberRoles() {
567 Set<String> result = new HashSet<>();
568 for (RelationMember rm : members) {
569 String role = rm.getRole();
570 if (!role.isEmpty()) {
571 result.add(role);
572 }
573 }
574 return result;
575 }
576}
Note: See TracBrowser for help on using the repository browser.