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 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<String, String>();
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<String, String>();
194        } else {
195            this.tags = new HashMap<String, String>(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}