001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.notes;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.time.Instant;
007import java.util.ArrayList;
008import java.util.Comparator;
009import java.util.List;
010import java.util.Objects;
011
012import org.openstreetmap.josm.data.coor.LatLon;
013
014import javax.annotation.Nullable;
015
016/**
017 * A map note. It always has at least one comment since a comment is required to create a note on osm.org.
018 * @since 7451
019 */
020public class Note {
021
022    /** Note state */
023    public enum State {
024        /** Note is open */
025        OPEN,
026        /** Note is closed */
027        CLOSED
028    }
029
030    /**
031     * Sorts notes in the following order:
032     * 1) Open notes
033     * 2) Closed notes
034     * 3) New notes
035     * Within each subgroup it sorts by ID
036     */
037    public static final Comparator<Note> DEFAULT_COMPARATOR = (n1, n2) -> {
038        if (n1.getId() < 0 && n2.getId() > 0) {
039            return 1;
040        }
041        if (n1.getId() > 0 && n2.getId() < 0) {
042            return -1;
043        }
044        if (n1.getState() == State.CLOSED && n2.getState() == State.OPEN) {
045            return 1;
046        }
047        if (n1.getState() == State.OPEN && n2.getState() == State.CLOSED) {
048            return -1;
049        }
050        return Long.compare(Math.abs(n1.getId()), Math.abs(n2.getId()));
051    };
052
053    /** Sorts notes strictly by creation date */
054    public static final Comparator<Note> DATE_COMPARATOR = Comparator.comparing(n -> n.createdAt);
055
056    /** Sorts notes by user, then creation date */
057    public static final Comparator<Note> USER_COMPARATOR =
058            Comparator.comparing(Note::getUserName, Comparator.nullsLast(Comparator.naturalOrder())).thenComparing(DATE_COMPARATOR);
059
060    /** Sorts notes by the last modified date */
061    public static final Comparator<Note> LAST_ACTION_COMPARATOR = Comparator.comparing(Note::getLastComment, NoteComment.DATE_COMPARATOR);
062
063    private long id;
064    private LatLon latLon;
065    private Instant createdAt;
066    private Instant closedAt;
067    private State state;
068    private List<NoteComment> comments = new ArrayList<>();
069
070    /**
071     * Create a note with a given location
072     * @param latLon Geographic location of this note
073     */
074    public Note(LatLon latLon) {
075        this.latLon = latLon;
076    }
077
078    /**
079     * Returns the unique OSM ID of this note.
080     * @return The unique OSM ID of this note
081     */
082    public long getId() {
083        return id;
084    }
085
086    /**
087     * Sets note id.
088     * @param id OSM ID of this note
089     */
090    public void setId(long id) {
091        this.id = id;
092    }
093
094    /**
095     * Returns the geographic location of the note.
096     * @return The geographic location of the note
097     */
098    public LatLon getLatLon() {
099        return latLon;
100    }
101
102    /**
103     * Returns the date at which this note was submitted.
104     * @return Date that this note was submitted
105     */
106    public Instant getCreatedAt() {
107        return createdAt;
108    }
109
110    /**
111     * Sets date at which this note has been created.
112     * @param createdAt date at which this note has been created
113     */
114    public void setCreatedAt(Instant createdAt) {
115        this.createdAt = createdAt;
116    }
117
118    /**
119     * Returns the date at which this note was closed.
120     * @return Date that this note was closed. Null if it is still open.
121     */
122    public Instant getClosedAt() {
123        return closedAt;
124    }
125
126    /**
127     * Sets date at which this note has been closed.
128     * @param closedAt date at which this note has been closed
129     */
130    public void setClosedAt(Instant closedAt) {
131        this.closedAt = closedAt;
132    }
133
134    /**
135     * Returns the open or closed state of this note.
136     * @return The open or closed state of this note
137     */
138    public State getState() {
139        return state;
140    }
141
142    /**
143     * Sets the note state.
144     * @param state note state (open or closed)
145     */
146    public void setState(State state) {
147        this.state = state;
148    }
149
150    /**
151     * Returns the list of comments associated with this note.
152     * @return An ordered list of comments associated with this note
153     */
154    public List<NoteComment> getComments() {
155        return comments;
156    }
157
158    /**
159     * Returns the last comment, or {@code null}.
160     * @return the last comment, or {@code null}
161     * @since 11821
162     */
163    @Nullable
164    public NoteComment getLastComment() {
165        return comments.isEmpty() ? null : comments.get(comments.size()-1);
166    }
167
168    /**
169     * Adds a comment.
170     * @param comment note comment
171     */
172    public void addComment(NoteComment comment) {
173        comments.add(comment);
174    }
175
176    /**
177     * Returns the comment that was submitted by the user when creating the note
178     * @return First comment object
179     */
180    @Nullable
181    public NoteComment getFirstComment() {
182        return comments.isEmpty() ? null : comments.get(0);
183    }
184
185    @Nullable
186    private String getUserName() {
187        return getFirstComment() == null ? null : getFirstComment().getUser().getName();
188    }
189
190    /**
191     * Copies values from a new note into an existing one. Used after a note
192     * has been updated on the server and the local copy needs refreshing.
193     * @param note New values to copy
194     */
195    public void updateWith(Note note) {
196        this.comments = note.comments;
197        this.createdAt = note.createdAt;
198        this.id = note.id;
199        this.state = note.state;
200        this.latLon = note.latLon;
201    }
202
203    @Override
204    public int hashCode() {
205        return Objects.hash(id);
206    }
207
208    @Override
209    public boolean equals(Object obj) {
210        if (this == obj)
211            return true;
212        if (obj == null || getClass() != obj.getClass())
213            return false;
214        Note note = (Note) obj;
215        return id == note.id;
216    }
217
218    @Override
219    public String toString() {
220        return tr("Note") + ' ' + id + ": " + getFirstComment();
221    }
222}