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 }