source: josm/trunk/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java@ 11499

Last change on this file since 11499 was 11499, checked in by Klumbumbus, 7 years ago

fix #14264 - displaying oneway direction arrows of aerialways according to wiki, don't display them if oneway=no is set

  • Property svn:eol-style set to native
File size: 47.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.text.MessageFormat;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.Date;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.LinkedHashSet;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Locale;
18import java.util.Map;
19import java.util.Objects;
20import java.util.Set;
21
22import org.openstreetmap.josm.Main;
23import org.openstreetmap.josm.actions.search.SearchCompiler;
24import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
25import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
26import org.openstreetmap.josm.data.osm.visitor.Visitor;
27import org.openstreetmap.josm.gui.mappaint.StyleCache;
28import org.openstreetmap.josm.tools.CheckParameterUtil;
29import org.openstreetmap.josm.tools.Utils;
30import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
31
32/**
33 * The base class for OSM objects ({@link Node}, {@link Way}, {@link Relation}).
34 *
35 * It can be created, deleted and uploaded to the OSM-Server.
36 *
37 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
38 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given
39 * by the server environment and not an extendible data stuff.
40 *
41 * @author imi
42 */
43public abstract class OsmPrimitive extends AbstractPrimitive implements Comparable<OsmPrimitive>, TemplateEngineDataProvider {
44 private static final String SPECIAL_VALUE_ID = "id";
45 private static final String SPECIAL_VALUE_LOCAL_NAME = "localname";
46
47 /**
48 * A tagged way that matches this pattern has a direction.
49 * @see #FLAG_HAS_DIRECTIONS
50 */
51 static volatile Match directionKeys;
52
53 /**
54 * A tagged way that matches this pattern has a direction that is reversed.
55 * <p>
56 * This pattern should be a subset of {@link #directionKeys}
57 * @see #FLAG_DIRECTION_REVERSED
58 */
59 private static volatile Match reversedDirectionKeys;
60
61 static {
62 String reversedDirectionDefault = "oneway=\"-1\"";
63
64 String directionDefault = "oneway? | "+
65 "(aerialway=chair_lift & -oneway=no) | (aerialway=rope_tow & -oneway=no) | (aerialway=magic_carpet & -oneway=no) | (aerialway=zip_line & -oneway=no) | "+
66 "(aerialway=drag_lift & -oneway=no) | (aerialway=t-bar & -oneway=no) | (aerialway=j-bar & -oneway=no) | (aerialway=platter & -oneway=no) | "+
67 "waterway=stream | waterway=river | waterway=ditch | waterway=drain | "+
68 "(\"piste:type\"=downhill & -area=yes) | (\"piste:type\"=sled & -area=yes) | (man_made=\"piste:halfpipe\" & -area=yes) | "+
69 "junction=roundabout | (highway=motorway & -oneway=no & -oneway=reversible) | "+
70 "(highway=motorway_link & -oneway=no & -oneway=reversible)";
71
72 reversedDirectionKeys = compileDirectionKeys("tags.reversed_direction", reversedDirectionDefault);
73 directionKeys = compileDirectionKeys("tags.direction", directionDefault);
74 }
75
76 /**
77 * Replies the sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in
78 * another collection of {@link OsmPrimitive}s. The result collection is a list.
79 *
80 * If <code>list</code> is null, replies an empty list.
81 *
82 * @param <T> type of data (must be one of the {@link OsmPrimitive} types
83 * @param list the original list
84 * @param type the type to filter for
85 * @return the sub-list of OSM primitives of type <code>type</code>
86 */
87 public static <T extends OsmPrimitive> List<T> getFilteredList(Collection<OsmPrimitive> list, Class<T> type) {
88 if (list == null) return Collections.emptyList();
89 List<T> ret = new LinkedList<>();
90 for (OsmPrimitive p: list) {
91 if (type.isInstance(p)) {
92 ret.add(type.cast(p));
93 }
94 }
95 return ret;
96 }
97
98 /**
99 * Replies the sub-collection of {@link OsmPrimitive}s of type <code>type</code> present in
100 * another collection of {@link OsmPrimitive}s. The result collection is a set.
101 *
102 * If <code>list</code> is null, replies an empty set.
103 *
104 * @param <T> type of data (must be one of the {@link OsmPrimitive} types
105 * @param set the original collection
106 * @param type the type to filter for
107 * @return the sub-set of OSM primitives of type <code>type</code>
108 */
109 public static <T extends OsmPrimitive> Set<T> getFilteredSet(Collection<OsmPrimitive> set, Class<T> type) {
110 Set<T> ret = new LinkedHashSet<>();
111 if (set != null) {
112 for (OsmPrimitive p: set) {
113 if (type.isInstance(p)) {
114 ret.add(type.cast(p));
115 }
116 }
117 }
118 return ret;
119 }
120
121 /**
122 * Replies the collection of referring primitives for the primitives in <code>primitives</code>.
123 *
124 * @param primitives the collection of primitives.
125 * @return the collection of referring primitives for the primitives in <code>primitives</code>;
126 * empty set if primitives is null or if there are no referring primitives
127 */
128 public static Set<OsmPrimitive> getReferrer(Collection<? extends OsmPrimitive> primitives) {
129 Set<OsmPrimitive> ret = new HashSet<>();
130 if (primitives == null || primitives.isEmpty()) return ret;
131 for (OsmPrimitive p: primitives) {
132 ret.addAll(p.getReferrers());
133 }
134 return ret;
135 }
136
137 /**
138 * Creates a new primitive for the given id.
139 *
140 * If allowNegativeId is set, provided id can be &lt; 0 and will be set to primitive without any processing.
141 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
142 * positive number.
143 *
144 * @param id the id
145 * @param allowNegativeId {@code true} to allow negative id
146 * @throws IllegalArgumentException if id &lt; 0 and allowNegativeId is false
147 */
148 protected OsmPrimitive(long id, boolean allowNegativeId) {
149 if (allowNegativeId) {
150 this.id = id;
151 } else {
152 if (id < 0)
153 throw new IllegalArgumentException(MessageFormat.format("Expected ID >= 0. Got {0}.", id));
154 else if (id == 0) {
155 this.id = generateUniqueId();
156 } else {
157 this.id = id;
158 }
159
160 }
161 this.version = 0;
162 this.setIncomplete(id > 0);
163 }
164
165 /**
166 * Creates a new primitive for the given id and version.
167 *
168 * If allowNegativeId is set, provided id can be &lt; 0 and will be set to primitive without any processing.
169 * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or
170 * positive number.
171 *
172 * If id is not &gt; 0 version is ignored and set to 0.
173 *
174 * @param id the id
175 * @param version the version (positive integer)
176 * @param allowNegativeId {@code true} to allow negative id
177 * @throws IllegalArgumentException if id &lt; 0 and allowNegativeId is false
178 */
179 protected OsmPrimitive(long id, int version, boolean allowNegativeId) {
180 this(id, allowNegativeId);
181 this.version = id > 0 ? version : 0;
182 setIncomplete(id > 0 && version == 0);
183 }
184
185 /*----------
186 * MAPPAINT
187 *--------*/
188 public StyleCache mappaintStyle;
189 private short mappaintCacheIdx;
190
191 /* This should not be called from outside. Fixing the UI to add relevant
192 get/set functions calling this implicitely is preferred, so we can have
193 transparent cache handling in the future. */
194 public void clearCachedStyle() {
195 mappaintStyle = null;
196 }
197
198 /**
199 * Returns mappaint cache index.
200 * @return mappaint cache index
201 */
202 public final short getMappaintCacheIdx() {
203 return mappaintCacheIdx;
204 }
205
206 /**
207 * Sets the mappaint cache index.
208 * @param mappaintCacheIdx mappaint cache index
209 */
210 public final void setMappaintCacheIdx(short mappaintCacheIdx) {
211 this.mappaintCacheIdx = mappaintCacheIdx;
212 }
213
214 /* end of mappaint data */
215
216 /*---------
217 * DATASET
218 *---------*/
219
220 /** the parent dataset */
221 private DataSet dataSet;
222
223 /**
224 * This method should never ever by called from somewhere else than Dataset.addPrimitive or removePrimitive methods
225 * @param dataSet the parent dataset
226 */
227 void setDataset(DataSet dataSet) {
228 if (this.dataSet != null && dataSet != null && this.dataSet != dataSet)
229 throw new DataIntegrityProblemException("Primitive cannot be included in more than one Dataset");
230 this.dataSet = dataSet;
231 }
232
233 /**
234 *
235 * @return DataSet this primitive is part of.
236 */
237 public DataSet getDataSet() {
238 return dataSet;
239 }
240
241 /**
242 * Throws exception if primitive is not part of the dataset
243 */
244 public void checkDataset() {
245 if (dataSet == null)
246 throw new DataIntegrityProblemException("Primitive must be part of the dataset: " + toString());
247 }
248
249 protected boolean writeLock() {
250 if (dataSet != null) {
251 dataSet.beginUpdate();
252 return true;
253 } else
254 return false;
255 }
256
257 protected void writeUnlock(boolean locked) {
258 if (locked) {
259 // It shouldn't be possible for dataset to become null because
260 // method calling setDataset would need write lock which is owned by this thread
261 dataSet.endUpdate();
262 }
263 }
264
265 /**
266 * Sets the id and the version of this primitive if it is known to the OSM API.
267 *
268 * Since we know the id and its version it can't be incomplete anymore. incomplete
269 * is set to false.
270 *
271 * @param id the id. &gt; 0 required
272 * @param version the version &gt; 0 required
273 * @throws IllegalArgumentException if id &lt;= 0
274 * @throws IllegalArgumentException if version &lt;= 0
275 * @throws DataIntegrityProblemException if id is changed and primitive was already added to the dataset
276 */
277 @Override
278 public void setOsmId(long id, int version) {
279 boolean locked = writeLock();
280 try {
281 if (id <= 0)
282 throw new IllegalArgumentException(tr("ID > 0 expected. Got {0}.", id));
283 if (version <= 0)
284 throw new IllegalArgumentException(tr("Version > 0 expected. Got {0}.", version));
285 if (dataSet != null && id != this.id) {
286 DataSet datasetCopy = dataSet;
287 // Reindex primitive
288 datasetCopy.removePrimitive(this);
289 this.id = id;
290 datasetCopy.addPrimitive(this);
291 }
292 super.setOsmId(id, version);
293 } finally {
294 writeUnlock(locked);
295 }
296 }
297
298 /**
299 * Clears the metadata, including id and version known to the OSM API.
300 * The id is a new unique id. The version, changeset and timestamp are set to 0.
301 * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead
302 *
303 * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@link DataSet}.
304 *
305 * @throws DataIntegrityProblemException If primitive was already added to the dataset
306 * @since 6140
307 */
308 @Override
309 public void clearOsmMetadata() {
310 if (dataSet != null)
311 throw new DataIntegrityProblemException("Method cannot be called after primitive was added to the dataset");
312 super.clearOsmMetadata();
313 }
314
315 @Override
316 public void setUser(User user) {
317 boolean locked = writeLock();
318 try {
319 super.setUser(user);
320 } finally {
321 writeUnlock(locked);
322 }
323 }
324
325 @Override
326 public void setChangesetId(int changesetId) {
327 boolean locked = writeLock();
328 try {
329 int old = this.changesetId;
330 super.setChangesetId(changesetId);
331 if (dataSet != null) {
332 dataSet.fireChangesetIdChanged(this, old, changesetId);
333 }
334 } finally {
335 writeUnlock(locked);
336 }
337 }
338
339 @Override
340 public void setTimestamp(Date timestamp) {
341 boolean locked = writeLock();
342 try {
343 super.setTimestamp(timestamp);
344 } finally {
345 writeUnlock(locked);
346 }
347 }
348
349
350 /* -------
351 /* FLAGS
352 /* ------*/
353
354 private void updateFlagsNoLock(short flag, boolean value) {
355 super.updateFlags(flag, value);
356 }
357
358 @Override
359 protected final void updateFlags(short flag, boolean value) {
360 boolean locked = writeLock();
361 try {
362 updateFlagsNoLock(flag, value);
363 } finally {
364 writeUnlock(locked);
365 }
366 }
367
368 /**
369 * Make the primitive disabled (e.g.&nbsp;if a filter applies).
370 *
371 * To enable the primitive again, use unsetDisabledState.
372 * @param hidden if the primitive should be completely hidden from view or
373 * just shown in gray color.
374 * @return true, any flag has changed; false if you try to set the disabled
375 * state to the value that is already preset
376 */
377 public boolean setDisabledState(boolean hidden) {
378 boolean locked = writeLock();
379 try {
380 int oldFlags = flags;
381 updateFlagsNoLock(FLAG_DISABLED, true);
382 updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, hidden);
383 return oldFlags != flags;
384 } finally {
385 writeUnlock(locked);
386 }
387 }
388
389 /**
390 * Remove the disabled flag from the primitive.
391 * Afterwards, the primitive is displayed normally and can be selected again.
392 * @return {@code true} if a change occurred
393 */
394 public boolean unsetDisabledState() {
395 boolean locked = writeLock();
396 try {
397 int oldFlags = flags;
398 updateFlagsNoLock(FLAG_DISABLED, false);
399 updateFlagsNoLock(FLAG_HIDE_IF_DISABLED, false);
400 return oldFlags != flags;
401 } finally {
402 writeUnlock(locked);
403 }
404 }
405
406 /**
407 * Set binary property used internally by the filter mechanism.
408 * @param isExplicit new "disabled type" flag value
409 */
410 public void setDisabledType(boolean isExplicit) {
411 updateFlags(FLAG_DISABLED_TYPE, isExplicit);
412 }
413
414 /**
415 * Set binary property used internally by the filter mechanism.
416 * @param isExplicit new "hidden type" flag value
417 */
418 public void setHiddenType(boolean isExplicit) {
419 updateFlags(FLAG_HIDDEN_TYPE, isExplicit);
420 }
421
422 /**
423 * Replies true, if this primitive is disabled. (E.g. a filter applies)
424 * @return {@code true} if this object has the "disabled" flag enabled
425 */
426 public boolean isDisabled() {
427 return (flags & FLAG_DISABLED) != 0;
428 }
429
430 /**
431 * Replies true, if this primitive is disabled and marked as completely hidden on the map.
432 * @return {@code true} if this object has both the "disabled" and "hide if disabled" flags enabled
433 */
434 public boolean isDisabledAndHidden() {
435 return ((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0);
436 }
437
438 /**
439 * Get binary property used internally by the filter mechanism.
440 * @return {@code true} if this object has the "hidden type" flag enabled
441 */
442 public boolean getHiddenType() {
443 return (flags & FLAG_HIDDEN_TYPE) != 0;
444 }
445
446 /**
447 * Get binary property used internally by the filter mechanism.
448 * @return {@code true} if this object has the "disabled type" flag enabled
449 */
450 public boolean getDisabledType() {
451 return (flags & FLAG_DISABLED_TYPE) != 0;
452 }
453
454 /**
455 * Determines if this object is selectable.
456 * <p>
457 * A primitive can be selected if all conditions are met:
458 * <ul>
459 * <li>it is drawable
460 * <li>it is not disabled (greyed out) by a filter.
461 * </ul>
462 * @return {@code true} if this object is selectable
463 */
464 public boolean isSelectable() {
465 // not synchronized -> check disabled twice just to be sure we did not have a race condition.
466 return !isDisabled() && isDrawable() && !isDisabled();
467 }
468
469 /**
470 * Determines if this object is drawable.
471 * <p>
472 * A primitive is complete if all conditions are met:
473 * <ul>
474 * <li>type and id is known
475 * <li>tags are known
476 * <li>it is not deleted
477 * <li>it is not hidden by a filter
478 * <li>for nodes: lat/lon are known
479 * <li>for ways: all nodes are known and complete
480 * <li>for relations: all members are known and complete
481 * </ul>
482 * @return {@code true} if this object is drawable
483 */
484 public boolean isDrawable() {
485 return (flags & (FLAG_DELETED + FLAG_INCOMPLETE + FLAG_HIDE_IF_DISABLED)) == 0;
486 }
487
488 @Override
489 public void setModified(boolean modified) {
490 boolean locked = writeLock();
491 try {
492 super.setModified(modified);
493 if (dataSet != null) {
494 dataSet.firePrimitiveFlagsChanged(this);
495 }
496 clearCachedStyle();
497 } finally {
498 writeUnlock(locked);
499 }
500 }
501
502 @Override
503 public void setVisible(boolean visible) {
504 boolean locked = writeLock();
505 try {
506 super.setVisible(visible);
507 clearCachedStyle();
508 } finally {
509 writeUnlock(locked);
510 }
511 }
512
513 @Override
514 public void setDeleted(boolean deleted) {
515 boolean locked = writeLock();
516 try {
517 super.setDeleted(deleted);
518 if (dataSet != null) {
519 if (deleted) {
520 dataSet.firePrimitivesRemoved(Collections.singleton(this), false);
521 } else {
522 dataSet.firePrimitivesAdded(Collections.singleton(this), false);
523 }
524 }
525 clearCachedStyle();
526 } finally {
527 writeUnlock(locked);
528 }
529 }
530
531 @Override
532 protected final void setIncomplete(boolean incomplete) {
533 boolean locked = writeLock();
534 try {
535 if (dataSet != null && incomplete != this.isIncomplete()) {
536 if (incomplete) {
537 dataSet.firePrimitivesRemoved(Collections.singletonList(this), true);
538 } else {
539 dataSet.firePrimitivesAdded(Collections.singletonList(this), true);
540 }
541 }
542 super.setIncomplete(incomplete);
543 } finally {
544 writeUnlock(locked);
545 }
546 }
547
548 /**
549 * Determines whether the primitive is selected
550 * @return whether the primitive is selected
551 * @see DataSet#isSelected(OsmPrimitive)
552 */
553 public boolean isSelected() {
554 return dataSet != null && dataSet.isSelected(this);
555 }
556
557 /**
558 * Determines if this primitive is a member of a selected relation.
559 * @return {@code true} if this primitive is a member of a selected relation, {@code false} otherwise
560 */
561 public boolean isMemberOfSelected() {
562 if (referrers == null)
563 return false;
564 if (referrers instanceof OsmPrimitive)
565 return referrers instanceof Relation && ((OsmPrimitive) referrers).isSelected();
566 for (OsmPrimitive ref : (OsmPrimitive[]) referrers) {
567 if (ref instanceof Relation && ref.isSelected())
568 return true;
569 }
570 return false;
571 }
572
573 /**
574 * Determines if this primitive is an outer member of a selected multipolygon relation.
575 * @return {@code true} if this primitive is an outer member of a selected multipolygon relation, {@code false} otherwise
576 * @since 7621
577 */
578 public boolean isOuterMemberOfSelected() {
579 if (referrers == null)
580 return false;
581 if (referrers instanceof OsmPrimitive) {
582 return isOuterMemberOfMultipolygon((OsmPrimitive) referrers);
583 }
584 for (OsmPrimitive ref : (OsmPrimitive[]) referrers) {
585 if (isOuterMemberOfMultipolygon(ref))
586 return true;
587 }
588 return false;
589 }
590
591 private boolean isOuterMemberOfMultipolygon(OsmPrimitive ref) {
592 if (ref instanceof Relation && ref.isSelected() && ((Relation) ref).isMultipolygon()) {
593 for (RelationMember rm : ((Relation) ref).getMembersFor(Collections.singleton(this))) {
594 if ("outer".equals(rm.getRole())) {
595 return true;
596 }
597 }
598 }
599 return false;
600 }
601
602 /**
603 * Updates the highlight flag for this primitive.
604 * @param highlighted The new highlight flag.
605 */
606 public void setHighlighted(boolean highlighted) {
607 if (isHighlighted() != highlighted) {
608 updateFlags(FLAG_HIGHLIGHTED, highlighted);
609 if (dataSet != null) {
610 dataSet.fireHighlightingChanged();
611 }
612 }
613 }
614
615 /**
616 * Checks if the highlight flag for this primitive was set
617 * @return The highlight flag.
618 */
619 public boolean isHighlighted() {
620 return (flags & FLAG_HIGHLIGHTED) != 0;
621 }
622
623 /*---------------------------------------------------
624 * WORK IN PROGRESS, UNINTERESTING AND DIRECTION KEYS
625 *--------------------------------------------------*/
626
627 private static volatile Collection<String> workinprogress;
628 private static volatile Collection<String> uninteresting;
629 private static volatile Collection<String> discardable;
630
631 /**
632 * Returns a list of "uninteresting" keys that do not make an object
633 * "tagged". Entries that end with ':' are causing a whole namespace to be considered
634 * "uninteresting". Only the first level namespace is considered.
635 * Initialized by isUninterestingKey()
636 * @return The list of uninteresting keys.
637 */
638 public static Collection<String> getUninterestingKeys() {
639 if (uninteresting == null) {
640 List<String> l = new LinkedList<>(Arrays.asList(
641 "source", "source_ref", "source:", "comment",
642 "watch", "watch:", "description", "attribution"));
643 l.addAll(getDiscardableKeys());
644 l.addAll(getWorkInProgressKeys());
645 uninteresting = new HashSet<>(Main.pref.getCollection("tags.uninteresting", l));
646 }
647 return uninteresting;
648 }
649
650 /**
651 * Returns a list of keys which have been deemed uninteresting to the point
652 * that they can be silently removed from data which is being edited.
653 * @return The list of discardable keys.
654 */
655 public static Collection<String> getDiscardableKeys() {
656 if (discardable == null) {
657 discardable = new HashSet<>(Main.pref.getCollection("tags.discardable",
658 Arrays.asList(
659 "created_by",
660 "converted_by",
661 "geobase:datasetName",
662 "geobase:uuid",
663 "KSJ2:ADS",
664 "KSJ2:ARE",
665 "KSJ2:AdminArea",
666 "KSJ2:COP_label",
667 "KSJ2:DFD",
668 "KSJ2:INT",
669 "KSJ2:INT_label",
670 "KSJ2:LOC",
671 "KSJ2:LPN",
672 "KSJ2:OPC",
673 "KSJ2:PubFacAdmin",
674 "KSJ2:RAC",
675 "KSJ2:RAC_label",
676 "KSJ2:RIC",
677 "KSJ2:RIN",
678 "KSJ2:WSC",
679 "KSJ2:coordinate",
680 "KSJ2:curve_id",
681 "KSJ2:curve_type",
682 "KSJ2:filename",
683 "KSJ2:lake_id",
684 "KSJ2:lat",
685 "KSJ2:long",
686 "KSJ2:river_id",
687 "odbl",
688 "odbl:note",
689 "SK53_bulk:load",
690 "sub_sea:type",
691 "tiger:source",
692 "tiger:separated",
693 "tiger:tlid",
694 "tiger:upload_uuid",
695 "yh:LINE_NAME",
696 "yh:LINE_NUM",
697 "yh:STRUCTURE",
698 "yh:TOTYUMONO",
699 "yh:TYPE",
700 "yh:WIDTH",
701 "yh:WIDTH_RANK"
702 )));
703 }
704 return discardable;
705 }
706
707 /**
708 * Returns a list of "work in progress" keys that do not make an object
709 * "tagged" but "annotated".
710 * @return The list of work in progress keys.
711 * @since 5754
712 */
713 public static Collection<String> getWorkInProgressKeys() {
714 if (workinprogress == null) {
715 workinprogress = new HashSet<>(Main.pref.getCollection("tags.workinprogress",
716 Arrays.asList("note", "fixme", "FIXME")));
717 }
718 return workinprogress;
719 }
720
721 /**
722 * Determines if key is considered "uninteresting".
723 * @param key The key to check
724 * @return true if key is considered "uninteresting".
725 */
726 public static boolean isUninterestingKey(String key) {
727 getUninterestingKeys();
728 if (uninteresting.contains(key))
729 return true;
730 int pos = key.indexOf(':');
731 if (pos > 0)
732 return uninteresting.contains(key.substring(0, pos + 1));
733 return false;
734 }
735
736 /**
737 * Returns {@link #getKeys()} for which {@code key} does not fulfill {@link #isUninterestingKey}.
738 * @return A map of interesting tags
739 */
740 public Map<String, String> getInterestingTags() {
741 Map<String, String> result = new HashMap<>();
742 String[] keys = this.keys;
743 if (keys != null) {
744 for (int i = 0; i < keys.length; i += 2) {
745 if (!isUninterestingKey(keys[i])) {
746 result.put(keys[i], keys[i + 1]);
747 }
748 }
749 }
750 return result;
751 }
752
753 private static Match compileDirectionKeys(String prefName, String defaultValue) throws AssertionError {
754 try {
755 return SearchCompiler.compile(Main.pref.get(prefName, defaultValue));
756 } catch (ParseError e) {
757 Main.error(e, "Unable to compile pattern for " + prefName + ", trying default pattern:");
758 }
759
760 try {
761 return SearchCompiler.compile(defaultValue);
762 } catch (ParseError e2) {
763 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage(), e2);
764 }
765 }
766
767 private void updateTagged() {
768 for (String key: keySet()) {
769 // 'area' is not really uninteresting (putting it in that list may have unpredictable side effects)
770 // but it's clearly not enough to consider an object as tagged (see #9261)
771 if (!isUninterestingKey(key) && !"area".equals(key)) {
772 updateFlagsNoLock(FLAG_TAGGED, true);
773 return;
774 }
775 }
776 updateFlagsNoLock(FLAG_TAGGED, false);
777 }
778
779 private void updateAnnotated() {
780 for (String key: keySet()) {
781 if (getWorkInProgressKeys().contains(key)) {
782 updateFlagsNoLock(FLAG_ANNOTATED, true);
783 return;
784 }
785 }
786 updateFlagsNoLock(FLAG_ANNOTATED, false);
787 }
788
789 /**
790 * Determines if this object is considered "tagged". To be "tagged", an object
791 * must have one or more "interesting" tags. "created_by" and "source"
792 * are typically considered "uninteresting" and do not make an object
793 * "tagged".
794 * @return true if this object is considered "tagged"
795 */
796 public boolean isTagged() {
797 return (flags & FLAG_TAGGED) != 0;
798 }
799
800 /**
801 * Determines if this object is considered "annotated". To be "annotated", an object
802 * must have one or more "work in progress" tags, such as "note" or "fixme".
803 * @return true if this object is considered "annotated"
804 * @since 5754
805 */
806 public boolean isAnnotated() {
807 return (flags & FLAG_ANNOTATED) != 0;
808 }
809
810 private void updateDirectionFlags() {
811 boolean hasDirections = false;
812 boolean directionReversed = false;
813 if (reversedDirectionKeys.match(this)) {
814 hasDirections = true;
815 directionReversed = true;
816 }
817 if (directionKeys.match(this)) {
818 hasDirections = true;
819 }
820
821 updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed);
822 updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections);
823 }
824
825 /**
826 * true if this object has direction dependent tags (e.g. oneway)
827 * @return {@code true} if this object has direction dependent tags
828 */
829 public boolean hasDirectionKeys() {
830 return (flags & FLAG_HAS_DIRECTIONS) != 0;
831 }
832
833 /**
834 * true if this object has the "reversed diretion" flag enabled
835 * @return {@code true} if this object has the "reversed diretion" flag enabled
836 */
837 public boolean reversedDirection() {
838 return (flags & FLAG_DIRECTION_REVERSED) != 0;
839 }
840
841 /*------------
842 * Keys handling
843 ------------*/
844
845 @Override
846 public final void setKeys(TagMap keys) {
847 boolean locked = writeLock();
848 try {
849 super.setKeys(keys);
850 } finally {
851 writeUnlock(locked);
852 }
853 }
854
855 @Override
856 public final void setKeys(Map<String, String> keys) {
857 boolean locked = writeLock();
858 try {
859 super.setKeys(keys);
860 } finally {
861 writeUnlock(locked);
862 }
863 }
864
865 @Override
866 public final void put(String key, String value) {
867 boolean locked = writeLock();
868 try {
869 super.put(key, value);
870 } finally {
871 writeUnlock(locked);
872 }
873 }
874
875 @Override
876 public final void remove(String key) {
877 boolean locked = writeLock();
878 try {
879 super.remove(key);
880 } finally {
881 writeUnlock(locked);
882 }
883 }
884
885 @Override
886 public final void removeAll() {
887 boolean locked = writeLock();
888 try {
889 super.removeAll();
890 } finally {
891 writeUnlock(locked);
892 }
893 }
894
895 @Override
896 protected void keysChangedImpl(Map<String, String> originalKeys) {
897 clearCachedStyle();
898 if (dataSet != null) {
899 for (OsmPrimitive ref : getReferrers()) {
900 ref.clearCachedStyle();
901 }
902 }
903 updateDirectionFlags();
904 updateTagged();
905 updateAnnotated();
906 if (dataSet != null) {
907 dataSet.fireTagsChanged(this, originalKeys);
908 }
909 }
910
911 /*------------
912 * Referrers
913 ------------*/
914
915 private Object referrers;
916
917 /**
918 * Add new referrer. If referrer is already included then no action is taken
919 * @param referrer The referrer to add
920 */
921 protected void addReferrer(OsmPrimitive referrer) {
922 if (referrers == null) {
923 referrers = referrer;
924 } else if (referrers instanceof OsmPrimitive) {
925 if (referrers != referrer) {
926 referrers = new OsmPrimitive[] {(OsmPrimitive) referrers, referrer};
927 }
928 } else {
929 for (OsmPrimitive primitive:(OsmPrimitive[]) referrers) {
930 if (primitive == referrer)
931 return;
932 }
933 referrers = Utils.addInArrayCopy((OsmPrimitive[]) referrers, referrer);
934 }
935 }
936
937 /**
938 * Remove referrer. No action is taken if referrer is not registered
939 * @param referrer The referrer to remove
940 */
941 protected void removeReferrer(OsmPrimitive referrer) {
942 if (referrers instanceof OsmPrimitive) {
943 if (referrers == referrer) {
944 referrers = null;
945 }
946 } else if (referrers instanceof OsmPrimitive[]) {
947 OsmPrimitive[] orig = (OsmPrimitive[]) referrers;
948 int idx = -1;
949 for (int i = 0; i < orig.length; i++) {
950 if (orig[i] == referrer) {
951 idx = i;
952 break;
953 }
954 }
955 if (idx == -1)
956 return;
957
958 if (orig.length == 2) {
959 referrers = orig[1-idx]; // idx is either 0 or 1, take the other
960 } else { // downsize the array
961 OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
962 System.arraycopy(orig, 0, smaller, 0, idx);
963 System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
964 referrers = smaller;
965 }
966 }
967 }
968
969 /**
970 * Find primitives that reference this primitive. Returns only primitives that are included in the same
971 * dataset as this primitive. <br>
972 *
973 * For example following code will add wnew as referer to all nodes of existingWay, but this method will
974 * not return wnew because it's not part of the dataset <br>
975 *
976 * <code>Way wnew = new Way(existingWay)</code>
977 *
978 * @param allowWithoutDataset If true, method will return empty list if primitive is not part of the dataset. If false,
979 * exception will be thrown in this case
980 *
981 * @return a collection of all primitives that reference this primitive.
982 */
983 public final List<OsmPrimitive> getReferrers(boolean allowWithoutDataset) {
984 // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
985 // when way is cloned
986
987 if (dataSet == null && allowWithoutDataset)
988 return Collections.emptyList();
989
990 checkDataset();
991 Object referrers = this.referrers;
992 List<OsmPrimitive> result = new ArrayList<>();
993 if (referrers != null) {
994 if (referrers instanceof OsmPrimitive) {
995 OsmPrimitive ref = (OsmPrimitive) referrers;
996 if (ref.dataSet == dataSet) {
997 result.add(ref);
998 }
999 } else {
1000 for (OsmPrimitive o:(OsmPrimitive[]) referrers) {
1001 if (dataSet == o.dataSet) {
1002 result.add(o);
1003 }
1004 }
1005 }
1006 }
1007 return result;
1008 }
1009
1010 public final List<OsmPrimitive> getReferrers() {
1011 return getReferrers(false);
1012 }
1013
1014 /**
1015 * <p>Visits {@code visitor} for all referrers.</p>
1016 *
1017 * @param visitor the visitor. Ignored, if null.
1018 */
1019 public void visitReferrers(Visitor visitor) {
1020 if (visitor == null) return;
1021 if (this.referrers == null)
1022 return;
1023 else if (this.referrers instanceof OsmPrimitive) {
1024 OsmPrimitive ref = (OsmPrimitive) this.referrers;
1025 if (ref.dataSet == dataSet) {
1026 ref.accept(visitor);
1027 }
1028 } else if (this.referrers instanceof OsmPrimitive[]) {
1029 OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers;
1030 for (OsmPrimitive ref: refs) {
1031 if (ref.dataSet == dataSet) {
1032 ref.accept(visitor);
1033 }
1034 }
1035 }
1036 }
1037
1038 /**
1039 Return true, if this primitive is referred by at least n ways
1040 @param n Minimal number of ways to return true. Must be positive
1041 * @return {@code true} if this primitive is referred by at least n ways
1042 */
1043 public final boolean isReferredByWays(int n) {
1044 // Count only referrers that are members of the same dataset (primitive can have some fake references, for example
1045 // when way is cloned
1046 Object referrers = this.referrers;
1047 if (referrers == null) return false;
1048 checkDataset();
1049 if (referrers instanceof OsmPrimitive)
1050 return n <= 1 && referrers instanceof Way && ((OsmPrimitive) referrers).dataSet == dataSet;
1051 else {
1052 int counter = 0;
1053 for (OsmPrimitive o : (OsmPrimitive[]) referrers) {
1054 if (dataSet == o.dataSet && o instanceof Way && ++counter >= n)
1055 return true;
1056 }
1057 return false;
1058 }
1059 }
1060
1061 /*-----------------
1062 * OTHER METHODS
1063 *----------------*/
1064
1065 /**
1066 * Implementation of the visitor scheme. Subclasses have to call the correct
1067 * visitor function.
1068 * @param visitor The visitor from which the visit() function must be called.
1069 */
1070 public abstract void accept(Visitor visitor);
1071
1072 /**
1073 * Get and write all attributes from the parameter. Does not fire any listener, so
1074 * use this only in the data initializing phase
1075 * @param other other primitive
1076 */
1077 public void cloneFrom(OsmPrimitive other) {
1078 // write lock is provided by subclasses
1079 if (id != other.id && dataSet != null)
1080 throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
1081
1082 super.cloneFrom(other);
1083 clearCachedStyle();
1084 }
1085
1086 /**
1087 * Merges the technical and semantical attributes from <code>other</code> onto this.
1088 *
1089 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
1090 * have an assigned OSM id, the IDs have to be the same.
1091 *
1092 * @param other the other primitive. Must not be null.
1093 * @throws IllegalArgumentException if other is null.
1094 * @throws DataIntegrityProblemException if either this is new and other is not, or other is new and this is not
1095 * @throws DataIntegrityProblemException if other isn't new and other.getId() != this.getId()
1096 */
1097 public void mergeFrom(OsmPrimitive other) {
1098 boolean locked = writeLock();
1099 try {
1100 CheckParameterUtil.ensureParameterNotNull(other, "other");
1101 if (other.isNew() ^ isNew())
1102 throw new DataIntegrityProblemException(
1103 tr("Cannot merge because either of the participating primitives is new and the other is not"));
1104 if (!other.isNew() && other.getId() != id)
1105 throw new DataIntegrityProblemException(
1106 tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
1107
1108 setKeys(other.hasKeys() ? other.getKeys() : null);
1109 timestamp = other.timestamp;
1110 version = other.version;
1111 setIncomplete(other.isIncomplete());
1112 flags = other.flags;
1113 user = other.user;
1114 changesetId = other.changesetId;
1115 } finally {
1116 writeUnlock(locked);
1117 }
1118 }
1119
1120 /**
1121 * Replies true if other isn't null and has the same interesting tags (key/value-pairs) as this.
1122 *
1123 * @param other the other object primitive
1124 * @return true if other isn't null and has the same interesting tags (key/value-pairs) as this.
1125 */
1126 public boolean hasSameInterestingTags(OsmPrimitive other) {
1127 return (keys == null && other.keys == null)
1128 || getInterestingTags().equals(other.getInterestingTags());
1129 }
1130
1131 /**
1132 * Replies true if this primitive and other are equal with respect to their semantic attributes.
1133 * <ol>
1134 * <li>equal id</li>
1135 * <li>both are complete or both are incomplete</li>
1136 * <li>both have the same tags</li>
1137 * </ol>
1138 * @param other other primitive to compare
1139 * @return true if this primitive and other are equal with respect to their semantic attributes.
1140 */
1141 public final boolean hasEqualSemanticAttributes(OsmPrimitive other) {
1142 return hasEqualSemanticAttributes(other, true);
1143 }
1144
1145 boolean hasEqualSemanticFlags(final OsmPrimitive other) {
1146 if (!isNew() && id != other.id)
1147 return false;
1148 if (isIncomplete() ^ other.isIncomplete()) // exclusive or operator for performance (see #7159)
1149 return false;
1150 return true;
1151 }
1152
1153 boolean hasEqualSemanticAttributes(final OsmPrimitive other, final boolean testInterestingTagsOnly) {
1154 return hasEqualSemanticFlags(other)
1155 && (testInterestingTagsOnly ? hasSameInterestingTags(other) : getKeys().equals(other.getKeys()));
1156 }
1157
1158 /**
1159 * Replies true if this primitive and other are equal with respect to their technical attributes.
1160 * The attributes:
1161 * <ol>
1162 * <li>deleted</li>
1163 * <li>modified</li>
1164 * <li>timestamp</li>
1165 * <li>version</li>
1166 * <li>visible</li>
1167 * <li>user</li>
1168 * </ol>
1169 * have to be equal
1170 * @param other the other primitive
1171 * @return true if this primitive and other are equal with respect to their technical attributes
1172 */
1173 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
1174 return other != null
1175 && timestamp == other.timestamp
1176 && version == other.version
1177 && changesetId == other.changesetId
1178 && isDeleted() == other.isDeleted()
1179 && isModified() == other.isModified()
1180 && isVisible() == other.isVisible()
1181 && Objects.equals(user, other.user);
1182 }
1183
1184 /**
1185 * Loads (clone) this primitive from provided PrimitiveData
1186 * @param data The object which should be cloned
1187 */
1188 public void load(PrimitiveData data) {
1189 // Write lock is provided by subclasses
1190 setKeys(data.hasKeys() ? data.getKeys() : null);
1191 setRawTimestamp(data.getRawTimestamp());
1192 user = data.getUser();
1193 setChangesetId(data.getChangesetId());
1194 setDeleted(data.isDeleted());
1195 setModified(data.isModified());
1196 setIncomplete(data.isIncomplete());
1197 version = data.getVersion();
1198 }
1199
1200 /**
1201 * Save parameters of this primitive to the transport object
1202 * @return The saved object data
1203 */
1204 public abstract PrimitiveData save();
1205
1206 /**
1207 * Save common parameters of primitives to the transport object
1208 * @param data The object to save the data into
1209 */
1210 protected void saveCommonAttributes(PrimitiveData data) {
1211 data.setId(id);
1212 data.setKeys(hasKeys() ? getKeys() : null);
1213 data.setRawTimestamp(getRawTimestamp());
1214 data.setUser(user);
1215 data.setDeleted(isDeleted());
1216 data.setModified(isModified());
1217 data.setVisible(isVisible());
1218 data.setIncomplete(isIncomplete());
1219 data.setChangesetId(changesetId);
1220 data.setVersion(version);
1221 }
1222
1223 /**
1224 * Fetch the bounding box of the primitive
1225 * @return Bounding box of the object
1226 */
1227 public abstract BBox getBBox();
1228
1229 /**
1230 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1231 */
1232 public abstract void updatePosition();
1233
1234 /*----------------
1235 * OBJECT METHODS
1236 *---------------*/
1237
1238 @Override
1239 protected String getFlagsAsString() {
1240 StringBuilder builder = new StringBuilder(super.getFlagsAsString());
1241
1242 if (isDisabled()) {
1243 if (isDisabledAndHidden()) {
1244 builder.append('h');
1245 } else {
1246 builder.append('d');
1247 }
1248 }
1249 if (isTagged()) {
1250 builder.append('T');
1251 }
1252 if (hasDirectionKeys()) {
1253 if (reversedDirection()) {
1254 builder.append('<');
1255 } else {
1256 builder.append('>');
1257 }
1258 }
1259 return builder.toString();
1260 }
1261
1262 /**
1263 * Equal, if the id (and class) is equal.
1264 *
1265 * An primitive is equal to its incomplete counter part.
1266 */
1267 @Override
1268 public boolean equals(Object obj) {
1269 if (this == obj) {
1270 return true;
1271 } else if (obj == null || getClass() != obj.getClass()) {
1272 return false;
1273 } else {
1274 OsmPrimitive that = (OsmPrimitive) obj;
1275 return id == that.id;
1276 }
1277 }
1278
1279 /**
1280 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
1281 *
1282 * An primitive has the same hashcode as its incomplete counterpart.
1283 */
1284 @Override
1285 public int hashCode() {
1286 return Long.hashCode(id);
1287 }
1288
1289 /**
1290 * Replies the display name of a primitive formatted by <code>formatter</code>
1291 * @param formatter formatter to use
1292 *
1293 * @return the display name
1294 */
1295 public abstract String getDisplayName(NameFormatter formatter);
1296
1297 @Override
1298 public Collection<String> getTemplateKeys() {
1299 Collection<String> keySet = keySet();
1300 List<String> result = new ArrayList<>(keySet.size() + 2);
1301 result.add(SPECIAL_VALUE_ID);
1302 result.add(SPECIAL_VALUE_LOCAL_NAME);
1303 result.addAll(keySet);
1304 return result;
1305 }
1306
1307 @Override
1308 public Object getTemplateValue(String name, boolean special) {
1309 if (special) {
1310 String lc = name.toLowerCase(Locale.ENGLISH);
1311 if (SPECIAL_VALUE_ID.equals(lc))
1312 return getId();
1313 else if (SPECIAL_VALUE_LOCAL_NAME.equals(lc))
1314 return getLocalName();
1315 else
1316 return null;
1317
1318 } else
1319 return getIgnoreCase(name);
1320 }
1321
1322 @Override
1323 public boolean evaluateCondition(Match condition) {
1324 return condition.match(this);
1325 }
1326
1327 /**
1328 * Replies the set of referring relations
1329 * @param primitives primitives to fetch relations from
1330 *
1331 * @return the set of referring relations
1332 */
1333 public static Set<Relation> getParentRelations(Collection<? extends OsmPrimitive> primitives) {
1334 Set<Relation> ret = new HashSet<>();
1335 for (OsmPrimitive w : primitives) {
1336 ret.addAll(OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class));
1337 }
1338 return ret;
1339 }
1340
1341 /**
1342 * Determines if this primitive has tags denoting an area.
1343 * @return {@code true} if this primitive has tags denoting an area, {@code false} otherwise.
1344 * @since 6491
1345 */
1346 public final boolean hasAreaTags() {
1347 return hasKey("landuse")
1348 || "yes".equals(get("area"))
1349 || "riverbank".equals(get("waterway"))
1350 || hasKey("natural")
1351 || hasKey("amenity")
1352 || hasKey("leisure")
1353 || hasKey("building")
1354 || hasKey("building:part");
1355 }
1356
1357 /**
1358 * Determines if this primitive semantically concerns an area.
1359 * @return {@code true} if this primitive semantically concerns an area, according to its type, geometry and tags, {@code false} otherwise.
1360 * @since 6491
1361 */
1362 public abstract boolean concernsArea();
1363
1364 /**
1365 * Tests if this primitive lies outside of the downloaded area of its {@link DataSet}.
1366 * @return {@code true} if this primitive lies outside of the downloaded area
1367 */
1368 public abstract boolean isOutsideDownloadArea();
1369
1370 /**
1371 * Determines if this object is a relation and behaves as a multipolygon.
1372 * @return {@code true} if it is a real mutlipolygon or a boundary relation
1373 * @since 10716
1374 */
1375 public boolean isMultipolygon() {
1376 return false;
1377 }
1378
1379 /**
1380 * If necessary, extend the bbox to contain this primitive
1381 * @param box a bbox instance
1382 * @param visited a set of visited members or null
1383 * @since 11269
1384 */
1385 protected abstract void addToBBox(BBox box, Set<PrimitiveId> visited);
1386}
Note: See TracBrowser for help on using the repository browser.