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

Last change on this file since 11296 was 11294, checked in by simon04, 7 years ago

Fix http://errorprone.info/bugpattern/NarrowingCompoundAssignment

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