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

Last change on this file since 16628 was 16628, checked in by simon04, 4 years ago

see #19334 - https://errorprone.info/bugpattern/MixedMutabilityReturnType

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