1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2001, 2011, 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.exec;
  28 
  29 import java.awt.Component;
  30 import java.awt.Dimension;
  31 import java.awt.EventQueue;
  32 import java.awt.Font;
  33 import java.awt.GridBagConstraints;
  34 import java.awt.GridBagLayout;
  35 import java.awt.Toolkit;
  36 import java.awt.datatransfer.StringSelection;
  37 import java.awt.event.ActionEvent;
  38 import java.awt.event.ActionListener;
  39 import java.awt.event.KeyEvent;
  40 import java.awt.event.MouseAdapter;
  41 import java.awt.event.MouseEvent;
  42 import java.util.Arrays;
  43 import java.util.LinkedList;
  44 import java.util.Vector;
  45 
  46 import javax.swing.AbstractAction;
  47 import javax.swing.BorderFactory;
  48 import javax.swing.JComponent;
  49 import javax.swing.JDialog;
  50 import javax.swing.JMenu;
  51 import javax.swing.JOptionPane;
  52 import javax.swing.JPopupMenu;
  53 import javax.swing.JScrollPane;
  54 import javax.swing.JTable;
  55 import javax.swing.JTextArea;
  56 import javax.swing.KeyStroke;
  57 import javax.swing.Timer;
  58 import javax.swing.ListSelectionModel;
  59 import javax.swing.border.Border;
  60 import javax.swing.event.ListSelectionEvent;
  61 import javax.swing.event.ListSelectionListener;
  62 import javax.swing.event.TableModelEvent;
  63 import javax.swing.table.AbstractTableModel;
  64 import javax.swing.table.DefaultTableCellRenderer;
  65 import javax.swing.table.TableCellRenderer;
  66 import javax.swing.table.TableColumn;
  67 import javax.swing.table.TableModel;
  68 
  69 import com.sun.javatest.Harness;
  70 import com.sun.javatest.JavaTestError;
  71 import com.sun.javatest.Status;
  72 import com.sun.javatest.TestResult;
  73 import com.sun.javatest.TestResultTable;
  74 import com.sun.javatest.WorkDirectory;
  75 import com.sun.javatest.tool.I18NUtils;
  76 import com.sun.javatest.tool.UIFactory;
  77 import com.sun.javatest.util.Debug;
  78 import com.sun.javatest.util.I18NResourceBundle;
  79 import com.sun.javatest.util.StringArray;
  80 import com.sun.javatest.tool.jthelp.ContextHelpManager;
  81 import java.awt.datatransfer.Clipboard;
  82 
  83 /**
  84  * This panel renders information about the tests which are "filtered out" in
  85  * the current node.
  86  * <p>
  87  * The background thread has a two-stage commit process so that the iterator can
  88  * run at full speed, ignoring the MT-unsafe swing list model. The changes are
  89  * reflected in the real list when the AWT event queue schedules the
  90  * notification thread which is created during the iteration. This class also
  91  * processes the list click events and usually dispatches changes to the branch
  92  * panel model.
  93  *
  94  * <p>
  95  * If you need to synchronize against both the vLock (for live data) and this
  96  * class' lock, then blocks should be synchronized against this outer class,
  97  * then the vLock.  The ordering is vital to avoiding deadlocks.
  98  */
  99 class BP_TestListSubpanel extends BP_BranchSubpanel {
 100     BP_TestListSubpanel(UIFactory uif, Harness h, ExecModel em,
 101             BP_Model bpm, TestTreeModel ttm, int state) {
 102         super("tl" + state, uif, bpm, ttm, "br.list");
 103         this.state = state;
 104         this.harness = h;
 105         this.execModel = em;
 106 
 107         init();
 108 
 109         // the enableHelp lines below are checked during the build
 110         switch (state) {
 111             case Status.PASSED:
 112                 ContextHelpManager.setHelpIDString(this, "browse.passedTab.csh");
 113                 break;
 114             case Status.FAILED:
 115                 ContextHelpManager.setHelpIDString(this, "browse.failedTab.csh");
 116                 break;
 117             case Status.ERROR:
 118                 ContextHelpManager.setHelpIDString(this, "browse.errorTab.csh");
 119                 break;
 120             case Status.NOT_RUN:
 121                 ContextHelpManager.setHelpIDString(this, "browse.notRunTab.csh");
 122                 break;
 123             case BranchPanel.STATUS_FILTERED:
 124                 ContextHelpManager.setHelpIDString(this, "browse.filteredOutTab.csh");
 125                 break;
 126         } // switch
 127 
 128         // must do this after state variable is set
 129         cacheWatcher = new CacheObserver(state);
 130     }
 131 
 132     /**
 133      * This is to provide BranchPanel the ability to do batch updates.
 134      */
 135     /*
 136      * void removeTest(TestResult tr) { mod.removeTest(tr); }
 137      *
 138      * void addTest(TestResult tr) { mod.addTest(tr, false); }
 139      *
 140      * void addTest(TestResult[] trs) { if (trs == null || trs.length == 0)
 141      * return;
 142      *
 143      * for (int i = 0; i < trs.length - 1; i++) mod.addTest(trs[i], true);
 144      *  // do a final add with notification mod.addTest(trs[trs.length-1],
 145      * false); }
 146      */
 147 
 148     /**
 149      * Clear the table contents and prepare to receive new data.
 150      */
 151     void reset(TT_NodeCache cache) {
 152         // grab cache lock first because many other threads may alter that
 153         // object, causing a deadlock here.
 154         // sync. to hold observer traffic until re-sync is done
 155         // also see TableSynchronizer thread
 156         if (this.cache != null) {
 157             final TT_NodeCache cacheCopy = this.cache;
 158             synchronized (cacheCopy) {
 159                 synchronized (BP_TestListSubpanel.this) {
 160                         cacheCopy.removeObserver(cacheWatcher);
 161                 }   // sync this panel
 162             }   // sync cache
 163         }
 164 
 165         synchronized (BP_TestListSubpanel.this) {
 166             this.cache = cache;
 167 
 168             if (resyncThread != null) {
 169                 resyncThread.halt();
 170             }
 171 
 172             if (mod != null)
 173                 mod.reset();
 174         } // sync
 175 
 176         validateEnableState();
 177         // table.clearSelection();
 178     }
 179 
 180     protected void invalidateFilters() {
 181         super.invalidateFilters();
 182 
 183         // if we didn't have one, we certainly don't need to disconnect,
 184         // and probably don't need to get a new one...
 185         // XXX see reset() and TableSynchronizer.run(), should do this in a
 186         //     synchronized block?
 187         if (cache != null) {
 188             cache.removeObserver(cacheWatcher);
 189 
 190             if (subpanelNode != null) {
 191                 cache = ttm.getNodeInfo(subpanelNode.getTableNode(), false);
 192                 validateEnableState();
 193             }
 194         }
 195     }
 196 
 197     public void setUpdateRequired(boolean updateRequired) {
 198          this.updateRequired = updateRequired;
 199     }
 200 
 201     /**
 202      * Only called when this panel is onscreen and needs to be kept up to date.
 203      */
 204     protected synchronized void updateSubpanel(TT_BasicNode currNode) {
 205         super.updateSubpanel(currNode);
 206 
 207         if (debug) {
 208             Debug.println("updating test list " + state);
 209             Debug.println("  -> size " + (mod == null? 0 : mod.getRowCount()));
 210         }
 211 
 212         // only run if we change nodes
 213         if (updateRequired || filtersInvalidated) {
 214             if (resyncThread != null) {
 215                 resyncThread.halt();
 216             }
 217             resyncThread = new TableSynchronizer(state);
 218             resyncThread.start();
 219 
 220             filtersInvalidated = false;
 221             validateEnableState();
 222             updateRequired = false;
 223         }
 224     }
 225 
 226     /**
 227      * Enable or disable this panel as necessary.
 228      */
 229     private void validateEnableState() {
 230         if (state < Status.NUM_STATES) { // handles all but filtered out
 231             if (cache.getStats()[state] > 0) {
 232                 model.setEnabled(BP_TestListSubpanel.this, true);
 233                 // setEmpty(false);
 234             } else if (cache.getStats()[state] == 0) {
 235                 model.setEnabled(BP_TestListSubpanel.this, false);
 236                 // setEmpty(true);
 237             } else {
 238             }
 239         } else
 240             throw new IllegalStateException();
 241     }
 242 
 243     /**
 244      * A special thread to repopulate the test lists.
 245      */
 246     private class TableSynchronizer extends Thread {
 247         TableSynchronizer(int whichList) {
 248             super("Test List Synchronizer" + whichList);
 249             setPriority(Thread.MIN_PRIORITY + 2);
 250         }
 251 
 252         public void run() {
 253             // grab cache lock first because many other threads may alter that
 254             // object, causing a deadlock here.
 255             // sync. to hold observer traffic until re-sync is done.
 256             // also see reset() for this panel
 257             final TT_NodeCache cacheCopy = cache;
 258             synchronized (cacheCopy) {
 259                 synchronized (BP_TestListSubpanel.this) {
 260                     // resync with this node cache
 261                     newData = cacheCopy.addObserver(cacheWatcher, true);
 262 
 263                     // add tests into the list model - this doesn't make the
 264                     // data
 265                     // live though
 266                     for (int j = 0; j < newData[state].size() - 1; j++) {
 267                         if (stopping)
 268                             break;
 269 
 270                         if (sortingRequested) {
 271                            mod.sortTests(mod.liveData, mod.SORTING_COLUMN,
 272                                     mod.SORTING_MODE);
 273                         } else {
 274                             mod.addTest(newData[state].elementAt(j), true);
 275                         }
 276 
 277                     } // for
 278 
 279                     if (newData[state].size() > 0 && !stopping) {
 280                         // final item with a notify
 281                         mod.addTest(newData[state].lastElement(), false);
 282                     }
 283 
 284                     // to indicate completion
 285                     resyncThread = null;
 286                 } // this sync
 287             } // cache sync
 288             validateEnableState();
 289         } // run()
 290 
 291         public void halt() {
 292             stopping = true;
 293         }
 294 
 295         private volatile boolean stopping;
 296     }
 297 
 298     private void init() {
 299         mod = new TestTableModel(uif);
 300         renderer = new TestCellRenderer(uif);
 301         listener = new InputListener();
 302 
 303         table = uif.createTable("br.fo.tbl", mod);
 304         table.setOpaque(true);
 305         table.setRowSelectionAllowed(true);
 306         table.setColumnSelectionAllowed(false);
 307         table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
 308         table.getSelectionModel().addListSelectionListener(listener);
 309 
 310         // setup col 0
 311         TableColumn tc = table.getColumnModel().getColumn(0);
 312         tc.setCellRenderer(renderer);
 313         tc.setResizable(true);
 314 
 315         // setup col 1
 316         tc = table.getColumnModel().getColumn(1);
 317         tc.setCellRenderer(renderer);
 318         tc.setResizable(true);
 319 
 320         uif.setAccessibleInfo(this, "br.fo");
 321 
 322         setLayout(new GridBagLayout());
 323 
 324         GridBagConstraints gbc = new GridBagConstraints();
 325         gbc.fill = GridBagConstraints.HORIZONTAL;
 326         gbc.anchor = GridBagConstraints.NORTH;
 327         gbc.gridy = 0;
 328         gbc.ipady = 12;
 329         gbc.weightx = 1.0;
 330         gbc.weighty = 0.0;
 331 
 332         infoTL = uif.createMessageArea("br.tl.info");
 333         infoTL.setOpaque(false);
 334         add(infoTL, gbc);
 335 
 336         gbc.gridy = 1;
 337         gbc.weightx = 2.0;
 338         gbc.weighty = 1.0;
 339         gbc.fill = GridBagConstraints.BOTH;
 340         add(uif.createScrollPane(table,
 341                 JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
 342                 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED), gbc);
 343 
 344         InputListener tableListener = new InputListener();
 345         table.addMouseListener(tableListener);
 346         table.getTableHeader().addMouseListener(tableListener);
 347         table.getSelectionModel().addListSelectionListener(tableListener);
 348 
 349         // to trigger test selection when enter is pressed
 350         table.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(
 351                 KeyEvent.VK_ENTER, 0, false), "gotoTest");
 352         table.getActionMap().put("gotoTest", new KbTableAction(uif.getI18NResourceBundle(),
 353                 "br.list.enter"));
 354 
 355         String[] showDescr = { "show.title", "show.keywords" };
 356         showDescrMenu = uif.createMenu("br.description", showDescr,
 357                 tableListener);
 358 
 359         String[] showRun = { "show.status", "show.time.start", "show.time.end" };
 360         showRunMenu = uif.createMenu("br.runtime", showRun, tableListener);
 361 
 362         popupHeader = uif.createPopupMenu("br");
 363         popupHeader.add(showDescrMenu);
 364         popupHeader.add(showRunMenu);
 365 
 366         String[] actions = { "action.run", "action.clear" };
 367         popupTable = uif.createPopupMenu("br", actions, tableListener);
 368 
 369         if (state == Status.NOT_RUN) { // ignore clear result in "not run"
 370             // panel
 371             showRunMenu.setEnabled(false);
 372             // XXX should avoid using absolute indexes
 373             popupTable.getComponent(1).setEnabled(false);
 374         }
 375 
 376         actions = new String[] { "action.cpnamelist", "action.cpnamestr" };
 377         popupTable.add(uif.createMenu("br.cp", actions, tableListener));
 378 
 379         // this is necessary to make sure that the split pane can resize
 380         // this panel. without setting the min., the panel seems to take
 381         // all it is given, and never gives it back.
 382         setMinimumSize(new Dimension(150, 100));
 383     }
 384 
 385     /**
 386      * It is assumed that this will run on the event thread. private void
 387      * setEmpty(boolean state) { if (state && list.getModel() !=
 388      * EmptyListModel.getInstance()) {
 389      * list.setModel(EmptyListModel.getInstance());
 390      * model.setEnabled(BP_TestListSubpanel.this, false); lastMsg = ""; } else
 391      * if (!state && list.getModel() == EmptyListModel.getInstance()) {
 392      * list.setModel(mod); model.setEnabled(BP_TestListSubpanel.this, true); } }
 393      */
 394 
 395     // ------------- inner class -------------
 396     /**
 397      * Enumerates tree in background to populate the list. If this thread is
 398      * running, consider list data incomplete. Swing cannot handle an updating
 399      * model, so there is a two-stage absorbtion of data. This thread runs with
 400      * no delay reading the TRT and placing that data into an "offline" queue.
 401      * It periodically schedules an event on the GUI event thread; when that
 402      * thread run, it copies the data from the offline area to the online area,
 403      * which is what the ListModel presents. This workaround assumes that Swing
 404      * will never dispatch more than one event at a time.
 405      */
 406     private class TestTableModel extends AbstractTableModel {
 407         TestTableModel(UIFactory uif) {
 408             super();
 409 
 410             colNames = new String[] { uif.getI18NString("br.list.col0.txt"),
 411                     uif.getI18NString("br.list.col1.txt") };
 412 
 413                     if (debug) {
 414                         Debug.println("TableModel constructed: ");
 415                         Debug.println("   -> " + this);
 416                     }
 417 
 418                     init();
 419         }
 420 
 421         // ---------- TableModel interface ----------
 422         public int getRowCount() {
 423             synchronized (liveData) {
 424                 return liveData.size();
 425             }
 426         }
 427 
 428         public int getColumnCount() {
 429             return COLUMN_COUNT;
 430         }
 431 
 432         public String getColumnName(int columnIndex) {
 433             if (columnIndex >= colNames.length)
 434                 throw new IndexOutOfBoundsException();
 435             else
 436                 return columnIndex == 0 ? colNames[0] : uif.getI18NString("br.runtime.show.status.mit");
 437         }
 438 
 439         public Object getValueAt(int row, int column) {
 440             if (column == 0) {
 441                 synchronized (liveData) {
 442                     return liveData.get(row);
 443                 }
 444             } else if (column == 1) {
 445                 synchronized (liveData) {
 446                     if ((liveData.get(row)) instanceof TestResult)
 447                         return (getSelectedProperty((TestResult) (liveData.get(row))));
 448                     else
 449                         // should not happen
 450                         return uif.getI18NString("br.list.notAvailable.txt");
 451                 }
 452             } else
 453                 throw new IndexOutOfBoundsException(
 454                         "Index into filtered out table is out of range: " + row
 455                         + ", " + column);
 456         }
 457 
 458         public boolean isCellEditable(int rowIndex, int colIndex) {
 459             return false;
 460         }
 461 
 462         // ---------- Custom methods for this model ----------
 463         /**
 464          * @param suppressNotify
 465          *        Actively request that no update be scheduled.
 466          */
 467         void addTest(Object tr, boolean suppressNotify) {
 468             synchronized (vLock) {
 469                 // make sure this item is not already in the list
 470                 if (!inQueue.contains(tr) && !liveData.contains(tr))
 471                     inQueue.addElement(tr);
 472             } // sync
 473 
 474             // try not to saturate the GUI event thread
 475             if (!suppressNotify && !isUpdateScheduled) {
 476                 TableNotifier tn = new TableNotifier(subpanelNode, this);
 477                 pendingEvents.addElement(tn);
 478                 EventQueue.invokeLater(tn);
 479             }
 480         }
 481 
 482         /**
 483          * Sorts data in the table
 484          *
 485          * @param v
 486          *        Current tests list
 487          * @param column
 488          *        Number of column sorting to be applied to
 489          * @param mode
 490          *        Indicates ascending (0) or descending (1) sorting
 491          */
 492         void sortTests(LinkedList v, int column, boolean mode) {
 493             synchronized (vLock) {
 494                 inQueue = sort(v, mode);
 495 
 496                 TableNotifier tn = new TableNotifier(subpanelNode, this);
 497                 pendingEvents.addElement(tn);
 498                 EventQueue.invokeLater(tn);
 499             }
 500         }
 501 
 502         /**
 503          * Remove the given test from the list. Ignored if the test is not in
 504          * the list.
 505          */
 506         void removeTest(Object tr) {
 507             synchronized (vLock) {
 508                 rmQueue.addElement(tr);
 509                 // try not to saturate the GUI event thread
 510                 if (!isUpdateScheduled) {
 511                     TableNotifier tn = new TableNotifier(subpanelNode, this);
 512 
 513                     pendingEvents.addElement(tn);
 514                     EventQueue.invokeLater(tn);
 515                 }
 516             } // sync
 517 
 518         }
 519 
 520         void reset() {
 521             synchronized (vLock) {
 522                 init();
 523             }
 524 
 525             // force GUI to update the now empty list
 526             notifyDone();
 527         }
 528 
 529         // ------------ private --------------
 530 
 531         private String getSelectedProperty(TestResult tst) {
 532             try {
 533                 if (show.equals("title"))
 534                     return ((TestResult) tst).getDescription().getTitle();
 535                 else if (show.equals("keywords")) {
 536                     String[] s = ((TestResult) tst).getDescription().getKeywords();
 537                     if (s.length == 0)
 538                         return (uif.getI18NString("br.list.noKeywords.txt"));
 539                     else {
 540                         StringBuffer sb = new StringBuffer();
 541                         for (int i = 0; i < s.length; i++) {
 542                             sb.append(s[i]);
 543                             sb.append(" ");
 544                         }
 545                         return (sb.toString());
 546                     }
 547                 } else if (show.equals(TestResult.EXEC_STATUS)) {
 548                     String tmpStr = ((TestResult) tst).getStatus().getReason();
 549                     return tmpStr == null || tmpStr.equals("") ?
 550                         uif.getI18NString("br.list.notAvailable.txt") : tmpStr;
 551                 }
 552                 return ((TestResult) tst).getProperty(show) == null ? uif.getI18NString("br.list.notAvailable.txt")
 553                         : ((TestResult) tst).getProperty(show);
 554             } catch (TestResult.Fault f) {
 555             }
 556             return (""); // should not be here
 557         }
 558 
 559         private void init() {
 560             // discard all pending events
 561             // this is necessary to ensure that update events which haven't
 562             // been processed are not processed after the model has changed
 563             // arguably, this should be solved by putting this init() onto
 564             // the event thread
 565             synchronized (pendingEvents) {
 566                 for (int i = 0; i < pendingEvents.size(); i++) {
 567                     TableNotifier tn = (TableNotifier) (pendingEvents.get(i));
 568                     tn.cancel();
 569                 } // for
 570             }
 571 
 572             inQueue = new Vector();
 573             rmQueue = new Vector();
 574             liveData = new LinkedList();
 575 
 576             isUpdateScheduled = false;
 577         }
 578 
 579         private Vector sort(LinkedList v, boolean mode) {
 580 
 581             o = new Object[v.size()];
 582 
 583             if (SORTING_COLUMN == 0)
 584                 o = v.toArray();
 585             else if (SORTING_COLUMN == 1) {
 586                 for (int i = 0; i < v.size(); i++) {
 587                     if (v.get(i) instanceof TestResult)
 588                         o[i] = getSelectedProperty((TestResult) v.get(i));
 589                     else
 590                         o[i] = ""; // should not happen
 591                 }
 592             } else {
 593                 throw new JavaTestError("Internal error: invalid "
 594                         + "column number specified: " + SORTING_COLUMN);
 595             }
 596 
 597             rows = new Sorter[o.length];
 598 
 599             for (int i = 0; i < rows.length; i++) {
 600                 rows[i] = new Sorter();
 601                 rows[i].index = i;
 602             }
 603 
 604             Arrays.sort(rows);
 605 
 606             int[] aaa = new int[v.size()];
 607             for (int i = 0; i < rows.length; i++) {
 608                 if (mode) // ascending
 609                     aaa[i] = rows[i].index;
 610                 else
 611                     // descending
 612                     aaa[i] = rows[rows.length - i - 1].index;
 613             }
 614 
 615             Vector temp = new Vector(v.size());
 616 
 617             for (int i = 0; i < v.size(); i++)
 618                 temp.addElement(v.get(aaa[i]));
 619 
 620             return temp;
 621         }
 622 
 623         private class Sorter implements Comparable {
 624             public int index;
 625 
 626             public int compareTo(Object other) {
 627                 Sorter otherRow = (Sorter) other;
 628                 if (o[index] instanceof TestResult) {
 629                     return ((Comparable) ((TestResult) o[index]).getTestName()).
 630                              compareTo(((TestResult) o[otherRow.index]).getTestName());
 631                 } else if (o[index] instanceof String) {
 632                     return ((Comparable) o[index]).compareTo(o[otherRow.index]);
 633                 } else {
 634                     return index - otherRow.index; // should not happen
 635                 }
 636             }
 637         }
 638 
 639         /**
 640          * Transfer data from the internal queue to the live data queue. This is
 641          * part of the Swing threading workaround. This method immediately exits
 642          * if there is no work to do. It also dispatches model update events if
 643          * necessary. This method always runs on the event dispatch thread.
 644          */
 645         private void goLive() {
 646             int firstNew, lastNew = 0;
 647             if (debug)
 648                 Debug.println("BP_TL.TLM - goLive() starting.");
 649             // this is sync. against the outer class because we may change the
 650             // list model object during execution of this block
 651             synchronized (BP_TestListSubpanel.this) {
 652                 synchronized (vLock) {
 653                     if (inQueue.size() == 0 && rmQueue.size() == 0) {
 654                         if (debug)
 655                             Debug.println("BP_TT.TTM - goLive() nothing to do, returning");
 656                         return;
 657                     }
 658 
 659                     processRemoveQueue();
 660                     //preprocessAddQueue();
 661 
 662                     // now add the new items
 663                     if (inQueue.size() != 0) {
 664                         synchronized (liveData) {
 665                             firstNew = liveData.size();
 666                             if (inQueue.size() < BATCH_SIZE) {
 667                                 if (sortingRequested) {
 668                                     liveData.clear();
 669                                     liveData.addAll(inQueue);
 670                                     sortingRequested = false;
 671                                 } else {
 672                                     liveData.addAll(inQueue);
 673                                     inQueue.setSize(0);
 674                                 }
 675                                 table.repaint();
 676                                 lastNew = liveData.size() - 1;
 677                             } else { // only add some of the new items
 678                                 if (sortingRequested) {
 679                                     liveData.clear();
 680                                     sortingRequested = false;
 681                                 }
 682 
 683                                 for (int i = 0; i < BATCH_SIZE; i++) {
 684                                     Object o = inQueue.remove(0);
 685 
 686                                     liveData.add(o);
 687                                 }
 688 
 689                                 // schedule a future update
 690                                 if (!isUpdateScheduled) {
 691                                     TableNotifier tn = new TableNotifier(
 692                                             subpanelNode, this);
 693                                     pendingEvents.addElement(tn);
 694                                     EventQueue.invokeLater(tn);
 695                                 }
 696                                 table.repaint();
 697                                 lastNew = liveData.size() - 1;
 698                             }
 699                         } // sync
 700                         // dispatch update range event to Swing
 701                         if (listenerList.getListenerCount() > 0) {
 702                             TableModelEvent e = new TableModelEvent(this,
 703                                     firstNew, lastNew,
 704                                     TableModelEvent.ALL_COLUMNS,
 705                                     TableModelEvent.INSERT);
 706 
 707                             TableNotifier tn = new TableNotifier(e, this);
 708                             pendingEvents.addElement(tn);
 709                             EventQueue.invokeLater(tn);
 710                         }
 711                     }
 712 
 713                     // enable this tab now that it has data
 714             /*
 715              * if (liveData.size() > 0) { // switch back from an empty
 716              * list setEmpty(false); } else { setEmpty(true); }
 717              */
 718 
 719                     // this clears the "please wait" message if needed
 720                     if (table.getSelectedRow() == -1 && inQueue.size() == 0)
 721                         showMessage("");
 722                 } // sync
 723             }
 724 
 725             if (debug)
 726                 Debug.println("BP_TL.LT - goLive() finished");
 727         }
 728 
 729         /**
 730          * Remove tests in the removal queue from the live data or the incoming
 731          * data. vLock should be locked when you call this method
 732          */
 733         private void processRemoveQueue() {
 734             if (rmQueue.size() == 0)
 735                 return;
 736 
 737             while (rmQueue.size() > 0) {
 738                 TestResult target = (TestResult) (rmQueue.remove(0));
 739                 int targetIndex = liveData.indexOf(target);
 740                 if (targetIndex != -1) {
 741                     synchronized (liveData) {
 742                         // necessary for proper synchronization
 743                         // should not be a problem really, based on how other
 744                         // locking is done, all work on liveData occurs in
 745                         // goLive()
 746                         targetIndex = liveData.indexOf(target);
 747 
 748                         // only should happen if the item disappears
 749                         if (targetIndex == -1)
 750                             continue;
 751 
 752                         liveData.remove(targetIndex);
 753 
 754                         // WARNING: since we are continually changing the
 755                         // contents of
 756                         // the data, you must notify the observers synchronously
 757                         // to get
 758                         // proper results
 759                         notifyRemoved(target, targetIndex);
 760                     } // sync
 761                 }
 762             } // while
 763         }
 764 
 765         /**
 766          * Remove duplicates in the add queue. vLock should be locked when you
 767          * call this method
 768          */
 769         private void preprocessAddQueue() {
 770             // make sure this list does not contain dups
 771             for (int i = 0; i < inQueue.size(); i++) {
 772                 if (liveData.contains(inQueue.elementAt(i))) {
 773                     inQueue.remove(i);
 774                     i--;
 775                 } else {
 776                 }
 777             } // for
 778         }
 779 
 780         // --------- event utility methods -----------
 781         /**
 782          * Notify observers that the given index was added
 783          */
 784         private void notifyAdded(TestResult what, int index) {
 785             if (listenerList.getListenerCount() > 0) {
 786                 // may want to buffer these messages for performance
 787                 TableModelEvent e = new TableModelEvent(this, index, index,
 788                         TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT);
 789 
 790                 if (EventQueue.isDispatchThread()) {
 791                     // XXX try without this to see perf. impact
 792                     // dispatch synchronously
 793                     fireTableChanged(e);
 794                 } else {
 795                     // switch event onto AWT event thread
 796                     TableNotifier tn = new TableNotifier(e, mod);
 797                     pendingEvents.addElement(tn);
 798                     EventQueue.invokeLater(tn);
 799                 }
 800             }
 801         }
 802 
 803         private void notifyRemoved(TestResult what, int index) {
 804             if (listenerList.getListenerCount() > 0) {
 805                 // may want to buffer these messages
 806                 TableModelEvent e = new TableModelEvent(this, index, index,
 807                         TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE);
 808 
 809                 if (EventQueue.isDispatchThread()) {
 810                     // XXX try without this to see perf. impact
 811                     // dispatch synchronously
 812                     fireTableChanged(e);
 813                 } else {
 814                     // switch event onto AWT event thread
 815                     TableNotifier tn = new TableNotifier(e, mod);
 816                     pendingEvents.addElement(tn);
 817                     EventQueue.invokeLater(tn);
 818                 }
 819             }
 820         }
 821 
 822         private void notifyDone() {
 823             if (listenerList.getListenerCount() > 0) {
 824                 // may want to buffer these messages
 825                 TableModelEvent e = new TableModelEvent(this);
 826                 // switch event onto AWT event thread
 827                 TableNotifier tn = new TableNotifier(e, mod);
 828                 pendingEvents.addElement(tn);
 829                 EventQueue.invokeLater(tn);
 830             }
 831         }
 832 
 833         private String[] colNames;
 834 
 835         // must sync. on vLock anytime you access inQueue or liveData
 836         private final Object vLock = new Object(); // lock for inQueue &
 837         // rmQueue
 838 
 839         private Vector inQueue; // queue of items to be added to live data
 840 
 841         private Vector rmQueue; // queue of items to be removed from live data
 842 
 843         private LinkedList liveData; // to allow manual synchronization
 844 
 845         Vector pendingEvents = new Vector();
 846 
 847         volatile boolean isUpdateScheduled; // are updates waiting in inQueue or
 848         // rmQueue
 849 
 850         private static final int BATCH_SIZE = 100;
 851 
 852         private static final int COLUMN_COUNT = 2;
 853 
 854         private Sorter[] rows;
 855 
 856         private Object[] o;
 857 
 858         private int SORTING_COLUMN = -1;
 859 
 860         private boolean SORTING_MODE = false;
 861     }
 862 
 863     private class CacheObserver extends TT_NodeCache.TT_NodeCacheObserver {
 864         CacheObserver(int state) {
 865             super();
 866             // configure our interest list
 867             interestList[MSGS_ALL] = false;
 868             interestList[MSGS_STATS] = false;
 869 
 870             switch (state) {
 871                 case Status.PASSED:
 872                     interestList[MSGS_PASSED] = true;
 873                     break;
 874                 case Status.FAILED:
 875                     interestList[MSGS_FAILED] = true;
 876                     break;
 877                 case Status.ERROR:
 878                     interestList[MSGS_ERRORS] = true;
 879                     break;
 880                 case Status.NOT_RUN:
 881                     interestList[MSGS_NOT_RUNS] = true;
 882                     break;
 883                 case Status.NUM_STATES:
 884                     interestList[MSGS_FILTERED] = true;
 885                     break;
 886                 default:
 887                     throw new IllegalStateException();
 888             } // switch
 889         }
 890 
 891         public void testAdded(int msgType, TestResultTable.TreeNode[] path,
 892                 TestResult what, int index) {
 893             synchronized (BP_TestListSubpanel.this) {
 894                 mod.addTest(what, false);
 895             }
 896         }
 897 
 898         public void testRemoved(int msgType, TestResultTable.TreeNode[] path,
 899                 TestResult what, int index) {
 900             synchronized (BP_TestListSubpanel.this) {
 901                 mod.removeTest(what);
 902             }
 903         }
 904 
 905         public void statsUpdated(int[] stats) {
 906             // ignore
 907         }
 908     }
 909 
 910     /**
 911      * This is a double duty class; it commits changes the model and also
 912      * dispatches given events. If instance var. lt is null, it dispatches
 913      * events, otherwise it triggers a commit on the list thread data using (<tt>goLive()</tt>).
 914      * This class is critical because it is the task which gets scheduled on the
 915      * event thread.
 916      */
 917     class TableNotifier implements Runnable {
 918         TableNotifier(TT_BasicNode n, TestTableModel m) {
 919             node = n;
 920             tm = m;
 921             tm.isUpdateScheduled = true;
 922         }
 923 
 924         TableNotifier(TableModelEvent e, TestTableModel m) {
 925             tm = m;
 926             tme = e;
 927         }
 928 
 929         public void run() {
 930             tm.pendingEvents.remove(this);
 931 
 932             // this message has been cancelled
 933             if (!isValid)
 934                 return;
 935 
 936             if (tme == null) {
 937                 // consume the update event
 938                 tm.isUpdateScheduled = false;
 939                 tm.goLive();
 940             } else {
 941                 tm.fireTableChanged(tme);
 942             }
 943         }
 944 
 945         public void cancel() {
 946             isValid = false;
 947         }
 948 
 949         // used to validate the event at dispatch time
 950         TT_BasicNode node;
 951 
 952         // go live data, no event dispatch
 953         TestTableModel tm;
 954 
 955         // event dispatch items
 956         private TableModelEvent tme;
 957 
 958         private boolean isValid = true;
 959     } // list notifier
 960 
 961     /**
 962      * One of these listeners is associated with each of the test lists.
 963      */
 964     class InputListener extends MouseAdapter implements ListSelectionListener,
 965             ActionListener {
 966 
 967         // ActionListener
 968         public void actionPerformed(ActionEvent e) {
 969             final int[] rows = table.getSelectedRows();
 970 
 971             if (e.getActionCommand().equals("action.clear")) {
 972                 if (rows.length > 0) {
 973                     final WorkDirectory wd = execModel.getWorkDirectory();
 974 
 975                     if (harness.isRunning()) {
 976                         uif.showInformationDialog("treep.cantPurgeRunning", null);
 977                         return;
 978                     }
 979 
 980                     Object[] toPurge = new Object[rows.length];
 981                     for (int i = 0; i < rows.length; i++) {
 982                         Object item = table.getValueAt(rows[i], 0);
 983                         if (item instanceof TestResult)
 984                             toPurge[i] = ((TestResult) item).getWorkRelativePath();
 985                         else {
 986                         } // should not happen
 987                     } // for
 988 
 989                     int confirm = uif.showYesNoDialog("treep.purgeItemsSure",
 990                             TestTreePanel.createNodeListString(TestTreePanel.createNodeList(toPurge)));
 991                     // user backs out
 992                     if (confirm == JOptionPane.NO_OPTION)
 993                         return;
 994                     else {
 995                         // this block is intended to do the following:
 996                         // - disable menu items
 997                         // - start purge on background thread
 998                         // - show a wait dialog if the operation exceeds a min.
 999                         // time
1000                         // - hide dialog and re-enable menu item when thread
1001                         // finishes
1002 
1003                         final JDialog d = uif.createWaitDialog(
1004                                 "treep.waitPurge", table);
1005                         final String[] finalList = TestTreePanel.createNodeList(toPurge);
1006 
1007                         // disable all menu items
1008                         // setPopupItemsEnabled(false);
1009 
1010                         final Thread t = new Thread() {
1011                             public void run() {
1012                                 for (int i = 0; i < rows.length; i++) {
1013                                     try {
1014                                         // this may take a long while...
1015                                         for (int j = 0; j < finalList.length; j++)
1016                                             wd.purge(finalList[j]);
1017                                     } // try
1018                                     catch (WorkDirectory.PurgeFault f) {
1019                                         // print something in log...
1020                                         I18NResourceBundle i18n = uif.getI18NResourceBundle();
1021                                         wd.log(i18n, "treep.purgeFail.err", f);
1022                                     } // catch
1023                                     finally {
1024                                         // fixup GUI on GUI thread
1025                                         try {
1026                                             EventQueue.invokeAndWait(new Runnable() {
1027                                                 public void run() {
1028                                                     if (d.isShowing())
1029                                                         d.hide();
1030                                                     // enable all menu
1031                                                     // items
1032                                                     // setPopupItemsEnabled(true);
1033                                                 }
1034                                             });
1035                                         } catch (InterruptedException e) {
1036                                         } catch (java.lang.reflect.InvocationTargetException e) {
1037                                         }
1038                                     } // outer try
1039                                 } // for
1040                             } // run()
1041                         }; // thread
1042 
1043                         ActionListener al = new ActionListener() {
1044                             public void actionPerformed(ActionEvent evt) {
1045                                 // show dialog if still processing
1046                                 if (t == null)
1047                                     return;
1048                                 else if (t.isAlive() && !d.isVisible()) {
1049                                     d.show();
1050                                 } else if (!t.isAlive() && d.isVisible()) {
1051                                     // just in case...a watchdog type check
1052                                     d.hide();
1053                                 }
1054                             }
1055                         };
1056 
1057                         // bgThread = t;
1058 
1059                         // show wait dialog if operation is still running after
1060                         // WAIT_DIALOG_DELAY
1061                         Timer timer = new Timer(WAIT_DIALOG_DELAY, al);
1062                         timer.setRepeats(false);
1063 
1064                         // do it!
1065                         // in this order to reduce race condition
1066                         timer.start();
1067                         t.start();
1068                     }
1069                 } else { // no rows selected
1070                     // XXX show error dialog
1071                 }
1072             } // "clear"
1073             else if (e.getActionCommand().equals("action.run")) {
1074                 if (rows.length > 0) {
1075                     // special case check, remove all items if root
1076                     // is selected
1077                     if (harness.isRunning()) {
1078                         uif.showInformationDialog("treep.cantRunRunning", null);
1079                         return;
1080                     }
1081                     String[] result = new String[rows.length];
1082                     for (int i = 0; i < rows.length; i++) {
1083                         if (table.getValueAt(rows[i], 0) instanceof TestResult) {
1084                             TestResult r = (TestResult) table.getValueAt(
1085                                     rows[i], 0);
1086                             result[i] = r.getTestName();
1087                         } else
1088                             // should not happen
1089                             result[i] = table.getValueAt(rows[i], 0).toString();
1090                     } // for
1091 
1092                     execModel.runTests(result);
1093                 } else { // now rows selected
1094                     // XXX show error dialog
1095                 }
1096             } // run
1097             else if (e.getActionCommand().equals("action.cpnamelist") ||
1098                     e.getActionCommand().equals("action.cpnamestr")) {
1099                 if (rows.length > 0) {
1100                     // special case check, remove all items if root
1101                     // is selected
1102                     if (harness.isRunning()) {
1103                         uif.showInformationDialog("bp.cp.isRunning", null);
1104                         return;
1105                     }
1106                     String[] result = new String[rows.length];
1107                     for (int i = 0; i < rows.length; i++) {
1108                         if (table.getValueAt(rows[i], 0) instanceof TestResult) {
1109                             TestResult r = (TestResult) table.getValueAt(
1110                                     rows[i], 0);
1111                             result[i] = r.getTestName();
1112                         } else
1113                             // should not happen
1114                             result[i] = table.getValueAt(rows[i], 0).toString();
1115                     } // for
1116 
1117                     StringSelection payload = null;
1118                     if (e.getActionCommand().equals("action.cpnamestr")) {
1119                         payload = new StringSelection(StringArray.join(result));
1120                     } else {
1121                         payload = new StringSelection(StringArray.join(result, "\n"));
1122                     }
1123 
1124                     // send to clipboard
1125                     if (payload != null) {
1126                         Toolkit.getDefaultToolkit().getSystemClipboard().
1127                             setContents(payload, null);
1128                         Clipboard selection = Toolkit.getDefaultToolkit().getSystemSelection();
1129                         if (selection != null)
1130                                 selection.setContents(payload, null);
1131                     }
1132 
1133                 } else { // now rows selected
1134                     // XXX show error dialog?
1135                 }
1136             } // run
1137             else if (e.getActionCommand().equals("show.title")) {
1138                 table.getColumnModel().getColumn(1).setHeaderValue(
1139                         uif.getI18NString("br.description.show.title.mit"));
1140                 table.getTableHeader().resizeAndRepaint();
1141                 show = "title";
1142             } else if (e.getActionCommand().equals("show.keywords")) {
1143                 table.getColumnModel().getColumn(1).setHeaderValue(
1144                         uif.getI18NString("br.description.show.keywords.mit"));
1145                 table.getTableHeader().resizeAndRepaint();
1146                 show = "keywords";
1147             } else if (e.getActionCommand().equals("show.status")) {
1148                 table.getColumnModel().getColumn(1).setHeaderValue(
1149                         uif.getI18NString("br.runtime.show.status.mit"));
1150                 table.getTableHeader().resizeAndRepaint();
1151                 show = TestResult.EXEC_STATUS;
1152             } // status
1153             else if (e.getActionCommand().equals("show.time.start")) {
1154                 table.getColumnModel().getColumn(1).setHeaderValue(
1155                         uif.getI18NString("br.runtime.show.time.start.mit"));
1156                 table.getTableHeader().resizeAndRepaint();
1157                 show = TestResult.START;
1158             } else if (e.getActionCommand().equals("show.time.end")) {
1159                 table.getColumnModel().getColumn(1).setHeaderValue(
1160                         uif.getI18NString("br.runtime.show.time.end.mit"));
1161                 table.getTableHeader().resizeAndRepaint();
1162                 show = TestResult.END;
1163             }
1164             table.repaint();
1165 
1166         } // action performed
1167 
1168         // Mouse Adapter
1169         public void mouseClicked(MouseEvent e) {
1170             if (e.getComponent() == table) {
1171                 if (e.getButton() == MouseEvent.BUTTON3) {
1172                     popupTable.show(e.getComponent(), e.getX(), e.getY());
1173                 } else {
1174                     JTable tbl = (JTable) (e.getComponent());
1175                     int col = table.columnAtPoint(e.getPoint());
1176                     int row = table.rowAtPoint(e.getPoint());
1177                     TableModel tm = table.getModel();
1178                     // an empty table can't do anything
1179                     if (tm.getRowCount() < 1) {
1180                         // clear the message field
1181                         showMessage("");
1182                         return;
1183                     }
1184 
1185                     // always use col 1, which is where the TestResult is
1186                     // we only really care which row was clicked on
1187                     TestResult tr = (TestResult) (tm.getValueAt(row, 0));
1188 
1189                     if (e.getClickCount() == 1) {
1190                         // show vital stats only
1191                         showMessage(I18NUtils.getStatusMessage(tr.getStatus()));
1192                     } else if (e.getClickCount() == 2) {
1193                         showTest(tr);
1194                     }
1195                 }
1196             } else if (e.getComponent() == table.getTableHeader()) {
1197                 if (e.getButton() == MouseEvent.BUTTON3) {
1198                     popupHeader.show(e.getComponent(), e.getX(), e.getY());
1199                 } else if (e.getButton() == MouseEvent.BUTTON1) {
1200                     int tableColumn = table.columnAtPoint(e.getPoint());
1201                     int modelColumn = table.convertColumnIndexToModel(tableColumn);
1202 
1203                     mod.SORTING_COLUMN = modelColumn;
1204                     mod.SORTING_MODE = !mod.SORTING_MODE;
1205 
1206                     sortingRequested = true;
1207 
1208                     mod.sortTests(mod.liveData, mod.SORTING_COLUMN, mod.SORTING_MODE);
1209                 }
1210             }
1211         }
1212 
1213         // ListSelectionListener
1214         public void valueChanged(ListSelectionEvent e) {
1215             int index = e.getLastIndex();
1216 
1217             if (mod.getRowCount() == 0 || index >= mod.getRowCount()) {
1218                 // swing seems to generate re-selection events when the
1219                 // list model is 'replaced' with a new one (completely
1220                 // invalidated). for some reason it changes the selection
1221                 // index without doing bounds checking
1222                 return; // ignore invalid event
1223             }
1224 
1225             if (index != lastIndex) {
1226                 TestResult tr = (TestResult) (mod.getValueAt(index, 0));
1227 
1228                 // show vital stats only
1229                 showMessage(I18NUtils.getStatusMessage(tr.getStatus()));
1230                 lastIndex = index;
1231             }
1232         }
1233 
1234         private int lastIndex = -2;
1235     }
1236 
1237     /**
1238      * Action to handle user pressing enter to select a test.
1239      */
1240     private class KbTableAction extends AbstractAction {
1241         KbTableAction(I18NResourceBundle bund, String key) {
1242             desc = bund.getString(key + ".desc");
1243             name = bund.getString(key + ".act");
1244         }
1245 
1246         public void actionPerformed(ActionEvent e) {
1247             int row = table.getSelectedRow();
1248 
1249             // nothing selected, ignore event
1250             if (row < 0)
1251                 return;
1252 
1253             Object target = table.getModel().getValueAt(row, 0);
1254 
1255             // this shouldn't be the case...
1256             if (!(target instanceof TestResult))
1257                 return;
1258 
1259             TestResult tr = (TestResult) target;
1260             showTest(tr);
1261         }
1262 
1263         public Object getValue(String key) {
1264             if (key == null)
1265                 throw new NullPointerException();
1266 
1267             if (key.equals(NAME))
1268                 return name;
1269             else if (key.equals(SHORT_DESCRIPTION))
1270                 return desc;
1271             else
1272                 return null;
1273         }
1274 
1275         private String name;
1276 
1277         private String desc;
1278     }
1279 
1280     class TestCellRenderer extends DefaultTableCellRenderer {
1281         public TestCellRenderer(UIFactory uif) {
1282             setOpaque(false);
1283         }
1284 
1285         public Component getTableCellRendererComponent(JTable table,
1286                 Object value, boolean isSelected, boolean hasFocus, int row,
1287                 int column) {
1288             if (value == null) // very strange...
1289                 return this;
1290 
1291             if (value instanceof TestResult) {
1292                 TestResult tr = (TestResult) value;
1293                 setText(tr.getTestName());
1294                 setToolTipText(I18NUtils.getStatusMessage(tr.getStatus()));
1295             } else if (value instanceof TT_TestNode) {
1296                 TestResult tr = ((TT_TestNode)value).getTestResult();
1297                 setText(tr.getTestName());
1298                 setToolTipText(I18NUtils.getStatusMessage(tr.getStatus()));
1299             } else { // this will run for the property column (1)
1300                 setText(value.toString());
1301             }
1302 
1303             setBorder(spacerBorder);
1304             setFont(getFont().deriveFont(Font.PLAIN));
1305 
1306             if (isSelected) {
1307                 setOpaque(true);
1308                 // setBackground(MetalLookAndFeel.getTextHighlightColor());
1309                 setBackground(table.getSelectionBackground());
1310                 setForeground(table.getSelectionForeground());
1311             } else {
1312                 // setForeground(MetalLookAndFeel.getPrimaryControlDarkShadow());
1313                 setForeground(table.getForeground());
1314                 setOpaque(false);
1315             }
1316 
1317             // would like to find a better way to do this
1318             // seems like we can't do this properly until the first item
1319             // is rendered though
1320             if (!rowHeightSet) {
1321                 table.setRowHeight(getFontMetrics(getFont()).getHeight()
1322                 + ROW_HEIGHT_PADDING);
1323                 rowHeightSet = true;
1324             }
1325 
1326             return this;
1327         }
1328 
1329         // border to pad left and right
1330         private Border spacerBorder = BorderFactory.createEmptyBorder(3, 3, 3,
1331                 3);
1332     }
1333 
1334     private int state;
1335     private JTable table;
1336     private TestTableModel mod; // JTable model
1337     private TT_NodeCache cache;
1338     private CacheObserver cacheWatcher;
1339     private volatile TableSynchronizer resyncThread;
1340     private TableCellRenderer renderer;
1341     private InputListener listener;
1342     private JTextArea infoTL;
1343     private boolean rowHeightSet;
1344 
1345     private static final int ROW_HEIGHT_PADDING = 3;
1346     private static final int WAIT_DIALOG_DELAY = 3000; // 3 second delay
1347 
1348     private JPopupMenu popupTable, popupHeader;
1349     private JMenu showDescrMenu, showRunMenu, sortMenu;
1350 
1351     private Harness harness;
1352     private ExecModel execModel;
1353 
1354     private boolean debug = Debug.getBoolean(BP_TestListSubpanel.class);
1355 
1356     private boolean sortingRequested = false;
1357 
1358     private String show = TestResult.EXEC_STATUS;
1359 
1360     private Vector[] newData;
1361 
1362     private boolean updateRequired;
1363 }