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.Component;
  31 import java.awt.Cursor;
  32 import java.awt.GridBagConstraints;
  33 import java.awt.GridBagLayout;
  34 import java.awt.Insets;
  35 import java.awt.event.ActionEvent;
  36 import java.awt.event.ItemEvent;
  37 import java.awt.event.ItemListener;
  38 import java.io.*;
  39 import java.net.MalformedURLException;
  40 import java.net.URL;
  41 import java.nio.charset.StandardCharsets;
  42 import java.util.Vector;
  43 
  44 import javax.swing.Action;
  45 import javax.swing.BorderFactory;
  46 import javax.swing.DefaultComboBoxModel;
  47 import javax.swing.DefaultListCellRenderer;
  48 import javax.swing.JButton;
  49 import javax.swing.JComboBox;
  50 import javax.swing.JEditorPane;
  51 import javax.swing.JLabel;
  52 import javax.swing.JList;
  53 import javax.swing.JPanel;
  54 import javax.swing.JScrollPane;
  55 import javax.swing.JTextField;
  56 import javax.swing.JToolBar;
  57 import javax.swing.ScrollPaneConstants;
  58 import javax.swing.event.HyperlinkEvent;
  59 import javax.swing.event.HyperlinkListener;
  60 import javax.swing.plaf.basic.BasicComboBoxUI;
  61 import javax.swing.plaf.basic.BasicComboPopup;
  62 import javax.swing.plaf.basic.ComboPopup;
  63 import javax.swing.text.html.HTMLDocument;
  64 import javax.swing.text.html.HTMLEditorKit;
  65 import javax.swing.text.html.HTMLFrameHyperlinkEvent;
  66 
  67 import com.sun.javatest.tool.ToolAction;
  68 import com.sun.javatest.tool.UIFactory;
  69 import com.sun.javatest.report.HTMLWriterEx;
  70 import com.sun.javatest.util.I18NResourceBundle;
  71 
  72 class BrowserPane extends JPanel {
  73     /**
  74      * This exception is used to report problems that arise when using
  75      * the FilesPane.
  76      */
  77     static class Fault extends Exception {
  78         Fault(I18NResourceBundle i18n, String s) {
  79             super(i18n.getString(s));
  80         }
  81 
  82         Fault(I18NResourceBundle i18n, String s, Object o) {
  83             super(i18n.getString(s, o));
  84         }
  85     }
  86 
  87     BrowserPane(UIFactory uif) {
  88         this.uif = uif;
  89 
  90         history = new History();
  91 
  92         initGUI();
  93     }
  94 
  95     void setBaseDirectory(File base) {
  96         baseDir = base;
  97     }
  98 
  99     File getBaseDirectory() {
 100         return baseDir;
 101     }
 102 
 103     void setFile(URL file) {
 104         if (file != null)
 105             setFiles(new URL[] { file });
 106     }
 107 
 108     boolean isEmpty() {
 109         return currURL == null;
 110     }
 111 
 112     void setPlainText(String text) {
 113         textArea.setContentType("text/html");
 114         textArea.setText(text);
 115         textHomePage = text;
 116     }
 117 
 118     void setPlainTextHomePage(String text) {
 119         textHomePage = text;
 120     }
 121 
 122     void setFiles(URL[] files) {
 123         clear();
 124 
 125         for (int i = 0; i < files.length; i++) {
 126             // set the first file as home and show it
 127             if (i == 0) {
 128 //              homeURL = files[i];
 129                 homeAction.setEnabled(true);
 130                 loadPage(files[i]);
 131             }
 132             updateCombo(files[i]);
 133         }
 134 
 135         // updateCombo leaves the last inserted selected, which is
 136         // useful elsewhere, but not here, we reset the selection
 137         if (model.getSize() > 0)
 138             selectBox.setSelectedIndex(0);
 139     }
 140 
 141     URL getPage() {
 142         return textArea.getPage();
 143     }
 144 
 145     //------------------------------------------------------------------------------------
 146 
 147     private void initGUI() {
 148         setName("fp");
 149         setFocusable(false);
 150 
 151         htmlKit = new HTMLEditorKit();
 152         initActions();
 153 
 154         setLayout(new BorderLayout());
 155         initHead();
 156         initBody();
 157 
 158         add(head, BorderLayout.NORTH);
 159         add(body, BorderLayout.CENTER);
 160 
 161         noteField = uif.createOutputField("fp.note");
 162         add(noteField, BorderLayout.SOUTH);
 163     }
 164 
 165     private void initActions() {
 166         homeAction = new ToolAction(uif, "fp.home", true) {
 167             public void actionPerformed(ActionEvent e) {
 168                 if (homeURL == null)
 169                     if (textHomePage != null) {
 170                         currURL = null;
 171                         updateCombo(null);
 172                         textArea.setContentType("text/html");
 173                         textArea.setText(textHomePage);
 174                     } else
 175                         textArea.setDocument(new HTMLDocument());
 176                 else
 177                     loadPage(homeURL);
 178             }
 179         };
 180 
 181         backAction = new ToolAction(uif, "fp.back", true) {
 182             public void actionPerformed(ActionEvent e) {
 183                 URL url = history.prev();
 184                 if (url != null)
 185                     loadPage(url);
 186             }
 187         };
 188 
 189         forwardAction = new ToolAction(uif, "fp.forward", true) {
 190             public void actionPerformed(ActionEvent e) {
 191                 URL url = history.next();
 192                 if (url != null)
 193                     loadPage(url);
 194             }
 195         };
 196     }
 197 
 198     private void initHead() {
 199         head = uif.createPanel("fp.head", false);
 200         head.setLayout(new GridBagLayout());
 201         head.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
 202 
 203         GridBagConstraints c = new GridBagConstraints();
 204         c.anchor = GridBagConstraints.EAST;
 205         c.gridx = 0;
 206         c.gridy = 0;
 207         c.insets = new Insets(0,0,0,5);
 208 
 209         JLabel fileLbl = uif.createLabel("fp.file", true);
 210         head.add(fileLbl, c);
 211 
 212         selectBox = uif.createChoice("fp.choice", fileLbl);
 213         selectBox.setRenderer(new Renderer());
 214         selectBox.setModel(createModel());
 215         selectBox.addItemListener(listener);
 216         selectBox.setMaximumRowCount(MAX_ROWS_DISPLAY);
 217         selectBox.setUI(new BasicComboBoxUI() {
 218             // wrap the content with a scrolling interface
 219             // would be nice if Swing did this for us
 220             protected ComboPopup createPopup() {
 221                 BasicComboPopup popup = new BasicComboPopup(selectBox) {
 222                     protected JScrollPane createScroller() {
 223                         return new JScrollPane(list,
 224                             ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
 225                             ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
 226                     }
 227                 };
 228                 return popup;
 229             }
 230             }   // class
 231             );
 232         uif.setAccessibleName(selectBox, "fp.choice");  // override default a11y name
 233 
 234         c.gridx = 1;
 235         c.weightx = 2.0;
 236         c.fill = GridBagConstraints.HORIZONTAL;
 237 
 238         head.add(selectBox, c);
 239 
 240         Action[] actions = { backAction, forwardAction, null, homeAction };
 241 
 242         toolBar = uif.createToolBar("fp.toolbar", actions );
 243         toolBar.setFloatable(false);
 244 
 245         c.weightx = 0;
 246         c.gridx = 2;
 247         c.insets.left = 5;
 248 
 249         head.add(toolBar, c);
 250         backAction.setEnabled(history.hasPrev());
 251         forwardAction.setEnabled(history.hasNext());
 252 
 253     }
 254 
 255     private void initBody() {
 256         body = uif.createPanel("fp.body", false);
 257         body.setLayout(new BorderLayout());
 258 
 259         textArea = new JEditorPane();
 260         textArea.setName("text");
 261         textArea.setEditable(false);
 262         textArea.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
 263         textArea.addHyperlinkListener(listener);
 264         uif.setAccessibleInfo(textArea, "fp");
 265         body.add(uif.createScrollPane(textArea,
 266                                  JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
 267                                  JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED),
 268                  BorderLayout.CENTER);
 269     }
 270 
 271     //------------------------------------------------------------------------------------
 272 
 273     private void clear() {
 274         if (model != null)
 275             model.removeAllElements();
 276 
 277         history.clear();
 278         backAction.setEnabled(false);
 279         forwardAction.setEnabled(false);
 280         homeAction.setEnabled(false);
 281 
 282         homeURL = null;
 283         currURL = null;
 284         textArea.setDocument(new HTMLDocument());
 285     }
 286 
 287     private void loadPage(URL url) {
 288         // avoid recursive callbacks from updating combo
 289         // URL.equals can result in a big performance hit
 290         if (currURL!= null && url.toString().equals(currURL.toString()))
 291             return;
 292 
 293         currURL = url;
 294 
 295         String protocol = url.getProtocol();
 296         File file = new File(url.getFile());
 297         if (protocol.equals("file") && file.isDirectory()) {
 298             String list = listLocalDirectory(file);
 299             HTMLDocument htmlDoc = (HTMLDocument) (htmlKit.createDefaultDocument());
 300             textArea.setDocument(htmlDoc);
 301             htmlDoc.setBase(url);
 302             textArea.setContentType("text/html");
 303             textArea.setText(list);
 304         }
 305         else if (protocol.equals("file")
 306                  && !url.getFile().endsWith(".htm")
 307                  && !url.getFile().endsWith(".html")) {
 308             textArea.setContentType("text/plain");
 309             try {
 310                 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));
 311                 textArea.read(br, url);
 312                 br.close();
 313             }
 314             catch (IOException e) {
 315                 uif.showError("fp.load.error", new Object[] { url, e });
 316             }
 317 
 318         }
 319         else {
 320             try {
 321                 URL loaded = textArea.getPage();
 322                 // this next stuff is just to avoid some screen flash if a new doc
 323                 // is being read
 324                 if (loaded == null || !loaded.sameFile(url)) {
 325                     HTMLDocument htmlDoc = (HTMLDocument) (htmlKit.createDefaultDocument());
 326                     textArea.setDocument(htmlDoc);
 327                 }
 328                 textArea.setPage(url);
 329             }
 330             catch (IOException e) {
 331                 uif.showError("fp.load.error", new Object[] { url, e });
 332             }
 333         }
 334 
 335         history.add(url);
 336         backAction.setEnabled(history.hasPrev());
 337         forwardAction.setEnabled(history.hasNext());
 338         updateCombo(url);
 339     }
 340 
 341     private String listLocalDirectory(File dir) {
 342         if (!dir.isAbsolute())
 343             dir = dir.getAbsoluteFile();
 344 
 345         String displayPath = dir.getPath();
 346         // if contains base dir, only show path relative to baseDir
 347         if (baseDir != null) {
 348             String p = baseDir.getParent();
 349             if (p != null
 350                 && displayPath.startsWith(p) &&
 351                 (displayPath.length() > p.length())) {
 352                 displayPath = displayPath.substring(p.length());
 353                 // in case of Unix
 354                 if (displayPath.startsWith(File.separator)) {
 355                     displayPath = displayPath.substring(1);
 356                 }
 357             }
 358         }
 359 
 360         String[] filelist = dir.list();
 361         StringWriter sw = new StringWriter();
 362         try {
 363             HTMLWriterEx out = new HTMLWriterEx(sw, uif.getI18NResourceBundle());
 364 
 365             out.startTag(HTMLWriterEx.HTML);
 366             out.startTag(HTMLWriterEx.HEAD);
 367             out.writeContentMeta();
 368             out.startTag(HTMLWriterEx.TITLE);
 369             out.write(displayPath);
 370             out.endTag(HTMLWriterEx.TITLE);
 371             out.endTag(HTMLWriterEx.HEAD);
 372             out.startTag(HTMLWriterEx.BODY);
 373             out.writeStyleAttr("font-family: SansSerif; font-size: 12pt");
 374             out.startTag(HTMLWriterEx.H3);
 375             out.writeI18N("fp.head", displayPath);
 376             out.endTag(HTMLWriterEx.H3);
 377             out.startTag(HTMLWriterEx.UL);
 378             out.writeStyleAttr("margin-left:0");
 379 
 380             File parent = dir.getParentFile();
 381             if (parent != null) {
 382                 out.startTag(HTMLWriterEx.LI);
 383                 out.startTag(HTMLWriterEx.OBJECT);
 384                 out.writeAttr(HTMLWriterEx.CLASSID, "com.sun.javatest.tool.IconLabel");
 385                 out.writeParam("type", "up");
 386                 out.endTag(HTMLWriterEx.OBJECT);
 387                 out.writeEntity("&nbsp;");
 388                 try {
 389                     out.startTag(HTMLWriterEx.A);
 390                     out.writeAttr(HTMLWriterEx.HREF, parent.toURL().toString());
 391                     out.writeI18N("fp.parent");
 392                     out.endTag(HTMLWriterEx.A);
 393                 }
 394                 catch (MalformedURLException e) {
 395                     out.writeI18N("fp.parent");
 396                 }
 397             }
 398 
 399             for (int i = 0; i < filelist.length; i++) {
 400                 File file = new File(dir, filelist[i]);
 401                 out.startTag(HTMLWriterEx.LI);
 402                 out.startTag(HTMLWriterEx.OBJECT);
 403                 out.writeAttr(HTMLWriterEx.CLASSID, "com.sun.javatest.tool.IconLabel");
 404                 out.writeParam("type", (file.isDirectory() ? "folder" : "file"));
 405                 out.endTag(HTMLWriterEx.OBJECT);
 406                 out.writeEntity("&nbsp;");
 407                 try {
 408                     out.writeLink(file.toURL(), file.getName());
 409                 }
 410                 catch (MalformedURLException e) {
 411                     out.write(file.getName());
 412                 }
 413             }
 414 
 415             out.endTag(HTMLWriterEx.UL);
 416             out.endTag(HTMLWriterEx.BODY);
 417             out.endTag(HTMLWriterEx.HTML);
 418             out.close();
 419         }
 420         catch (IOException e) {
 421             // should not happen, writing to StringWriter
 422         }
 423 
 424         return sw.toString();
 425     }
 426 
 427     private void updateCombo(URL s) {
 428         // check if the new element exists in the combo box...
 429         if (model.getIndexOf(s) < 0)
 430             model.addElement(s);
 431 
 432         URL item = (URL) selectBox.getSelectedItem();
 433         // check if the new element is already selected.
 434         // URL.equals can result in a big performance hit
 435         if (s != null && !item.toString().equals(s.toString()))
 436             selectBox.setSelectedItem(s);
 437     }
 438 
 439     private DefaultComboBoxModel<URL> createModel() {
 440         if (model == null)
 441             model = new DefaultComboBoxModel<>();
 442         return model;
 443     }
 444 
 445     private File baseDir;
 446     private URL homeURL;
 447     private File[] files;
 448     private String textHomePage;
 449 
 450     private JButton homeBtn;
 451     private JButton backBtn;
 452     private JButton forwardBtn;
 453 
 454     private JComboBox<URL> selectBox;
 455     private JPanel head;
 456     private JPanel body;
 457     private JTextField noteField;
 458 
 459     private HTMLEditorKit htmlKit;
 460     private JEditorPane textArea;
 461     private URL currURL;
 462 
 463     private History history;
 464 
 465     private DefaultComboBoxModel<URL> model;
 466     private Listener listener = new Listener();
 467     private JToolBar toolBar;
 468     private UIFactory uif;
 469 
 470     private Action homeAction;
 471     private Action backAction;
 472     private Action forwardAction;
 473 
 474     private static final int MAX_ROWS_DISPLAY = 20;
 475 
 476     static protected boolean debug = Boolean.getBoolean("debug." + BrowserPane.class.getName());
 477 
 478     //------------------------------------------------------------------------------------
 479 
 480     private class History {
 481         boolean hasPrev() {
 482             return (index > 0);
 483         }
 484 
 485         URL prev() {
 486             if (index == 0)
 487                 return null;
 488 
 489             return entries.elementAt(--index);
 490         }
 491 
 492         boolean hasNext() {
 493             return (index < entries.size() - 1);
 494         }
 495 
 496         URL next() {
 497             if (index == entries.size() - 1)
 498                 return null;
 499 
 500             return entries.elementAt(++index);
 501         }
 502 
 503         void add(URL u) {
 504             if (u == null)
 505                 throw new NullPointerException();
 506 
 507             // if there is a current entry, and it matches the one to be added, we're done
 508             if (index >= 0 && index < entries.size() && entries.elementAt(index).equals(u))
 509                 return;
 510 
 511             // if current entry not the last one, truncate to the current entry
 512             if (index < entries.size() - 1)
 513                 entries.setSize(index + 1);
 514 
 515             // finally, add new entry
 516             entries.addElement(u);
 517             index = entries.size() - 1;
 518         }
 519 
 520         void clear() {
 521             entries.setSize(0);
 522             index = -1;
 523         }
 524 
 525         private Vector<URL> entries = new Vector<>();
 526         private int index;
 527     }
 528 
 529     private class Listener implements HyperlinkListener, ItemListener {
 530         public void hyperlinkUpdate(HyperlinkEvent e) {
 531             HyperlinkEvent.EventType et = e.getEventType();
 532             if (et == HyperlinkEvent.EventType.ACTIVATED) {
 533                 if (e instanceof HTMLFrameHyperlinkEvent) {
 534                     HTMLDocument doc = (HTMLDocument)
 535                         ((JEditorPane) e.getSource()).getDocument();
 536                     doc.processHTMLFrameHyperlinkEvent((HTMLFrameHyperlinkEvent) e);
 537                 }
 538                 else
 539                     loadPage(e.getURL());
 540             }
 541             else if (et == HyperlinkEvent.EventType.ENTERED) {
 542                 URL u = e.getURL();
 543                 if (u != null) {
 544                     textArea.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
 545                     noteField.setText(u.toString());
 546                 }
 547             }
 548             else if (et == HyperlinkEvent.EventType.EXITED) {
 549                 textArea.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
 550                 noteField.setText("");
 551             }
 552         }
 553 
 554         public void itemStateChanged(ItemEvent e) {
 555             if (e.getStateChange() == ItemEvent.SELECTED) {
 556                 URL url = (URL) e.getItem();
 557                 loadPage(url);
 558             }
 559         }
 560     }
 561 
 562     //------------------------------------------------------------------------------------
 563 
 564     private class Renderer extends DefaultListCellRenderer {
 565         public Component getListCellRendererComponent(JList<?> list, Object o, int index, boolean isSelected, boolean cellHasFocus) {
 566             String name = null;
 567             if (o instanceof URL) {
 568                 URL url = (URL) o;
 569 
 570                 // if not file URL
 571                 if (!url.getProtocol().equals("file")) {
 572                     name = url.toString();
 573                 }
 574                 else {
 575                     // if file URL, remove the "file:" prefix
 576                     name = extractPrefix(url.toString(), "file:");
 577                     String baseName = null;
 578                     name = new File(name).getAbsolutePath();
 579                     if (baseDir != null && baseDir.getParentFile() != null) {
 580                         baseName = baseDir.getParentFile().getAbsolutePath();
 581                     }
 582                     // if contains base dir, only show file name
 583                     if (baseName != null &&
 584                         name.startsWith(baseName) &&
 585                         (name.length() > baseName.length())) {
 586                         name = name.substring(baseName.length() );
 587                         // in case of Unix
 588                         if (name.startsWith(File.separator)) {
 589                             name = name.substring(1);
 590                         }
 591                     }
 592                 }
 593             } else if (o != null) {
 594                 name = String.valueOf(o);
 595                 return super.getListCellRendererComponent(list, name, index, isSelected, cellHasFocus);
 596             }
 597 
 598             return super.getListCellRendererComponent(list, o, index, isSelected, cellHasFocus);
 599         }
 600 
 601         private String extractPrefix(String origStr, String target) {
 602             return (!origStr.startsWith(target)) ? origStr : origStr.substring(target.length());
 603         }
 604     }
 605 }