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 > 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}