1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.atlassian.theplugin.notification.crucible;
18
19 import com.atlassian.theplugin.commons.crucible.ValueNotYetInitialized;
20 import com.atlassian.theplugin.commons.crucible.api.model.*;
21 import com.atlassian.theplugin.idea.IdeaHelper;
22 import com.atlassian.theplugin.idea.crucible.CrucibleStatusListener;
23 import com.atlassian.theplugin.idea.crucible.ReviewData;
24 import com.atlassian.theplugin.idea.crucible.ReviewNotificationBean;
25 import com.atlassian.theplugin.idea.crucible.comments.CrucibleReviewActionListener;
26 import com.atlassian.theplugin.idea.crucible.events.GeneralCommentAdded;
27 import com.atlassian.theplugin.idea.crucible.events.GeneralCommentReplyAdded;
28 import com.atlassian.theplugin.idea.crucible.events.VersionedCommentAdded;
29 import com.atlassian.theplugin.idea.crucible.events.VersionedCommentReplyAdded;
30 import com.intellij.openapi.project.Project;
31
32 import java.util.*;
33
34
35
36
37 public class CrucibleReviewNotifier implements CrucibleStatusListener {
38 private final List<CrucibleNotificationListener> listenerList = new ArrayList<CrucibleNotificationListener>();
39
40 private Set<ReviewData> reviews = new HashSet<ReviewData>();
41 private List<CrucibleNotification> notifications = new ArrayList<CrucibleNotification>();
42 private HashMap<PredefinedFilter, NewExceptionNotification> exceptionNotifications =
43 new HashMap<PredefinedFilter, NewExceptionNotification>();
44
45 private boolean firstRun = true;
46 private Project project;
47
48 public CrucibleReviewNotifier(final Project project) {
49 this.project = project;
50 }
51
52 public Project getProject() {
53 return project;
54 }
55
56 public void setProject(Project project) {
57 this.project = project;
58 }
59
60 public void registerListener(CrucibleNotificationListener listener) {
61 synchronized (listenerList) {
62 listenerList.add(listener);
63 }
64 }
65
66 public void unregisterListener(CrucibleNotificationListener listener) {
67 synchronized (listenerList) {
68 listenerList.remove(listener);
69 }
70 }
71
72 private void checkNewReviewItems(ReviewData oldReview, ReviewData newReview) throws ValueNotYetInitialized {
73 for (CrucibleFileInfo item : newReview.getFiles()) {
74 boolean found = false;
75 for (CrucibleFileInfo oldItem : oldReview.getFiles()) {
76 if (item.getPermId().getId().equals(oldItem.getPermId().getId())) {
77 found = true;
78 break;
79 }
80 }
81 if (!found) {
82 notifications.add(new NewReviewItemNotification(newReview, item));
83 }
84 }
85 }
86
87 private void checkReviewersStatus(ReviewData oldReview, ReviewData newReview) throws ValueNotYetInitialized {
88 boolean allCompleted = true;
89 boolean atLeastOneChanged = false;
90 for (Reviewer reviewer : newReview.getReviewers()) {
91 for (Reviewer oldReviewer : oldReview.getReviewers()) {
92 if (reviewer.getUserName().equals(oldReviewer.getUserName())) {
93 if (reviewer.isCompleted() != oldReviewer.isCompleted()) {
94 notifications.add(new ReviewerCompletedNotification(newReview, reviewer));
95 atLeastOneChanged = true;
96 }
97 }
98 }
99 if (!reviewer.isCompleted()) {
100 allCompleted = false;
101 }
102 }
103 if (allCompleted && atLeastOneChanged) {
104 notifications.add(new ReviewCompletedNotification(newReview));
105 }
106 }
107
108 private void checkGeneralReplies(ReviewData review, GeneralComment oldComment, GeneralComment newComment) {
109 for (GeneralComment reply : newComment.getReplies()) {
110 GeneralComment existingReply = null;
111 for (GeneralComment oldReply : oldComment.getReplies()) {
112 if (reply.getPermId().getId().equals(oldReply.getPermId().getId())) {
113 existingReply = oldReply;
114 break;
115 }
116 }
117 if (existingReply == null) {
118 notifications.add(new NewReplyCommentNotification(review, newComment, reply));
119 GeneralCommentReplyAdded event = new GeneralCommentReplyAdded(CrucibleReviewActionListener.ANONYMOUS,
120 review, newComment, reply);
121 IdeaHelper.getReviewActionEventBroker(project).trigger(event);
122 }
123 }
124 }
125
126 private void checkVersionedReplies(ReviewData review, final CrucibleFileInfo file, VersionedComment oldComment,
127 VersionedComment newComment) {
128 for (VersionedComment reply : newComment.getReplies()) {
129 VersionedComment existingReply = null;
130 for (VersionedComment oldReply : oldComment.getReplies()) {
131 if (reply.getPermId().getId().equals(oldReply.getPermId().getId())) {
132 existingReply = oldReply;
133 break;
134 }
135 }
136 if (existingReply == null) {
137 notifications.add(new NewReplyCommentNotification(review, newComment, reply));
138 VersionedCommentReplyAdded event = new VersionedCommentReplyAdded(CrucibleReviewActionListener.ANONYMOUS,
139 review, file, newComment, reply);
140 IdeaHelper.getReviewActionEventBroker(project).trigger(event);
141 }
142 }
143 }
144
145
146 private void checkComments(ReviewData oldReview, ReviewData newReview) throws ValueNotYetInitialized {
147 for (GeneralComment comment : newReview.getGeneralComments()) {
148 GeneralComment existing = null;
149 for (GeneralComment oldComment : oldReview.getGeneralComments()) {
150 if (comment.getPermId().getId().equals(oldComment.getPermId().getId())) {
151 existing = oldComment;
152 break;
153 }
154 }
155 if (existing == null) {
156 notifications.add(new NewGeneralCommentNotification(newReview, comment));
157 GeneralCommentAdded event = new GeneralCommentAdded(CrucibleReviewActionListener.ANONYMOUS, newReview, comment);
158 IdeaHelper.getReviewActionEventBroker(project).trigger(event);
159 } else {
160 checkGeneralReplies(newReview, existing, comment);
161 }
162 }
163
164 for (CrucibleFileInfo file : newReview.getFiles()) {
165 for (VersionedComment comment : file.getVersionedComments()) {
166 VersionedComment existing = null;
167 for (VersionedComment oldComment : oldReview.getVersionedComments()) {
168 if (comment.getPermId().getId().equals(oldComment.getPermId().getId())) {
169 existing = oldComment;
170 break;
171 }
172 }
173 if (existing == null) {
174 notifications.add(new NewVersionedCommentNotification(newReview, comment));
175 if (project != null) {
176 VersionedCommentAdded event = new VersionedCommentAdded(CrucibleReviewActionListener.ANONYMOUS,
177 newReview, file, comment);
178 IdeaHelper.getReviewActionEventBroker(project).trigger(event);
179 }
180 } else {
181 checkVersionedReplies(newReview, file, existing, comment);
182 }
183 }
184 }
185 }
186
187 public void updateReviews(Map<PredefinedFilter, ReviewNotificationBean> incomingReviews,
188 Map<String, ReviewNotificationBean> customIncomingReviews) {
189
190 notifications.clear();
191 boolean exceptionFound = false;
192
193 Set<ReviewData> processedReviews = new HashSet<ReviewData>();
194 if (!incomingReviews.isEmpty()) {
195 for (PredefinedFilter predefinedFilter : incomingReviews.keySet()) {
196 if (incomingReviews.get(predefinedFilter).getException() == null) {
197 List<ReviewData> incomingCategory = incomingReviews.get(predefinedFilter).getReviews();
198
199 for (ReviewData reviewDataInfo : incomingCategory) {
200 if (processedReviews.contains(reviewDataInfo)) {
201 continue;
202 }
203 if (reviews.contains(reviewDataInfo)) {
204 ReviewData existing = null;
205 for (ReviewData review : reviews) {
206 if (review.equals(reviewDataInfo)) {
207 existing = review;
208 }
209 }
210
211
212 if (!reviewDataInfo.getState().equals(existing.getState())) {
213 notifications.add(new ReviewStateChangedNotification(reviewDataInfo, existing.getState()));
214 }
215
216
217 try {
218 checkNewReviewItems(existing, reviewDataInfo);
219 } catch (ValueNotYetInitialized valueNotYetInitialized) {
220
221 }
222
223
224 try {
225 checkReviewersStatus(existing, reviewDataInfo);
226 } catch (ValueNotYetInitialized valueNotYetInitialized) {
227
228 }
229
230
231 try {
232 checkComments(existing, reviewDataInfo);
233 } catch (ValueNotYetInitialized valueNotYetInitialized) {
234
235 }
236
237 processedReviews.add(reviewDataInfo);
238 } else {
239 notifications.add(new NewReviewNotification(reviewDataInfo));
240 processedReviews.add(reviewDataInfo);
241 }
242 }
243
244 exceptionNotifications.remove(predefinedFilter);
245 } else {
246
247
248 NewExceptionNotification prevNotificationException = exceptionNotifications.get(predefinedFilter);
249 NewExceptionNotification newException =
250 new NewExceptionNotification(incomingReviews.get(predefinedFilter).getException());
251
252 if ((prevNotificationException != null && !prevNotificationException.equals(newException))
253 || exceptionNotifications.size() <= 0 || prevNotificationException == null) {
254
255 exceptionNotifications.put(predefinedFilter, newException);
256
257
258
259 if (!notifications.contains(newException)) {
260 notifications.add(newException);
261 }
262
263 exceptionFound = true;
264 }
265 }
266
267 }
268 if (!exceptionFound) {
269 reviews.clear();
270 reviews.addAll(processedReviews);
271 }
272 }
273
274 if (!firstRun) {
275 for (CrucibleNotificationListener listener : listenerList) {
276 listener.updateNotifications(notifications);
277 }
278 }
279 firstRun = false;
280 }
281
282 public void resetState() {
283 reviews.clear();
284 exceptionNotifications.clear();
285
286 for (CrucibleNotificationListener listener : listenerList) {
287 listener.resetState();
288 }
289 }
290
291 public List<CrucibleNotification> getNotifications() {
292 return notifications;
293 }
294 }