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

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

where applicable, replace System.arraycopy by Arrays.copyOf

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