aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien HENRY <julien.henry@sonarsource.com>2015-10-14 15:59:51 +0200
committerJulien HENRY <julien.henry@sonarsource.com>2015-11-09 08:40:05 +0100
commiteb5fd4fe77eb1b82b3aa5e1c3474d85ac39ee9ce (patch)
tree63ad2b709ae01f8fb7fe47540df792c2cc9a1d91
parent7567526369d1082a8023623c016b970cac314c64 (diff)
downloadsonarqube-eb5fd4fe77eb1b82b3aa5e1c3474d85ac39ee9ce.tar.gz
sonarqube-eb5fd4fe77eb1b82b3aa5e1c3474d85ac39ee9ce.zip
SONAR-6922 Initial version of report viewer
-rw-r--r--sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java5
-rw-r--r--sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/TextLineNumber.java460
-rw-r--r--sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/ViewerApplication.java341
3 files changed, 806 insertions, 0 deletions
diff --git a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java
index cdb1d542fe7..786fcaaa9fb 100644
--- a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java
+++ b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/output/BatchReportReader.java
@@ -120,6 +120,11 @@ public class BatchReportReader {
return emptyCloseableIterator();
}
+ public boolean hasCoverage(int componentRef) {
+ File file = fileStructure.fileFor(FileStructure.Domain.COVERAGES, componentRef);
+ return file.exists();
+ }
+
public CloseableIterator<BatchReport.Coverage> readComponentCoverage(int fileRef) {
File file = fileStructure.fileFor(FileStructure.Domain.COVERAGES, fileRef);
if (fileExists(file)) {
diff --git a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/TextLineNumber.java b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/TextLineNumber.java
new file mode 100644
index 00000000000..f0aa1e3215b
--- /dev/null
+++ b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/TextLineNumber.java
@@ -0,0 +1,460 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.protocol.viewer;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.HashMap;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.MatteBorder;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Element;
+import javax.swing.text.JTextComponent;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.Utilities;
+
+/**
+ * This class will display line numbers for a related text component. The text
+ * component must use the same line height for each line. TextLineNumber
+ * supports wrapped lines and will highlight the line number of the current
+ * line in the text component.
+ *
+ * This class was designed to be used as a component added to the row header
+ * of a JScrollPane.
+ */
+public class TextLineNumber extends JPanel
+ implements CaretListener, DocumentListener, PropertyChangeListener {
+ public final static float LEFT = 0.0f;
+ public final static float CENTER = 0.5f;
+ public final static float RIGHT = 1.0f;
+
+ private final static Border OUTER = new MatteBorder(0, 0, 0, 2, Color.GRAY);
+
+ private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
+
+ // Text component this TextTextLineNumber component is in sync with
+
+ private JTextComponent component;
+
+ // Properties that can be changed
+
+ private boolean updateFont;
+ private int borderGap;
+ private Color currentLineForeground;
+ private float digitAlignment;
+ private int minimumDisplayDigits;
+
+ // Keep history information to reduce the number of times the component
+ // needs to be repainted
+
+ private int lastDigits;
+ private int lastHeight;
+ private int lastLine;
+
+ private HashMap<String, FontMetrics> fonts;
+
+ /**
+ * Create a line number component for a text component. This minimum
+ * display width will be based on 3 digits.
+ *
+ * @param component the related text component
+ */
+ public TextLineNumber(JTextComponent component) {
+ this(component, 3);
+ }
+
+ /**
+ * Create a line number component for a text component.
+ *
+ * @param component the related text component
+ * @param minimumDisplayDigits the number of digits used to calculate
+ * the minimum width of the component
+ */
+ public TextLineNumber(JTextComponent component, int minimumDisplayDigits) {
+ this.component = component;
+
+ setFont(component.getFont());
+
+ setBorderGap(5);
+ setCurrentLineForeground(Color.RED);
+ setDigitAlignment(RIGHT);
+ setMinimumDisplayDigits(minimumDisplayDigits);
+
+ component.getDocument().addDocumentListener(this);
+ component.addCaretListener(this);
+ component.addPropertyChangeListener("font", this);
+ }
+
+ /**
+ * Gets the update font property
+ *
+ * @return the update font property
+ */
+ public boolean getUpdateFont() {
+ return updateFont;
+ }
+
+ /**
+ * Set the update font property. Indicates whether this Font should be
+ * updated automatically when the Font of the related text component
+ * is changed.
+ *
+ * @param updateFont when true update the Font and repaint the line
+ * numbers, otherwise just repaint the line numbers.
+ */
+ public void setUpdateFont(boolean updateFont) {
+ this.updateFont = updateFont;
+ }
+
+ /**
+ * Gets the border gap
+ *
+ * @return the border gap in pixels
+ */
+ public int getBorderGap() {
+ return borderGap;
+ }
+
+ /**
+ * The border gap is used in calculating the left and right insets of the
+ * border. Default value is 5.
+ *
+ * @param borderGap the gap in pixels
+ */
+ public void setBorderGap(int borderGap) {
+ this.borderGap = borderGap;
+ Border inner = new EmptyBorder(0, borderGap, 0, borderGap);
+ setBorder(new CompoundBorder(OUTER, inner));
+ lastDigits = 0;
+ setPreferredWidth();
+ }
+
+ /**
+ * Gets the current line rendering Color
+ *
+ * @return the Color used to render the current line number
+ */
+ public Color getCurrentLineForeground() {
+ return currentLineForeground == null ? getForeground() : currentLineForeground;
+ }
+
+ /**
+ * The Color used to render the current line digits. Default is Coolor.RED.
+ *
+ * @param currentLineForeground the Color used to render the current line
+ */
+ public void setCurrentLineForeground(Color currentLineForeground) {
+ this.currentLineForeground = currentLineForeground;
+ }
+
+ /**
+ * Gets the digit alignment
+ *
+ * @return the alignment of the painted digits
+ */
+ public float getDigitAlignment() {
+ return digitAlignment;
+ }
+
+ /**
+ * Specify the horizontal alignment of the digits within the component.
+ * Common values would be:
+ * <ul>
+ * <li>TextLineNumber.LEFT
+ * <li>TextLineNumber.CENTER
+ * <li>TextLineNumber.RIGHT (default)
+ * </ul>
+ * @param currentLineForeground the Color used to render the current line
+ */
+ public void setDigitAlignment(float digitAlignment) {
+ this.digitAlignment = digitAlignment > 1.0f ? 1.0f : digitAlignment < 0.0f ? -1.0f : digitAlignment;
+ }
+
+ /**
+ * Gets the minimum display digits
+ *
+ * @return the minimum display digits
+ */
+ public int getMinimumDisplayDigits() {
+ return minimumDisplayDigits;
+ }
+
+ /**
+ * Specify the mimimum number of digits used to calculate the preferred
+ * width of the component. Default is 3.
+ *
+ * @param minimumDisplayDigits the number digits used in the preferred
+ * width calculation
+ */
+ public void setMinimumDisplayDigits(int minimumDisplayDigits) {
+ this.minimumDisplayDigits = minimumDisplayDigits;
+ setPreferredWidth();
+ }
+
+ /**
+ * Calculate the width needed to display the maximum line number
+ */
+ private void setPreferredWidth() {
+ Element root = component.getDocument().getDefaultRootElement();
+ int lines = root.getElementCount();
+ int digits = Math.max(String.valueOf(lines).length(), minimumDisplayDigits);
+
+ // Update sizes when number of digits in the line number changes
+
+ if (lastDigits != digits) {
+ lastDigits = digits;
+ FontMetrics fontMetrics = getFontMetrics(getFont());
+ int width = fontMetrics.charWidth('0') * digits;
+ Insets insets = getInsets();
+ int preferredWidth = insets.left + insets.right + width;
+
+ Dimension d = getPreferredSize();
+ d.setSize(preferredWidth, HEIGHT);
+ setPreferredSize(d);
+ setSize(d);
+ }
+ }
+
+ /**
+ * Draw the line numbers
+ */
+ @Override
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ // Determine the width of the space available to draw the line number
+
+ FontMetrics fontMetrics = component.getFontMetrics(component.getFont());
+ Insets insets = getInsets();
+ int availableWidth = getSize().width - insets.left - insets.right;
+
+ // Determine the rows to draw within the clipped bounds.
+
+ Rectangle clip = g.getClipBounds();
+ int rowStartOffset = component.viewToModel(new Point(0, clip.y));
+ int endOffset = component.viewToModel(new Point(0, clip.y + clip.height));
+
+ while (rowStartOffset <= endOffset) {
+ try {
+ if (isCurrentLine(rowStartOffset))
+ g.setColor(getCurrentLineForeground());
+ else
+ g.setColor(getForeground());
+
+ // Get the line number as a string and then determine the
+ // "X" and "Y" offsets for drawing the string.
+
+ String lineNumber = getTextLineNumber(rowStartOffset);
+ int stringWidth = fontMetrics.stringWidth(lineNumber);
+ int x = getOffsetX(availableWidth, stringWidth) + insets.left;
+ int y = getOffsetY(rowStartOffset, fontMetrics);
+ g.drawString(lineNumber, x, y);
+
+ // Move to the next row
+
+ rowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1;
+ } catch (Exception e) {
+ break;
+ }
+ }
+ }
+
+ /*
+ * We need to know if the caret is currently positioned on the line we
+ * are about to paint so the line number can be highlighted.
+ */
+ private boolean isCurrentLine(int rowStartOffset) {
+ int caretPosition = component.getCaretPosition();
+ Element root = component.getDocument().getDefaultRootElement();
+
+ if (root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition))
+ return true;
+ else
+ return false;
+ }
+
+ /*
+ * Get the line number to be drawn. The empty string will be returned
+ * when a line of text has wrapped.
+ */
+ protected String getTextLineNumber(int rowStartOffset) {
+ Element root = component.getDocument().getDefaultRootElement();
+ int index = root.getElementIndex(rowStartOffset);
+ Element line = root.getElement(index);
+
+ if (line.getStartOffset() == rowStartOffset)
+ return String.valueOf(index + 1);
+ else
+ return "";
+ }
+
+ /*
+ * Determine the X offset to properly align the line number when drawn
+ */
+ private int getOffsetX(int availableWidth, int stringWidth) {
+ return (int) ((availableWidth - stringWidth) * digitAlignment);
+ }
+
+ /*
+ * Determine the Y offset for the current row
+ */
+ private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics)
+ throws BadLocationException {
+ // Get the bounding rectangle of the row
+
+ Rectangle r = component.modelToView(rowStartOffset);
+ int lineHeight = fontMetrics.getHeight();
+ int y = r.y + r.height;
+ int descent = 0;
+
+ // The text needs to be positioned above the bottom of the bounding
+ // rectangle based on the descent of the font(s) contained on the row.
+
+ if (r.height == lineHeight) // default font is being used
+ {
+ descent = fontMetrics.getDescent();
+ } else // We need to check all the attributes for font changes
+ {
+ if (fonts == null)
+ fonts = new HashMap<String, FontMetrics>();
+
+ Element root = component.getDocument().getDefaultRootElement();
+ int index = root.getElementIndex(rowStartOffset);
+ Element line = root.getElement(index);
+
+ for (int i = 0; i < line.getElementCount(); i++) {
+ Element child = line.getElement(i);
+ AttributeSet as = child.getAttributes();
+ String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily);
+ Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize);
+ String key = fontFamily + fontSize;
+
+ FontMetrics fm = fonts.get(key);
+
+ if (fm == null) {
+ Font font = new Font(fontFamily, Font.PLAIN, fontSize);
+ fm = component.getFontMetrics(font);
+ fonts.put(key, fm);
+ }
+
+ descent = Math.max(descent, fm.getDescent());
+ }
+ }
+
+ return y - descent;
+ }
+
+ //
+ // Implement CaretListener interface
+ //
+ @Override
+ public void caretUpdate(CaretEvent e) {
+ // Get the line the caret is positioned on
+
+ int caretPosition = component.getCaretPosition();
+ Element root = component.getDocument().getDefaultRootElement();
+ int currentLine = root.getElementIndex(caretPosition);
+
+ // Need to repaint so the correct line number can be highlighted
+
+ if (lastLine != currentLine) {
+ repaint();
+ lastLine = currentLine;
+ }
+ }
+
+ //
+ // Implement DocumentListener interface
+ //
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ documentChanged();
+ }
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ documentChanged();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ documentChanged();
+ }
+
+ /*
+ * A document change may affect the number of displayed lines of text.
+ * Therefore the lines numbers will also change.
+ */
+ private void documentChanged() {
+ // View of the component has not been updated at the time
+ // the DocumentEvent is fired
+
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ int endPos = component.getDocument().getLength();
+ Rectangle rect = component.modelToView(endPos);
+
+ if (rect != null && rect.y != lastHeight) {
+ setPreferredWidth();
+ repaint();
+ lastHeight = rect.y;
+ }
+ } catch (BadLocationException ex) {
+ /* nothing to do */ }
+ }
+ });
+ }
+
+ //
+ // Implement PropertyChangeListener interface
+ //
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (evt.getNewValue() instanceof Font) {
+ if (updateFont) {
+ Font newFont = (Font) evt.getNewValue();
+ setFont(newFont);
+ lastDigits = 0;
+ setPreferredWidth();
+ } else {
+ repaint();
+ }
+ }
+ }
+}
diff --git a/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/ViewerApplication.java b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/ViewerApplication.java
new file mode 100644
index 00000000000..410cb6110a5
--- /dev/null
+++ b/sonar-batch-protocol/src/main/java/org/sonar/batch/protocol/viewer/ViewerApplication.java
@@ -0,0 +1,341 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.protocol.viewer;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.EventQueue;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.Scanner;
+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.sonar.batch.protocol.output.BatchReport;
+import org.sonar.batch.protocol.output.BatchReport.Component;
+import org.sonar.batch.protocol.output.BatchReport.Metadata;
+import org.sonar.batch.protocol.output.BatchReportReader;
+import org.sonar.batch.protocol.output.FileStructure.Domain;
+import org.sonar.core.util.CloseableIterator;
+
+public class ViewerApplication {
+
+ private JFrame frame;
+ private BatchReportReader 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 JEditorPane componentEditor;
+ private JEditorPane highlightingEditor;
+ private JScrollPane sourceTab;
+ private JEditorPane sourceEditor;
+ private JScrollPane coverageTab;
+ private JEditorPane coverageEditor;
+ private TextLineNumber textLineNumber;
+
+ /**
+ * Launch the application.
+ */
+ public static void main(String[] args) {
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ try {
+ ViewerApplication window = new ViewerApplication();
+ window.frame.setVisible(true);
+
+ window.loadReport();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ });
+ }
+
+ private void loadReport() {
+ final JFileChooser fc = new JFileChooser();
+ fc.setDialogTitle("Choose batch report directory");
+ fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ fc.setApproveButtonText("Open batch report");
+ int returnVal = fc.showOpenDialog(frame);
+ if (returnVal == JFileChooser.APPROVE_OPTION) {
+ File file = fc.getSelectedFile();
+ try {
+ loadReport(file);
+ } catch (Exception e) {
+ JOptionPane.showMessageDialog(frame, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
+ exit();
+ }
+ } else {
+ exit();
+ }
+
+ }
+
+ private void exit() {
+ frame.setVisible(false);
+ frame.dispose();
+ }
+
+ private void loadReport(File file) {
+ reader = new BatchReportReader(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 DefaultMutableTreeNode createNode(Component component) {
+ DefaultMutableTreeNode node = new DefaultMutableTreeNode(component) {
+ @Override
+ public String toString() {
+ return getNodeName((Component) getUserObject());
+ }
+ };
+ return node;
+ }
+
+ private 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() + (metadata.hasBranch() ? (" (" + metadata.getBranch() + ")") : "") + " " + sdf.format(new Date(metadata.getAnalysisDate())));
+ }
+
+ private void updateDetails(Component component) {
+ componentEditor.setText(component.toString());
+ updateHighlighting(component);
+ updateSource(component);
+ updateCoverage(component);
+ }
+
+ private void updateCoverage(Component component) {
+ coverageEditor.setText("");
+ if (reader.hasCoverage(component.getRef())) {
+ try (CloseableIterator<BatchReport.Coverage> it = reader.readComponentCoverage(component.getRef())) {
+ while (it.hasNext()) {
+ BatchReport.Coverage coverage = it.next();
+ coverageEditor.getDocument().insertString(coverageEditor.getDocument().getEndPosition().getOffset(), coverage.toString() + "\n", null);
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Can't read code coverage for " + getNodeName(component), e);
+ }
+ }
+ }
+
+ private void updateSource(Component component) {
+ File sourceFile = reader.getFileStructure().fileFor(Domain.SOURCE, component.getRef());
+ if (sourceFile.exists()) {
+ try (Scanner s = new Scanner(sourceFile, StandardCharsets.UTF_8.name()).useDelimiter("\\Z")) {
+ sourceEditor.setText(s.next());
+ } catch (IOException ex) {
+ StringWriter errors = new StringWriter();
+ ex.printStackTrace(new PrintWriter(errors));
+ sourceEditor.setText(errors.toString());
+ }
+ } else {
+ sourceEditor.setText("");
+ }
+ }
+
+ private void updateHighlighting(Component component) {
+ highlightingEditor.setText("");
+ if (reader.hasSyntaxHighlighting(component.getRef())) {
+ try (CloseableIterator<BatchReport.SyntaxHighlighting> it = reader.readComponentSyntaxHighlighting(component.getRef())) {
+ while (it.hasNext()) {
+ BatchReport.SyntaxHighlighting rule = it.next();
+ highlightingEditor.getDocument().insertString(highlightingEditor.getDocument().getEndPosition().getOffset(), rule.toString() + "\n", null);
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Can't read syntax highlighting for " + getNodeName(component), e);
+ }
+ }
+ }
+
+ /**
+ * Create the application.
+ */
+ public ViewerApplication() {
+ initialize();
+ }
+
+ /**
+ * 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);
+
+ coverageTab = new JScrollPane();
+ tabbedPane.addTab("Coverage", null, coverageTab, null);
+
+ coverageEditor = new JEditorPane();
+ coverageTab.setViewportView(coverageEditor);
+
+ 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() {
+ public void valueChanged(TreeSelectionEvent e) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) componentTree.getLastSelectedPathComponent();
+
+ if (node == null) {
+ // Nothing is selected.
+ return;
+ }
+
+ updateDetails((Component) node.getUserObject());
+
+ }
+
+ });
+ 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;
+ }
+}