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

Last change on this file since 6629 was 6629, checked in by simon04, 10 years ago

Replace NodesDuplicatingWayTags test by a corresponding MapCSS test

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