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