001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import java.util.Collection;
005import java.util.Date;
006import java.util.HashMap;
007import java.util.Map;
008
009import org.openstreetmap.josm.data.Bounds;
010import org.openstreetmap.josm.data.coor.LatLon;
011import org.openstreetmap.josm.data.osm.visitor.Visitor;
012
013/**
014 * Represents a single changeset in JOSM. For now its only used during
015 * upload but in the future we may do more.
016 *
017 */
018public final class Changeset implements Tagged {
019
020    /** The maximum changeset comment text length allowed by API 0.6 **/
021    public static final int MAX_COMMENT_LENGTH = 255;
022
023    /** the changeset id */
024    private int id;
025    /** the user who owns the changeset */
026    private User user;
027    /** date this changeset was created at */
028    private Date createdAt;
029    /** the date this changeset was closed at*/
030    private Date closedAt;
031    /** indicates whether this changeset is still open or not */
032    private boolean open;
033    /** the min. coordinates of the bounding box of this changeset */
034    private LatLon min;
035    /** the max. coordinates of the bounding box of this changeset */
036    private LatLon max;
037    /** the map of tags */
038    private Map<String,String> tags;
039    /** indicates whether this changeset is incomplete. For an
040     * incomplete changeset we only know its id
041     */
042    private boolean incomplete;
043    /** the changeset content */
044    private ChangesetDataSet content = null;
045
046    /**
047     * Creates a new changeset with id 0.
048     */
049    public Changeset() {
050        this(0);
051    }
052
053    /**
054     * Creates a changeset with id <code>id</code>. If id &gt; 0, sets incomplete to true.
055     *
056     * @param id the id
057     */
058    public Changeset(int id) {
059        this.id = id;
060        this.incomplete = id > 0;
061        this.tags = new HashMap<>();
062    }
063
064    /**
065     * Creates a clone of <code>other</code>
066     *
067     * @param other the other changeset. If null, creates a new changeset with id 0.
068     */
069    public Changeset(Changeset other) {
070        if (other == null) {
071            this.id = 0;
072            this.tags = new HashMap<>();
073        } else if (other.isIncomplete()) {
074            setId(other.getId());
075            this.incomplete = true;
076            this.tags = new HashMap<>();
077        } else {
078            this.id = other.id;
079            mergeFrom(other);
080            this.incomplete = false;
081        }
082    }
083
084    public void visit(Visitor v) {
085        v.visit(this);
086    }
087
088    public int compareTo(Changeset other) {
089        return Integer.valueOf(getId()).compareTo(other.getId());
090    }
091
092    public String getName() {
093        // no translation
094        return "changeset " + getId();
095    }
096
097    public String getDisplayName(NameFormatter formatter) {
098        return formatter.format(this);
099    }
100
101    public int getId() {
102        return id;
103    }
104
105    public void setId(int id) {
106        this.id = id;
107    }
108
109    public User getUser() {
110        return user;
111    }
112
113    public void setUser(User user) {
114        this.user = user;
115    }
116
117    public Date getCreatedAt() {
118        return createdAt;
119    }
120
121    public void setCreatedAt(Date createdAt) {
122        this.createdAt = createdAt;
123    }
124
125    public Date getClosedAt() {
126        return closedAt;
127    }
128
129    public void setClosedAt(Date closedAt) {
130        this.closedAt = closedAt;
131    }
132
133    public boolean isOpen() {
134        return open;
135    }
136
137    public void setOpen(boolean open) {
138        this.open = open;
139    }
140
141    public LatLon getMin() {
142        return min;
143    }
144
145    public void setMin(LatLon min) {
146        this.min = min;
147    }
148
149    public LatLon getMax() {
150        return max;
151    }
152
153    public Bounds getBounds() {
154        if (min != null && max != null)
155            return new Bounds(min,max);
156        return null;
157    }
158
159    public void setMax(LatLon max) {
160        this.max = max;
161    }
162
163    @Override
164    public Map<String, String> getKeys() {
165        return tags;
166    }
167
168    @Override
169    public void setKeys(Map<String, String> keys) {
170        this.tags = keys;
171    }
172
173    public boolean isIncomplete() {
174        return incomplete;
175    }
176
177    public void setIncomplete(boolean incomplete) {
178        this.incomplete = incomplete;
179    }
180
181    @Override
182    public void put(String key, String value) {
183        this.tags.put(key, value);
184    }
185
186    @Override
187    public String get(String key) {
188        return this.tags.get(key);
189    }
190
191    @Override
192    public void remove(String key) {
193        this.tags.remove(key);
194    }
195
196    @Override
197    public void removeAll() {
198        this.tags.clear();
199    }
200
201    public boolean hasEqualSemanticAttributes(Changeset other) {
202        if (other == null)
203            return false;
204        if (closedAt == null) {
205            if (other.closedAt != null)
206                return false;
207        } else if (!closedAt.equals(other.closedAt))
208            return false;
209        if (createdAt == null) {
210            if (other.createdAt != null)
211                return false;
212        } else if (!createdAt.equals(other.createdAt))
213            return false;
214        if (id != other.id)
215            return false;
216        if (max == null) {
217            if (other.max != null)
218                return false;
219        } else if (!max.equals(other.max))
220            return false;
221        if (min == null) {
222            if (other.min != null)
223                return false;
224        } else if (!min.equals(other.min))
225            return false;
226        if (open != other.open)
227            return false;
228        if (tags == null) {
229            if (other.tags != null)
230                return false;
231        } else if (!tags.equals(other.tags))
232            return false;
233        if (user == null) {
234            if (other.user != null)
235                return false;
236        } else if (!user.equals(other.user))
237            return false;
238        return true;
239    }
240
241    @Override
242    public int hashCode() {
243        if (id > 0)
244            return id;
245        else
246            return super.hashCode();
247    }
248
249    @Override
250    public boolean equals(Object obj) {
251        if (this == obj)
252            return true;
253        if (obj == null)
254            return false;
255        if (getClass() != obj.getClass())
256            return false;
257        Changeset other = (Changeset) obj;
258        if (this.id > 0 && other.id == this.id)
259            return true;
260        return this == obj;
261    }
262
263    @Override
264    public boolean hasKeys() {
265        return !tags.keySet().isEmpty();
266    }
267
268    @Override
269    public Collection<String> keySet() {
270        return tags.keySet();
271    }
272
273    public boolean isNew() {
274        return id <= 0;
275    }
276
277    public void mergeFrom(Changeset other) {
278        if (other == null)
279            return;
280        if (id != other.id)
281            return;
282        this.user = other.user;
283        this.createdAt = other.createdAt;
284        this.closedAt = other.closedAt;
285        this.open  = other.open;
286        this.min = other.min;
287        this.max = other.max;
288        this.tags = new HashMap<>(other.tags);
289        this.incomplete = other.incomplete;
290
291        // FIXME: merging of content required?
292        this.content = other.content;
293    }
294
295    public boolean hasContent() {
296        return content != null;
297    }
298
299    public ChangesetDataSet getContent() {
300        return content;
301    }
302
303    public void setContent(ChangesetDataSet content) {
304        this.content = content;
305    }
306}