View Javadoc

1   package com.atlassian.theplugin.idea.jira.tree;
2   
3   import com.atlassian.theplugin.cfg.CfgUtil;
4   import com.atlassian.theplugin.commons.cfg.ServerId;
5   import com.atlassian.theplugin.idea.config.ProjectCfgManagerImpl;
6   import com.atlassian.theplugin.idea.jira.CachedIconLoader;
7   import com.atlassian.theplugin.idea.jira.JiraIssueGroupBy;
8   import com.atlassian.theplugin.idea.jira.JiraIssueListTree;
9   import com.atlassian.theplugin.idea.ui.tree.paneltree.TreeRenderer;
10  import com.atlassian.theplugin.idea.ui.tree.paneltree.TreeUISetup;
11  import com.atlassian.theplugin.jira.api.JIRAIssue;
12  import com.atlassian.theplugin.jira.model.FrozenModel;
13  import com.atlassian.theplugin.jira.model.FrozenModelListener;
14  import com.atlassian.theplugin.jira.model.JIRAIssueListModel;
15  import com.intellij.openapi.project.Project;
16  import com.intellij.openapi.util.Pair;
17  import org.joda.time.DateMidnight;
18  import org.joda.time.DateTime;
19  
20  import javax.swing.*;
21  import javax.swing.tree.*;
22  import java.text.DateFormat;
23  import java.text.SimpleDateFormat;
24  import java.util.*;
25  
26  public class JIRAIssueTreeBuilder {
27  
28  	private JiraIssueGroupBy groupBy;
29  	private final JIRAIssueListModel issueModel;
30  	private Project project;
31  	private ProjectCfgManagerImpl projectCfgManager;
32  	private SortableGroupsTreeModel treeModel;
33  	private static final TreeCellRenderer TREE_RENDERER = new TreeRenderer();
34  	private JTree lastTree;
35  	private boolean isGroupSubtasksUnderParent;
36  
37  	public enum UpdateGroup {
38  		UPDATED_TODAY("Today"),
39  		UPDATED_YESTERDAY("Yesterday"),
40  		UPDATED_TWO_DAYS_AGO("2 Days Ago"),
41  		UPDATED_THIS_WEEK("This Week"),
42  		UPDATED_LAST_WEEK("Last Week"),
43  		UPDATED_THIS_MONTH("This Month"),
44  		UPDATED_LAST_MONTH("Last Month"),
45  		UPDATED_EARLIER("Older"),
46  		UPDATED_INVALID("Invalid or Unknown");
47  
48  		private String name;
49  
50  		private UpdateGroup(String name) {
51  			this.name = name;
52  		}
53  
54  		@Override
55  		public String toString() {
56  			return name;
57  		}
58  	}
59  
60  	private final class GroupByDateTreeNode extends JIRAIssueGroupTreeNode {
61  		private final UpdateGroup group;
62  
63  		private GroupByDateTreeNode(UpdateGroup group) {
64  			super(issueModel, group.toString(), null, null);
65  			this.group = group;
66  		}
67  
68  		private final Comparator<JIRAIssueGroupTreeNode> comparator = new Comparator<JIRAIssueGroupTreeNode>() {
69  			public int compare(JIRAIssueGroupTreeNode lhs, JIRAIssueGroupTreeNode rhs) {
70  				if (lhs instanceof GroupByDateTreeNode && rhs instanceof GroupByDateTreeNode) {
71  					return ((GroupByDateTreeNode) lhs).group.ordinal() - ((GroupByDateTreeNode) rhs).group.ordinal();
72  				}
73  				return lhs.getComparator().compare(lhs, rhs);
74  			}
75  		};
76  
77  		public Comparator<JIRAIssueGroupTreeNode> getComparator() {
78  			return comparator;
79  		}
80  	}
81  
82  	private Map<Pair<String, ServerId>, String> projectKeysToNames;
83  
84  	public JIRAIssueTreeBuilder(JiraIssueGroupBy groupBy, boolean groupSubtasksUnderParent, JIRAIssueListModel model,
85  			final Project project, final ProjectCfgManagerImpl projectCfgManager) {
86  		this.groupBy = groupBy;
87  		isGroupSubtasksUnderParent = groupSubtasksUnderParent;
88  		this.issueModel = model;
89  		this.project = project;
90  		this.projectCfgManager = projectCfgManager;
91  		lastTree = null;
92  
93  //		issueModel.addModelListener(new JIRAIssueListModelListener() {
94  //
95  //			public void modelChanged(JIRAIssueListModel aModel) {
96  //			}
97  //
98  //			public void issuesLoaded(JIRAIssueListModel aModel, int loadedIssues) {
99  //			}
100 //
101 //		});
102 
103 		issueModel.addFrozenModelListener(new FrozenModelListener() {
104 			public void modelFrozen(FrozenModel aModel, boolean frozen) {
105 				if (lastTree != null) {
106 					lastTree.setEnabled(!frozen);
107 				}
108 			}
109 		});
110 	}
111 
112 	public void setGroupBy(JiraIssueGroupBy groupBy) {
113 		this.groupBy = groupBy;
114 	}
115 
116 	public void setProjectKeysToNames(Map<Pair<String, ServerId>, String> projectKeysToNames) {
117 		this.projectKeysToNames = projectKeysToNames;
118 	}
119 
120 	public synchronized void rebuild(JiraIssueListTree tree, JComponent treeParent) {
121 
122 		JIRAIssue selectedIsse = tree.getSelectedIssue();
123 
124 		DefaultMutableTreeNode root = new DefaultMutableTreeNode();
125 		reCreateTree(tree, treeParent, root);
126 		if (isGroupSubtasksUnderParent) {
127 			for (JIRAIssue issue : issueModel.getIssuesNoSubtasks()) {
128 				JIRAIssueTreeNode node = new JIRAIssueTreeNode(issue);
129 				getPlace(issue, root).add(node);
130 				if (!issue.getSubTaskKeys().isEmpty()) {
131 					for (JIRAIssue sub : issueModel.getSubtasks(issue)) {
132 						node.add(new JIRAIssueTreeNode(sub));
133 					}
134 				}
135 			}
136 			Collection<JIRAIssue> orphans = issueModel.getSubtasks(null);
137 			if (!orphans.isEmpty()) {
138 				for (JIRAIssue i : orphans) {
139 					JIRAIssueTreeNode node = new JIRAIssueTreeNode(i);
140 					getPlace(i, root).add(node);
141 				}
142 			}
143 		} else {
144 			for (JIRAIssue issue : issueModel.getIssues()) {
145 				getPlace(issue, root).add(new JIRAIssueTreeNode(issue));
146 			}
147 		}
148 		treeModel.nodeStructureChanged(root);
149 
150 		// expand tree
151 		for (int i = 0; i < tree.getRowCount(); i++) {
152 			tree.expandRow(i);
153 		}
154 
155 		selectIssueNode(tree, selectedIsse);
156 	}
157 
158 	private void selectIssueNode(final JTree tree, final JIRAIssue selectedIssue) {
159 		if (selectedIssue == null) {
160 			tree.clearSelection();
161 			return;
162 		}
163 
164 		for (int i = 0; i < tree.getRowCount(); i++) {
165 			TreePath path = tree.getPathForRow(i);
166 			Object object = path.getLastPathComponent();
167 			if (object instanceof JIRAIssueTreeNode) {
168 				JIRAIssueTreeNode node = (JIRAIssueTreeNode) object;
169 				if (node.getIssue().getKey().equals(selectedIssue.getKey())
170 						&& node.getIssue().getServerUrl().equals(selectedIssue.getServerUrl())) {
171 					tree.expandPath(path);
172 					tree.makeVisible(path);
173 					tree.setSelectionPath(path);
174 					break;
175 				}
176 			}
177 		}
178 	}
179 
180 	private void reCreateTree(final JTree tree, JComponent treeParent, DefaultMutableTreeNode root) {
181 		tree.removeAll();
182 		treeModel = new SortableGroupsTreeModel(root, groupBy);
183 		tree.setModel(treeModel);
184 		TreeUISetup uiSetup = new TreeUISetup(TREE_RENDERER);
185 		uiSetup.registerUI(tree);
186 		if (this.lastTree != tree) {
187 			this.lastTree = tree;
188 			tree.setShowsRootHandles(true);
189 			tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
190 //			tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
191 //				public void valueChanged(TreeSelectionEvent e) {
192 //					final TreePath selectionPath = tree.getSelectionModel().getSelectionPath();
193 //					if (selectionPath != null && selectionPath.getLastPathComponent() != null) {
194 //						((AbstractTreeNode) selectionPath.getLastPathComponent()).onSelect();
195 //					} else {
196 //						issueModel.setSeletedIssue(null);
197 //					}
198 //				}
199 //			});
200 			uiSetup.initializeUI(tree, treeParent);
201 			tree.setRootVisible(false);
202 		}
203 	}
204 
205 	public void setGroupSubtasksUnderParent(boolean groupSubtasksUnderParent) {
206 		isGroupSubtasksUnderParent = groupSubtasksUnderParent;
207 	}
208 
209 	private final class SortableGroupsTreeModel extends DefaultTreeModel {
210 
211 		private final Comparator<JIRAIssueGroupTreeNode> comparator = new Comparator<JIRAIssueGroupTreeNode>() {
212 			public int compare(JIRAIssueGroupTreeNode lhs, JIRAIssueGroupTreeNode rhs) {
213 				return lhs.getComparator().compare(lhs, rhs);
214 			}
215 		};
216 
217 		private Set<JIRAIssueGroupTreeNode> set = new TreeSet<JIRAIssueGroupTreeNode>(comparator);
218 
219 		private boolean isFlat;
220 
221 		private SortableGroupsTreeModel(TreeNode root, JiraIssueGroupBy groupBy) {
222 			super(root);
223 			isFlat = groupBy == JiraIssueGroupBy.NONE;
224 		}
225 
226 		public DefaultMutableTreeNode getGroupNode(JIRAIssue issue, String name, Icon icon, Icon disabledIcon) {
227 			if (isFlat) {
228 				return (DefaultMutableTreeNode) getRoot();
229 			}
230 			JIRAIssueGroupTreeNode n = findGroupNode(name);
231 			if (n != null) {
232 				return n;
233 			}
234 
235 			if (groupBy == JiraIssueGroupBy.LAST_UPDATED) {
236 				n = new GroupByDateTreeNode(updatedDate2Name(issue));
237 			} else {
238 				n = new JIRAIssueGroupTreeNode(issueModel, name, icon, disabledIcon);
239 			}
240 
241 			set.add(n);
242 			((DefaultMutableTreeNode) getRoot()).removeAllChildren();
243 			for (JIRAIssueGroupTreeNode node : set) {
244 				((DefaultMutableTreeNode) getRoot()).add(node);
245 			}
246 			nodeStructureChanged((DefaultMutableTreeNode) getRoot());
247 			return n;
248 		}
249 
250 		private JIRAIssueGroupTreeNode findGroupNode(String name) {
251 			for (int i = 0; i < getChildCount(getRoot()); ++i) {
252 				JIRAIssueGroupTreeNode node = (JIRAIssueGroupTreeNode) getChild(getRoot(), i);
253 				if (node.toString().equals(name)) {
254 					return node;
255 				}
256 			}
257 			return null;
258 		}
259 
260 		@Override
261 		public Object getChild(Object parent, int index) {
262 			if (parent != getRoot() || isFlat) {
263 				return super.getChild(parent, index);
264 			}
265 			return new ArrayList<JIRAIssueGroupTreeNode>(set).get(index);
266 		}
267 
268 		@Override
269 		public int getIndexOfChild(Object parent, Object child) {
270 			if (parent != getRoot() || isFlat) {
271 				return super.getIndexOfChild(parent, child);
272 			}
273 			int i = 0;
274 			for (JIRAIssueGroupTreeNode n : set) {
275 				if (n == child) {
276 					return i;
277 				}
278 				++i;
279 			}
280 			return -1;
281 		}
282 
283 		@Override
284 		public int getChildCount(Object parent) {
285 			if (parent != getRoot() || isFlat) {
286 				return super.getChildCount(parent);
287 			}
288 			return set.size();
289 		}
290 
291 		@Override
292 		public boolean isLeaf(Object node) {
293 			if (isFlat) {
294 				return super.isLeaf(node);
295 			}
296 			return !(node instanceof JIRAIssueGroupTreeNode) && super.isLeaf(node);
297 		}
298 	}
299 
300 	private DefaultMutableTreeNode getPlace(JIRAIssue issue, DefaultMutableTreeNode root) {
301 		String name;
302 		String iconUrl = null;
303 		switch (groupBy) {
304 			case PRIORITY:
305 				name = issue.getPriority();
306 				iconUrl = issue.getPriorityIconUrl();
307 				break;
308 			case PROJECT:
309 				name = getProjectName(issue);
310 				break;
311 			case STATUS:
312 				name = issue.getStatus();
313 				iconUrl = issue.getStatusTypeUrl();
314 				break;
315 			case TYPE:
316 				name = issue.getType();
317 				iconUrl = issue.getTypeIconUrl();
318 				break;
319 			case LAST_UPDATED:
320 				name = updatedDate2Name(issue).toString();
321 				break;
322 			default:
323 				return root;
324 		}
325 		if (name == null) {
326 			name = "None";
327 		}
328 
329 		return treeModel.getGroupNode(issue, name, CachedIconLoader.getIcon(iconUrl),
330 				CachedIconLoader.getDisabledIcon(iconUrl));
331 	}
332 
333 	// isn't this constant defined somewhere?
334 	private static final int DAYS_IN_WEEK = 7;
335 
336 	private UpdateGroup updatedDate2Name(JIRAIssue issue) {
337 		DateFormat df = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.US);
338 		UpdateGroup groupName;
339 		try {
340 			DateMidnight midnight = new DateMidnight();
341 			DateTime updated = new DateTime(df.parse(issue.getUpdated()).getTime());
342 
343 			if (updated.isAfter(midnight)) {
344 				groupName = UpdateGroup.UPDATED_TODAY;
345 			} else if (updated.isAfter(midnight.minusDays(1))) {
346 				groupName = UpdateGroup.UPDATED_YESTERDAY;
347 			} else if (updated.isAfter(midnight.minusDays(2))) {
348 				groupName = UpdateGroup.UPDATED_TWO_DAYS_AGO;
349 			} else if (updated.isAfter(midnight.minusDays(midnight.getDayOfWeek()))) {
350 				groupName = UpdateGroup.UPDATED_THIS_WEEK;
351 			} else if (updated.isAfter(midnight.minusDays(midnight.getDayOfWeek() + DAYS_IN_WEEK))) {
352 				groupName = UpdateGroup.UPDATED_LAST_WEEK;
353 			} else if (updated.isAfter(midnight.minusDays(midnight.getDayOfMonth()))) {
354 				groupName = UpdateGroup.UPDATED_THIS_MONTH;
355 			} else if (updated.isAfter(midnight.minusMonths(1))) {
356 				groupName = UpdateGroup.UPDATED_LAST_MONTH;
357 			} else {
358 				groupName = UpdateGroup.UPDATED_EARLIER;
359 			}
360 		} catch (java.text.ParseException e) {
361 			groupName = UpdateGroup.UPDATED_INVALID;
362 		}
363 		return groupName;
364 	}
365 
366 	private String getProjectName(JIRAIssue issue) {
367 		if (projectKeysToNames == null
368 				|| !projectKeysToNames.containsKey(new Pair<String, ServerId>(issue.getProjectKey(),
369 				CfgUtil.getJiraServerCfgByUrl(project, projectCfgManager, issue.getServerUrl()).getServerId()))) {
370 			return issue.getProjectKey();
371 		}
372 		return projectKeysToNames.get(new Pair<String, ServerId>(issue.getProjectKey(),
373 				CfgUtil.getJiraServerCfgByUrl(project, projectCfgManager, issue.getServerUrl()).getServerId()));
374 	}
375 }