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