1 /* 2 * $Id$ 3 * 4 * Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved. 5 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 6 * 7 * This code is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License version 2 only, as 9 * published by the Free Software Foundation. Oracle designates this 10 * particular file as subject to the "Classpath" exception as provided 11 * by Oracle in the LICENSE file that accompanied this code. 12 * 13 * This code is distributed in the hope that it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16 * version 2 for more details (a copy is included in the LICENSE file that 17 * accompanied this code). 18 * 19 * You should have received a copy of the GNU General Public License version 20 * 2 along with this work; if not, write to the Free Software Foundation, 21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 22 * 23 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 24 * or visit www.oracle.com if you need additional information or have any 25 * questions. 26 */ 27 package com.sun.javatest.mrep; 28 29 import java.awt.BorderLayout; 30 import java.awt.Cursor; 31 import java.awt.Dimension; 32 import java.awt.EventQueue; 33 import java.awt.GridBagConstraints; 34 import java.awt.GridBagLayout; 35 import java.awt.event.ActionEvent; 36 import java.awt.event.ActionListener; 37 import java.awt.event.HierarchyEvent; 38 import java.awt.event.HierarchyListener; 39 import java.awt.event.ItemEvent; 40 import java.awt.event.ItemListener; 41 import java.io.*; 42 import java.net.MalformedURLException; 43 import java.net.URL; 44 import java.nio.charset.StandardCharsets; 45 import java.util.Map; 46 47 import javax.swing.BorderFactory; 48 import javax.swing.JEditorPane; 49 import javax.swing.JFileChooser; 50 import javax.swing.JMenu; 51 import javax.swing.JMenuBar; 52 import javax.swing.JPanel; 53 import javax.swing.event.HyperlinkEvent; 54 import javax.swing.event.HyperlinkListener; 55 import javax.swing.text.JTextComponent; 56 import javax.swing.text.html.HTMLDocument; 57 import javax.swing.text.html.HTMLEditorKit; 58 import javax.swing.text.html.HTMLFrameHyperlinkEvent; 59 60 import com.sun.javatest.report.CustomReport; 61 import com.sun.javatest.report.Report; 62 import com.sun.javatest.report.ReportSettings; 63 import com.sun.javatest.tool.Desktop; 64 import com.sun.javatest.tool.Tool; 65 import com.sun.javatest.tool.ToolManager; 66 import com.sun.javatest.report.HTMLWriterEx; 67 import com.sun.javatest.util.I18NResourceBundle; 68 import java.awt.Component; 69 import java.lang.reflect.InvocationTargetException; 70 import javax.swing.JButton; 71 import javax.swing.JDialog; 72 import javax.swing.Timer; 73 import javax.swing.SwingUtilities; 74 75 76 class ReportTool extends Tool { 77 78 // desktop is used to get custom reports 79 // for results processing 80 private Desktop desktop; 81 82 protected ReportTool(ToolManager m, Desktop d) { 83 super(m, "report", "mergeReports.window.csh"); 84 setI18NTitle("tool.title"); 85 setShortTitle(uif.getI18NString("tool.shortTitle")); 86 this.desktop = d; 87 initGUI(); 88 I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(ReportTool.class); 89 textShowing = i18n.getString("tool.helptext.showing"); 90 textHidden = i18n.getString("tool.helptext.hidden"); 91 } 92 93 public JMenuBar getMenuBar() { 94 return menuBar; 95 } 96 97 @Override 98 protected void restore(Map map) { 99 } 100 101 protected void save(Map m) { 102 } 103 104 private void initGUI(){ 105 int dpi = uif.getDotsPerInch(); 106 setPreferredSize(new Dimension(6 * dpi, 4 * dpi)); 107 setLayout(new BorderLayout()); 108 htmlKit = new HTMLEditorKit(); 109 110 addHierarchyListener(listener); 111 112 menuBar = uif.createMenuBar("tool"); 113 String[] reportMenuEntries = { 114 NEW, 115 OPEN 116 }; 117 118 JMenu reportMenu = uif.createMenu("tool.report", reportMenuEntries, new Listener()); 119 menuBar.add(reportMenu); 120 menuBar.add(uif.createHorizontalGlue("tool.pad")); 121 122 JPanel head = uif.createPanel("head", false); 123 head.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 124 head.setLayout(new GridBagLayout()); 125 126 GridBagConstraints c = new GridBagConstraints(); 127 c.fill = GridBagConstraints.BOTH; 128 c.gridwidth = 2; 129 c.gridheight = GridBagConstraints.REMAINDER; 130 c.gridx = 0; 131 c.gridy = 2; 132 c.weightx = 1.0; 133 c.weighty = 1.0; 134 browserPane = new BrowserPane(uif); 135 head.add(browserPane, c); 136 add(head); 137 updateGUI(); 138 139 } 140 141 void updateGUI() { 142 if (!EventQueue.isDispatchThread()) { 143 EventQueue.invokeLater(new Runnable() { 144 public void run() { 145 updateGUI(); 146 } 147 }); 148 return; 149 } 150 151 if (browserPane.isEmpty()) { 152 if (optionsDialog == null || !optionsDialog.isVisible()) { 153 browserPane.setPlainText(textHidden); 154 } else { 155 browserPane.setPlainText(textShowing); 156 } 157 } else { 158 if (optionsDialog == null || !optionsDialog.isVisible()) { 159 browserPane.setPlainTextHomePage(textHidden); 160 } else { 161 browserPane.setPlainTextHomePage(textShowing); 162 } 163 } 164 } 165 166 private synchronized void showOptions() { 167 if (worker != null) { 168 uif.showError("tool.reportInProgress"); 169 return; 170 } 171 172 if (optionsDialog == null) { 173 // should arguably worry about standard ownership problem 174 optionsDialog = new OptionsDialog(this, new OkListener(), uif, desktop); 175 } 176 optionsDialog.updateCustomReports(); 177 optionsDialog.setVisible(true); 178 } 179 180 private boolean setOptions() { 181 182 if (!optionsDialog.checkInput()) { 183 return false; 184 } 185 186 out = new File(optionsDialog.getResultPath()); 187 188 in = new File[optionsDialog.getXmlFiles().length]; 189 for (int i = 0; i < optionsDialog.getXmlFiles().length; i++) { 190 in[i] = new File(optionsDialog.getXmlFiles()[i]); 191 } 192 193 resolveByRecent = optionsDialog.resolveByRecent(); 194 customReports = optionsDialog.getCustomReports(); 195 isXmlReport = optionsDialog.isXmlReport(); 196 197 if (!isXmlReport && (customReports == null || customReports.length ==0)) { 198 uif.showError("tool.no_report_types"); 199 return false; 200 } 201 202 File outXML = new File(out, xmlreportFileName); 203 for (int i = 0; i < in.length; i++) { 204 String path = outXML.getAbsolutePath(); 205 if (in[i].getAbsolutePath().equals(path)) { 206 uif.showError("tool.outinput", path); 207 return false; 208 } 209 } 210 211 try { 212 optionsDialog.setVisible(false); 213 startMerge(); 214 return true; 215 } catch (Exception e) { 216 uif.showError("tool.execpt", e.getMessage()); 217 } 218 return false; 219 } 220 221 private synchronized void startMerge() { 222 if (worker != null) { 223 uif.showError("tool.reportInProgress"); 224 return; 225 } 226 227 waitDialog = uif.createWaitDialog("tool.wait", this); 228 GridBagConstraints gbc = new GridBagConstraints(); 229 gbc.fill = GridBagConstraints.NONE; 230 gbc.anchor = GridBagConstraints.CENTER; 231 gbc.insets.bottom = 10; 232 gbc.insets.top = 10; 233 gbc.gridy = 2; 234 gbc.gridx = 0; 235 JButton cancelBtn = uif.createButton("tool.cancel"); 236 waitDialog.getContentPane().add(cancelBtn, gbc); 237 waitDialog.pack(); 238 waitDialogController = new WaitDialogController(waitDialog); 239 final String cancelling = uif.getI18NString("tool.cancelling"); 240 cancelBtn.addActionListener( new ActionListener() { 241 public void actionPerformed(ActionEvent e) { 242 JButton butt = (JButton) e.getSource(); 243 butt.setEnabled(false); 244 Component[] cmp = waitDialog.getContentPane().getComponents(); 245 if (worker != null && worker.isAlive()) { 246 worker.interrupt(); 247 } 248 for ( int i = 0; i < cmp.length; i++) { 249 if("tool.wait".equals(cmp[i].getName())) { 250 if (cmp[i] instanceof JTextComponent) { 251 ((JTextComponent)cmp[i]).setText(cancelling); 252 } 253 break; 254 } 255 } 256 257 } 258 }); 259 260 worker = new Thread() { 261 public void run() { 262 try { 263 Merger merger = new Merger(); 264 ConflictResolver resolver; 265 if (!resolveByRecent) { 266 resolver = new ManualConfilctResolver(waitDialogController); 267 } else { 268 resolver = new MostRecentConfilctResolver(); 269 } 270 out.mkdir(); 271 File xmlOut = new File(out, xmlreportFileName); 272 if (merger.merge(in,xmlOut, resolver)) { 273 for (int i = 0; i < customReports.length; i++) { 274 if (Thread.currentThread().isInterrupted()) { 275 return ; 276 } 277 CustomReport cr = customReports[i]; 278 ReportSettings re = cr.getReportEnviroment(); 279 re.setMergingFiles(in); 280 re.setXMLReportFile(xmlOut); 281 cr.createReport(new File(out, cr.getReportId())); 282 } 283 284 waitDialogController.finish(); 285 286 if (!isXmlReport) { 287 xmlOut.delete(); 288 } 289 showReportDialog(out); 290 } 291 } catch (Exception e) { 292 showError("tool.exceptionInProgress", e.getMessage(), waitDialog, waitDialogController); 293 } finally { 294 295 if (!waitDialogController.wasFinished) 296 waitDialogController.finish(); 297 298 synchronized (ReportTool.this) { 299 worker = null; 300 } 301 updateGUI(); 302 } 303 } 304 305 }; 306 307 ActionListener al = new ActionListener() { 308 public void actionPerformed(ActionEvent evt) { 309 // show dialog if still processing 310 if (worker != null && worker.isAlive()) { 311 waitDialogController.show(); 312 } 313 } 314 }; 315 316 // show wait dialog if operation is still running after 317 // WAIT_DIALOG_DELAY 318 Timer timer = new Timer(WAIT_DIALOG_DELAY, al); 319 timer.setRepeats(false); 320 timer.start(); 321 322 worker.start(); 323 324 updateGUI(); 325 } 326 327 private void showError(final String uiKey, final String msg, 328 final JDialog waitDialog, final WaitDialogController waitDialogController) { 329 if (!waitDialogController.wasFinished) 330 waitDialogController.finish(); 331 // switch back to GUI thread 332 EventQueue.invokeLater(new Runnable() { 333 public void run() { 334 uif.showError(uiKey, msg); 335 } 336 } 337 ); 338 } 339 340 private static class WaitDialogController { 341 WaitDialogController(JDialog waitDialog) { 342 this.waitDialog = waitDialog; 343 } 344 345 synchronized void show() { 346 // show only once 347 if (!wasFinished) { 348 wasShown = true; 349 if (!wasHidden) { 350 setVisible(true); 351 } 352 } 353 } 354 355 synchronized void finish() { 356 wasFinished = true; 357 setVisible(false); 358 } 359 360 361 synchronized void hide() { 362 wasHidden = true; 363 if (wasShown && ! wasFinished) { 364 setVisible(false); 365 } 366 } 367 368 synchronized void restore() { 369 wasHidden = false; 370 if (wasShown && ! wasFinished) { 371 setVisible(true); 372 } 373 } 374 375 private synchronized void setVisible(final boolean b) { 376 // should we care about EventDispatchThread here? Yes I guess. 377 if (EventQueue.isDispatchThread()) { 378 waitDialog.setVisible(b); 379 } else { 380 try { 381 SwingUtilities.invokeAndWait(new Runnable() { 382 public void run() { 383 waitDialog.setVisible(b); 384 } 385 }); 386 } catch (InterruptedException e) { 387 e.printStackTrace(); 388 } catch (InvocationTargetException e) { 389 e.printStackTrace(); 390 } 391 } 392 } 393 394 private JDialog waitDialog; 395 private boolean wasFinished = false; 396 private boolean wasShown = false; 397 private boolean wasHidden = false; 398 399 } 400 401 class MostRecentConfilctResolver implements ConflictResolver { 402 403 public int resolve(String testUrl, TestResultDescr[] descrs) { 404 int res = 0; 405 406 for (int i = 1; i < descrs.length; i++) { 407 // priority of NOT_RUN status is the lowerest 408 boolean newer = descrs[i].getTime() > descrs[res].getTime(); 409 if (descrs[res].isNotRun()) { 410 if (!descrs[i].isNotRun() || newer) { 411 res = i; 412 } 413 } else { 414 if (!descrs[i].isNotRun() && newer) { 415 res = i; 416 } 417 } 418 } 419 return res; 420 421 } 422 423 } 424 425 class ManualConfilctResolver implements ConflictResolver { 426 private File preffered = null; 427 private ConflictResolutionDialog conflictResolutionDialog; 428 private WaitDialogController wdc; 429 430 public ManualConfilctResolver(WaitDialogController wdc) { 431 this.wdc = wdc; 432 } 433 434 public int resolve(String testUrl, TestResultDescr[] descrs) { 435 String[] conflictFiles = new String[descrs.length]; 436 for (int i = 0; i < descrs.length; i++) { 437 conflictFiles[i] = descrs[i].getFile().getAbsolutePath() + " " 438 + descrs[i].getStatus() ; 439 } 440 441 if (preffered != null) { 442 for (int i = 0; i < descrs.length; i++) { 443 if (descrs[i].getFile().equals(preffered)) 444 return i; 445 } 446 } 447 448 conflictResolutionDialog = 449 new ConflictResolutionDialog( 450 null, testUrl, conflictFiles, false, uif); 451 452 wdc.hide(); 453 conflictResolutionDialog.setVisible(true); 454 455 if (conflictResolutionDialog.wasCanceled()) { 456 return -1; 457 } 458 wdc.restore(); 459 460 if (conflictResolutionDialog.getUseMostRecent()) { 461 int res = 0; 462 for (int i = 0; i < descrs.length; i++) { 463 if (descrs[i].getTime() > descrs[res].getTime()) { 464 res = i; 465 } 466 } 467 return res; 468 } 469 470 471 int selected = conflictResolutionDialog.getSelectedIndex(); 472 if (selected != -1) { 473 if (conflictResolutionDialog.getPreferredReport()) { 474 preffered = descrs[selected].getFile(); 475 } 476 return selected; 477 } 478 return 0; 479 } 480 481 } 482 483 484 private String listLocalDirectory(File dir) { 485 if (!dir.isAbsolute()) 486 dir = dir.getAbsoluteFile(); 487 488 String displayPath = dir.getPath(); 489 490 String[] filelist = dir.list(); 491 StringWriter sw = new StringWriter(); 492 try { 493 HTMLWriterEx out = new HTMLWriterEx(sw, uif.getI18NResourceBundle()); 494 495 out.startTag(HTMLWriterEx.HTML); 496 out.startTag(HTMLWriterEx.HEAD); 497 out.writeContentMeta(); 498 out.startTag(HTMLWriterEx.TITLE); 499 out.write(displayPath); 500 out.endTag(HTMLWriterEx.TITLE); 501 out.endTag(HTMLWriterEx.HEAD); 502 out.startTag(HTMLWriterEx.BODY); 503 out.writeStyleAttr("font-family: SansSerif; font-size: 12pt"); 504 out.startTag(HTMLWriterEx.H3); 505 out.writeI18N("fp.head", displayPath); 506 out.endTag(HTMLWriterEx.H3); 507 out.startTag(HTMLWriterEx.UL); 508 out.writeStyleAttr("margin-left:0"); 509 510 File parent = dir.getParentFile(); 511 if (parent != null) { 512 out.startTag(HTMLWriterEx.LI); 513 out.startTag(HTMLWriterEx.OBJECT); 514 out.writeAttr(HTMLWriterEx.CLASSID, "com.sun.javatest.tool.IconLabel"); 515 out.writeParam("type", "up"); 516 out.endTag(HTMLWriterEx.OBJECT); 517 out.writeEntity(" "); 518 try { 519 out.startTag(HTMLWriterEx.A); 520 out.writeAttr(HTMLWriterEx.HREF, parent.toURL().toString()); 521 out.writeI18N("fp.parent"); 522 out.endTag(HTMLWriterEx.A); 523 } catch (MalformedURLException e) { 524 out.writeI18N("fp.parent"); 525 } 526 } 527 528 for (int i = 0; i < filelist.length; i++) { 529 File file = new File(dir, filelist[i]); 530 out.startTag(HTMLWriterEx.LI); 531 out.startTag(HTMLWriterEx.OBJECT); 532 out.writeAttr(HTMLWriterEx.CLASSID, "com.sun.javatest.tool.IconLabel"); 533 out.writeParam("type", (file.isDirectory() ? "folder" : "file")); 534 out.endTag(HTMLWriterEx.OBJECT); 535 out.writeEntity(" "); 536 try { 537 out.writeLink(file.toURL(), file.getName()); 538 } catch (MalformedURLException e) { 539 out.write(file.getName()); 540 } 541 } 542 543 out.endTag(HTMLWriterEx.UL); 544 out.endTag(HTMLWriterEx.BODY); 545 out.endTag(HTMLWriterEx.HTML); 546 out.close(); 547 } catch (IOException e) { 548 // should not happen, writing to StringWriter 549 } 550 551 return sw.toString(); 552 } 553 554 private void loadPage(URL url) { 555 // avoid recursive callbacks from updating combo 556 // URL.equals can result in a big performance hit 557 if (currURL != null && url.toString().equals(currURL.toString())) 558 return; 559 560 currURL = url; 561 562 String protocol = url.getProtocol(); 563 File file = new File(url.getFile()); 564 if (protocol.equals("file") && file.isDirectory()) { 565 String list = listLocalDirectory(file); 566 HTMLDocument htmlDoc = (HTMLDocument) (htmlKit.createDefaultDocument()); 567 textArea.setDocument(htmlDoc); 568 htmlDoc.setBase(url); 569 textArea.setContentType("text/html"); 570 textArea.setText(list); 571 } else if (protocol.equals("file") 572 && !url.getFile().endsWith(".htm") 573 && !url.getFile().endsWith(".html")) { 574 textArea.setContentType("text/plain"); 575 try { 576 Reader r = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); 577 textArea.read(r, url); 578 r.close(); 579 } catch (IOException e) { 580 uif.showError("fp.load.error", new Object[] { url, e }); 581 } 582 583 } else { 584 try { 585 URL loaded = textArea.getPage(); 586 // this next stuff is just to avoid some screen flash if a new doc 587 // is being read 588 if (loaded == null || !loaded.sameFile(url)) { 589 HTMLDocument htmlDoc = (HTMLDocument) (htmlKit.createDefaultDocument()); 590 textArea.setDocument(htmlDoc); 591 } 592 textArea.setPage(url); 593 } catch (IOException e) { 594 uif.showError("fp.load.error", new Object[] { url, e }); 595 } 596 } 597 598 } 599 600 601 // The UI components 602 private JMenuBar menuBar; 603 private BrowserPane browserPane; 604 private JDialog waitDialog; 605 private WaitDialogController waitDialogController; 606 private static final int WAIT_DIALOG_DELAY = 3000; // 3 second delay 607 608 // The merge options 609 private File[] in; 610 private File out; 611 private boolean resolveByRecent; 612 private boolean isXmlReport; 613 private CustomReport[] customReports; 614 615 private String xmlreportFileName = "report.xml"; 616 617 private boolean autoShowOptions = true; 618 619 private Thread worker; 620 private URL currURL; 621 622 private HTMLEditorKit htmlKit; 623 private JEditorPane textArea; 624 625 private static final String NEW = "new"; 626 private static final String OPEN = "open"; 627 628 private OptionsDialog optionsDialog; 629 private Listener listener = new Listener(); 630 631 private final String textShowing; 632 private final String textHidden; 633 634 class OkListener implements ActionListener { 635 public OkListener() { 636 } 637 638 public void actionPerformed(ActionEvent e) { 639 if(setOptions()) 640 optionsDialog.cleanUp(); 641 updateGUI(); 642 } 643 }; 644 645 private void showReportBrowser(File reportDir) { 646 // if if is a dir, try to find a particular file to show 647 // since there may be multiple choices, use the one with the 648 // most recent date 649 String[] names = Report.getHtmlReportFilenames(); 650 File target = reportDir; 651 long newestTime = 0; 652 653 for (int i = 0; i < names.length; i++) { 654 File f = new File(reportDir, names[i]); 655 if (f.exists() && f.lastModified() > newestTime) { 656 target = f; 657 newestTime = f.lastModified(); 658 } 659 } 660 661 try { 662 browserPane.setFile(target.toURL()); 663 } catch (MalformedURLException e) { 664 uif.showError("tool.report.browser", e.getMessage()); 665 } 666 } 667 668 void showReportDialog(File init) { 669 JFileChooser rdc = new JFileChooser(init); 670 671 int option = rdc.showDialog(this, uif.getI18NString("tool.report.open")); 672 if (option != JFileChooser.APPROVE_OPTION) 673 return; 674 675 File f = rdc.getSelectedFile(); 676 showReportBrowser(f); 677 678 } 679 680 private class Listener implements ActionListener, HierarchyListener { 681 public void actionPerformed(ActionEvent e) { 682 String cmd = e.getActionCommand(); 683 if (cmd.equals(NEW)) { 684 showOptions(); 685 } else if (cmd.equals(OPEN)) { 686 showReportDialog(out); 687 } 688 updateGUI(); 689 } 690 691 public void hierarchyChanged(HierarchyEvent e) { 692 if (isShowing() && autoShowOptions) { 693 EventQueue.invokeLater(new Runnable() { 694 public void run() { 695 showOptions(); 696 updateGUI(); 697 } 698 }); 699 autoShowOptions = false; 700 } 701 } 702 703 }; 704 705 private class HTMLListener implements HyperlinkListener, ItemListener { 706 public void hyperlinkUpdate(HyperlinkEvent e) { 707 HyperlinkEvent.EventType et = e.getEventType(); 708 if (et == HyperlinkEvent.EventType.ACTIVATED) { 709 if (e instanceof HTMLFrameHyperlinkEvent) { 710 HTMLDocument doc = (HTMLDocument) 711 ((JEditorPane) e.getSource()).getDocument(); 712 doc.processHTMLFrameHyperlinkEvent((HTMLFrameHyperlinkEvent) e); 713 } else 714 loadPage(e.getURL()); 715 } else if (et == HyperlinkEvent.EventType.ENTERED) { 716 URL u = e.getURL(); 717 if (u != null) { 718 textArea.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 719 } 720 } else if (et == HyperlinkEvent.EventType.EXITED) { 721 textArea.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 722 } 723 } 724 725 public void itemStateChanged(ItemEvent e) { 726 if (e.getStateChange() == ItemEvent.SELECTED) { 727 URL url = (URL) e.getItem(); 728 loadPage(url); 729 } 730 } 731 } 732 733 }