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

Last change on this file since 13818 was 13766, checked in by Don-vip, 6 years ago

API alignment between Relation/RelationData and RelationMember/RelationMemberData: update of IRelation/IRelationMember interfaces

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