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

Last change on this file since 5608 was 5608, checked in by stoecker, 11 years ago

don't hardcode created_by for DuplicateRelation validator test, javadoc fixes

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