source: josm/trunk/src/org/openstreetmap/josm/data/osm/NoteData.java@ 16548

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

fix #18801 - Allow layers to determine autosave functionality (patch by taylor.smock, modified)

  • Property svn:eol-style set to native
File size: 10.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import java.util.ArrayList;
5import java.util.Collection;
6import java.util.Collections;
7import java.util.Comparator;
8import java.util.Date;
9import java.util.List;
10import java.util.Map;
11
12import org.openstreetmap.josm.data.Data;
13import org.openstreetmap.josm.data.DataSource;
14import org.openstreetmap.josm.data.UserIdentityManager;
15import org.openstreetmap.josm.data.coor.LatLon;
16import org.openstreetmap.josm.data.notes.Note;
17import org.openstreetmap.josm.data.notes.Note.State;
18import org.openstreetmap.josm.data.notes.NoteComment;
19import org.openstreetmap.josm.tools.ListenerList;
20import org.openstreetmap.josm.tools.Logging;
21
22/**
23 * Class to hold and perform operations on a set of notes
24 */
25public class NoteData implements Data {
26
27 /**
28 * A listener that can be informed on note data changes.
29 * @author Michael Zangl
30 * @since 12343
31 */
32 public interface NoteDataUpdateListener {
33 /**
34 * Called when the note data is updated
35 * @param data The data that was changed
36 */
37 void noteDataUpdated(NoteData data);
38
39 /**
40 * The selected node was changed
41 * @param noteData The data of which the selected node was changed
42 */
43 void selectedNoteChanged(NoteData noteData);
44 }
45
46 private long newNoteId = -1;
47
48 private final Storage<Note> noteList;
49 private Note selectedNote;
50 private Comparator<Note> comparator = Note.DEFAULT_COMPARATOR;
51
52 private final ListenerList<NoteDataUpdateListener> listeners = ListenerList.create();
53
54 /**
55 * Construct a new note container without notes
56 * @since 14101
57 */
58 public NoteData() {
59 this(null);
60 }
61
62 /**
63 * Construct a new note container with a given list of notes
64 * @param notes The list of notes to populate the container with
65 */
66 public NoteData(Collection<Note> notes) {
67 noteList = new Storage<>();
68 if (notes != null) {
69 for (Note note : notes) {
70 noteList.add(note);
71 if (note.getId() <= newNoteId) {
72 newNoteId = note.getId() - 1;
73 }
74 }
75 }
76 }
77
78 /**
79 * Returns the notes stored in this layer
80 * @return collection of notes
81 */
82 public Collection<Note> getNotes() {
83 return Collections.unmodifiableCollection(noteList);
84 }
85
86 /**
87 * Returns the notes stored in this layer sorted according to {@link #comparator}
88 * @return sorted collection of notes
89 */
90 public Collection<Note> getSortedNotes() {
91 final List<Note> list = new ArrayList<>(noteList);
92 list.sort(comparator);
93 return list;
94 }
95
96 /**
97 * Returns the currently selected note
98 * @return currently selected note
99 */
100 public Note getSelectedNote() {
101 return selectedNote;
102 }
103
104 /**
105 * Set a selected note. Causes the dialog to select the note and
106 * the note layer to draw the selected note's comments.
107 * @param note Selected note. Null indicates no selection
108 */
109 public void setSelectedNote(Note note) {
110 selectedNote = note;
111 listeners.fireEvent(l -> l.selectedNoteChanged(this));
112 }
113
114 /**
115 * Return whether or not there are any changes in the note data set.
116 * These changes may need to be either uploaded or saved.
117 * @return true if local modifications have been made to the note data set. False otherwise.
118 */
119 public synchronized boolean isModified() {
120 for (Note note : noteList) {
121 if (note.getId() < 0) { //notes with negative IDs are new
122 return true;
123 }
124 for (NoteComment comment : note.getComments()) {
125 if (comment.isNew()) {
126 return true;
127 }
128 }
129 }
130 return false;
131 }
132
133 /**
134 * Merge notes from an existing note data.
135 * @param from existing note data
136 * @since 13437
137 */
138 public synchronized void mergeFrom(NoteData from) {
139 if (this != from) {
140 addNotes(from.noteList);
141 }
142 }
143
144 /**
145 * Add notes to the data set. It only adds a note if the ID is not already present
146 * @param newNotes A list of notes to add
147 */
148 public synchronized void addNotes(Collection<Note> newNotes) {
149 for (Note newNote : newNotes) {
150 if (!noteList.contains(newNote)) {
151 noteList.add(newNote);
152 } else {
153 final Note existingNote = noteList.get(newNote);
154 final boolean isDirty = existingNote.getComments().stream().anyMatch(NoteComment::isNew);
155 if (!isDirty) {
156 noteList.put(newNote);
157 } else {
158 // TODO merge comments?
159 Logging.info("Keeping existing note id={0} with uncommitted changes", String.valueOf(newNote.getId()));
160 }
161 }
162 if (newNote.getId() <= newNoteId) {
163 newNoteId = newNote.getId() - 1;
164 }
165 }
166 dataUpdated();
167 }
168
169 /**
170 * Create a new note
171 * @param location Location of note
172 * @param text Required comment with which to open the note
173 */
174 public synchronized void createNote(LatLon location, String text) {
175 if (text == null || text.isEmpty()) {
176 throw new IllegalArgumentException("Comment can not be blank when creating a note");
177 }
178 Note note = new Note(location);
179 note.setCreatedAt(new Date());
180 note.setState(State.OPEN);
181 note.setId(newNoteId--);
182 NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.OPENED, true);
183 note.addComment(comment);
184 if (Logging.isDebugEnabled()) {
185 Logging.debug("Created note {0} with comment: {1}", note.getId(), text);
186 }
187 noteList.add(note);
188 dataUpdated();
189 }
190
191 /**
192 * Add a new comment to an existing note
193 * @param note Note to add comment to. Must already exist in the layer
194 * @param text Comment to add
195 */
196 public synchronized void addCommentToNote(Note note, String text) {
197 if (!noteList.contains(note)) {
198 throw new IllegalArgumentException("Note to modify must be in layer");
199 }
200 if (note.getState() == State.CLOSED) {
201 throw new IllegalStateException("Cannot add a comment to a closed note");
202 }
203 if (Logging.isDebugEnabled()) {
204 Logging.debug("Adding comment to note {0}: {1}", note.getId(), text);
205 }
206 NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.COMMENTED, true);
207 note.addComment(comment);
208 dataUpdated();
209 }
210
211 /**
212 * Close note with comment
213 * @param note Note to close. Must already exist in the layer
214 * @param text Comment to attach to close action, if desired
215 */
216 public synchronized void closeNote(Note note, String text) {
217 if (!noteList.contains(note)) {
218 throw new IllegalArgumentException("Note to close must be in layer");
219 }
220 if (note.getState() != State.OPEN) {
221 throw new IllegalStateException("Cannot close a note that isn't open");
222 }
223 if (Logging.isDebugEnabled()) {
224 Logging.debug("closing note {0} with comment: {1}", note.getId(), text);
225 }
226 NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.CLOSED, true);
227 note.addComment(comment);
228 note.setState(State.CLOSED);
229 note.setClosedAt(new Date());
230 dataUpdated();
231 }
232
233 /**
234 * Reopen a closed note.
235 * @param note Note to reopen. Must already exist in the layer
236 * @param text Comment to attach to the reopen action, if desired
237 */
238 public synchronized void reOpenNote(Note note, String text) {
239 if (!noteList.contains(note)) {
240 throw new IllegalArgumentException("Note to reopen must be in layer");
241 }
242 if (note.getState() != State.CLOSED) {
243 throw new IllegalStateException("Cannot reopen a note that isn't closed");
244 }
245 Logging.debug("reopening note {0} with comment: {1}", note.getId(), text);
246 NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.REOPENED, true);
247 note.addComment(comment);
248 note.setState(State.OPEN);
249 dataUpdated();
250 }
251
252 private void dataUpdated() {
253 listeners.fireEvent(l -> l.noteDataUpdated(this));
254 }
255
256 private static User getCurrentUser() {
257 UserIdentityManager userMgr = UserIdentityManager.getInstance();
258 return User.createOsmUser(userMgr.getUserId(), userMgr.getUserName());
259 }
260
261 /**
262 * Updates notes with new state. Primarily to be used when updating the
263 * note layer after uploading note changes to the server.
264 * @param updatedNotes Map containing the original note as the key and the updated note as the value
265 */
266 public synchronized void updateNotes(Map<Note, Note> updatedNotes) {
267 for (Map.Entry<Note, Note> entry : updatedNotes.entrySet()) {
268 Note oldNote = entry.getKey();
269 Note newNote = entry.getValue();
270 boolean reindex = oldNote.hashCode() != newNote.hashCode();
271 if (reindex) {
272 noteList.removeElem(oldNote);
273 }
274 oldNote.updateWith(newNote);
275 if (reindex) {
276 noteList.add(oldNote);
277 }
278 }
279 dataUpdated();
280 }
281
282 /**
283 * Returns the current comparator being used to sort the note list.
284 * @return The current comparator being used to sort the note list
285 */
286 public Comparator<Note> getCurrentSortMethod() {
287 return comparator;
288 }
289
290 /** Set the comparator to be used to sort the note list. Several are available
291 * as public static members of this class.
292 * @param comparator - The Note comparator to sort by
293 */
294 public void setSortMethod(Comparator<Note> comparator) {
295 this.comparator = comparator;
296 dataUpdated();
297 }
298
299 /**
300 * Adds a listener that listens to node data changes
301 * @param listener The listener
302 */
303 public void addNoteDataUpdateListener(NoteDataUpdateListener listener) {
304 listeners.addListener(listener);
305 }
306
307 /**
308 * Removes a listener that listens to node data changes
309 * @param listener The listener
310 */
311 public void removeNoteDataUpdateListener(NoteDataUpdateListener listener) {
312 listeners.removeListener(listener);
313 }
314
315 @Override
316 public Collection<DataSource> getDataSources() {
317 return Collections.emptyList(); // Notes don't currently store data sources
318 }
319}
Note: See TracBrowser for help on using the repository browser.