001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.Date;
008import java.util.HashMap;
009import java.util.List;
010import java.util.Map;
011
012import org.openstreetmap.josm.data.Bounds;
013import org.openstreetmap.josm.data.coor.LatLon;
014import org.openstreetmap.josm.data.osm.visitor.Visitor;
015import org.openstreetmap.josm.tools.CheckParameterUtil;
016
017/**
018 * Represents a single changeset in JOSM. For now its only used during
019 * upload but in the future we may do more.
020 *
021 */
022public final class Changeset implements Tagged {
023
024    /** The maximum changeset tag length allowed by API 0.6 **/
025    public static final int MAX_CHANGESET_TAG_LENGTH = 255;
026
027    /** the changeset id */
028    private int id;
029    /** the user who owns the changeset */
030    private User user;
031    /** date this changeset was created at */
032    private Date createdAt;
033    /** the date this changeset was closed at*/
034    private Date closedAt;
035    /** indicates whether this changeset is still open or not */
036    private boolean open;
037    /** the min. coordinates of the bounding box of this changeset */
038    private LatLon min;
039    /** the max. coordinates of the bounding box of this changeset */
040    private LatLon max;
041    /** the number of comments for this changeset */
042    private int commentsCount;
043    /** the map of tags */
044    private Map<String,String> tags;
045    /** indicates whether this changeset is incomplete. For an incomplete changeset we only know its id */
046    private boolean incomplete;
047    /** the changeset content */
048    private ChangesetDataSet content = null;
049    /** the changeset discussion */
050    private List<ChangesetDiscussionComment> discussion = null;
051
052    /**
053     * Creates a new changeset with id 0.
054     */
055    public Changeset() {
056        this(0);
057    }
058
059    /**
060     * Creates a changeset with id <code>id</code>. If id &gt; 0, sets incomplete to true.
061     *
062     * @param id the id
063     */
064    public Changeset(int id) {
065        this.id = id;
066        this.incomplete = id > 0;
067        this.tags = new HashMap<>();
068    }
069
070    /**
071     * Creates a clone of <code>other</code>
072     *
073     * @param other the other changeset. If null, creates a new changeset with id 0.
074     */
075    public Changeset(Changeset other) {
076        if (other == null) {
077            this.id = 0;
078            this.tags = new HashMap<>();
079        } else if (other.isIncomplete()) {
080            setId(other.getId());
081            this.incomplete = true;
082            this.tags = new HashMap<>();
083        } else {
084            this.id = other.id;
085            mergeFrom(other);
086            this.incomplete = false;
087        }
088    }
089
090    public void visit(Visitor v) {
091        v.visit(this);
092    }
093
094    public int compareTo(Changeset other) {
095        return Integer.valueOf(getId()).compareTo(other.getId());
096    }
097
098    public String getName() {
099        // no translation
100        return "changeset " + getId();
101    }
102
103    public String getDisplayName(NameFormatter formatter) {
104        return formatter.format(this);
105    }
106
107    public int getId() {
108        return id;
109    }
110
111    public void setId(int id) {
112        this.id = id;
113    }
114
115    public User getUser() {
116        return user;
117    }
118
119    public void setUser(User user) {
120        this.user = user;
121    }
122
123    public Date getCreatedAt() {
124        return createdAt;
125    }
126
127    public void setCreatedAt(Date createdAt) {
128        this.createdAt = createdAt;
129    }
130
131    public Date getClosedAt() {
132        return closedAt;
133    }
134
135    public void setClosedAt(Date closedAt) {
136        this.closedAt = closedAt;
137    }
138
139    public boolean isOpen() {
140        return open;
141    }
142
143    public void setOpen(boolean open) {
144        this.open = open;
145    }
146
147    public LatLon getMin() {
148        return min;
149    }
150
151    public void setMin(LatLon min) {
152        this.min = min;
153    }
154
155    public LatLon getMax() {
156        return max;
157    }
158
159    public Bounds getBounds() {
160        if (min != null && max != null)
161            return new Bounds(min,max);
162        return null;
163    }
164
165    public void setMax(LatLon max) {
166        this.max = max;
167    }
168
169    /**
170     * Replies the number of comments for this changeset.
171     * @return the number of comments for this changeset
172     * @since 7700
173     */
174    public final int getCommentsCount() {
175        return commentsCount;
176    }
177
178    /**
179     * Sets the number of comments for this changeset.
180     * @param commentsCount the number of comments for this changeset
181     * @since 7700
182     */
183    public final void setCommentsCount(int commentsCount) {
184        this.commentsCount = commentsCount;
185    }
186
187    @Override
188    public Map<String, String> getKeys() {
189        return tags;
190    }
191
192    @Override
193    public void setKeys(Map<String, String> keys) {
194        CheckParameterUtil.ensureParameterNotNull(keys, "keys");
195        for (String value : keys.values()) {
196            if (value != null && value.length() > MAX_CHANGESET_TAG_LENGTH) {
197                throw new IllegalArgumentException("Changeset tag value is too long: "+value);
198            }
199        }
200        this.tags = keys;
201    }
202
203    public boolean isIncomplete() {
204        return incomplete;
205    }
206
207    public void setIncomplete(boolean incomplete) {
208        this.incomplete = incomplete;
209    }
210
211    @Override
212    public void put(String key, String value) {
213        CheckParameterUtil.ensureParameterNotNull(key, "key");
214        if (value != null && value.length() > MAX_CHANGESET_TAG_LENGTH) {
215            throw new IllegalArgumentException("Changeset tag value is too long: "+value);
216        }
217        this.tags.put(key, value);
218    }
219
220    @Override
221    public String get(String key) {
222        return this.tags.get(key);
223    }
224
225    @Override
226    public void remove(String key) {
227        this.tags.remove(key);
228    }
229
230    @Override
231    public void removeAll() {
232        this.tags.clear();
233    }
234
235    public boolean hasEqualSemanticAttributes(Changeset other) {
236        if (other == null)
237            return false;
238        if (closedAt == null) {
239            if (other.closedAt != null)
240                return false;
241        } else if (!closedAt.equals(other.closedAt))
242            return false;
243        if (createdAt == null) {
244            if (other.createdAt != null)
245                return false;
246        } else if (!createdAt.equals(other.createdAt))
247            return false;
248        if (id != other.id)
249            return false;
250        if (max == null) {
251            if (other.max != null)
252                return false;
253        } else if (!max.equals(other.max))
254            return false;
255        if (min == null) {
256            if (other.min != null)
257                return false;
258        } else if (!min.equals(other.min))
259            return false;
260        if (open != other.open)
261            return false;
262        if (tags == null) {
263            if (other.tags != null)
264                return false;
265        } else if (!tags.equals(other.tags))
266            return false;
267        if (user == null) {
268            if (other.user != null)
269                return false;
270        } else if (!user.equals(other.user))
271            return false;
272        if (commentsCount != other.commentsCount) {
273            return false;
274        }
275        return true;
276    }
277
278    @Override
279    public int hashCode() {
280        if (id > 0)
281            return id;
282        else
283            return super.hashCode();
284    }
285
286    @Override
287    public boolean equals(Object obj) {
288        if (this == obj)
289            return true;
290        if (obj == null)
291            return false;
292        if (getClass() != obj.getClass())
293            return false;
294        Changeset other = (Changeset) obj;
295        if (this.id > 0 && other.id == this.id)
296            return true;
297        return this == obj;
298    }
299
300    @Override
301    public boolean hasKeys() {
302        return !tags.keySet().isEmpty();
303    }
304
305    @Override
306    public Collection<String> keySet() {
307        return tags.keySet();
308    }
309
310    public boolean isNew() {
311        return id <= 0;
312    }
313
314    public void mergeFrom(Changeset other) {
315        if (other == null)
316            return;
317        if (id != other.id)
318            return;
319        this.user = other.user;
320        this.createdAt = other.createdAt;
321        this.closedAt = other.closedAt;
322        this.open  = other.open;
323        this.min = other.min;
324        this.max = other.max;
325        this.commentsCount = other.commentsCount;
326        this.tags = new HashMap<>(other.tags);
327        this.incomplete = other.incomplete;
328        this.discussion = other.discussion != null ? new ArrayList<>(other.discussion) : null;
329
330        // FIXME: merging of content required?
331        this.content = other.content;
332    }
333
334    public boolean hasContent() {
335        return content != null;
336    }
337
338    public ChangesetDataSet getContent() {
339        return content;
340    }
341
342    public void setContent(ChangesetDataSet content) {
343        this.content = content;
344    }
345
346    /**
347     * Replies the list of comments in the changeset discussion, if any.
348     * @return the list of comments in the changeset discussion. May be empty but never null
349     * @since 7704
350     */
351    public synchronized final List<ChangesetDiscussionComment> getDiscussion() {
352        if (discussion == null) {
353            return Collections.emptyList();
354        }
355        return new ArrayList<>(discussion);
356    }
357
358    /**
359     * Adds a comment to the changeset discussion.
360     * @param comment the comment to add. Ignored if null
361     * @since 7704
362     */
363    public synchronized final void addDiscussionComment(ChangesetDiscussionComment comment) {
364        if (comment == null) {
365            return;
366        }
367        if (discussion == null) {
368            discussion = new ArrayList<>();
369        }
370        discussion.add(comment);
371    }
372}