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