diff --git a/src/main/java/CallGraphToolWindow.form b/src/main/java/CallGraphToolWindow.form index 4bf1ed2..176bdf1 100644 --- a/src/main/java/CallGraphToolWindow.form +++ b/src/main/java/CallGraphToolWindow.form @@ -433,7 +433,7 @@ - + @@ -445,7 +445,7 @@ - + @@ -459,7 +459,7 @@ - + @@ -468,6 +468,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/CallGraphToolWindow.java b/src/main/java/CallGraphToolWindow.java index 112efda..9e1875f 100644 --- a/src/main/java/CallGraphToolWindow.java +++ b/src/main/java/CallGraphToolWindow.java @@ -44,10 +44,20 @@ public class CallGraphToolWindow { private JComboBox nodeSelectionComboBox; private JTextField searchTextField; private JComboBox nodeColorComboBox; + private JCheckBox filterAccessPublicCheckbox; + private JCheckBox filterAccessProtectedCheckbox; + private JCheckBox filterAccessPackageLocalCheckbox; + private JCheckBox filterAccessPrivateCheckbox; private final CanvasBuilder canvasBuilder = new CanvasBuilder(); private Canvas canvas; private final Set focusedMethods = new HashSet<>(); + private final List filterAccessCheckboxes = Arrays.asList( + this.filterAccessPublicCheckbox, + this.filterAccessProtectedCheckbox, + this.filterAccessPackageLocalCheckbox, + this.filterAccessPrivateCheckbox + ); public CallGraphToolWindow() { // drop-down options @@ -89,6 +99,8 @@ public void keyReleased(KeyEvent keyEvent) { canvas.repaint(); } }); + this.filterAccessCheckboxes.forEach(checkbox -> + checkbox.addActionListener(e -> this.canvas.filterAccessChangeHandler())); // click handlers for buttons this.projectScopeButton.addActionListener(e -> projectScopeButtonHandler()); @@ -184,6 +196,22 @@ boolean isNodeColorByClassName() { return getSelectedComboBoxOption(this.nodeColorComboBox) == ComboBoxOptions.NODE_COLOR_CLASS; } + boolean isFilterAccessPublicChecked() { + return this.filterAccessPublicCheckbox.isSelected(); + } + + boolean isFilterAccessProtectedChecked() { + return this.filterAccessProtectedCheckbox.isSelected(); + } + + boolean isFilterAccessPackageLocalChecked() { + return this.filterAccessPackageLocalCheckbox.isSelected(); + } + + boolean isFilterAccessPrivateChecked() { + return this.filterAccessPrivateCheckbox.isSelected(); + } + void run(@NotNull CanvasConfig.BuildType buildType) { Project project = Utils.getActiveProject(); if (project != null) { @@ -311,21 +339,28 @@ private void setupUiBeforeRun(@NotNull CanvasConfig canvasConfig) { break; } // disable some checkboxes and buttons - this.viewPackageNameComboBox.setEnabled(false); - this.viewFilePathComboBox.setEnabled(false); - this.nodeSelectionComboBox.setEnabled(false); - this.nodeColorComboBox.setEnabled(false); - this.fitGraphToBestRatioButton.setEnabled(false); - this.fitGraphToViewButton.setEnabled(false); - this.increaseXGridButton.setEnabled(false); - this.decreaseXGridButton.setEnabled(false); - this.increaseYGridButton.setEnabled(false); - this.decreaseYGridButton.setEnabled(false); - this.viewSourceCodeButton.setEnabled(false); - this.showOnlyUpstreamButton.setEnabled(false); - this.showOnlyDownstreamButton.setEnabled(false); - this.showOnlyUpstreamDownstreamButton.setEnabled(false); - this.searchTextField.setEnabled(false); + Arrays.asList( + this.viewPackageNameComboBox, + this.viewFilePathComboBox, + this.nodeSelectionComboBox, + this.nodeColorComboBox, + this.fitGraphToBestRatioButton, + this.fitGraphToViewButton, + this.increaseXGridButton, + this.decreaseXGridButton, + this.increaseYGridButton, + this.decreaseYGridButton, + this.viewSourceCodeButton, + this.showOnlyUpstreamButton, + this.showOnlyDownstreamButton, + this.showOnlyUpstreamDownstreamButton, + this.searchTextField + ).forEach(component -> component.setEnabled(false)); + // filter-related checkboxes + this.filterAccessCheckboxes.forEach(checkbox -> { + checkbox.setEnabled(false); + checkbox.setSelected(true); + }); // progress bar this.loadingProgressBar.setVisible(true); // clear the canvas panel, ready for new graph @@ -342,18 +377,22 @@ private void setupUiAfterRun() { // hide progress bar this.loadingProgressBar.setVisible(false); // enable some checkboxes and buttons - this.viewPackageNameComboBox.setEnabled(true); - this.viewFilePathComboBox.setEnabled(true); - this.nodeSelectionComboBox.setEnabled(true); - this.nodeColorComboBox.setEnabled(true); - this.fitGraphToBestRatioButton.setEnabled(true); - this.fitGraphToViewButton.setEnabled(true); - this.increaseXGridButton.setEnabled(true); - this.decreaseXGridButton.setEnabled(true); - this.increaseYGridButton.setEnabled(true); - this.decreaseYGridButton.setEnabled(true); - this.searchTextField.setEnabled(true); enableFocusedMethodButtons(); + Arrays.asList( + this.viewPackageNameComboBox, + this.viewFilePathComboBox, + this.nodeSelectionComboBox, + this.nodeColorComboBox, + this.fitGraphToBestRatioButton, + this.fitGraphToViewButton, + this.increaseXGridButton, + this.decreaseXGridButton, + this.increaseYGridButton, + this.decreaseYGridButton, + this.searchTextField + ).forEach(component -> component.setEnabled(true)); + // filter-related checkboxes + this.filterAccessCheckboxes.forEach(checkbox -> checkbox.setEnabled(true)); } @NotNull diff --git a/src/main/java/Canvas.java b/src/main/java/Canvas.java index 97f1906..35e5b18 100644 --- a/src/main/java/Canvas.java +++ b/src/main/java/Canvas.java @@ -1,5 +1,6 @@ import com.google.common.collect.ImmutableMap; import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiModifierList; import org.jetbrains.annotations.NotNull; @@ -21,6 +22,8 @@ class Canvas extends JPanel { private JPanel canvasPanel; private final CallGraphToolWindow callGraphToolWindow; private Map nodeShapesMap = Collections.emptyMap(); + private Collection visibleNodes; + private Collection visibleEdges; private Node hoveredNode; private final Point2D defaultCameraOrigin = new Point2D.Float(0, 0); private final float defaultZoomRatio = 1.0f; @@ -41,6 +44,8 @@ class Canvas extends JPanel { super(); this.callGraphToolWindow = callGraphToolWindow; this.graph = graph; + this.visibleNodes = graph.getNodes(); + this.visibleEdges = graph.getEdges(); } @Override @@ -57,21 +62,18 @@ public void paintComponent(Graphics graphics) { graphics2D.fillRect(0, 0, this.getWidth(), this.getHeight()); // draw un-highlighted and highlighted self loops - this.graph.getEdges() - .stream() + this.visibleEdges.stream() .filter(edge -> edge.getSourceNode() == edge.getTargetNode()) .forEach(edge -> drawSelfLoopEdge(graphics2D, edge, isNodeHighlighted(edge.getSourceNode()))); // draw un-highlighted edges - this.graph.getEdges() - .stream() + this.visibleEdges.stream() .filter(edge -> edge.getSourceNode() != edge.getTargetNode() && !isNodeHighlighted(edge.getSourceNode()) && !isNodeHighlighted(edge.getTargetNode())) .forEach(edge -> drawNonLoopEdge(graphics2D, edge, Colors.unHighlightedColor)); // draw upstream/downstream edges - Set highlightedNodes = this.graph.getNodes() - .stream() + Set highlightedNodes = this.visibleNodes.stream() .filter(this::isNodeHighlighted) .collect(Collectors.toSet()); Set upstreamEdges = highlightedNodes.stream() @@ -86,8 +88,7 @@ public void paintComponent(Graphics graphics) { // draw un-highlighted labels Set upstreamNodes = upstreamEdges.stream().map(Edge::getSourceNode).collect(Collectors.toSet()); Set downstreamNodes = downstreamEdges.stream().map(Edge::getTargetNode).collect(Collectors.toSet()); - Set unHighlightedNodes = this.graph.getNodes() - .stream() + Set unHighlightedNodes = this.visibleNodes.stream() .filter(node -> !isNodeHighlighted(node) && !upstreamNodes.contains(node) && !downstreamNodes.contains(node) ) @@ -107,8 +108,7 @@ public void paintComponent(Graphics graphics) { downstreamNodes.forEach(node -> drawNode(graphics2D, node, Colors.downstreamColor)); // draw highlighted node and label - this.graph.getNodes() - .stream() + this.visibleNodes.stream() .filter(this::isNodeHighlighted) .forEach(node -> { drawNode(graphics2D, node, Colors.highlightedColor); @@ -199,6 +199,31 @@ int getNodesCount() { return this.graph.getNodes().size(); } + void filterAccessChangeHandler() { + this.visibleNodes = this.graph.getNodes() + .stream() + .filter(node -> { + PsiMethod method = node.getMethod(); + if (Utils.isPublic(method)) { + return this.callGraphToolWindow.isFilterAccessPublicChecked(); + } else if (Utils.isProtected(method)) { + return this.callGraphToolWindow.isFilterAccessProtectedChecked(); + } else if (Utils.isPackageLocal(method)) { + return this.callGraphToolWindow.isFilterAccessPackageLocalChecked(); + } else if (Utils.isPrivate(method)) { + return this.callGraphToolWindow.isFilterAccessPrivateChecked(); + } + return true; + }) + .collect(Collectors.toSet()); + this.visibleEdges = this.graph.getEdges() + .stream() + .filter(edge -> this.visibleNodes.contains(edge.getSourceNode()) && + this.visibleNodes.contains(edge.getTargetNode())) + .collect(Collectors.toSet()); + repaint(); + } + @NotNull private Point2D toCameraView(@NotNull Point2D point) { Dimension canvasSize = this.canvasPanel.getSize(); diff --git a/src/main/java/Utils.java b/src/main/java/Utils.java index 3e7a06a..5b43d95 100644 --- a/src/main/java/Utils.java +++ b/src/main/java/Utils.java @@ -538,4 +538,20 @@ static Set getSourceCodeFiles(@NotNull CanvasConfig canvasConfig) { }) .collect(Collectors.toSet()); } + + static boolean isPublic(@NotNull PsiMethod method) { + return method.getModifierList().hasModifierProperty(PsiModifier.PUBLIC); + } + + static boolean isProtected(@NotNull PsiMethod method) { + return method.getModifierList().hasModifierProperty(PsiModifier.PROTECTED); + } + + static boolean isPackageLocal(@NotNull PsiMethod method) { + return method.getModifierList().hasModifierProperty(PsiModifier.PACKAGE_LOCAL); + } + + static boolean isPrivate(@NotNull PsiMethod method) { + return method.getModifierList().hasModifierProperty(PsiModifier.PRIVATE); + } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 24ea4e3..a3c0812 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -67,6 +67,9 @@
  • Support color-coding nodes by function access level (public, protected, package local, private) and class name.
  • +
  • + Support filtering nodes by function access level (public, protected, package local, private). +
  • ]]> diff --git a/src/main/resources/icons/filter.png b/src/main/resources/icons/filter.png new file mode 100755 index 0000000..f10a09f Binary files /dev/null and b/src/main/resources/icons/filter.png differ