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