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 @Override 265 public int getNumKeys() { 266 return tags.size(); 267 } 268 269 /** 270 * Replies the key/value map. 271 * @return the key/value map 272 */ 273 public Map<String, String> getTags() { 274 return Collections.unmodifiableMap(tags); 275 } 276 277 /** 278 * Returns the changeset for this history primitive. 279 * @return the changeset for this history primitive 280 */ 281 public Changeset getChangeset() { 282 return changeset; 283 } 284 285 /** 286 * Sets the changeset for this history primitive. 287 * @param changeset the changeset for this history primitive 288 */ 289 public void setChangeset(Changeset changeset) { 290 this.changeset = changeset; 291 } 292 293 /** 294 * Sets the tags for this history primitive. Removes all tags if <code>tags</code> is null. 295 * 296 * @param tags the tags. May be null. 297 */ 298 public void setTags(Map<String, String> tags) { 299 if (tags == null) { 300 this.tags = new HashMap<>(); 301 } else { 302 this.tags = new HashMap<>(tags); 303 } 304 } 305 306 /** 307 * Replies the name of this primitive. The default implementation replies the value 308 * of the tag <code>name</code> or null, if this tag is not present. 309 * 310 * @return the name of this primitive 311 */ 312 public String getName() { 313 if (get("name") != null) 314 return get("name"); 315 return null; 316 } 317 318 /** 319 * Replies the display name of a primitive formatted by <code>formatter</code> 320 * @param formatter The formatter used to generate a display name 321 * 322 * @return the display name 323 */ 324 public abstract String getDisplayName(HistoryNameFormatter formatter); 325 326 /** 327 * Replies the a localized name for this primitive given by the value of the tags (in this order) 328 * <ul> 329 * <li>name:lang_COUNTRY_Variant of the current locale</li> 330 * <li>name:lang_COUNTRY of the current locale</li> 331 * <li>name:lang of the current locale</li> 332 * <li>name of the current locale</li> 333 * </ul> 334 * 335 * null, if no such tag exists 336 * 337 * @return the name of this primitive 338 */ 339 public String getLocalName() { 340 String key = "name:" + Locale.getDefault(); 341 if (get(key) != null) 342 return get(key); 343 key = "name:" + Locale.getDefault().getLanguage() + '_' + Locale.getDefault().getCountry(); 344 if (get(key) != null) 345 return get(key); 346 key = "name:" + Locale.getDefault().getLanguage(); 347 if (get(key) != null) 348 return get(key); 349 return getName(); 350 } 351 352 /** 353 * Fills the attributes common to all primitives with values from this history. 354 * @param data primitive data to fill 355 */ 356 protected void fillPrimitiveCommonData(PrimitiveData data) { 357 data.setUser(user); 358 try { 359 data.setVisible(visible); 360 } catch (IllegalStateException e) { 361 Logging.log(Logging.LEVEL_ERROR, "Cannot change visibility for "+data+':', e); 362 } 363 data.setTimestamp(timestamp); 364 data.setKeys(tags); 365 data.setOsmId(id, (int) version); 366 } 367 368 @Override 369 public int hashCode() { 370 return Objects.hash(id, version); 371 } 372 373 @Override 374 public boolean equals(Object obj) { 375 if (this == obj) return true; 376 if (obj == null || getClass() != obj.getClass()) return false; 377 HistoryOsmPrimitive that = (HistoryOsmPrimitive) obj; 378 return id == that.id && version == that.version; 379 } 380 381 @Override 382 public String toString() { 383 return getClass().getSimpleName() + " [version=" + version + ", id=" + id + ", visible=" + visible + ", " 384 + (timestamp != null ? ("timestamp=" + timestamp) : "") + ", " 385 + (user != null ? ("user=" + user + ", ") : "") + "changesetId=" 386 + changesetId 387 + ']'; 388 } 389}