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

Last change on this file since 5497 was 5497, checked in by bastiK, 12 years ago

applied #7915 - Automatically discard some TIGER tags on upload (based on patch by ToeBee)

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