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

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

fix #21041 - Tagging preset validation: clone properly primitives children for nominal execution of validation tests

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