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

Last change on this file since 5589 was 5589, checked in by bastiK, 11 years ago

drop unnecessary properties for upload to the OSM API in order to save bandwidth

The properties are: user, uid, visible, action and timestamp. In addition drop lat & lon for deleted nodes. (To undelete a primitive, it suffices to include it in a <modify> section, the 'visible' property is ignored by the server.)

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