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