001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm; 003 004import java.util.ArrayList; 005import java.util.Collections; 006import java.util.Comparator; 007import java.util.Date; 008import java.util.List; 009import java.util.Map; 010 011import org.openstreetmap.josm.Main; 012import org.openstreetmap.josm.data.coor.LatLon; 013import org.openstreetmap.josm.data.notes.Note; 014import org.openstreetmap.josm.data.notes.Note.State; 015import org.openstreetmap.josm.data.notes.NoteComment; 016import org.openstreetmap.josm.gui.JosmUserIdentityManager; 017 018/** 019 * Class to hold and perform operations on a set of notes 020 */ 021public class NoteData { 022 023 private long newNoteId = -1; 024 025 private final List<Note> noteList; 026 private Note selectedNote = null; 027 private Comparator<Note> comparator = DEFAULT_COMPARATOR; 028 029 /** 030 * Sorts notes in the following order: 031 * 1) Open notes 032 * 2) Closed notes 033 * 3) New notes 034 * Within each subgroup it sorts by ID 035 */ 036 public static final Comparator<Note> DEFAULT_COMPARATOR = new Comparator<Note>() { 037 @Override 038 public int compare(Note n1, Note n2) { 039 if (n1.getId() < 0 && n2.getId() > 0) { 040 return 1; 041 } 042 if (n1.getId() > 0 && n2.getId() < 0) { 043 return -1; 044 } 045 if (n1.getState() == State.closed && n2.getState() == State.open) { 046 return 1; 047 } 048 if (n1.getState() == State.open && n2.getState() == State.closed) { 049 return -1; 050 } 051 return Long.valueOf(Math.abs(n1.getId())).compareTo(Long.valueOf(Math.abs(n2.getId()))); 052 } 053 }; 054 055 /** Sorts notes strictly by creation date */ 056 public static final Comparator<Note> DATE_COMPARATOR = new Comparator<Note>() { 057 @Override 058 public int compare(Note n1, Note n2) { 059 return n1.getCreatedAt().compareTo(n2.getCreatedAt()); 060 } 061 }; 062 063 /** Sorts notes by user, then creation date */ 064 public static final Comparator<Note> USER_COMPARATOR = new Comparator<Note>() { 065 @Override 066 public int compare(Note n1, Note n2) { 067 String n1User = n1.getFirstComment().getUser().getName(); 068 String n2User = n2.getFirstComment().getUser().getName(); 069 if (n1User.equals(n2User)) { 070 return n1.getCreatedAt().compareTo(n2.getCreatedAt()); 071 } 072 return n1.getFirstComment().getUser().getName().compareTo(n2.getFirstComment().getUser().getName()); 073 } 074 }; 075 076 /** Sorts notes by the last modified date */ 077 public static final Comparator<Note> LAST_ACTION_COMPARATOR = new Comparator<Note>() { 078 @Override 079 public int compare(Note n1, Note n2) { 080 Date n1Date = n1.getComments().get(n1.getComments().size()-1).getCommentTimestamp(); 081 Date n2Date = n2.getComments().get(n2.getComments().size()-1).getCommentTimestamp(); 082 return n1Date.compareTo(n2Date); 083 } 084 }; 085 086 /** 087 * Construct a new note container with an empty note list 088 */ 089 public NoteData() { 090 noteList = new ArrayList<>(); 091 } 092 093 /** 094 * Construct a new note container with a given list of notes 095 * @param notes The list of notes to populate the container with 096 */ 097 public NoteData(List<Note> notes) { 098 noteList = notes; 099 Collections.sort(notes, comparator); 100 for (Note note : notes) { 101 if (note.getId() <= newNoteId) { 102 newNoteId = note.getId() - 1; 103 } 104 } 105 } 106 107 /** 108 * Returns the notes stored in this layer 109 * @return List of Note objects 110 */ 111 public List<Note> getNotes() { 112 return noteList; 113 } 114 115 /** Returns the currently selected note 116 * @return currently selected note 117 */ 118 public Note getSelectedNote() { 119 return selectedNote; 120 } 121 122 /** Set a selected note. Causes the dialog to select the note and 123 * the note layer to draw the selected note's comments. 124 * @param note Selected note. Null indicates no selection 125 */ 126 public void setSelectedNote(Note note) { 127 selectedNote = note; 128 if (Main.map != null) { 129 Main.map.noteDialog.selectionChanged(); 130 Main.map.mapView.repaint(); 131 } 132 } 133 134 /** 135 * Return whether or not there are any changes in the note data set. 136 * These changes may need to be either uploaded or saved. 137 * @return true if local modifications have been made to the note data set. False otherwise. 138 */ 139 public synchronized boolean isModified() { 140 for (Note note : noteList) { 141 if (note.getId() < 0) { //notes with negative IDs are new 142 return true; 143 } 144 for (NoteComment comment : note.getComments()) { 145 if (comment.getIsNew()) { 146 return true; 147 } 148 } 149 } 150 return false; 151 } 152 153 /** 154 * Add notes to the data set. It only adds a note if the ID is not already present 155 * @param newNotes A list of notes to add 156 */ 157 public synchronized void addNotes(List<Note> newNotes) { 158 for (Note newNote : newNotes) { 159 if (!noteList.contains(newNote)) { 160 noteList.add(newNote); 161 } 162 if (newNote.getId() <= newNoteId) { 163 newNoteId = newNote.getId() - 1; 164 } 165 } 166 dataUpdated(); 167 if (Main.isDebugEnabled()) { 168 Main.debug("notes in current set: " + noteList.size()); 169 } 170 } 171 172 /** 173 * Create a new note 174 * @param location Location of note 175 * @param text Required comment with which to open the note 176 */ 177 public synchronized void createNote(LatLon location, String text) { 178 if(text == null || text.isEmpty()) { 179 throw new IllegalArgumentException("Comment can not be blank when creating a note"); 180 } 181 Note note = new Note(location); 182 note.setCreatedAt(new Date()); 183 note.setState(State.open); 184 note.setId(newNoteId--); 185 NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.opened, true); 186 note.addComment(comment); 187 if (Main.isDebugEnabled()) { 188 Main.debug("Created note {0} with comment: {1}", note.getId(), text); 189 } 190 noteList.add(note); 191 dataUpdated(); 192 } 193 194 /** 195 * Add a new comment to an existing note 196 * @param note Note to add comment to. Must already exist in the layer 197 * @param text Comment to add 198 */ 199 public synchronized void addCommentToNote(Note note, String text) { 200 if (!noteList.contains(note)) { 201 throw new IllegalArgumentException("Note to modify must be in layer"); 202 } 203 if (note.getState() == State.closed) { 204 throw new IllegalStateException("Cannot add a comment to a closed note"); 205 } 206 if (Main.isDebugEnabled()) { 207 Main.debug("Adding comment to note {0}: {1}", note.getId(), text); 208 } 209 NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.commented, true); 210 note.addComment(comment); 211 dataUpdated(); 212 } 213 214 /** 215 * Close note with comment 216 * @param note Note to close. Must already exist in the layer 217 * @param text Comment to attach to close action, if desired 218 */ 219 public synchronized void closeNote(Note note, String text) { 220 if (!noteList.contains(note)) { 221 throw new IllegalArgumentException("Note to close must be in layer"); 222 } 223 if (note.getState() != State.open) { 224 throw new IllegalStateException("Cannot close a note that isn't open"); 225 } 226 if (Main.isDebugEnabled()) { 227 Main.debug("closing note {0} with comment: {1}", note.getId(), text); 228 } 229 NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.closed, true); 230 note.addComment(comment); 231 note.setState(State.closed); 232 note.setClosedAt(new Date()); 233 dataUpdated(); 234 } 235 236 /** 237 * Reopen a closed note. 238 * @param note Note to reopen. Must already exist in the layer 239 * @param text Comment to attach to the reopen action, if desired 240 */ 241 public synchronized void reOpenNote(Note note, String text) { 242 if (!noteList.contains(note)) { 243 throw new IllegalArgumentException("Note to reopen must be in layer"); 244 } 245 if (note.getState() != State.closed) { 246 throw new IllegalStateException("Cannot reopen a note that isn't closed"); 247 } 248 if (Main.isDebugEnabled()) { 249 Main.debug("reopening note {0} with comment: {1}", note.getId(), text); 250 } 251 NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.reopened, true); 252 note.addComment(comment); 253 note.setState(State.open); 254 dataUpdated(); 255 } 256 257 private void dataUpdated() { 258 Collections.sort(noteList, comparator); 259 Main.map.noteDialog.setNoteList(noteList); 260 Main.map.mapView.repaint(); 261 } 262 263 private User getCurrentUser() { 264 JosmUserIdentityManager userMgr = JosmUserIdentityManager.getInstance(); 265 return User.createOsmUser(userMgr.getUserId(), userMgr.getUserName()); 266 } 267 268 /** 269 * Updates notes with new state. Primarily to be used when updating the 270 * note layer after uploading note changes to the server. 271 * @param updatedNotes Map containing the original note as the key and the updated note as the value 272 */ 273 public synchronized void updateNotes(Map<Note, Note> updatedNotes) { 274 for (Map.Entry<Note, Note> entry : updatedNotes.entrySet()) { 275 Note oldNote = entry.getKey(); 276 Note newNote = entry.getValue(); 277 oldNote.updateWith(newNote); 278 } 279 dataUpdated(); 280 } 281 282 /** @return The current comparator being used to sort the note list */ 283 public Comparator<Note> getCurrentSortMethod() { 284 return comparator; 285 } 286 287 /** Set the comparator to be used to sort the note list. Several are available 288 * as public static members of this class. 289 * @param comparator - The Note comparator to sort by 290 */ 291 public void setSortMethod(Comparator<Note> comparator) { 292 this.comparator = comparator; 293 dataUpdated(); 294 } 295}