View Javadoc

1   /**
2    * Copyright (C) 2008 Atlassian
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * This one is supposed to be per project.
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 							// check state change
212 							if (!reviewDataInfo.getState().equals(existing.getState())) {
213 								notifications.add(new ReviewStateChangedNotification(reviewDataInfo, existing.getState()));
214 							}
215 
216 							// check review items
217 							try {
218 								checkNewReviewItems(existing, reviewDataInfo);
219 							} catch (ValueNotYetInitialized valueNotYetInitialized) {
220 								// TODO all is it correct
221 							}
222 
223 							// check reviewers status
224 							try {
225 								checkReviewersStatus(existing, reviewDataInfo);
226 							} catch (ValueNotYetInitialized valueNotYetInitialized) {
227 								// TODO all is it correct
228 							}
229 
230 							// check comments status
231 							try {
232 								checkComments(existing, reviewDataInfo);
233 							} catch (ValueNotYetInitialized valueNotYetInitialized) {
234 								// TODO all is it correct
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 					// do not analyze events when exception was raised.
247 					// maybe next time wil be better
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 						//add exception only if differs in text
257 						// exceptions texts are defined as string server_url + exception message
258 						//so no excepion will be missed
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 }