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

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

see #11390 - sonar - squid:S1604 - Java 8: Anonymous inner classes containing only one method should become lambdas

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