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

Last change on this file since 5531 was 5531, checked in by stoecker, 12 years ago

fix #8109 - drop some yh: tags automatically

  • Property svn:eol-style set to native
File size: 40.6 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 "yh:LINE_NAME", "yh:LINE_NUM", "yh:STRUCTURE", "yh:TOTYUMONO",
642 "yh:TYPE", "yh:WIDTH_RANK"));
643 }
644 return discardable;
645 }
646
647 /**
648 * Returns true if key is considered "uninteresting".
649 */
650 public static boolean isUninterestingKey(String key) {
651 getUninterestingKeys();
652 if (uninteresting.contains(key))
653 return true;
654 int pos = key.indexOf(':');
655 if (pos > 0)
656 return uninteresting.contains(key.substring(0, pos + 1));
657 return false;
658 }
659
660 private static volatile Match directionKeys = null;
661 private static volatile Match reversedDirectionKeys = null;
662
663 /**
664 * Contains a list of direction-dependent keys that make an object
665 * direction dependent.
666 * Initialized by checkDirectionTagged()
667 */
668 static {
669 String reversedDirectionDefault = "oneway=\"-1\"";
670
671 String directionDefault = "oneway? | aerialway=* | "+
672 "waterway=stream | waterway=river | waterway=canal | waterway=drain | waterway=rapids | "+
673 "\"piste:type\"=downhill | \"piste:type\"=sled | man_made=\"piste:halfpipe\" | "+
674 "junction=roundabout";
675
676 try {
677 reversedDirectionKeys = SearchCompiler.compile(Main.pref.get("tags.reversed_direction", reversedDirectionDefault), false, false);
678 } catch (ParseError e) {
679 System.err.println("Unable to compile pattern for tags.reversed_direction, trying default pattern: " + e.getMessage());
680
681 try {
682 reversedDirectionKeys = SearchCompiler.compile(reversedDirectionDefault, false, false);
683 } catch (ParseError e2) {
684 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
685 }
686 }
687 try {
688 directionKeys = SearchCompiler.compile(Main.pref.get("tags.direction", directionDefault), false, false);
689 } catch (ParseError e) {
690 System.err.println("Unable to compile pattern for tags.direction, trying default pattern: " + e.getMessage());
691
692 try {
693 directionKeys = SearchCompiler.compile(directionDefault, false, false);
694 } catch (ParseError e2) {
695 throw new AssertionError("Unable to compile default pattern for direction keys: " + e2.getMessage());
696 }
697 }
698 }
699
700 private void updateTagged() {
701 if (keys != null) {
702 for (String key: keySet()) {
703 if (!isUninterestingKey(key)) {
704 updateFlagsNoLock(FLAG_TAGGED, true);
705 return;
706 }
707 }
708 }
709 updateFlagsNoLock(FLAG_TAGGED, false);
710 }
711
712 /**
713 * true if this object is considered "tagged". To be "tagged", an object
714 * must have one or more "interesting" tags. "created_by" and "source"
715 * are typically considered "uninteresting" and do not make an object
716 * "tagged".
717 */
718 public boolean isTagged() {
719 return (flags & FLAG_TAGGED) != 0;
720 }
721
722 private void updateDirectionFlags() {
723 boolean hasDirections = false;
724 boolean directionReversed = false;
725 if (reversedDirectionKeys.match(this)) {
726 hasDirections = true;
727 directionReversed = true;
728 }
729 if (directionKeys.match(this)) {
730 hasDirections = true;
731 }
732
733 updateFlagsNoLock(FLAG_DIRECTION_REVERSED, directionReversed);
734 updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections);
735 }
736
737 /**
738 * true if this object has direction dependent tags (e.g. oneway)
739 */
740 public boolean hasDirectionKeys() {
741 return (flags & FLAG_HAS_DIRECTIONS) != 0;
742 }
743
744 public boolean reversedDirection() {
745 return (flags & FLAG_DIRECTION_REVERSED) != 0;
746 }
747
748 /*------------
749 * Keys handling
750 ------------*/
751
752 @Override
753 public final void setKeys(Map<String, String> keys) {
754 boolean locked = writeLock();
755 try {
756 super.setKeys(keys);
757 } finally {
758 writeUnlock(locked);
759 }
760 }
761
762 @Override
763 public final void put(String key, String value) {
764 boolean locked = writeLock();
765 try {
766 super.put(key, value);
767 } finally {
768 writeUnlock(locked);
769 }
770 }
771
772 @Override
773 public final void remove(String key) {
774 boolean locked = writeLock();
775 try {
776 super.remove(key);
777 } finally {
778 writeUnlock(locked);
779 }
780 }
781
782 @Override
783 public final void removeAll() {
784 boolean locked = writeLock();
785 try {
786 super.removeAll();
787 } finally {
788 writeUnlock(locked);
789 }
790 }
791
792 @Override
793 protected final void keysChangedImpl(Map<String, String> originalKeys) {
794 clearCachedStyle();
795 if (dataSet != null) {
796 for (OsmPrimitive ref : getReferrers()) {
797 ref.clearCachedStyle();
798 }
799 }
800 updateDirectionFlags();
801 updateTagged();
802 if (dataSet != null) {
803 dataSet.fireTagsChanged(this, originalKeys);
804 }
805 }
806
807 /*------------
808 * Referrers
809 ------------*/
810
811 private Object referrers;
812
813 /**
814 * Add new referrer. If referrer is already included then no action is taken
815 * @param referrer
816 */
817 protected void addReferrer(OsmPrimitive referrer) {
818 // Based on methods from josm-ng
819 if (referrers == null) {
820 referrers = referrer;
821 } else if (referrers instanceof OsmPrimitive) {
822 if (referrers != referrer) {
823 referrers = new OsmPrimitive[] { (OsmPrimitive)referrers, referrer };
824 }
825 } else {
826 for (OsmPrimitive primitive:(OsmPrimitive[])referrers) {
827 if (primitive == referrer)
828 return;
829 }
830 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
831 OsmPrimitive[] bigger = new OsmPrimitive[orig.length+1];
832 System.arraycopy(orig, 0, bigger, 0, orig.length);
833 bigger[orig.length] = referrer;
834 referrers = bigger;
835 }
836 }
837
838 /**
839 * Remove referrer. No action is taken if referrer is not registered
840 * @param referrer
841 */
842 protected void removeReferrer(OsmPrimitive referrer) {
843 // Based on methods from josm-ng
844 if (referrers instanceof OsmPrimitive) {
845 if (referrers == referrer) {
846 referrers = null;
847 }
848 } else if (referrers instanceof OsmPrimitive[]) {
849 OsmPrimitive[] orig = (OsmPrimitive[])referrers;
850 int idx = -1;
851 for (int i=0; i<orig.length; i++) {
852 if (orig[i] == referrer) {
853 idx = i;
854 break;
855 }
856 }
857 if (idx == -1)
858 return;
859
860 if (orig.length == 2) {
861 referrers = orig[1-idx]; // idx is either 0 or 1, take the other
862 } else { // downsize the array
863 OsmPrimitive[] smaller = new OsmPrimitive[orig.length-1];
864 System.arraycopy(orig, 0, smaller, 0, idx);
865 System.arraycopy(orig, idx+1, smaller, idx, smaller.length-idx);
866 referrers = smaller;
867 }
868 }
869 }
870 /**
871 * Find primitives that reference this primitive. Returns only primitives that are included in the same
872 * dataset as this primitive. <br>
873 *
874 * For example following code will add wnew as referer to all nodes of existingWay, but this method will
875 * not return wnew because it's not part of the dataset <br>
876 *
877 * <code>Way wnew = new Way(existingWay)</code>
878 *
879 * @param allowWithoutDataset If true, method will return empty list if primitive is not part of the dataset. If false,
880 * exception will be thrown in this case
881 *
882 * @return a collection of all primitives that reference this primitive.
883 */
884
885 public final List<OsmPrimitive> getReferrers(boolean allowWithoutDataset) {
886 // Method copied from OsmPrimitive in josm-ng
887 // Returns only referrers that are members of the same dataset (primitive can have some fake references, for example
888 // when way is cloned
889
890 if (dataSet == null && allowWithoutDataset)
891 return Collections.emptyList();
892
893 checkDataset();
894 Object referrers = this.referrers;
895 List<OsmPrimitive> result = new ArrayList<OsmPrimitive>();
896 if (referrers != null) {
897 if (referrers instanceof OsmPrimitive) {
898 OsmPrimitive ref = (OsmPrimitive)referrers;
899 if (ref.dataSet == dataSet) {
900 result.add(ref);
901 }
902 } else {
903 for (OsmPrimitive o:(OsmPrimitive[])referrers) {
904 if (dataSet == o.dataSet) {
905 result.add(o);
906 }
907 }
908 }
909 }
910 return result;
911 }
912
913 public final List<OsmPrimitive> getReferrers() {
914 return getReferrers(false);
915 }
916
917 /**
918 * <p>Visits {@code visitor} for all referrers.</p>
919 *
920 * @param visitor the visitor. Ignored, if null.
921 */
922 public void visitReferrers(Visitor visitor){
923 if (visitor == null) return;
924 if (this.referrers == null)
925 return;
926 else if (this.referrers instanceof OsmPrimitive) {
927 OsmPrimitive ref = (OsmPrimitive) this.referrers;
928 if (ref.dataSet == dataSet) {
929 ref.visit(visitor);
930 }
931 } else if (this.referrers instanceof OsmPrimitive[]) {
932 OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers;
933 for (OsmPrimitive ref: refs) {
934 if (ref.dataSet == dataSet) {
935 ref.visit(visitor);
936 }
937 }
938 }
939 }
940
941 /**
942 Return true, if this primitive is referred by at least n ways
943 @param n Minimal number of ways to return true. Must be positive
944 */
945 public final boolean isReferredByWays(int n) {
946 // Count only referrers that are members of the same dataset (primitive can have some fake references, for example
947 // when way is cloned
948 Object referrers = this.referrers;
949 if (referrers == null) return false;
950 checkDataset();
951 if (referrers instanceof OsmPrimitive)
952 return n<=1 && referrers instanceof Way && ((OsmPrimitive)referrers).dataSet == dataSet;
953 else {
954 int counter=0;
955 for (OsmPrimitive o : (OsmPrimitive[])referrers) {
956 if (dataSet == o.dataSet && o instanceof Way) {
957 if (++counter >= n)
958 return true;
959 }
960 }
961 return false;
962 }
963 }
964
965
966 /*-----------------
967 * OTHER METHODS
968 *----------------/
969
970 /**
971 * Implementation of the visitor scheme. Subclasses have to call the correct
972 * visitor function.
973 * @param visitor The visitor from which the visit() function must be called.
974 */
975 abstract public void visit(Visitor visitor);
976
977 /**
978 * Get and write all attributes from the parameter. Does not fire any listener, so
979 * use this only in the data initializing phase
980 */
981 public void cloneFrom(OsmPrimitive other) {
982 // write lock is provided by subclasses
983 if (id != other.id && dataSet != null)
984 throw new DataIntegrityProblemException("Osm id cannot be changed after primitive was added to the dataset");
985
986 super.cloneFrom(other);
987 clearCachedStyle();
988 }
989
990 /**
991 * Merges the technical and semantical attributes from <code>other</code> onto this.
992 *
993 * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
994 * have an assigend OSM id, the IDs have to be the same.
995 *
996 * @param other the other primitive. Must not be null.
997 * @throws IllegalArgumentException thrown if other is null.
998 * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
999 * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId()
1000 */
1001 public void mergeFrom(OsmPrimitive other) {
1002 boolean locked = writeLock();
1003 try {
1004 CheckParameterUtil.ensureParameterNotNull(other, "other");
1005 if (other.isNew() ^ isNew())
1006 throw new DataIntegrityProblemException(tr("Cannot merge because either of the participating primitives is new and the other is not"));
1007 if (! other.isNew() && other.getId() != id)
1008 throw new DataIntegrityProblemException(tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId()));
1009
1010 setKeys(other.getKeys());
1011 timestamp = other.timestamp;
1012 version = other.version;
1013 setIncomplete(other.isIncomplete());
1014 flags = other.flags;
1015 user= other.user;
1016 changesetId = other.changesetId;
1017 } finally {
1018 writeUnlock(locked);
1019 }
1020 }
1021
1022 /**
1023 * Replies true if this primitive and other are equal with respect to their
1024 * semantic attributes.
1025 * <ol>
1026 * <li>equal id</ol>
1027 * <li>both are complete or both are incomplete</li>
1028 * <li>both have the same tags</li>
1029 * </ol>
1030 * @param other
1031 * @return true if this primitive and other are equal with respect to their
1032 * semantic attributes.
1033 */
1034 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
1035 if (!isNew() && id != other.id)
1036 return false;
1037 // if (isIncomplete() && ! other.isIncomplete() || !isIncomplete() && other.isIncomplete())
1038 if (isIncomplete() ^ other.isIncomplete()) // exclusive or operator for performance (see #7159)
1039 return false;
1040 // can't do an equals check on the internal keys array because it is not ordered
1041 //
1042 return hasSameTags(other);
1043 }
1044
1045 /**
1046 * Replies true if this primitive and other are equal with respect to their
1047 * technical attributes. The attributes:
1048 * <ol>
1049 * <li>deleted</ol>
1050 * <li>modified</ol>
1051 * <li>timestamp</ol>
1052 * <li>version</ol>
1053 * <li>visible</ol>
1054 * <li>user</ol>
1055 * </ol>
1056 * have to be equal
1057 * @param other the other primitive
1058 * @return true if this primitive and other are equal with respect to their
1059 * technical attributes
1060 */
1061 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
1062 if (other == null) return false;
1063
1064 return
1065 isDeleted() == other.isDeleted()
1066 && isModified() == other.isModified()
1067 && timestamp == other.timestamp
1068 && version == other.version
1069 && isVisible() == other.isVisible()
1070 && (user == null ? other.user==null : user==other.user)
1071 && changesetId == other.changesetId;
1072 }
1073
1074 /**
1075 * Loads (clone) this primitive from provided PrimitiveData
1076 * @param data
1077 */
1078 public void load(PrimitiveData data) {
1079 // Write lock is provided by subclasses
1080 setKeys(data.getKeys());
1081 setTimestamp(data.getTimestamp());
1082 user = data.getUser();
1083 setChangesetId(data.getChangesetId());
1084 setDeleted(data.isDeleted());
1085 setModified(data.isModified());
1086 setIncomplete(data.isIncomplete());
1087 version = data.getVersion();
1088 }
1089
1090 /**
1091 * Save parameters of this primitive to the transport object
1092 * @return
1093 */
1094 public abstract PrimitiveData save();
1095
1096 protected void saveCommonAttributes(PrimitiveData data) {
1097 data.setId(id);
1098 data.setKeys(getKeys());
1099 data.setTimestamp(getTimestamp());
1100 data.setUser(user);
1101 data.setDeleted(isDeleted());
1102 data.setModified(isModified());
1103 data.setVisible(isVisible());
1104 data.setIncomplete(isIncomplete());
1105 data.setChangesetId(changesetId);
1106 data.setVersion(version);
1107 }
1108
1109 public abstract BBox getBBox();
1110
1111 /**
1112 * Called by Dataset to update cached position information of primitive (bbox, cached EarthNorth, ...)
1113 */
1114 public abstract void updatePosition();
1115
1116 /*----------------
1117 * OBJECT METHODS
1118 *---------------*/
1119
1120 @Override
1121 protected String getFlagsAsString() {
1122 StringBuilder builder = new StringBuilder(super.getFlagsAsString());
1123
1124 if (isDisabled()) {
1125 if (isDisabledAndHidden()) {
1126 builder.append("h");
1127 } else {
1128 builder.append("d");
1129 }
1130 }
1131 if (isTagged()) {
1132 builder.append("T");
1133 }
1134 if (hasDirectionKeys()) {
1135 if (reversedDirection()) {
1136 builder.append("<");
1137 } else {
1138 builder.append(">");
1139 }
1140 }
1141 return builder.toString();
1142 }
1143
1144 /**
1145 * Equal, if the id (and class) is equal.
1146 *
1147 * An primitive is equal to its incomplete counter part.
1148 */
1149 @Override public boolean equals(Object obj) {
1150 if (obj instanceof OsmPrimitive)
1151 return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
1152 return false;
1153 }
1154
1155 /**
1156 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
1157 *
1158 * An primitive has the same hashcode as its incomplete counterpart.
1159 */
1160 @Override public final int hashCode() {
1161 return (int)id;
1162 }
1163
1164 /**
1165 * Replies the display name of a primitive formatted by <code>formatter</code>
1166 *
1167 * @return the display name
1168 */
1169 public abstract String getDisplayName(NameFormatter formatter);
1170
1171 @Override
1172 public Collection<String> getTemplateKeys() {
1173 Collection<String> keySet = keySet();
1174 List<String> result = new ArrayList<String>(keySet.size() + 2);
1175 result.add(SPECIAL_VALUE_ID);
1176 result.add(SPECIAL_VALUE_LOCAL_NAME);
1177 result.addAll(keySet);
1178 return result;
1179 }
1180
1181 @Override
1182 public Object getTemplateValue(String name, boolean special) {
1183 if (special) {
1184 String lc = name.toLowerCase();
1185 if (SPECIAL_VALUE_ID.equals(lc))
1186 return getId();
1187 else if (SPECIAL_VALUE_LOCAL_NAME.equals(lc))
1188 return getLocalName();
1189 else
1190 return null;
1191
1192 } else
1193 return getIgnoreCase(name);
1194 }
1195
1196 @Override
1197 public boolean evaluateCondition(Match condition) {
1198 return condition.match(this);
1199 }
1200
1201 /**
1202 * Replies the set of referring relations
1203 *
1204 * @return the set of referring relations
1205 */
1206 public static Set<Relation> getParentRelations(Collection<? extends OsmPrimitive> primitives) {
1207 HashSet<Relation> ret = new HashSet<Relation>();
1208 for (OsmPrimitive w : primitives) {
1209 ret.addAll(OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class));
1210 }
1211 return ret;
1212 }
1213}
Note: See TracBrowser for help on using the repository browser.