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

Last change on this file since 12549 was 11553, checked in by Don-vip, 7 years ago

refactor handling of null values - use Java 8 Optional where possible

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