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

Last change on this file since 12867 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

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