source: josm/trunk/src/org/openstreetmap/josm/data/osm/Changeset.java @ 14231

Last change on this file since 14231 was 14231, checked in by Don-vip, 6 weeks ago

fix #16723 - Display changes count in changeset manager

  • Property svn:eol-style set to native
File size: 13.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import java.util.ArrayList;
5import java.util.Collection;
6import java.util.Collections;
7import java.util.Date;
8import java.util.HashMap;
9import java.util.List;
10import java.util.Map;
11import java.util.Objects;
12import java.util.Optional;
13
14import org.openstreetmap.josm.data.Bounds;
15import org.openstreetmap.josm.data.coor.LatLon;
16import org.openstreetmap.josm.tools.CheckParameterUtil;
17import org.openstreetmap.josm.tools.date.DateUtils;
18
19/**
20 * Represents a single changeset in JOSM. For now its only used during
21 * upload but in the future we may do more.
22 * @since 625
23 */
24public final class Changeset implements Tagged, Comparable<Changeset> {
25
26    /** The maximum changeset tag length allowed by API 0.6 **/
27    public static final int MAX_CHANGESET_TAG_LENGTH = MAX_TAG_LENGTH;
28
29    /** the changeset id */
30    private int id;
31    /** the user who owns the changeset */
32    private User user;
33    /** date this changeset was created at */
34    private Date createdAt;
35    /** the date this changeset was closed at*/
36    private Date closedAt;
37    /** indicates whether this changeset is still open or not */
38    private boolean open;
39    /** the min. coordinates of the bounding box of this changeset */
40    private LatLon min;
41    /** the max. coordinates of the bounding box of this changeset */
42    private LatLon max;
43    /** the number of comments for this changeset */
44    private int commentsCount;
45    /** the number of changes for this changeset */
46    private int changesCount;
47    /** the map of tags */
48    private Map<String, String> tags;
49    /** indicates whether this changeset is incomplete. For an incomplete changeset we only know its id */
50    private boolean incomplete;
51    /** the changeset content */
52    private ChangesetDataSet content;
53    /** the changeset discussion */
54    private List<ChangesetDiscussionComment> discussion;
55
56    /**
57     * Creates a new changeset with id 0.
58     */
59    public Changeset() {
60        this(0);
61    }
62
63    /**
64     * Creates a changeset with id <code>id</code>. If id &gt; 0, sets incomplete to true.
65     *
66     * @param id the id
67     */
68    public Changeset(int id) {
69        this.id = id;
70        this.incomplete = id > 0;
71        this.tags = new HashMap<>();
72    }
73
74    /**
75     * Creates a clone of <code>other</code>
76     *
77     * @param other the other changeset. If null, creates a new changeset with id 0.
78     */
79    public Changeset(Changeset other) {
80        if (other == null) {
81            this.id = 0;
82            this.tags = new HashMap<>();
83        } else if (other.isIncomplete()) {
84            setId(other.getId());
85            this.incomplete = true;
86            this.tags = new HashMap<>();
87        } else {
88            this.id = other.id;
89            mergeFrom(other);
90            this.incomplete = false;
91        }
92    }
93
94    /**
95     * Creates a changeset with the data obtained from the given preset, i.e.,
96     * the {@link AbstractPrimitive#getChangesetId() changeset id}, {@link AbstractPrimitive#getUser() user}, and
97     * {@link AbstractPrimitive#getTimestamp() timestamp}.
98     * @param primitive the primitive to use
99     * @return the created changeset
100     */
101    public static Changeset fromPrimitive(final OsmPrimitive primitive) {
102        final Changeset changeset = new Changeset(primitive.getChangesetId());
103        changeset.setUser(primitive.getUser());
104        changeset.setCreatedAt(primitive.getTimestamp()); // not accurate in all cases
105        return changeset;
106    }
107
108    /**
109     * Compares this changeset to another, based on their identifier.
110     * @param other other changeset
111     * @return the value {@code 0} if {@code getId() == other.getId()};
112     *         a value less than {@code 0} if {@code getId() < other.getId()}; and
113     *         a value greater than {@code 0} if {@code getId() > other.getId()}
114     */
115    @Override
116    public int compareTo(Changeset other) {
117        return Integer.compare(getId(), other.getId());
118    }
119
120    /**
121     * Returns the changeset name.
122     * @return the changeset name (untranslated: "changeset &lt;identifier&gt;")
123     */
124    public String getName() {
125        // no translation
126        return "changeset " + getId();
127    }
128
129    /**
130     * Returns the changeset display name, as per given name formatter.
131     * @param formatter name formatter
132     * @return the changeset display name, as per given name formatter
133     */
134    public String getDisplayName(NameFormatter formatter) {
135        return formatter.format(this);
136    }
137
138    /**
139     * Returns the changeset identifier.
140     * @return the changeset identifier
141     */
142    public int getId() {
143        return id;
144    }
145
146    /**
147     * Sets the changeset identifier.
148     * @param id changeset identifier
149     */
150    public void setId(int id) {
151        this.id = id;
152    }
153
154    /**
155     * Returns the changeset user.
156     * @return the changeset user
157     */
158    public User getUser() {
159        return user;
160    }
161
162    /**
163     * Sets the changeset user.
164     * @param user changeset user
165     */
166    public void setUser(User user) {
167        this.user = user;
168    }
169
170    /**
171     * Returns the changeset creation date.
172     * @return the changeset creation date
173     */
174    public Date getCreatedAt() {
175        return DateUtils.cloneDate(createdAt);
176    }
177
178    /**
179     * Sets the changeset creation date.
180     * @param createdAt changeset creation date
181     */
182    public void setCreatedAt(Date createdAt) {
183        this.createdAt = DateUtils.cloneDate(createdAt);
184    }
185
186    /**
187     * Returns the changeset closure date.
188     * @return the changeset closure date
189     */
190    public Date getClosedAt() {
191        return DateUtils.cloneDate(closedAt);
192    }
193
194    /**
195     * Sets the changeset closure date.
196     * @param closedAt changeset closure date
197     */
198    public void setClosedAt(Date closedAt) {
199        this.closedAt = DateUtils.cloneDate(closedAt);
200    }
201
202    /**
203     * Determines if this changeset is open.
204     * @return {@code true} if this changeset is open
205     */
206    public boolean isOpen() {
207        return open;
208    }
209
210    /**
211     * Sets whether this changeset is open.
212     * @param open {@code true} if this changeset is open
213     */
214    public void setOpen(boolean open) {
215        this.open = open;
216    }
217
218    /**
219     * Returns the min lat/lon of the changeset bounding box.
220     * @return the min lat/lon of the changeset bounding box
221     */
222    public LatLon getMin() {
223        return min;
224    }
225
226    /**
227     * Sets the min lat/lon of the changeset bounding box.
228     * @param min min lat/lon of the changeset bounding box
229     */
230    public void setMin(LatLon min) {
231        this.min = min;
232    }
233
234    /**
235     * Returns the max lat/lon of the changeset bounding box.
236     * @return the max lat/lon of the changeset bounding box
237     */
238    public LatLon getMax() {
239        return max;
240    }
241
242    /**
243     * Sets the max lat/lon of the changeset bounding box.
244     * @param max min lat/lon of the changeset bounding box
245     */
246    public void setMax(LatLon max) {
247        this.max = max;
248    }
249
250    /**
251     * Returns the changeset bounding box.
252     * @return the changeset bounding box
253     */
254    public Bounds getBounds() {
255        if (min != null && max != null)
256            return new Bounds(min, max);
257        return null;
258    }
259
260    /**
261     * Replies this changeset comment.
262     * @return this changeset comment (empty string if missing)
263     * @since 12494
264     */
265    public String getComment() {
266        return Optional.ofNullable(get("comment")).orElse("");
267    }
268
269    /**
270     * Replies the number of comments for this changeset discussion.
271     * @return the number of comments for this changeset discussion
272     * @since 7700
273     */
274    public int getCommentsCount() {
275        return commentsCount;
276    }
277
278    /**
279     * Sets the number of comments for this changeset discussion.
280     * @param commentsCount the number of comments for this changeset discussion
281     * @since 7700
282     */
283    public void setCommentsCount(int commentsCount) {
284        this.commentsCount = commentsCount;
285    }
286
287    /**
288     * Replies the number of changes for this changeset.
289     * @return the number of changes for this changeset
290     * @since 14231
291     */
292    public int getChangesCount() {
293        return changesCount;
294    }
295
296    /**
297     * Sets the number of changes for this changeset.
298     * @param changesCount the number of changes for this changeset
299     * @since 14231
300     */
301    public void setChangesCount(int changesCount) {
302        this.changesCount = changesCount;
303    }
304
305    @Override
306    public Map<String, String> getKeys() {
307        return tags;
308    }
309
310    @Override
311    public void setKeys(Map<String, String> keys) {
312        CheckParameterUtil.ensureParameterNotNull(keys, "keys");
313        keys.values().stream()
314                .filter(value -> value != null && value.length() > MAX_CHANGESET_TAG_LENGTH)
315                .findFirst()
316                .ifPresent(value -> {
317                throw new IllegalArgumentException("Changeset tag value is too long: "+value);
318        });
319        this.tags = keys;
320    }
321
322    /**
323     * Determines if this changeset is incomplete.
324     * @return {@code true} if this changeset is incomplete
325     */
326    public boolean isIncomplete() {
327        return incomplete;
328    }
329
330    /**
331     * Sets whether this changeset is incomplete
332     * @param incomplete {@code true} if this changeset is incomplete
333     */
334    public void setIncomplete(boolean incomplete) {
335        this.incomplete = incomplete;
336    }
337
338    @Override
339    public void put(String key, String value) {
340        CheckParameterUtil.ensureParameterNotNull(key, "key");
341        if (value != null && value.length() > MAX_CHANGESET_TAG_LENGTH) {
342            throw new IllegalArgumentException("Changeset tag value is too long: "+value);
343        }
344        this.tags.put(key, value);
345    }
346
347    @Override
348    public String get(String key) {
349        return this.tags.get(key);
350    }
351
352    @Override
353    public void remove(String key) {
354        this.tags.remove(key);
355    }
356
357    @Override
358    public void removeAll() {
359        this.tags.clear();
360    }
361
362    /**
363     * Determines if this changeset has equals semantic attributes with another one.
364     * @param other other changeset
365     * @return {@code true} if this changeset has equals semantic attributes with other changeset
366     */
367    public boolean hasEqualSemanticAttributes(Changeset other) {
368        return other != null
369            && id == other.id
370            && open == other.open
371            && commentsCount == other.commentsCount
372            && changesCount == other.changesCount
373            && Objects.equals(closedAt, other.closedAt)
374            && Objects.equals(createdAt, other.createdAt)
375            && Objects.equals(min, other.min)
376            && Objects.equals(max, other.max)
377            && Objects.equals(tags, other.tags)
378            && Objects.equals(user, other.user);
379    }
380
381    @Override
382    public int hashCode() {
383        return Objects.hash(id);
384    }
385
386    @Override
387    public boolean equals(Object obj) {
388        if (this == obj) return true;
389        if (obj == null || getClass() != obj.getClass()) return false;
390        Changeset changeset = (Changeset) obj;
391        return id == changeset.id;
392    }
393
394    @Override
395    public boolean hasKeys() {
396        return !tags.keySet().isEmpty();
397    }
398
399    @Override
400    public Collection<String> keySet() {
401        return tags.keySet();
402    }
403
404    @Override
405    public int getNumKeys() {
406        return tags.size();
407    }
408
409    /**
410     * Determines if this changeset is new.
411     * @return {@code true} if this changeset is new ({@code id <= 0})
412     */
413    public boolean isNew() {
414        return id <= 0;
415    }
416
417    /**
418     * Merges changeset metadata from another changeset.
419     * @param other other changeset
420     */
421    public void mergeFrom(Changeset other) {
422        if (other == null)
423            return;
424        if (id != other.id)
425            return;
426        this.user = other.user;
427        this.createdAt = DateUtils.cloneDate(other.createdAt);
428        this.closedAt = DateUtils.cloneDate(other.closedAt);
429        this.open = other.open;
430        this.min = other.min;
431        this.max = other.max;
432        this.commentsCount = other.commentsCount;
433        this.changesCount = other.changesCount;
434        this.tags = new HashMap<>(other.tags);
435        this.incomplete = other.incomplete;
436        this.discussion = other.discussion != null ? new ArrayList<>(other.discussion) : null;
437
438        // FIXME: merging of content required?
439        this.content = other.content;
440    }
441
442    /**
443     * Determines if this changeset has contents.
444     * @return {@code true} if this changeset has contents
445     */
446    public boolean hasContent() {
447        return content != null;
448    }
449
450    /**
451     * Returns the changeset contents.
452     * @return the changeset contents, can be null
453     */
454    public ChangesetDataSet getContent() {
455        return content;
456    }
457
458    /**
459     * Sets the changeset contents.
460     * @param content changeset contents, can be null
461     */
462    public void setContent(ChangesetDataSet content) {
463        this.content = content;
464    }
465
466    /**
467     * Replies the list of comments in the changeset discussion, if any.
468     * @return the list of comments in the changeset discussion. May be empty but never null
469     * @since 7704
470     */
471    public synchronized List<ChangesetDiscussionComment> getDiscussion() {
472        if (discussion == null) {
473            return Collections.emptyList();
474        }
475        return new ArrayList<>(discussion);
476    }
477
478    /**
479     * Adds a comment to the changeset discussion.
480     * @param comment the comment to add. Ignored if null
481     * @since 7704
482     */
483    public synchronized void addDiscussionComment(ChangesetDiscussionComment comment) {
484        if (comment == null) {
485            return;
486        }
487        if (discussion == null) {
488            discussion = new ArrayList<>();
489        }
490        discussion.add(comment);
491    }
492}
Note: See TracBrowser for help on using the repository browser.