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

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

see #14043 - review use of short type

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