package org.infernus.idea.checkstyle.toolwindow; import java.util.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileSystemItem; import org.infernus.idea.checkstyle.CheckStyleBundle; import org.infernus.idea.checkstyle.checker.Problem; import org.infernus.idea.checkstyle.csapi.SeverityLevel; import org.jetbrains.annotations.Nullable; import static java.util.Comparator.*; public class ResultTreeModel extends DefaultTreeModel { private static final long serialVersionUID = 2161855162879365203L; private final DefaultMutableTreeNode visibleRootNode; public ResultTreeModel() { super(new DefaultMutableTreeNode()); visibleRootNode = new DefaultMutableTreeNode(); ((DefaultMutableTreeNode) getRoot()).add(visibleRootNode); setRootMessage(null); } public void clear() { visibleRootNode.removeAllChildren(); nodeStructureChanged(visibleRootNode); } public TreeNode getVisibleRoot() { return visibleRootNode; } /** * Set the root message. * <p> * This will trigger a reload on the model, thanks to JTree's lack of support for * a node changed event for the root node. * * @param messageText the text to display. */ public void setRootText(@Nullable final String messageText) { if (messageText == null) { visibleRootNode.setUserObject(new ResultTreeNode(CheckStyleBundle.message("plugin.results.no-scan"))); } else { visibleRootNode.setUserObject(new ResultTreeNode(messageText)); } nodeChanged(visibleRootNode); } /** * Set the root message. * <p> * This will trigger a reload on the model, thanks to JTree's lack of support for * a node changed event for the root node. * * @param messageKey the message key to display. */ public void setRootMessage(@Nullable final String messageKey, @Nullable final Object... messageArgs) { if (messageKey == null) { setRootText(null); } else { setRootText(CheckStyleBundle.message(messageKey, messageArgs)); } } /** * Display only the passed severity levels. * * @param levels the levels. Null is treated as 'none'. */ public void filter(final SeverityLevel... levels) { filter(true, levels); } private void filter(final boolean sendEvents, final SeverityLevel... levels) { final Set<TogglableTreeNode> changedNodes = new HashSet<>(); for (int fileIndex = 0; fileIndex < visibleRootNode.getChildCount(); ++fileIndex) { final TogglableTreeNode fileNode = (TogglableTreeNode) visibleRootNode.getChildAt(fileIndex); for (final TogglableTreeNode problemNode : fileNode.getAllChildren()) { final ResultTreeNode result = (ResultTreeNode) problemNode.getUserObject(); final boolean currentVisible = problemNode.isVisible(); final boolean desiredVisible = levels != null && contains(levels, result.getSeverity()); if (currentVisible != desiredVisible) { problemNode.setVisible(desiredVisible); changedNodes.add(fileNode); } } } if (sendEvents) { for (final TogglableTreeNode node : changedNodes) { nodeStructureChanged(node); } } } /* * This is a port from commons-lang 2.4, in order to get around the absence of commons-lang in * some packages of IDEA 7.x. */ private boolean contains(final Object[] array, final Object objectToFind) { if (array == null) { return false; } if (objectToFind == null) { for (final Object anArray : array) { if (anArray == null) { return true; } } } else { for (final Object anArray : array) { if (objectToFind.equals(anArray)) { return true; } } } return false; } /** * Set the displayed model. * * @param results the model. */ public void setModel(final Map<PsiFile, List<Problem>> results) { setModel(results, SeverityLevel.Error, SeverityLevel.Warning, SeverityLevel.Info); } /** * Set the displayed model. * * @param results the model. * @param levels the levels to display. */ public void setModel(final Map<PsiFile, List<Problem>> results, final SeverityLevel... levels) { visibleRootNode.removeAllChildren(); int itemCount = 0; for (final PsiFile file : sortedFileNames(results)) { final TogglableTreeNode fileNode = new TogglableTreeNode(); final List<Problem> problems = results.get(file); int problemCount = 0; if (problems != null) { for (final Problem problem : problems) { if (problem.severityLevel() != SeverityLevel.Ignore) { final ResultTreeNode problemObj = new ResultTreeNode(file, problem); final TogglableTreeNode problemNode = new TogglableTreeNode(problemObj); fileNode.add(problemNode); ++problemCount; } } } itemCount += problemCount; if (problemCount > 0) { final ResultTreeNode nodeObject = new ResultTreeNode(file.getName(), problemCount); fileNode.setUserObject(nodeObject); visibleRootNode.add(fileNode); } } if (itemCount == 0) { setRootMessage("plugin.results.scan-no-results"); } else { setRootText(CheckStyleBundle.message("plugin.results.scan-results", itemCount, results.size())); } filter(false, levels); nodeStructureChanged(visibleRootNode); } private Iterable<PsiFile> sortedFileNames(final Map<PsiFile, List<Problem>> results) { if (results == null || results.isEmpty()) { return Collections.emptyList(); } final List<PsiFile> sortedFiles = new ArrayList<>(results.keySet()); sortedFiles.sort(comparing(PsiFileSystemItem::getName)); return sortedFiles; } }