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}