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

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

move primitive comparison logic to interfaces

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