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

Last change on this file since 16445 was 16445, checked in by simon04, 4 years ago

see #19251 - Java 8: use Stream

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