/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.scanner.protocol.viewer;
import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Scanner;
import javax.annotation.CheckForNull;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeSelectionModel;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.sonar.core.util.CloseableIterator;
import org.sonar.scanner.protocol.output.FileStructure.Domain;
import org.sonar.scanner.protocol.output.ScannerReport;
import org.sonar.scanner.protocol.output.ScannerReport.Changesets;
import org.sonar.scanner.protocol.output.ScannerReport.Changesets.Changeset;
import org.sonar.scanner.protocol.output.ScannerReport.Component;
import org.sonar.scanner.protocol.output.ScannerReport.Issue;
import org.sonar.scanner.protocol.output.ScannerReport.Metadata;
import org.sonar.scanner.protocol.output.ScannerReportReader;
public class ScannerReportViewerApp {
private JFrame frame;
private ScannerReportReader reader;
private Metadata metadata;
private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
private JTree componentTree;
private JSplitPane splitPane;
private JTabbedPane tabbedPane;
private JScrollPane treeScrollPane;
private JScrollPane componentDetailsTab;
private JScrollPane highlightingTab;
private JScrollPane symbolTab;
private JEditorPane componentEditor;
private JEditorPane highlightingEditor;
private JEditorPane symbolEditor;
private JScrollPane sourceTab;
private JEditorPane sourceEditor;
private JScrollPane coverageTab;
private JEditorPane coverageEditor;
private JScrollPane testsTab;
private JEditorPane testsEditor;
private TextLineNumber textLineNumber;
private JScrollPane duplicationTab;
private JEditorPane duplicationEditor;
private JScrollPane issuesTab;
private JEditorPane issuesEditor;
private JScrollPane measuresTab;
private JEditorPane measuresEditor;
private JScrollPane scmTab;
private JEditorPane scmEditor;
/**
* Create the application.
*/
public ScannerReportViewerApp() {
initialize();
}
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
ScannerReportViewerApp window = new ScannerReportViewerApp();
window.frame.setVisible(true);
window.loadReport();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
private void loadReport() {
final JFileChooser fc = new JFileChooser();
fc.setDialogTitle("Choose scanner report directory");
File lastReport = getLastUsedReport();
if (lastReport != null) {
fc.setCurrentDirectory(lastReport);
}
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
fc.setFileHidingEnabled(false);
fc.setApproveButtonText("Open scanner report");
int returnVal = fc.showOpenDialog(frame);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
try {
setLastUsedReport(file);
loadReport(file);
} catch (Exception e) {
JOptionPane.showMessageDialog(frame, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
exit();
}
} else {
exit();
}
}
@CheckForNull
private File getLastUsedReport() {
File f = new File(System.getProperty("java.io.tmpdir"), ".last_batch_report_dir");
if (f.exists()) {
String path;
try {
path = FileUtils.readFileToString(f, StandardCharsets.UTF_8);
} catch (IOException e) {
return null;
}
File lastReport = new File(path);
if (lastReport.exists() && lastReport.isDirectory()) {
return lastReport;
}
}
return null;
}
private void setLastUsedReport(File lastReport) throws IOException {
File f = new File(System.getProperty("java.io.tmpdir"), ".last_batch_report_dir");
String fullPath = lastReport.getAbsolutePath();
FileUtils.write(f, fullPath, StandardCharsets.UTF_8);
}
private void exit() {
frame.setVisible(false);
frame.dispose();
}
private void loadReport(File file) {
reader = new ScannerReportReader(file);
metadata = reader.readMetadata();
updateTitle();
loadComponents();
}
private void loadComponents() {
int rootComponentRef = metadata.getRootComponentRef();
Component component = reader.readComponent(rootComponentRef);
DefaultMutableTreeNode project = createNode(component);
loadChildren(component, project);
getComponentTree().setModel(new DefaultTreeModel(project));
}
private static DefaultMutableTreeNode createNode(Component component) {
return new DefaultMutableTreeNode(component) {
@Override
public String toString() {
return getNodeName((Component) getUserObject());
}
};
}
private static String getNodeName(Component component) {
switch (component.getType()) {
case PROJECT:
case MODULE:
return component.getName();
case DIRECTORY:
case FILE:
return component.getPath();
default:
throw new IllegalArgumentException("Unknow component type: " + component.getType());
}
}
private void loadChildren(Component parentComponent, DefaultMutableTreeNode parentNode) {
for (int ref : parentComponent.getChildRefList()) {
Component child = reader.readComponent(ref);
DefaultMutableTreeNode childNode = createNode(child);
parentNode.add(childNode);
loadChildren(child, childNode);
}
}
private void updateTitle() {
frame.setTitle(metadata.getProjectKey() + (StringUtils.isNotEmpty(metadata.getBranch()) ? (" (" + metadata.getBranch() + ")") : "") + " "
+ sdf.format(new Date(metadata.getAnalysisDate())));
}
private void updateDetails(Component component) {
componentEditor.setText(component.toString());
updateHighlighting(component);
updateSymbols(component);
updateSource(component);
updateCoverage(component);
updateTests(component);
updateDuplications(component);
updateIssues(component);
updateMeasures(component);
updateScm(component);
}
private void updateDuplications(Component component) {
duplicationEditor.setText("");
if (reader.hasCoverage(component.getRef())) {
try (CloseableIterator<ScannerReport.Duplication> it = reader.readComponentDuplications(component.getRef())) {
while (it.hasNext()) {
ScannerReport.Duplication dup = it.next();
duplicationEditor.getDocument().insertString(duplicationEditor.getDocument().getEndPosition().getOffset(), dup + "\n", null);
}
} catch (Exception e) {
throw new IllegalStateException("Can't read duplications for " + getNodeName(component), e);
}
}
}
private void updateIssues(Component component) {
issuesEditor.setText("");
try (CloseableIterator<Issue> it = reader.readComponentIssues(component.getRef())) {
while (it.hasNext()) {
Issue issue = it.next();
int offset = issuesEditor.getDocument().getEndPosition().getOffset();
issuesEditor.getDocument().insertString(offset, issue.toString(), null);
}
} catch (Exception e) {
throw new IllegalStateException("Can't read issues for " + getNodeName(component), e);
}
}
private void updateCoverage(Component component) {
coverageEditor.setText("");
try (CloseableIterator<ScannerReport.LineCoverage> it = reader.readComponentCoverage(component.getRef())) {
while (it.hasNext()) {
ScannerReport.LineCoverage coverage = it.next();
coverageEditor.getDocument().insertString(coverageEditor.getDocument().getEndPosition().getOffset(), coverage + "\n", null);
}
} catch (Exception e) {
throw new IllegalStateException("Can't read code coverage for " + getNodeName(component), e);
}
}
private void updateTests(Component component) {
testsEditor.setText("");
File tests = reader.readTests(component.getRef());
if (tests == null) {
return;
}
try (InputStream inputStream = FileUtils.openInputStream(tests)) {
ScannerReport.Test test = ScannerReport.Test.parser().parseDelimitedFrom(inputStream);
while (test != null) {
testsEditor.getDocument().insertString(testsEditor.getDocument().getEndPosition().getOffset(), test + "\n", null);
test = ScannerReport.Test.parser().parseDelimitedFrom(inputStream);
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private void updateSource(Component component) {
File sourceFile = reader.getFileStructure().fileFor(Domain.SOURCE, component.getRef());
sourceEditor.setText("");
if (sourceFile.exists()) {
try (Scanner s = new Scanner(sourceFile, StandardCharsets.UTF_8.name()).useDelimiter("\\Z")) {
if (s.hasNext()) {
sourceEditor.setText(s.next());
}
} catch (IOException ex) {
StringWriter errors = new StringWriter();
ex.printStackTrace(new PrintWriter(errors));
sourceEditor.setText(errors.toString());
}
}
}
private void updateHighlighting(Component component) {
highlightingEditor.setText("");
try (CloseableIterator<ScannerReport.SyntaxHighlightingRule> it = reader.readComponentSyntaxHighlighting(component.getRef())) {
while (it.hasNext()) {
ScannerReport.SyntaxHighlightingRule rule = it.next();
highlightingEditor.getDocument().insertString(highlightingEditor.getDocument().getEndPosition().getOffset(), rule + "\n", null);
}
} catch (Exception e) {
throw new IllegalStateException("Can't read syntax highlighting for " + getNodeName(component), e);
}
}
private void updateMeasures(Component component) {
measuresEditor.setText("");
try (CloseableIterator<ScannerReport.Measure> it = reader.readComponentMeasures(component.getRef())) {
while (it.hasNext()) {
ScannerReport.Measure measure = it.next();
measuresEditor.getDocument().insertString(measuresEditor.getDocument().getEndPosition().getOffset(), measure + "\n", null);
}
} catch (Exception e) {
throw new IllegalStateException("Can't read measures for " + getNodeName(component), e);
}
}
private void updateScm(Component component) {
scmEditor.setText("");
Changesets changesets = reader.readChangesets(component.getRef());
if (changesets == null) {
return;
}
List<Integer> changesetIndexByLine = changesets.getChangesetIndexByLineList();
try {
int index = 0;
for (Changeset changeset : changesets.getChangesetList()) {
scmEditor.getDocument().insertString(scmEditor.getDocument().getEndPosition().getOffset(), Integer.toString(index) + "\n", null);
scmEditor.getDocument().insertString(scmEditor.getDocument().getEndPosition().getOffset(), changeset + "\n", null);
index++;
}
scmEditor.getDocument().insertString(scmEditor.getDocument().getEndPosition().getOffset(), "\n", null);
int line = 1;
for (Integer idx : changesetIndexByLine) {
scmEditor.getDocument().insertString(scmEditor.getDocument().getEndPosition().getOffset(), Integer.toString(line) + ": " + idx + "\n", null);
line++;
}
} catch (Exception e) {
throw new IllegalStateException("Can't read SCM for " + getNodeName(component), e);
}
}
private void updateSymbols(Component component) {
symbolEditor.setText("");
try (CloseableIterator<ScannerReport.Symbol> it = reader.readComponentSymbols(component.getRef())) {
while (it.hasNext()) {
ScannerReport.Symbol symbol = it.next();
symbolEditor.getDocument().insertString(symbolEditor.getDocument().getEndPosition().getOffset(), symbol + "\n", null);
}
} catch (Exception e) {
throw new IllegalStateException("Can't read symbol references for " + getNodeName(component), e);
}
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
try {
for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (Exception e) {
// If Nimbus is not available, you can set the GUI to another look and feel.
}
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
splitPane = new JSplitPane();
frame.getContentPane().add(splitPane, BorderLayout.CENTER);
tabbedPane = new JTabbedPane(JTabbedPane.TOP);
tabbedPane.setPreferredSize(new Dimension(500, 7));
splitPane.setRightComponent(tabbedPane);
componentDetailsTab = new JScrollPane();
tabbedPane.addTab("Component details", null, componentDetailsTab, null);
componentEditor = new JEditorPane();
componentDetailsTab.setViewportView(componentEditor);
sourceTab = new JScrollPane();
tabbedPane.addTab("Source", null, sourceTab, null);
sourceEditor = createSourceEditor();
sourceEditor.setEditable(false);
sourceTab.setViewportView(sourceEditor);
textLineNumber = createTextLineNumber();
sourceTab.setRowHeaderView(textLineNumber);
highlightingTab = new JScrollPane();
tabbedPane.addTab("Highlighting", null, highlightingTab, null);
highlightingEditor = new JEditorPane();
highlightingTab.setViewportView(highlightingEditor);
symbolTab = new JScrollPane();
tabbedPane.addTab("Symbol references", null, symbolTab, null);
symbolEditor = new JEditorPane();
symbolTab.setViewportView(symbolEditor);
coverageTab = new JScrollPane();
tabbedPane.addTab("Coverage", null, coverageTab, null);
coverageEditor = new JEditorPane();
coverageTab.setViewportView(coverageEditor);
duplicationTab = new JScrollPane();
tabbedPane.addTab("Duplications", null, duplicationTab, null);
duplicationEditor = new JEditorPane();
duplicationTab.setViewportView(duplicationEditor);
testsTab = new JScrollPane();
tabbedPane.addTab("Tests", null, testsTab, null);
testsEditor = new JEditorPane();
testsTab.setViewportView(testsEditor);
issuesTab = new JScrollPane();
tabbedPane.addTab("Issues", null, issuesTab, null);
issuesEditor = new JEditorPane();
issuesTab.setViewportView(issuesEditor);
measuresTab = new JScrollPane();
tabbedPane.addTab("Measures", null, measuresTab, null);
measuresEditor = new JEditorPane();
measuresTab.setViewportView(measuresEditor);
scmTab = new JScrollPane();
tabbedPane.addTab("SCM", null, scmTab, null);
scmEditor = new JEditorPane();
scmTab.setViewportView(scmEditor);
treeScrollPane = new JScrollPane();
treeScrollPane.setPreferredSize(new Dimension(200, 400));
splitPane.setLeftComponent(treeScrollPane);
componentTree = new JTree();
componentTree.setModel(new DefaultTreeModel(
new DefaultMutableTreeNode("empty") {
{
}
}));
treeScrollPane.setViewportView(componentTree);
componentTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
componentTree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) componentTree.getLastSelectedPathComponent();
if (node == null) {
// Nothing is selected.
return;
}
frame.setCursor(new Cursor(Cursor.WAIT_CURSOR));
updateDetails((Component) node.getUserObject());
frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
});
frame.pack();
}
public JTree getComponentTree() {
return componentTree;
}
/**
* @wbp.factory
*/
public static JPanel createComponentPanel() {
JPanel panel = new JPanel();
return panel;
}
protected JEditorPane getComponentEditor() {
return componentEditor;
}
/**
* @wbp.factory
*/
public static JEditorPane createSourceEditor() {
JEditorPane editorPane = new JEditorPane();
return editorPane;
}
/**
* @wbp.factory
*/
public TextLineNumber createTextLineNumber() {
TextLineNumber textLineNumber = new TextLineNumber(sourceEditor);
return textLineNumber;
}
}