diff --git a/pmd-ui/pom.xml b/pmd-ui/pom.xml index a55967c8ff..c1a331ca7e 100644 --- a/pmd-ui/pom.xml +++ b/pmd-ui/pom.xml @@ -31,5 +31,10 @@ richtextfx 0.6.10 + + net.sourceforge.pmd + pmd-apex + 6.0.0-SNAPSHOT + diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/DesignerWindowPresenter.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/DesignerWindowPresenter.java index a89c1eac81..f776aefc49 100644 --- a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/DesignerWindowPresenter.java +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/DesignerWindowPresenter.java @@ -18,6 +18,7 @@ import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery; import net.sourceforge.pmd.util.fxdesigner.model.ASTManager; +import net.sourceforge.pmd.util.fxdesigner.model.MetricResult; import net.sourceforge.pmd.util.fxdesigner.model.ParseTimeException; import net.sourceforge.pmd.util.fxdesigner.model.XPathEvaluationException; import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil; @@ -28,6 +29,7 @@ import net.sourceforge.pmd.util.fxdesigner.view.DesignerWindow; import javafx.beans.binding.Bindings; import javafx.beans.binding.ObjectBinding; import javafx.beans.binding.StringBinding; +import javafx.beans.property.ReadOnlyObjectProperty; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.scene.control.Alert; @@ -38,6 +40,7 @@ import javafx.scene.control.RadioMenuItem; import javafx.scene.control.ScrollPane; import javafx.scene.control.TextArea; import javafx.scene.control.ToggleGroup; +import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; /** @@ -66,6 +69,7 @@ public class DesignerWindowPresenter { initializeLanguageVersionMenu(); initializeASTTreeView(); initializeXPath(); + initialiseNodeInfoSection(); bindModelToView(); try { @@ -109,6 +113,11 @@ public class DesignerWindowPresenter { } + private void initialiseNodeInfoSection() { + view.getMetricResultsListView().setCellFactory(param -> new MetricResultListCell()); + } + + private void initializeXPath() { ToggleGroup xpathVersionToggleGroup = view.getXpathVersionToggleGroup(); @@ -136,13 +145,20 @@ public class DesignerWindowPresenter { TreeView astTreeView = view.getAstTreeView(); astTreeView.setCellFactory(param -> new ASTTreeCell()); - astTreeView.getSelectionModel() - .selectedItemProperty() - .addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - onNodeItemSelected(newValue.getValue()); - } - }); + + ReadOnlyObjectProperty> selectedItemProperty + = astTreeView.getSelectionModel().selectedItemProperty(); + + selectedItemProperty.addListener(observable -> { + view.getMetricResultsListView().getItems().clear(); + view.getXpathAttributesListView().getItems().clear(); + }); + + selectedItemProperty.addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + onNodeItemSelected(newValue.getValue()); + } + }); } @@ -152,6 +168,14 @@ public class DesignerWindowPresenter { ObservableList atts = DesignerUtil.getAttributes(selectedValue); view.getXpathAttributesListView().setItems(atts); + ObservableList metrics = model.evaluateAllMetrics(selectedValue); + view.getMetricResultsListView().setItems(metrics); + view.notifyMetricsAvailable(metrics.stream() + .map(MetricResult::getValue) + .filter(result -> !result.isNaN()) + .count()); + + DesignerUtil.highlightNode(view.getCodeEditorArea(), selectedValue); } } diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/MetricResultListCell.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/MetricResultListCell.java new file mode 100644 index 0000000000..f04d9f31a5 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/MetricResultListCell.java @@ -0,0 +1,45 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner; + +import java.util.Locale; + +import net.sourceforge.pmd.util.fxdesigner.model.MetricResult; + +import javafx.scene.control.ListCell; + +/** + * List cell for a metric result. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class MetricResultListCell extends ListCell { + + + @Override + protected void updateItem(MetricResult item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(null); + setGraphic(null); + } else { + setText(item.getKey().name() + " = " + niceDoubleString(item.getValue())); + } + } + + + /** Gets a nice string representation of a double. */ + private String niceDoubleString(double val) { + if (val == (int) val) { + return String.valueOf((int) val); + } else { + return String.format(Locale.ROOT, "%.4f", val); + } + } + + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ASTManager.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ASTManager.java index 57205edf25..8f6d8129b6 100644 --- a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ASTManager.java +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ASTManager.java @@ -38,9 +38,12 @@ public class ASTManager { /** Selected language version. */ private ObjectProperty languageVersion = new SimpleObjectProperty<>(); - /** Evaluates XPath queries and stores the results. */ + /** Evaluates XPath queries. */ private XPathEvaluator xpathEvaluator = new XPathEvaluator(); + /** Evaluates metrics on a node. */ + private MetricEvaluator metricEvaluator = new MetricEvaluator(); + public LanguageVersion getLanguageVersion() { return languageVersion.get(); @@ -62,6 +65,22 @@ public class ASTManager { } + /** + * Evaluates all available metrics for that node. + * + * @param n Node + * + * @return A list of all the metric results that could be computed, possibly with some Double.NaN results + */ + public ObservableList evaluateAllMetrics(Node n) { + try { + return FXCollections.observableArrayList(metricEvaluator.evaluateAllMetrics(n)); + } catch (UnsupportedOperationException e) { + return FXCollections.emptyObservableList(); + } + } + + /** * Evaluates an XPath request, returns the matching nodes. * @@ -94,6 +113,7 @@ public class ASTManager { Node node = parser.parse(null, new StringReader(source)); languageVersionHandler.getSymbolFacade().start(node); languageVersionHandler.getTypeResolutionFacade(ASTManager.class.getClassLoader()).start(node); + languageVersionHandler.getMetricsVisitorFacade().start(node); compilationUnit = node; lastValidSource = source; lastLanguageVersion = languageVersion.get(); diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/MetricEvaluator.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/MetricEvaluator.java new file mode 100644 index 0000000000..2bbc5ead86 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/MetricEvaluator.java @@ -0,0 +1,92 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.model; + +import java.util.ArrayList; +import java.util.List; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.metrics.ApexMetrics; +import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetricKey; +import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.metrics.JavaMetrics; +import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey; +import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; + +/** + * Evaluates metrics. + * + * @author Clément Fournier + * @since 6.0.0 + */ +class MetricEvaluator { + + /** + * Evaluates all available metrics and returns a list of results. + * + * @param node Node + * + * @return List of all metric results (metric key + result), including NaN results + * + * @throws UnsupportedOperationException If no metrics are available for this node + */ + List evaluateAllMetrics(Node node) throws UnsupportedOperationException { + if (ASTAnyTypeDeclaration.class.isInstance(node)) { + return evaluateAllMetrics((ASTAnyTypeDeclaration) node); + } else if (ASTMethodOrConstructorDeclaration.class.isInstance(node)) { + return evaluateAllMetrics((ASTMethodOrConstructorDeclaration) node); + } else if (ASTMethod.class.isInstance(node)) { + return evaluateAllMetrics((ASTMethod) node); + } else if (ASTUserClass.class.isInstance(node)) { + return evaluateAllMetrics((ASTUserClass) node); + } + throw new UnsupportedOperationException("That language does not support metrics"); + } + + + private List evaluateAllMetrics(ASTMethodOrConstructorDeclaration node) { + List metricResults = new ArrayList<>(); + for (JavaOperationMetricKey key : JavaOperationMetricKey.values()) { + metricResults.add(new MetricResult(key, JavaMetrics.get(key, node))); + } + + return metricResults; + } + + + private List evaluateAllMetrics(ASTAnyTypeDeclaration node) { + List metricResults = new ArrayList<>(); + for (JavaClassMetricKey key : JavaClassMetricKey.values()) { + metricResults.add(new MetricResult(key, JavaMetrics.get(key, node))); + } + + return metricResults; + } + + + private List evaluateAllMetrics(ASTMethod node) { + List metricResults = new ArrayList<>(); + for (ApexOperationMetricKey key : ApexOperationMetricKey.values()) { + metricResults.add(new MetricResult(key, ApexMetrics.get(key, node))); + } + + return metricResults; + } + + + private List evaluateAllMetrics(ASTUserClass node) { + List metricResults = new ArrayList<>(); + for (ApexClassMetricKey key : ApexClassMetricKey.values()) { + metricResults.add(new MetricResult(key, ApexMetrics.get(key, node))); + } + + return metricResults; + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/MetricResult.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/MetricResult.java new file mode 100644 index 0000000000..34b86828e1 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/MetricResult.java @@ -0,0 +1,39 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.model; + +import java.util.AbstractMap.SimpleEntry; +import java.util.Map.Entry; + +import net.sourceforge.pmd.lang.metrics.MetricKey; + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public class MetricResult { + + private final SimpleEntry, Double> simpleEntry; + + + public MetricResult(MetricKey key, Double value) { + simpleEntry = new SimpleEntry<>(key, value); + } + + + MetricResult(Entry, ? extends Double> entry) { + simpleEntry = new SimpleEntry<>(entry); + } + + + public MetricKey getKey() { + return simpleEntry.getKey(); + } + + + public Double getValue() { + return simpleEntry.getValue(); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/DesignerUtil.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/DesignerUtil.java index a6a7cd1569..6669296e49 100644 --- a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/DesignerUtil.java +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/DesignerUtil.java @@ -69,6 +69,10 @@ public class DesignerUtil { */ public static void highlightNode(CodeArea codeArea, Node node) { + if (node.getBeginLine() == node.getEndLine() + && node.getBeginColumn() == node.getEndColumn()) { + return; + } StyleSpansBuilder> spansBuilder = new StyleSpansBuilder<>(); diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/view/DesignerWindow.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/view/DesignerWindow.java index 8394a287a2..3dd34519e4 100644 --- a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/view/DesignerWindow.java +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/view/DesignerWindow.java @@ -12,6 +12,7 @@ import org.fxmisc.richtext.LineNumberFactory; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.util.fxdesigner.DesignerWindowPresenter; +import net.sourceforge.pmd.util.fxdesigner.model.MetricResult; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; @@ -40,6 +41,8 @@ import javafx.util.Duration; */ public class DesignerWindow implements Initializable { + @FXML + public TitledPane metricResultsTitledPane; @FXML private CodeArea codeEditorArea; @FXML @@ -72,6 +75,9 @@ public class DesignerWindow implements Initializable { private TitledPane astTitledPane; @FXML private SplitPane mainVerticalSplitPane; + @FXML + private ListView metricResultsListView; + /* */ private StringProperty sourceCodeProperty; @@ -125,6 +131,12 @@ public class DesignerWindow implements Initializable { } + public void notifyMetricsAvailable(long numMetrics) { + metricResultsTitledPane.setText("Metrics\t(" + (numMetrics == 0 ? "none" : numMetrics) + " available)"); + metricResultsTitledPane.setDisable(numMetrics == 0); + } + + public void notifyOutdatedAST() { astTitledPane.setText("Abstract syntax tree (outdated)"); } @@ -214,4 +226,9 @@ public class DesignerWindow implements Initializable { public SplitPane getMainVerticalSplitPane() { return mainVerticalSplitPane; } + + + public ListView getMetricResultsListView() { + return metricResultsListView; + } } diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/designer.fxml b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/designer.fxml index 3590bd8b06..842c153429 100644 --- a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/designer.fxml +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/designer.fxml @@ -1,5 +1,10 @@ + + + + + @@ -21,82 +26,67 @@ - + + - + - + - + - +
- + - + - - + + - + - + - + - + + + + + + + + + + + - - - + - + - + @@ -105,42 +95,26 @@ - +
- + - + - + - + - + - + @@ -151,51 +125,37 @@ - - + - - + + - + - + - + - + - +