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

Last change on this file since 11716 was 11608, checked in by Don-vip, 7 years ago

fix #14402 - add blacklist for leisure area values to avoid false positives - improve globally the detection of keys/tags

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