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

Last change on this file since 4099 was 4099, checked in by bastiK, 13 years ago

Move common code for OsmPrimitive & PrimitiveData to abstract subclass.
PrimitiveData benefits now from prior memory optimizations in OsmPrimitive.

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