001/*
002 * SonarQube
003 * Copyright (C) 2009-2016 SonarSource SA
004 * mailto:contact AT sonarsource DOT com
005 *
006 * This program is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020package org.sonar.batch.protocol.viewer;
021
022import java.awt.BorderLayout;
023import java.awt.Cursor;
024import java.awt.Dimension;
025import java.awt.EventQueue;
026import java.io.File;
027import java.io.IOException;
028import java.io.PrintWriter;
029import java.io.StringWriter;
030import java.nio.charset.StandardCharsets;
031import java.sql.Date;
032import java.text.SimpleDateFormat;
033import java.util.Scanner;
034import javax.swing.JEditorPane;
035import javax.swing.JFileChooser;
036import javax.swing.JFrame;
037import javax.swing.JOptionPane;
038import javax.swing.JPanel;
039import javax.swing.JScrollPane;
040import javax.swing.JSplitPane;
041import javax.swing.JTabbedPane;
042import javax.swing.JTree;
043import javax.swing.UIManager;
044import javax.swing.UIManager.LookAndFeelInfo;
045import javax.swing.event.TreeSelectionEvent;
046import javax.swing.event.TreeSelectionListener;
047import javax.swing.tree.DefaultMutableTreeNode;
048import javax.swing.tree.DefaultTreeModel;
049import javax.swing.tree.TreeSelectionModel;
050import org.sonar.batch.protocol.output.BatchReport;
051import org.sonar.batch.protocol.output.BatchReport.Component;
052import org.sonar.batch.protocol.output.BatchReport.Metadata;
053import org.sonar.batch.protocol.output.BatchReportReader;
054import org.sonar.batch.protocol.output.FileStructure.Domain;
055import org.sonar.core.util.CloseableIterator;
056
057public class ViewerApplication {
058
059  private JFrame frame;
060  private BatchReportReader reader;
061  private Metadata metadata;
062  private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
063  private JTree componentTree;
064  private JSplitPane splitPane;
065  private JTabbedPane tabbedPane;
066  private JScrollPane treeScrollPane;
067  private JScrollPane componentDetailsTab;
068  private JScrollPane highlightingTab;
069  private JEditorPane componentEditor;
070  private JEditorPane highlightingEditor;
071  private JScrollPane sourceTab;
072  private JEditorPane sourceEditor;
073  private JScrollPane coverageTab;
074  private JEditorPane coverageEditor;
075  private TextLineNumber textLineNumber;
076  private JScrollPane duplicationTab;
077  private JEditorPane duplicationEditor;
078
079  /**
080   * Create the application.
081   */
082  public ViewerApplication() {
083    initialize();
084  }
085
086  /**
087   * Launch the application.
088   */
089  public static void main(String[] args) {
090    EventQueue.invokeLater(new Runnable() {
091      @Override
092      public void run() {
093        try {
094          ViewerApplication window = new ViewerApplication();
095          window.frame.setVisible(true);
096
097          window.loadReport();
098        } catch (Exception e) {
099          e.printStackTrace();
100        }
101      }
102
103    });
104  }
105
106  private void loadReport() {
107    final JFileChooser fc = new JFileChooser();
108    fc.setDialogTitle("Choose scanner report directory");
109    fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
110    fc.setFileHidingEnabled(false);
111    fc.setApproveButtonText("Open scanner report");
112    int returnVal = fc.showOpenDialog(frame);
113    if (returnVal == JFileChooser.APPROVE_OPTION) {
114      File file = fc.getSelectedFile();
115      try {
116        loadReport(file);
117      } catch (Exception e) {
118        JOptionPane.showMessageDialog(frame, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
119        exit();
120      }
121    } else {
122      exit();
123    }
124
125  }
126
127  private void exit() {
128    frame.setVisible(false);
129    frame.dispose();
130  }
131
132  private void loadReport(File file) {
133    reader = new BatchReportReader(file);
134    metadata = reader.readMetadata();
135    updateTitle();
136    loadComponents();
137  }
138
139  private void loadComponents() {
140    int rootComponentRef = metadata.getRootComponentRef();
141    Component component = reader.readComponent(rootComponentRef);
142    DefaultMutableTreeNode project = createNode(component);
143    loadChildren(component, project);
144    getComponentTree().setModel(new DefaultTreeModel(project));
145  }
146
147  private static DefaultMutableTreeNode createNode(Component component) {
148    return new DefaultMutableTreeNode(component) {
149      @Override
150      public String toString() {
151        return getNodeName((Component) getUserObject());
152      }
153    };
154  }
155
156  private static String getNodeName(Component component) {
157    switch (component.getType()) {
158      case PROJECT:
159      case MODULE:
160        return component.getName();
161      case DIRECTORY:
162      case FILE:
163        return component.getPath();
164      default:
165        throw new IllegalArgumentException("Unknow component type: " + component.getType());
166    }
167  }
168
169  private void loadChildren(Component parentComponent, DefaultMutableTreeNode parentNode) {
170    for (int ref : parentComponent.getChildRefList()) {
171      Component child = reader.readComponent(ref);
172      DefaultMutableTreeNode childNode = createNode(child);
173      parentNode.add(childNode);
174      loadChildren(child, childNode);
175    }
176
177  }
178
179  private void updateTitle() {
180    frame.setTitle(metadata.getProjectKey() + (metadata.hasBranch() ? (" (" + metadata.getBranch() + ")") : "") + " " + sdf.format(new Date(metadata.getAnalysisDate())));
181  }
182
183  private void updateDetails(Component component) {
184    componentEditor.setText(component.toString());
185    updateHighlighting(component);
186    updateSource(component);
187    updateCoverage(component);
188    updateDuplications(component);
189  }
190
191  private void updateDuplications(Component component) {
192    duplicationEditor.setText("");
193    if (reader.hasCoverage(component.getRef())) {
194      try (CloseableIterator<BatchReport.Duplication> it = reader.readComponentDuplications(component.getRef())) {
195        while (it.hasNext()) {
196          BatchReport.Duplication dup = it.next();
197          duplicationEditor.getDocument().insertString(duplicationEditor.getDocument().getEndPosition().getOffset(), dup.toString() + "\n", null);
198        }
199      } catch (Exception e) {
200        throw new IllegalStateException("Can't read duplications for " + getNodeName(component), e);
201      }
202    }
203  }
204
205  private void updateCoverage(Component component) {
206    coverageEditor.setText("");
207    try (CloseableIterator<BatchReport.Coverage> it = reader.readComponentCoverage(component.getRef())) {
208      while (it.hasNext()) {
209        BatchReport.Coverage coverage = it.next();
210        coverageEditor.getDocument().insertString(coverageEditor.getDocument().getEndPosition().getOffset(), coverage.toString() + "\n", null);
211      }
212    } catch (Exception e) {
213      throw new IllegalStateException("Can't read code coverage for " + getNodeName(component), e);
214    }
215  }
216
217  private void updateSource(Component component) {
218    File sourceFile = reader.getFileStructure().fileFor(Domain.SOURCE, component.getRef());
219    sourceEditor.setText("");
220
221    if (sourceFile.exists()) {
222      try (Scanner s = new Scanner(sourceFile, StandardCharsets.UTF_8.name()).useDelimiter("\\Z")) {
223        if (s.hasNext()) {
224          sourceEditor.setText(s.next());
225        }
226      } catch (IOException ex) {
227        StringWriter errors = new StringWriter();
228        ex.printStackTrace(new PrintWriter(errors));
229        sourceEditor.setText(errors.toString());
230      }
231    }
232  }
233
234  private void updateHighlighting(Component component) {
235    highlightingEditor.setText("");
236    try (CloseableIterator<BatchReport.SyntaxHighlighting> it = reader.readComponentSyntaxHighlighting(component.getRef())) {
237      while (it.hasNext()) {
238        BatchReport.SyntaxHighlighting rule = it.next();
239        highlightingEditor.getDocument().insertString(highlightingEditor.getDocument().getEndPosition().getOffset(), rule.toString() + "\n", null);
240      }
241    } catch (Exception e) {
242      throw new IllegalStateException("Can't read syntax highlighting for " + getNodeName(component), e);
243    }
244  }
245
246  /**
247   * Initialize the contents of the frame.
248   */
249  private void initialize() {
250    try {
251      for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
252        if ("Nimbus".equals(info.getName())) {
253          UIManager.setLookAndFeel(info.getClassName());
254          break;
255        }
256      }
257    } catch (Exception e) {
258      // If Nimbus is not available, you can set the GUI to another look and feel.
259    }
260    frame = new JFrame();
261    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
262
263    splitPane = new JSplitPane();
264    frame.getContentPane().add(splitPane, BorderLayout.CENTER);
265
266    tabbedPane = new JTabbedPane(JTabbedPane.TOP);
267    tabbedPane.setPreferredSize(new Dimension(500, 7));
268    splitPane.setRightComponent(tabbedPane);
269
270    componentDetailsTab = new JScrollPane();
271    tabbedPane.addTab("Component details", null, componentDetailsTab, null);
272
273    componentEditor = new JEditorPane();
274    componentDetailsTab.setViewportView(componentEditor);
275
276    sourceTab = new JScrollPane();
277    tabbedPane.addTab("Source", null, sourceTab, null);
278
279    sourceEditor = createSourceEditor();
280    sourceEditor.setEditable(false);
281    sourceTab.setViewportView(sourceEditor);
282
283    textLineNumber = createTextLineNumber();
284    sourceTab.setRowHeaderView(textLineNumber);
285
286    highlightingTab = new JScrollPane();
287    tabbedPane.addTab("Highlighting", null, highlightingTab, null);
288
289    highlightingEditor = new JEditorPane();
290    highlightingTab.setViewportView(highlightingEditor);
291
292    coverageTab = new JScrollPane();
293    tabbedPane.addTab("Coverage", null, coverageTab, null);
294
295    coverageEditor = new JEditorPane();
296    coverageTab.setViewportView(coverageEditor);
297
298    duplicationTab = new JScrollPane();
299    tabbedPane.addTab("Duplications", null, duplicationTab, null);
300
301    duplicationEditor = new JEditorPane();
302    duplicationTab.setViewportView(duplicationEditor);
303
304    treeScrollPane = new JScrollPane();
305    treeScrollPane.setPreferredSize(new Dimension(200, 400));
306    splitPane.setLeftComponent(treeScrollPane);
307
308    componentTree = new JTree();
309    componentTree.setModel(new DefaultTreeModel(
310      new DefaultMutableTreeNode("empty") {
311        {
312        }
313      }));
314    treeScrollPane.setViewportView(componentTree);
315    componentTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
316    componentTree.addTreeSelectionListener(new TreeSelectionListener() {
317      @Override
318      public void valueChanged(TreeSelectionEvent e) {
319        DefaultMutableTreeNode node = (DefaultMutableTreeNode) componentTree.getLastSelectedPathComponent();
320
321        if (node == null) {
322          // Nothing is selected.
323          return;
324        }
325
326        frame.setCursor(new Cursor(Cursor.WAIT_CURSOR));
327        updateDetails((Component) node.getUserObject());
328        frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
329
330      }
331
332    });
333    frame.pack();
334  }
335
336  public JTree getComponentTree() {
337    return componentTree;
338  }
339
340  /**
341   * @wbp.factory
342   */
343  public static JPanel createComponentPanel() {
344    JPanel panel = new JPanel();
345    return panel;
346  }
347
348  protected JEditorPane getComponentEditor() {
349    return componentEditor;
350  }
351
352  /**
353   * @wbp.factory
354   */
355  public static JEditorPane createSourceEditor() {
356    JEditorPane editorPane = new JEditorPane();
357    return editorPane;
358  }
359
360  /**
361   * @wbp.factory
362   */
363  public TextLineNumber createTextLineNumber() {
364    TextLineNumber textLineNumber = new TextLineNumber(sourceEditor);
365    return textLineNumber;
366  }
367}