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

Last change on this file since 729 was 655, checked in by ramack, 16 years ago

patch by bruce89, closes #812; thanks bruce

  • Property svn:eol-style set to native
File size: 8.9 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.data.osm;
3
4import java.text.ParseException;
5import java.text.SimpleDateFormat;
6import java.util.Arrays;
7import java.util.Collection;
8import java.util.Collections;
9import java.util.Date;
10import java.util.HashMap;
11import java.util.HashSet;
12import java.util.Map;
13import java.util.Map.Entry;
14
15import org.openstreetmap.josm.data.osm.visitor.Visitor;
16import org.openstreetmap.josm.tools.DateParser;
17
18
19/**
20 * An OSM primitive can be associated with a key/value pair. It can be created, deleted
21 * and updated within the OSM-Server.
22 *
23 * Although OsmPrimitive is designed as a base class, it is not to be meant to subclass
24 * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed set that are given
25 * by the server environment and not an extendible data stuff.
26 *
27 * @author imi
28 */
29abstract public class OsmPrimitive implements Comparable<OsmPrimitive> {
30
31 /**
32 * The key/value list for this primitive.
33 */
34 public Map<String, String> keys;
35
36 /**
37 * Unique identifier in OSM. This is used to identify objects on the server.
38 * An id of 0 means an unknown id. The object has not been uploaded yet to
39 * know what id it will get.
40 *
41 * Do not write to this attribute except you know exactly what you are doing.
42 * More specific, it is not good to set this to 0 and think the object is now
43 * new to the server! To create a new object, call the default constructor of
44 * the respective class.
45 */
46 public long id = 0;
47
48 /**
49 * <code>true</code> if the object has been modified since it was loaded from
50 * the server. In this case, on next upload, this object will be updated.
51 * Deleted objects are deleted from the server. If the objects are added (id=0),
52 * the modified is ignored and the object is added to the server.
53 */
54 public boolean modified = false;
55
56 /**
57 * <code>true</code>, if the object has been deleted.
58 */
59 public boolean deleted = false;
60
61 /**
62 * Visibility status as specified by the server. The visible attribute was
63 * introduced with the 0.4 API to be able to communicate deleted objects
64 * (they will have visible=false). Currently JOSM does never deal with
65 * these, so this is really for future use only.
66 */
67 public boolean visible = true;
68
69 /**
70 * User that last modified this primitive, as specified by the server.
71 * Never changed by JOSM.
72 */
73 public User user = null;
74
75 /**
76 * true if this object is considered "tagged". To be "tagged", an object
77 * must have one or more "non-standard" tags. "created_by" and "source"
78 * are typically considered "standard" tags and do not make an object
79 * "tagged".
80 */
81 public boolean tagged = false;
82
83 /**
84 * true if this object has direction dependent tags (e.g. oneway)
85 */
86 public boolean hasDirectionKeys = false;
87
88 /**
89 * If set to true, this object is currently selected.
90 */
91 public volatile boolean selected = false;
92
93 /**
94 * Time of last modification to this object. This is not set by JOSM but
95 * read from the server and delivered back to the server unmodified. It is
96 * used to check against edit conflicts.
97 */
98 public String timestamp = null;
99
100 /**
101 * The timestamp is only parsed when this is really necessary, and this
102 * is the cache for the result.
103 */
104 public Date parsedTimestamp = null;
105
106 /**
107 * If set to true, this object is incomplete, which means only the id
108 * and type is known (type is the objects instance class)
109 */
110 public boolean incomplete = false;
111
112 /**
113 * Contains the version number as returned by the API. Needed to
114 * ensure update consistency
115 */
116 public int version = -1;
117
118 /**
119 * Contains a list of "uninteresting" keys that do not make an object
120 * "tagged".
121 */
122 public static Collection<String> uninteresting =
123 new HashSet<String>(Arrays.asList(new String[] {"source", "note", "created_by"}));
124
125 /**
126 * Contains a list of direction-dependent keys that make an object
127 * direction dependent.
128 */
129 public static Collection<String> directionKeys =
130 new HashSet<String>(Arrays.asList(new String[] {"oneway", "incline", "incline_steep", "aerialway"}));
131
132 /**
133 * Implementation of the visitor scheme. Subclasses have to call the correct
134 * visitor function.
135 * @param visitor The visitor from which the visit() function must be called.
136 */
137 abstract public void visit(Visitor visitor);
138
139 public final void delete(boolean deleted) {
140 this.deleted = deleted;
141 selected = false;
142 modified = true;
143 }
144
145 /**
146 * Returns the timestamp for this object, or the current time if none is set.
147 * Internally, parses the timestamp from XML into a Date object and caches it
148 * for possible repeated calls.
149 */
150 public Date getTimestamp() {
151 if (parsedTimestamp == null) {
152 try {
153 parsedTimestamp = DateParser.parse(timestamp);
154 } catch (ParseException ex) {
155 parsedTimestamp = new Date();
156 }
157 }
158 return parsedTimestamp;
159 }
160
161 /**
162 * Equal, if the id (and class) is equal.
163 *
164 * An primitive is equal to its incomplete counter part.
165 */
166 @Override public boolean equals(Object obj) {
167 if (id == 0) return obj == this;
168 if (obj instanceof OsmPrimitive) { // not null too
169 return ((OsmPrimitive)obj).id == id && obj.getClass() == getClass();
170 }
171 return false;
172 }
173
174 /**
175 * Return the id plus the class type encoded as hashcode or super's hashcode if id is 0.
176 *
177 * An primitive has the same hashcode as its incomplete counterpart.
178 */
179 @Override public final int hashCode() {
180 if (id == 0)
181 return super.hashCode();
182 final int[] ret = new int[1];
183 Visitor v = new Visitor(){
184 public void visit(Node n) { ret[0] = 1; }
185 public void visit(Way w) { ret[0] = 2; }
186 public void visit(Relation e) { ret[0] = 3; }
187 };
188 visit(v);
189 return id == 0 ? super.hashCode() : (int)(id<<2)+ret[0];
190 }
191
192 /**
193 * Set the given value to the given key
194 * @param key The key, for which the value is to be set.
195 * @param value The value for the key.
196 */
197 public final void put(String key, String value) {
198 if (value == null)
199 remove(key);
200 else {
201 if (keys == null)
202 keys = new HashMap<String, String>();
203 keys.put(key, value);
204 }
205 checkTagged();
206 checkDirectionTagged();
207 }
208 /**
209 * Remove the given key from the list.
210 */
211 public final void remove(String key) {
212 if (keys != null) {
213 keys.remove(key);
214 if (keys.isEmpty())
215 keys = null;
216 }
217 checkTagged();
218 checkDirectionTagged();
219 }
220
221 public final String get(String key) {
222 return keys == null ? null : keys.get(key);
223 }
224
225 public final Collection<Entry<String, String>> entrySet() {
226 if (keys == null)
227 return Collections.emptyList();
228 return keys.entrySet();
229 }
230
231 public final Collection<String> keySet() {
232 if (keys == null)
233 return Collections.emptyList();
234 return keys.keySet();
235 }
236
237 /**
238 * Get and write all attributes from the parameter. Does not fire any listener, so
239 * use this only in the data initializing phase
240 */
241 public void cloneFrom(OsmPrimitive osm) {
242 keys = osm.keys == null ? null : new HashMap<String, String>(osm.keys);
243 id = osm.id;
244 modified = osm.modified;
245 deleted = osm.deleted;
246 selected = osm.selected;
247 timestamp = osm.timestamp;
248 version = osm.version;
249 tagged = osm.tagged;
250 incomplete = osm.incomplete;
251 }
252
253 /**
254 * Perform an equality compare for all non-volatile fields not only for the id
255 * but for the whole object (for conflict resolving)
256 * @param semanticOnly if <code>true</code>, modified flag and timestamp are not compared
257 */
258 public boolean realEqual(OsmPrimitive osm, boolean semanticOnly) {
259 return
260 id == osm.id &&
261 incomplete == osm.incomplete &&
262 (semanticOnly || (modified == osm.modified)) &&
263 deleted == osm.deleted &&
264 (semanticOnly || (timestamp == null ? osm.timestamp==null : timestamp.equals(osm.timestamp))) &&
265 (semanticOnly || (version==osm.version)) &&
266 (semanticOnly || (user == null ? osm.user==null : user==osm.user)) &&
267 (semanticOnly || (visible == osm.visible)) &&
268 (keys == null ? osm.keys==null : keys.equals(osm.keys));
269 }
270
271 public String getTimeStr() {
272 return timestamp == null ? null : new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(timestamp);
273 }
274
275 /**
276 * Updates the "tagged" flag. "keys" property should probably be made private
277 * to make sure this gets called when keys are set.
278 */
279 public void checkTagged() {
280 tagged = false;
281 if (keys != null) {
282 for (Entry<String,String> e : keys.entrySet()) {
283 if (!uninteresting.contains(e.getKey())) {
284 tagged = true;
285 break;
286 }
287 }
288 }
289 }
290 /**
291 * Updates the "hasDirectionKeys" flag. "keys" property should probably be made private
292 * to make sure this gets called when keys are set.
293 */
294 public void checkDirectionTagged() {
295 hasDirectionKeys = false;
296 if (keys != null) {
297 for (Entry<String,String> e : keys.entrySet()) {
298 if (directionKeys.contains(e.getKey())) {
299 hasDirectionKeys = true;
300 break;
301 }
302 }
303 }
304
305 }
306
307}
Note: See TracBrowser for help on using the repository browser.