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

Last change on this file since 2120 was 2120, checked in by stoecker, 15 years ago

see #3475 - patch by Petr Dlouhý - code rework for display filtering

  • Property svn:eol-style set to native
File size: 22.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.util.ArrayList;
7import java.util.Arrays;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.Date;
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Locale;
16import java.util.Map;
17import java.util.Set;
18import java.util.Map.Entry;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.data.osm.visitor.Visitor;
22import org.openstreetmap.josm.gui.mappaint.ElemStyle;
23
24
25/**
26 * An OSM primitive can be associated with a key/value pair. It can be created, deleted
27 * and updated within the OSM-Server.
28 *
29 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
30 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given
31 * by the server environment and not an extendible data stuff.
32 *
33 * @author imi
34 */
35abstract public class OsmPrimitive implements Comparable<OsmPrimitive>, Tagged {
36
37 static public <T extends OsmPrimitive> List<T> getFilteredList(Collection<OsmPrimitive> list, Class<T> type) {
38 if (list == null) return null;
39 List<T> ret = new LinkedList<T>();
40 for(OsmPrimitive p: list) {
41 if (type.isInstance(p)) {
42 ret.add(type.cast(p));
43 }
44 }
45 return ret;
46 }
47
48 static public <T extends OsmPrimitive> Set<T> getFilteredSet(Collection<OsmPrimitive> set, Class<T> type) {
49 if (set == null) return null;
50 HashSet<T> ret = new HashSet<T>();
51 for(OsmPrimitive p: set) {
52 if (type.isInstance(p)) {
53 ret.add(type.cast(p));
54 }
55 }
56 return ret;
57 }
58
59
60 /* mappaint data */
61 public ElemStyle mappaintStyle = null;
62 public Integer mappaintVisibleCode = 0;
63 public Integer mappaintDrawnCode = 0;
64 public Collection<String> errors;
65
66 public void putError(String text, Boolean isError)
67 {
68 if(errors == null) {
69 errors = new ArrayList<String>();
70 }
71 String s = isError ? tr("Error: {0}", text) : tr("Warning: {0}", text);
72 errors.add(s);
73 }
74 public void clearErrors()
75 {
76 errors = null;
77 }
78 /* This should not be called from outside. Fixing the UI to add relevant
79 get/set functions calling this implicitely is preferred, so we can have
80 transparent cache handling in the future. */
81 protected void clearCached()
82 {
83 mappaintVisibleCode = 0;
84 mappaintDrawnCode = 0;
85 mappaintStyle = null;
86 }
87 /* end of mappaint data */
88
89 /**
90 * Unique identifier in OSM. This is used to identify objects on the server.
91 * An id of 0 means an unknown id. The object has not been uploaded yet to
92 * know what id it will get.
93 *
94 * Do not write to this attribute except you know exactly what you are doing.
95 * More specific, it is not good to set this to 0 and think the object is now
96 * new to the server! To create a new object, call the default constructor of
97 * the respective class.
98 *
99 */
100 private long id = 0;
101
102 /**
103 * <code>true</code> if the object has been modified since it was loaded from
104 * the server. In this case, on next upload, this object will be updated.
105 * Deleted objects are deleted from the server. If the objects are added (id=0),
106 * the modified is ignored and the object is added to the server.
107 *
108 */
109 private boolean modified = false;
110
111 /**
112 * <code>true</code>, if the object has been deleted.
113 *
114 */
115 private boolean deleted = false;
116
117 /**
118 * Visibility status as specified by the server. The visible attribute was
119 * introduced with the 0.4 API to be able to communicate deleted objects
120 * (they will have visible=false).
121 *
122 */
123 private boolean visible = true;
124
125 /**
126 * <code>true</code>, if the object has been set inactive
127 *
128 */
129 private boolean disabled = false;
130
131 /**
132 * <code>true</code>, if the object has been filtered out
133 *
134 */
135 private boolean filtered = false;
136
137 /**
138 * User that last modified this primitive, as specified by the server.
139 * Never changed by JOSM.
140 */
141 public User user = null;
142
143 /**
144 * If set to true, this object is currently selected.
145 *
146 */
147 private volatile boolean selected = false;
148
149 /**
150 * If set to true, this object is incomplete, which means only the id
151 * and type is known (type is the objects instance class)
152 */
153 public boolean incomplete = false;
154
155 /**
156 * Contains the version number as returned by the API. Needed to
157 * ensure update consistency
158 */
159 private int version = 0;
160
161 /**
162 * Creates a new primitive with id 0.
163 *
164 */
165 public OsmPrimitive() {
166 this(0);
167 }
168
169 /**
170 * Creates a new primitive for the given id. If the id > 0, the primitive is marked
171 * as incomplete.
172 *
173 * @param id the id. > 0 required
174 * @throws IllegalArgumentException thrown if id < 0
175 */
176 public OsmPrimitive(long id) throws IllegalArgumentException {
177 if (id < 0)
178 throw new IllegalArgumentException(tr("expected id >= 0. Got {0}", id));
179 this.id = id;
180 this.version = 0;
181 this.incomplete = id >0;
182 }
183
184 /* ------------------------------------------------------------------------------------ */
185 /* accessors */
186 /* ------------------------------------------------------------------------------------ */
187 /**
188 * Sets whether this primitive is disabled or not.
189 *
190 * @param selected true, if this primitive is disabled; false, otherwise
191 */
192 public void setDisabled(boolean disabled) {
193 this.disabled = disabled;
194 }
195
196 /**
197 * Replies true, if this primitive is disabled.
198 *
199 * @return true, if this primitive is disabled
200 */
201 public boolean isDisabled() {
202 return disabled;
203 }
204 /**
205 * Sets whether this primitive is filtered out or not.
206 *
207 * @param selected true, if this primitive is filtered out; false, otherwise
208 */
209 public void setFiltered(boolean filtered) {
210 this.filtered = filtered;
211 }
212 /**
213 * Replies true, if this primitive is filtered out.
214 *
215 * @return true, if this primitive is filtered out
216 */
217 public boolean isFiltered() {
218 return filtered;
219 }
220
221 /**
222 * Sets whether this primitive is selected or not.
223 *
224 * @param selected true, if this primitive is selected; false, otherwise
225 * @since 1899
226 */
227 public void setSelected(boolean selected) {
228 this.selected = selected;
229 }
230 /**
231 * Replies true, if this primitive is selected.
232 *
233 * @return true, if this primitive is selected
234 * @since 1899
235 */
236 public boolean isSelected() {
237 return selected;
238 }
239
240 /**
241 * Marks this primitive as being modified.
242 *
243 * @param modified true, if this primitive is to be modified
244 */
245 public void setModified(boolean modified) {
246 this.modified = modified;
247 }
248
249 /**
250 * Replies <code>true</code> if the object has been modified since it was loaded from
251 * the server. In this case, on next upload, this object will be updated.
252 *
253 * @return <code>true</code> if the object has been modified since it was loaded from
254 * the server
255 */
256 public boolean isModified() {
257 return modified;
258 }
259
260 /**
261 * Replies <code>true</code>, if the object has been deleted.
262 *
263 * @return <code>true</code>, if the object has been deleted.
264 * @see #setDeleted(boolean)
265 */
266 public boolean isDeleted() {
267 return deleted;
268 }
269
270 /**
271 * Replies <code>true</code>, if the object is usable.
272 *
273 * @return <code>true</code>, if the object is unusable.
274 * @see #delete(boolean)
275 */
276 public boolean isUsable() {
277 return !deleted && !incomplete && !disabled;
278 }
279
280 /**
281 * Replies true if this primitive is either unknown to the server (i.e. its id
282 * is 0) or it is known to the server and it hasn't be deleted on the server.
283 * Replies false, if this primitive is known on the server and has been deleted
284 * on the server.
285 *
286 * @see #setVisible(boolean)
287 */
288 public boolean isVisible() {
289 return visible;
290 }
291
292 /**
293 * Sets whether this primitive is visible, i.e. whether it is known on the server
294 * and not deleted on the server.
295 *
296 * @see #isVisible()
297 * @throws IllegalStateException thrown if visible is set to false on an primitive with
298 * id==0
299 */
300 public void setVisible(boolean visible) throws IllegalStateException{
301 if (id == 0 && visible == false)
302 throw new IllegalStateException(tr("a primitive with id=0 can't be invisible"));
303 this.visible = visible;
304 }
305
306 /**
307 * Replies the version number as returned by the API. The version is 0 if the id is 0 or
308 * if this primitive is incomplete.
309 *
310 * @see #setVersion(int)
311 */
312 public long getVersion() {
313 return version;
314 }
315
316 /**
317 * Replies the id of this primitive.
318 *
319 * @return the id of this primitive.
320 */
321 public long getId() {
322 return id;
323 }
324
325 /**
326 * Sets the id and the version of this primitive if it is known to the OSM API.
327 *
328 * Since we know the id and its version it can't be incomplete anymore. incomplete
329 * is set to false.
330 *
331 * @param id the id. > 0 required
332 * @param version the version > 0 required
333 * @throws IllegalArgumentException thrown if id <= 0
334 * @throws IllegalArgumentException thrown if version <= 0
335 */
336 public void setOsmId(long id, int version) {
337 if (id <= 0)
338 throw new IllegalArgumentException(tr("id > 0 expected. Got {0}", id));
339 if (version <= 0)
340 throw new IllegalArgumentException(tr("version > 0 expected. Got {0}", version));
341 this.id = id;
342 this.version = version;
343 this.incomplete = false;
344 }
345
346 /**
347 * Clears the id and version known to the OSM API. The id and the version is set to 0.
348 * incomplete is set to false.
349 *
350 * <strong>Caution</strong>: Do not use this method on primitives which are already added to a {@see DataSet}.
351 * Ways and relations might already refer to the primitive and clearing the OSM ID
352 * result in corrupt data.
353 *
354 * Here's an example use case:
355 * <pre>
356 * // create a clone of an already existing node
357 * Node copy = new Node(otherExistingNode);
358 *
359 * // reset the clones OSM id
360 * copy.clearOsmId();
361 * </pre>
362 *
363 */
364 public void clearOsmId() {
365 this.id = 0;
366 this.version = 0;
367 this.incomplete = false;
368 }
369
370 public void setTimestamp(Date timestamp) {
371 this.timestamp = (int)(timestamp.getTime() / 1000);
372 }
373
374 /**
375 * Time of last modification to this object. This is not set by JOSM but
376 * read from the server and delivered back to the server unmodified. It is
377 * used to check against edit conflicts.
378 *
379 */
380 public Date getTimestamp() {
381 return new Date(timestamp * 1000l);
382 }
383
384 public boolean isTimestampEmpty() {
385 return timestamp == 0;
386 }
387
388 /**
389 * If set to true, this object is highlighted. Currently this is only used to
390 * show which ways/nodes will connect
391 */
392 public volatile boolean highlighted = false;
393
394 private int timestamp;
395
396 private static Collection<String> uninteresting = null;
397 /**
398 * Contains a list of "uninteresting" keys that do not make an object
399 * "tagged".
400 * Initialized by checkTagged()
401 */
402 public static Collection<String> getUninterestingKeys() {
403 if (uninteresting == null) {
404 uninteresting = Main.pref.getCollection("tags.uninteresting",
405 Arrays.asList(new String[]{"source","note","comment","converted_by","created_by"}));
406 }
407 return uninteresting;
408 }
409
410
411 private static Collection<String> directionKeys = null;
412
413 /**
414 * Contains a list of direction-dependent keys that make an object
415 * direction dependent.
416 * Initialized by checkDirectionTagged()
417 */
418 public static Collection<String> getDirectionKeys() {
419 if(directionKeys == null) {
420 directionKeys = Main.pref.getCollection("tags.direction",
421 Arrays.asList(new String[]{"oneway","incline","incline_steep","aerialway"}));
422 }
423 return directionKeys;
424 }
425
426 /**
427 * Implementation of the visitor scheme. Subclasses have to call the correct
428 * visitor function.
429 * @param visitor The visitor from which the visit() function must be called.
430 */
431 abstract public void visit(Visitor visitor);
432
433 /**
434 * Sets whether this primitive is deleted or not.
435 *
436 * Also marks this primitive as modified if deleted is true and sets selected to false.
437 *
438 * @param deleted true, if this primitive is deleted; false, otherwise
439 */
440 public void setDeleted(boolean deleted) {
441 this.modified = deleted;
442 this.deleted = deleted;
443 this.selected = false;
444 }
445
446 /**
447 * Equal, if the id (and class) is equal.
448 *
449 * An primitive is equal to its incomplete counter part.
450 */
451 @Override public boolean equals(Object obj) {
452 if (id == 0) return obj == this;
453 if (obj instanceof OsmPrimitive)
454 return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
455 return false;
456 }
457
458 /**
459 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
460 *
461 * An primitive has the same hashcode as its incomplete counterpart.
462 */
463 @Override public final int hashCode() {
464 if (id == 0)
465 return super.hashCode();
466 final int[] ret = new int[1];
467 Visitor v = new Visitor(){
468 public void visit(Node n) { ret[0] = 0; }
469 public void visit(Way w) { ret[0] = 1; }
470 public void visit(Relation e) { ret[0] = 2; }
471 public void visit(Changeset cs) { ret[0] = 3; }
472 };
473 visit(v);
474 return id == 0 ? super.hashCode() : (int)(id<<2)+ret[0];
475 }
476
477 /*------------
478 * Keys handling
479 ------------*/
480
481 /**
482 * The key/value list for this primitive.
483 *
484 */
485 private Map<String, String> keys;
486
487 /**
488 * Replies the map of key/value pairs. Never replies null. The map can be empty, though.
489 *
490 * @return Keys of this primitive. Changes made in returned map are not mapped
491 * back to the primitive, use setKeys() to modify the keys
492 * @since 1924
493 */
494 public Map<String, String> getKeys() {
495 // TODO More effective map
496 // fix for #3218
497 if (keys == null)
498 return new HashMap<String, String>();
499 else
500 return new HashMap<String, String>(keys);
501 }
502
503 /**
504 * Sets the keys of this primitives to the key/value pairs in <code>keys</code>.
505 * If <code>keys</code> is null removes all existing key/value pairs.
506 *
507 * @param keys the key/value pairs to set. If null, removes all existing key/value pairs.
508 * @since 1924
509 */
510 public void setKeys(Map<String, String> keys) {
511 if (keys == null) {
512 this.keys = null;
513 } else {
514 this.keys = new HashMap<String, String>(keys);
515 }
516 }
517
518 /**
519 * Set the given value to the given key. If key is null, does nothing. If value is null,
520 * removes the key and behaves like {@see #remove(String)}.
521 *
522 * @param key The key, for which the value is to be set. Can be null, does nothing in this case.
523 * @param value The value for the key. If null, removes the respective key/value pair.
524 *
525 * @see #remove(String)
526 */
527 public final void put(String key, String value) {
528 if (key == null)
529 return;
530 else if (value == null) {
531 remove(key);
532 } else {
533 if (keys == null) {
534 keys = new HashMap<String, String>();
535 }
536 keys.put(key, value);
537 }
538 mappaintStyle = null;
539 }
540 /**
541 * Remove the given key from the list
542 *
543 * @param key the key to be removed. Ignored, if key is null.
544 */
545 public final void remove(String key) {
546 if (keys != null) {
547 keys.remove(key);
548 if (keys.isEmpty()) {
549 keys = null;
550 }
551 }
552 mappaintStyle = null;
553 }
554
555 /**
556 * Removes all keys from this primitive.
557 *
558 * @since 1843
559 */
560 public final void removeAll() {
561 keys = null;
562 mappaintStyle = null;
563 }
564
565 /**
566 * Replies the value for key <code>key</code>. Replies null, if <code>key</code> is null.
567 * Replies null, if there is no value for the given key.
568 *
569 * @param key the key. Can be null, replies null in this case.
570 * @return the value for key <code>key</code>.
571 */
572 public final String get(String key) {
573 if (key == null) return null;
574 return keys == null ? null : keys.get(key);
575 }
576
577 public final Collection<Entry<String, String>> entrySet() {
578 if (keys == null)
579 return Collections.emptyList();
580 return keys.entrySet();
581 }
582
583 public final Collection<String> keySet() {
584 if (keys == null)
585 return Collections.emptyList();
586 return keys.keySet();
587 }
588
589 /**
590 * Replies true, if the map of key/value pairs of this primitive is not empty.
591 *
592 * @return true, if the map of key/value pairs of this primitive is not empty; false
593 * otherwise
594 *
595 * @since 1843
596 */
597 public final boolean hasKeys() {
598 return keys != null && !keys.isEmpty();
599 }
600
601 /**
602 * Get and write all attributes from the parameter. Does not fire any listener, so
603 * use this only in the data initializing phase
604 */
605 public void cloneFrom(OsmPrimitive osm) {
606 keys = osm.keys == null ? null : new HashMap<String, String>(osm.keys);
607 id = osm.id;
608 modified = osm.modified;
609 deleted = osm.deleted;
610 setSelected(osm.isSelected());
611 timestamp = osm.timestamp;
612 version = osm.version;
613 incomplete = osm.incomplete;
614 visible = osm.visible;
615 clearCached();
616 clearErrors();
617 }
618
619 /**
620 * Replies true if this primitive and other are equal with respect to their
621 * semantic attributes.
622 * <ol>
623 * <li>equal id</ol>
624 * <li>both are complete or both are incomplete</li>
625 * <li>both have the same tags</li>
626 * </ol>
627 * @param other
628 * @return true if this primitive and other are equal with respect to their
629 * semantic attributes.
630 */
631 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
632 if (id != other.id)
633 return false;
634 if (incomplete && ! other.incomplete || !incomplete && other.incomplete)
635 return false;
636 return (keys == null ? other.keys==null : keys.equals(other.keys));
637 }
638
639 /**
640 * Replies true if this primitive and other are equal with respect to their
641 * technical attributes. The attributes:
642 * <ol>
643 * <li>deleted</ol>
644 * <li>modified</ol>
645 * <li>timestamp</ol>
646 * <li>version</ol>
647 * <li>visible</ol>
648 * <li>user</ol>
649 * </ol>
650 * have to be equal
651 * @param other the other primitive
652 * @return true if this primitive and other are equal with respect to their
653 * technical attributes
654 */
655 public boolean hasEqualTechnicalAttributes(OsmPrimitive other) {
656 if (other == null) return false;
657
658 return
659 deleted == other.deleted
660 && modified == other.modified
661 && timestamp == other.timestamp
662 && version == other.version
663 && visible == other.visible
664 && (user == null ? other.user==null : user==other.user);
665 }
666
667 /**
668 * true if this object is considered "tagged". To be "tagged", an object
669 * must have one or more "interesting" tags. "created_by" and "source"
670 * are typically considered "uninteresting" and do not make an object
671 * "tagged".
672 */
673 public boolean isTagged() {
674 // TODO Cache value after keys are made private
675 getUninterestingKeys();
676 if (keys != null) {
677 for (Entry<String,String> e : keys.entrySet()) {
678 if (!uninteresting.contains(e.getKey()))
679 return true;
680 }
681 }
682 return false;
683 }
684 /**
685 * true if this object has direction dependent tags (e.g. oneway)
686 */
687 public boolean hasDirectionKeys() {
688 // TODO Cache value after keys are made private
689 getDirectionKeys();
690 if (keys != null) {
691 for (Entry<String,String> e : keys.entrySet()) {
692 if (directionKeys.contains(e.getKey()))
693 return true;
694 }
695 }
696 return false;
697 }
698
699
700 /**
701 * Replies the name of this primitive. The default implementation replies the value
702 * of the tag <tt>name</tt> or null, if this tag is not present.
703 *
704 * @return the name of this primitive
705 */
706 public String getName() {
707 if (get("name") != null)
708 return get("name");
709 return null;
710 }
711
712 /**
713 * Replies the a localized name for this primitive given by the value of the tags (in this order)
714 * <ul>
715 * <li>name:lang_COUNTRY_Variant of the current locale</li>
716 * <li>name:lang_COUNTRY of the current locale</li>
717 * <li>name:lang of the current locale</li>
718 * <li>name of the current locale</li>
719 * </ul>
720 *
721 * null, if no such tag exists
722 *
723 * @return the name of this primitive
724 */
725 public String getLocalName() {
726 String key = "name:" + Locale.getDefault().toString();
727 if (get(key) != null)
728 return get(key);
729 key = "name:" + Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry();
730 if (get(key) != null)
731 return get(key);
732 key = "name:" + Locale.getDefault().getLanguage();
733 if (get(key) != null)
734 return get(key);
735 return getName();
736 }
737
738 /**
739 * Replies the display name of a primitive formatted by <code>formatter</code>
740 *
741 * @return the display name
742 */
743 public abstract String getDisplayName(NameFormatter formatter);
744
745}
746
747
Note: See TracBrowser for help on using the repository browser.