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.Collections; 008import java.util.Date; 009import java.util.HashMap; 010import java.util.Locale; 011import java.util.Map; 012import java.util.Objects; 013 014import org.openstreetmap.josm.data.osm.Changeset; 015import org.openstreetmap.josm.data.osm.Node; 016import org.openstreetmap.josm.data.osm.OsmPrimitive; 017import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 018import org.openstreetmap.josm.data.osm.PrimitiveId; 019import org.openstreetmap.josm.data.osm.Relation; 020import org.openstreetmap.josm.data.osm.SimplePrimitiveId; 021import org.openstreetmap.josm.data.osm.User; 022import org.openstreetmap.josm.data.osm.Way; 023import org.openstreetmap.josm.tools.CheckParameterUtil; 024 025/** 026 * Represents an immutable OSM primitive in the context of a historical view on 027 * OSM data. 028 * 029 */ 030public abstract class HistoryOsmPrimitive implements Comparable<HistoryOsmPrimitive> { 031 032 private long id; 033 private boolean visible; 034 private User user; 035 private long changesetId; 036 private Changeset changeset; 037 private Date timestamp; 038 private long version; 039 private Map<String, String> tags; 040 041 protected final void ensurePositiveLong(long value, String name) { 042 if (value <= 0) { 043 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected. Got ''{1}''.", name, value)); 044 } 045 } 046 047 /** 048 * Constructs a new {@code HistoryOsmPrimitive}. 049 * 050 * @param id the id (> 0 required) 051 * @param version the version (> 0 required) 052 * @param visible whether the primitive is still visible 053 * @param user the user (!= null required) 054 * @param changesetId the changeset id (> 0 required) 055 * @param timestamp the timestamp (!= null required) 056 * 057 * @throws IllegalArgumentException if preconditions are violated 058 */ 059 public HistoryOsmPrimitive(long id, long version, boolean visible, User user, long changesetId, Date timestamp) { 060 this(id, version, visible, user, changesetId, timestamp, true); 061 } 062 063 /** 064 * Constructs a new {@code HistoryOsmPrimitive} with a configurable checking of historic parameters. 065 * This is needed to build virtual HistoryOsmPrimitives for modified primitives, which do not have a timestamp and a changeset id. 066 * 067 * @param id the id (> 0 required) 068 * @param version the version (> 0 required) 069 * @param visible whether the primitive is still visible 070 * @param user the user (!= null required) 071 * @param changesetId the changeset id (> 0 required if {@code checkHistoricParams} is true) 072 * @param timestamp the timestamp (!= null required if {@code checkHistoricParams} is true) 073 * @param checkHistoricParams if true, checks values of {@code changesetId} and {@code timestamp} 074 * 075 * @throws IllegalArgumentException if preconditions are violated 076 * @since 5440 077 */ 078 public HistoryOsmPrimitive(long id, long version, boolean visible, User user, long changesetId, Date timestamp, 079 boolean checkHistoricParams) { 080 ensurePositiveLong(id, "id"); 081 ensurePositiveLong(version, "version"); 082 CheckParameterUtil.ensureParameterNotNull(user, "user"); 083 if (checkHistoricParams) { 084 ensurePositiveLong(changesetId, "changesetId"); 085 CheckParameterUtil.ensureParameterNotNull(timestamp, "timestamp"); 086 } 087 this.id = id; 088 this.version = version; 089 this.visible = visible; 090 this.user = user; 091 this.changesetId = changesetId; 092 this.timestamp = timestamp; 093 tags = new HashMap<>(); 094 } 095 096 /** 097 * Constructs a new {@code HistoryOsmPrimitive} from an existing {@link OsmPrimitive}. 098 * @param p the primitive 099 */ 100 public HistoryOsmPrimitive(OsmPrimitive p) { 101 this(p.getId(), p.getVersion(), p.isVisible(), p.getUser(), p.getChangesetId(), p.getTimestamp()); 102 } 103 104 /** 105 * Replies a new {@link HistoryNode}, {@link HistoryWay} or {@link HistoryRelation} from an existing {@link OsmPrimitive}. 106 * @param p the primitive 107 * @return a new {@code HistoryNode}, {@code HistoryWay} or {@code HistoryRelation} from {@code p}. 108 */ 109 public static HistoryOsmPrimitive forOsmPrimitive(OsmPrimitive p) { 110 if (p instanceof Node) { 111 return new HistoryNode((Node) p); 112 } else if (p instanceof Way) { 113 return new HistoryWay((Way) p); 114 } else if (p instanceof Relation) { 115 return new HistoryRelation((Relation) p); 116 } else { 117 return null; 118 } 119 } 120 121 public long getId() { 122 return id; 123 } 124 125 public PrimitiveId getPrimitiveId() { 126 return new SimplePrimitiveId(id, getType()); 127 } 128 129 public boolean isVisible() { 130 return visible; 131 } 132 133 public User getUser() { 134 return user; 135 } 136 137 public long getChangesetId() { 138 return changesetId; 139 } 140 141 public Date getTimestamp() { 142 return timestamp; 143 } 144 145 public long getVersion() { 146 return version; 147 } 148 149 public boolean matches(long id, long version) { 150 return this.id == id && this.version == version; 151 } 152 153 public boolean matches(long id) { 154 return this.id == id; 155 } 156 157 public abstract OsmPrimitiveType getType(); 158 159 @Override 160 public int compareTo(HistoryOsmPrimitive o) { 161 if (this.id != o.id) 162 throw new ClassCastException(tr("Cannot compare primitive with ID ''{0}'' to primitive with ID ''{1}''.", o.id, this.id)); 163 return Long.compare(this.version, o.version); 164 } 165 166 public void put(String key, String value) { 167 tags.put(key, value); 168 } 169 170 public String get(String key) { 171 return tags.get(key); 172 } 173 174 public boolean hasTag(String key) { 175 return tags.get(key) != null; 176 } 177 178 public Map<String, String> getTags() { 179 return Collections.unmodifiableMap(tags); 180 } 181 182 /** 183 * Returns the changeset for this history primitive. 184 * @return the changeset for this history primitive 185 */ 186 public Changeset getChangeset() { 187 return changeset; 188 } 189 190 /** 191 * Sets the changeset for this history primitive. 192 * @param changeset the changeset for this history primitive 193 */ 194 public void setChangeset(Changeset changeset) { 195 this.changeset = changeset; 196 } 197 198 /** 199 * Sets the tags for this history primitive. Removes all 200 * tags if <code>tags</code> is null. 201 * 202 * @param tags the tags. May be null. 203 */ 204 public void setTags(Map<String, String> tags) { 205 if (tags == null) { 206 this.tags = new HashMap<>(); 207 } else { 208 this.tags = new HashMap<>(tags); 209 } 210 } 211 212 /** 213 * Replies the name of this primitive. The default implementation replies the value 214 * of the tag <tt>name</tt> or null, if this tag is not present. 215 * 216 * @return the name of this primitive 217 */ 218 public String getName() { 219 if (get("name") != null) 220 return get("name"); 221 return null; 222 } 223 224 /** 225 * Replies the display name of a primitive formatted by <code>formatter</code> 226 * @param formatter The formatter used to generate a display name 227 * 228 * @return the display name 229 */ 230 public abstract String getDisplayName(HistoryNameFormatter formatter); 231 232 /** 233 * Replies the a localized name for this primitive given by the value of the tags (in this order) 234 * <ul> 235 * <li>name:lang_COUNTRY_Variant of the current locale</li> 236 * <li>name:lang_COUNTRY of the current locale</li> 237 * <li>name:lang of the current locale</li> 238 * <li>name of the current locale</li> 239 * </ul> 240 * 241 * null, if no such tag exists 242 * 243 * @return the name of this primitive 244 */ 245 public String getLocalName() { 246 String key = "name:" + Locale.getDefault(); 247 if (get(key) != null) 248 return get(key); 249 key = "name:" + Locale.getDefault().getLanguage() + '_' + Locale.getDefault().getCountry(); 250 if (get(key) != null) 251 return get(key); 252 key = "name:" + Locale.getDefault().getLanguage(); 253 if (get(key) != null) 254 return get(key); 255 return getName(); 256 } 257 258 @Override 259 public int hashCode() { 260 return Objects.hash(id, version); 261 } 262 263 @Override 264 public boolean equals(Object obj) { 265 if (this == obj) return true; 266 if (obj == null || getClass() != obj.getClass()) return false; 267 HistoryOsmPrimitive that = (HistoryOsmPrimitive) obj; 268 return id == that.id && 269 version == that.version; 270 } 271 272 @Override 273 public String toString() { 274 return getClass().getSimpleName() + " [version=" + version + ", id=" + id + ", visible=" + visible + ", " 275 + (timestamp != null ? "timestamp=" + timestamp : "") + ", " 276 + (user != null ? "user=" + user + ", " : "") + "changesetId=" 277 + changesetId 278 + ']'; 279 } 280}