001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm.history; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.text.MessageFormat; 007import java.time.Instant; 008import java.util.Collection; 009import java.util.Collections; 010import java.util.Date; 011import java.util.HashMap; 012import java.util.Locale; 013import java.util.Map; 014import java.util.Objects; 015 016import org.openstreetmap.josm.data.osm.Changeset; 017import org.openstreetmap.josm.data.osm.Node; 018import org.openstreetmap.josm.data.osm.OsmPrimitive; 019import org.openstreetmap.josm.data.osm.PrimitiveData; 020import org.openstreetmap.josm.data.osm.PrimitiveId; 021import org.openstreetmap.josm.data.osm.Relation; 022import org.openstreetmap.josm.data.osm.SimplePrimitiveId; 023import org.openstreetmap.josm.data.osm.Tagged; 024import org.openstreetmap.josm.data.osm.User; 025import org.openstreetmap.josm.data.osm.Way; 026import org.openstreetmap.josm.tools.CheckParameterUtil; 027import org.openstreetmap.josm.tools.Logging; 028 029/** 030 * Represents an immutable OSM primitive in the context of a historical view on OSM data. 031 * @since 1670 032 */ 033public abstract class HistoryOsmPrimitive implements Tagged, Comparable<HistoryOsmPrimitive>, PrimitiveId { 034 035 private final long id; 036 private final boolean visible; 037 private final User user; 038 private final long changesetId; 039 private Changeset changeset; 040 private final Instant timestamp; 041 private final long version; 042 private Map<String, String> tags; 043 044 /** 045 * Constructs a new {@code HistoryOsmPrimitive}. 046 * 047 * @param id the id (> 0 required) 048 * @param version the version (> 0 required) 049 * @param visible whether the primitive is still visible 050 * @param user the user (!= null required) 051 * @param changesetId the changeset id (> 0 required) 052 * @param timestamp the timestamp (!= null required) 053 * 054 * @throws IllegalArgumentException if preconditions are violated 055 */ 056 protected HistoryOsmPrimitive(long id, long version, boolean visible, User user, long changesetId, Instant timestamp) { 057 this(id, version, visible, user, changesetId, timestamp, true); 058 } 059 060 /** 061 * Constructs a new {@code HistoryOsmPrimitive} with a configurable checking of historic parameters. 062 * This is needed to build virtual HistoryOsmPrimitives for modified primitives, which do not have a timestamp and a changeset id. 063 * 064 * @param id the id (> 0 required) 065 * @param version the version (> 0 required) 066 * @param visible whether the primitive is still visible 067 * @param user the user (!= null required) 068 * @param changesetId the changeset id (> 0 required if {@code checkHistoricParams} is true) 069 * @param timestamp the timestamp (!= null required if {@code checkHistoricParams} is true) 070 * @param checkHistoricParams if true, checks values of {@code changesetId} and {@code timestamp} 071 * 072 * @throws IllegalArgumentException if preconditions are violated 073 * @since 5440 074 */ 075 protected HistoryOsmPrimitive(long id, long version, boolean visible, User user, long changesetId, Instant timestamp, 076 boolean checkHistoricParams) { 077 ensurePositiveLong(id, "id"); 078 ensurePositiveLong(version, "version"); 079 CheckParameterUtil.ensureParameterNotNull(user, "user"); 080 if (checkHistoricParams) { 081 ensurePositiveLong(changesetId, "changesetId"); 082 CheckParameterUtil.ensureParameterNotNull(timestamp, "timestamp"); 083 } 084 this.id = id; 085 this.version = version; 086 this.visible = visible; 087 this.user = user; 088 this.changesetId = changesetId; 089 this.timestamp = timestamp; 090 this.tags = new HashMap<>(); 091 } 092 093 /** 094 * Constructs a new {@code HistoryOsmPrimitive} from an existing {@link OsmPrimitive}. 095 * @param p the primitive 096 */ 097 protected HistoryOsmPrimitive(OsmPrimitive p) { 098 this(p.getId(), p.getVersion(), p.isVisible(), p.getUser(), p.getChangesetId(), p.getInstant()); 099 } 100 101 /** 102 * Replies a new {@link HistoryNode}, {@link HistoryWay} or {@link HistoryRelation} from an existing {@link OsmPrimitive}. 103 * @param p the primitive 104 * @return a new {@code HistoryNode}, {@code HistoryWay} or {@code HistoryRelation} from {@code p}. 105 */ 106 public static HistoryOsmPrimitive forOsmPrimitive(OsmPrimitive p) { 107 if (p instanceof Node) { 108 return new HistoryNode((Node) p); 109 } else if (p instanceof Way) { 110 return new HistoryWay((Way) p); 111 } else if (p instanceof Relation) { 112 return new HistoryRelation((Relation) p); 113 } else { 114 return null; 115 } 116 } 117 118 /** 119 * Returns the id. 120 * @return the id 121 */ 122 public long getId() { 123 return id; 124 } 125 126 /** 127 * Returns the primitive id. 128 * @return the primitive id 129 */ 130 public PrimitiveId getPrimitiveId() { 131 return new SimplePrimitiveId(id, getType()); 132 } 133 134 /** 135 * Determines if the primitive is still visible. 136 * @return {@code true} if the primitive is still visible 137 */ 138 public boolean isVisible() { 139 return visible; 140 } 141 142 /** 143 * Returns the user. 144 * @return the user 145 */ 146 public User getUser() { 147 return user; 148 } 149 150 /** 151 * Returns the changeset id. 152 * @return the changeset id 153 */ 154 public long getChangesetId() { 155 return changesetId; 156 } 157 158 /** 159 * Returns the timestamp. 160 * @return the timestamp 161 * @deprecated Use {@link #getInstant()} 162 */ 163 @Deprecated 164 public Date getTimestamp() { 165 return Date.from(timestamp); 166 } 167 168 /** 169 * Returns the timestamp. 170 * @return the timestamp 171 */ 172 public Instant getInstant() { 173 return timestamp; 174 } 175 176 /** 177 * Returns the version. 178 * @return the version 179 */ 180 public long getVersion() { 181 return version; 182 } 183 184 /** 185 * Checks that value is positive. 186 * @param value value to check 187 * @param name parameter name for error message 188 * @throws IllegalArgumentException if {@code value <= 0} 189 */ 190 protected final void ensurePositiveLong(long value, String name) { 191 if (value <= 0) { 192 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected. Got ''{1}''.", name, value)); 193 } 194 } 195 196 /** 197 * Determines if this history matches given id and version. 198 * @param id Primitive identifier 199 * @param version Primitive version 200 * @return {@code true} if this history matches given id and version 201 */ 202 public boolean matches(long id, long version) { 203 return this.id == id && this.version == version; 204 } 205 206 /** 207 * Determines if this history matches given id. 208 * @param id Primitive identifier 209 * @return {@code true} if this history matches given id 210 */ 211 public boolean matches(long id) { 212 return this.id == id; 213 } 214 215 @Override 216 public final long getUniqueId() { 217 return getId(); 218 } 219 220 @Override 221 public final boolean isNew() { 222 return false; 223 } 224 225 @Override 226 public int compareTo(HistoryOsmPrimitive o) { 227 if (this.id != o.id) 228 throw new ClassCastException(tr("Cannot compare primitive with ID ''{0}'' to primitive with ID ''{1}''.", o.id, this.id)); 229 return Long.compare(this.version, o.version); 230 } 231 232 @Override 233 public final void put(String key, String value) { 234 tags.put(key, value); 235 } 236 237 @Override 238 public final String get(String key) { 239 return tags.get(key); 240 } 241 242 @Override 243 public final boolean hasKey(String key) { 244 return tags.containsKey(key); 245 } 246 247 @Override 248 public final Map<String, String> getKeys() { 249 return getTags(); 250 } 251 252 @Override 253 public final void setKeys(Map<String, String> keys) { 254 throw new UnsupportedOperationException(); 255 } 256 257 @Override 258 public final void remove(String key) { 259 throw new UnsupportedOperationException(); 260 } 261 262 @Override 263 public final void removeAll() { 264 throw new UnsupportedOperationException(); 265 } 266 267 @Override 268 public final boolean hasKeys() { 269 return !tags.isEmpty(); 270 } 271 272 @Override 273 public final Collection<String> keySet() { 274 return Collections.unmodifiableSet(tags.keySet()); 275 } 276 277 @Override 278 public int getNumKeys() { 279 return tags.size(); 280 } 281 282 /** 283 * Replies the key/value map. 284 * @return the key/value map 285 */ 286 public Map<String, String> getTags() { 287 return Collections.unmodifiableMap(tags); 288 } 289 290 /** 291 * Returns the changeset for this history primitive. 292 * @return the changeset for this history primitive 293 */ 294 public Changeset getChangeset() { 295 return changeset; 296 } 297 298 /** 299 * Sets the changeset for this history primitive. 300 * @param changeset the changeset for this history primitive 301 */ 302 public void setChangeset(Changeset changeset) { 303 this.changeset = changeset; 304 } 305 306 /** 307 * Sets the tags for this history primitive. Removes all tags if <code>tags</code> is null. 308 * 309 * @param tags the tags. May be null. 310 */ 311 public void setTags(Map<String, String> tags) { 312 if (tags == null) { 313 this.tags = new HashMap<>(); 314 } else { 315 this.tags = new HashMap<>(tags); 316 } 317 } 318 319 /** 320 * Replies the name of this primitive. The default implementation replies the value 321 * of the tag <code>name</code> or null, if this tag is not present. 322 * 323 * @return the name of this primitive 324 */ 325 public String getName() { 326 if (get("name") != null) 327 return get("name"); 328 return null; 329 } 330 331 /** 332 * Replies the display name of a primitive formatted by <code>formatter</code> 333 * @param formatter The formatter used to generate a display name 334 * 335 * @return the display name 336 */ 337 public abstract String getDisplayName(HistoryNameFormatter formatter); 338 339 /** 340 * Replies the a localized name for this primitive given by the value of the tags (in this order) 341 * <ul> 342 * <li>name:lang_COUNTRY_Variant of the current locale</li> 343 * <li>name:lang_COUNTRY of the current locale</li> 344 * <li>name:lang of the current locale</li> 345 * <li>name of the current locale</li> 346 * </ul> 347 * 348 * null, if no such tag exists 349 * 350 * @return the name of this primitive 351 */ 352 public String getLocalName() { 353 String key = "name:" + Locale.getDefault(); 354 if (get(key) != null) 355 return get(key); 356 key = "name:" + Locale.getDefault().getLanguage() + '_' + Locale.getDefault().getCountry(); 357 if (get(key) != null) 358 return get(key); 359 key = "name:" + Locale.getDefault().getLanguage(); 360 if (get(key) != null) 361 return get(key); 362 return getName(); 363 } 364 365 /** 366 * Fills the attributes common to all primitives with values from this history. 367 * @param data primitive data to fill 368 */ 369 protected void fillPrimitiveCommonData(PrimitiveData data) { 370 data.setUser(user); 371 try { 372 data.setVisible(visible); 373 } catch (IllegalStateException e) { 374 Logging.log(Logging.LEVEL_ERROR, "Cannot change visibility for "+data+':', e); 375 } 376 data.setInstant(timestamp); 377 data.setKeys(tags); 378 data.setOsmId(id, (int) version); 379 } 380 381 @Override 382 public int hashCode() { 383 return Objects.hash(id, version); 384 } 385 386 @Override 387 public boolean equals(Object obj) { 388 if (this == obj) return true; 389 if (obj == null || getClass() != obj.getClass()) return false; 390 HistoryOsmPrimitive that = (HistoryOsmPrimitive) obj; 391 return id == that.id && version == that.version; 392 } 393 394 @Override 395 public String toString() { 396 return getClass().getSimpleName() + " [version=" + version + ", id=" + id + ", visible=" + visible + ", " 397 + (timestamp != null ? ("timestamp=" + timestamp) : "") + ", " 398 + (user != null ? ("user=" + user + ", ") : "") + "changesetId=" 399 + changesetId 400 + ']'; 401 } 402}