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    }