/* * $Id$ * * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.javatest.exec; import java.awt.Component; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Arrays; import java.util.LinkedList; import java.util.Vector; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JMenu; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.KeyStroke; import javax.swing.Timer; import javax.swing.ListSelectionModel; import javax.swing.border.Border; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TableModelEvent; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableModel; import com.sun.javatest.Harness; import com.sun.javatest.JavaTestError; import com.sun.javatest.Status; import com.sun.javatest.TestResult; import com.sun.javatest.TestResultTable; import com.sun.javatest.WorkDirectory; import com.sun.javatest.tool.I18NUtils; import com.sun.javatest.tool.UIFactory; import com.sun.javatest.util.Debug; import com.sun.javatest.util.I18NResourceBundle; import com.sun.javatest.util.StringArray; import com.sun.javatest.tool.jthelp.ContextHelpManager; import java.awt.datatransfer.Clipboard; /** * This panel renders information about the tests which are "filtered out" in * the current node. *
* The background thread has a two-stage commit process so that the iterator can * run at full speed, ignoring the MT-unsafe swing list model. The changes are * reflected in the real list when the AWT event queue schedules the * notification thread which is created during the iteration. This class also * processes the list click events and usually dispatches changes to the branch * panel model. * *
* If you need to synchronize against both the vLock (for live data) and this
* class' lock, then blocks should be synchronized against this outer class,
* then the vLock. The ordering is vital to avoiding deadlocks.
*/
class BP_TestListSubpanel extends BP_BranchSubpanel {
BP_TestListSubpanel(UIFactory uif, Harness h, ExecModel em,
BP_Model bpm, TestTreeModel ttm, int state) {
super("tl" + state, uif, bpm, ttm, "br.list");
this.state = state;
this.harness = h;
this.execModel = em;
init();
// the enableHelp lines below are checked during the build
switch (state) {
case Status.PASSED:
ContextHelpManager.setHelpIDString(this, "browse.passedTab.csh");
break;
case Status.FAILED:
ContextHelpManager.setHelpIDString(this, "browse.failedTab.csh");
break;
case Status.ERROR:
ContextHelpManager.setHelpIDString(this, "browse.errorTab.csh");
break;
case Status.NOT_RUN:
ContextHelpManager.setHelpIDString(this, "browse.notRunTab.csh");
break;
case BranchPanel.STATUS_FILTERED:
ContextHelpManager.setHelpIDString(this, "browse.filteredOutTab.csh");
break;
} // switch
// must do this after state variable is set
cacheWatcher = new CacheObserver(state);
}
/**
* This is to provide BranchPanel the ability to do batch updates.
*/
/*
* void removeTest(TestResult tr) { mod.removeTest(tr); }
*
* void addTest(TestResult tr) { mod.addTest(tr, false); }
*
* void addTest(TestResult[] trs) { if (trs == null || trs.length == 0)
* return;
*
* for (int i = 0; i < trs.length - 1; i++) mod.addTest(trs[i], true);
* // do a final add with notification mod.addTest(trs[trs.length-1],
* false); }
*/
/**
* Clear the table contents and prepare to receive new data.
*/
void reset(TT_NodeCache cache) {
// grab cache lock first because many other threads may alter that
// object, causing a deadlock here.
// sync. to hold observer traffic until re-sync is done
// also see TableSynchronizer thread
if (this.cache != null) {
final TT_NodeCache cacheCopy = this.cache;
synchronized (cacheCopy) {
synchronized (BP_TestListSubpanel.this) {
cacheCopy.removeObserver(cacheWatcher);
} // sync this panel
} // sync cache
}
synchronized (BP_TestListSubpanel.this) {
this.cache = cache;
if (resyncThread != null) {
resyncThread.halt();
}
if (mod != null)
mod.reset();
} // sync
validateEnableState();
// table.clearSelection();
}
protected void invalidateFilters() {
super.invalidateFilters();
// if we didn't have one, we certainly don't need to disconnect,
// and probably don't need to get a new one...
// XXX see reset() and TableSynchronizer.run(), should do this in a
// synchronized block?
if (cache != null) {
cache.removeObserver(cacheWatcher);
if (subpanelNode != null) {
cache = ttm.getNodeInfo(subpanelNode.getTableNode(), false);
validateEnableState();
}
}
}
public void setUpdateRequired(boolean updateRequired) {
this.updateRequired = updateRequired;
}
/**
* Only called when this panel is onscreen and needs to be kept up to date.
*/
protected synchronized void updateSubpanel(TT_BasicNode currNode) {
super.updateSubpanel(currNode);
if (debug) {
Debug.println("updating test list " + state);
Debug.println(" -> size " + (mod == null? 0 : mod.getRowCount()));
}
// only run if we change nodes
if (updateRequired || filtersInvalidated) {
if (resyncThread != null) {
resyncThread.halt();
}
resyncThread = new TableSynchronizer(state);
resyncThread.start();
filtersInvalidated = false;
validateEnableState();
updateRequired = false;
}
}
/**
* Enable or disable this panel as necessary.
*/
private void validateEnableState() {
if (state < Status.NUM_STATES) { // handles all but filtered out
if (cache.getStats()[state] > 0) {
model.setEnabled(BP_TestListSubpanel.this, true);
// setEmpty(false);
} else if (cache.getStats()[state] == 0) {
model.setEnabled(BP_TestListSubpanel.this, false);
// setEmpty(true);
} else {
}
} else
throw new IllegalStateException();
}
/**
* A special thread to repopulate the test lists.
*/
private class TableSynchronizer extends Thread {
TableSynchronizer(int whichList) {
super("Test List Synchronizer" + whichList);
setPriority(Thread.MIN_PRIORITY + 2);
}
public void run() {
// grab cache lock first because many other threads may alter that
// object, causing a deadlock here.
// sync. to hold observer traffic until re-sync is done.
// also see reset() for this panel
final TT_NodeCache cacheCopy = cache;
synchronized (cacheCopy) {
synchronized (BP_TestListSubpanel.this) {
// resync with this node cache
newData = cacheCopy.addObserver(cacheWatcher, true);
// add tests into the list model - this doesn't make the
// data
// live though
for (int j = 0; j < newData[state].size() - 1; j++) {
if (stopping)
break;
if (sortingRequested) {
mod.sortTests(mod.liveData, mod.SORTING_COLUMN,
mod.SORTING_MODE);
} else {
mod.addTest(newData[state].elementAt(j), true);
}
} // for
if (newData[state].size() > 0 && !stopping) {
// final item with a notify
mod.addTest(newData[state].lastElement(), false);
}
// to indicate completion
resyncThread = null;
} // this sync
} // cache sync
validateEnableState();
} // run()
public void halt() {
stopping = true;
}
private volatile boolean stopping;
}
private void init() {
mod = new TestTableModel(uif);
renderer = new TestCellRenderer(uif);
listener = new InputListener();
table = uif.createTable("br.fo.tbl", mod);
table.setOpaque(true);
table.setRowSelectionAllowed(true);
table.setColumnSelectionAllowed(false);
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.getSelectionModel().addListSelectionListener(listener);
// setup col 0
TableColumn tc = table.getColumnModel().getColumn(0);
tc.setCellRenderer(renderer);
tc.setResizable(true);
// setup col 1
tc = table.getColumnModel().getColumn(1);
tc.setCellRenderer(renderer);
tc.setResizable(true);
uif.setAccessibleInfo(this, "br.fo");
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.NORTH;
gbc.gridy = 0;
gbc.ipady = 12;
gbc.weightx = 1.0;
gbc.weighty = 0.0;
infoTL = uif.createMessageArea("br.tl.info");
infoTL.setOpaque(false);
add(infoTL, gbc);
gbc.gridy = 1;
gbc.weightx = 2.0;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.BOTH;
add(uif.createScrollPane(table,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED), gbc);
InputListener tableListener = new InputListener();
table.addMouseListener(tableListener);
table.getTableHeader().addMouseListener(tableListener);
table.getSelectionModel().addListSelectionListener(tableListener);
// to trigger test selection when enter is pressed
table.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(
KeyEvent.VK_ENTER, 0, false), "gotoTest");
table.getActionMap().put("gotoTest", new KbTableAction(uif.getI18NResourceBundle(),
"br.list.enter"));
String[] showDescr = { "show.title", "show.keywords" };
showDescrMenu = uif.createMenu("br.description", showDescr,
tableListener);
String[] showRun = { "show.status", "show.time.start", "show.time.end" };
showRunMenu = uif.createMenu("br.runtime", showRun, tableListener);
popupHeader = uif.createPopupMenu("br");
popupHeader.add(showDescrMenu);
popupHeader.add(showRunMenu);
String[] actions = { "action.run", "action.clear" };
popupTable = uif.createPopupMenu("br", actions, tableListener);
if (state == Status.NOT_RUN) { // ignore clear result in "not run"
// panel
showRunMenu.setEnabled(false);
// XXX should avoid using absolute indexes
popupTable.getComponent(1).setEnabled(false);
}
actions = new String[] { "action.cpnamelist", "action.cpnamestr" };
popupTable.add(uif.createMenu("br.cp", actions, tableListener));
// this is necessary to make sure that the split pane can resize
// this panel. without setting the min., the panel seems to take
// all it is given, and never gives it back.
setMinimumSize(new Dimension(150, 100));
}
/**
* It is assumed that this will run on the event thread. private void
* setEmpty(boolean state) { if (state && list.getModel() !=
* EmptyListModel.getInstance()) {
* list.setModel(EmptyListModel.getInstance());
* model.setEnabled(BP_TestListSubpanel.this, false); lastMsg = ""; } else
* if (!state && list.getModel() == EmptyListModel.getInstance()) {
* list.setModel(mod); model.setEnabled(BP_TestListSubpanel.this, true); } }
*/
// ------------- inner class -------------
/**
* Enumerates tree in background to populate the list. If this thread is
* running, consider list data incomplete. Swing cannot handle an updating
* model, so there is a two-stage absorbtion of data. This thread runs with
* no delay reading the TRT and placing that data into an "offline" queue.
* It periodically schedules an event on the GUI event thread; when that
* thread run, it copies the data from the offline area to the online area,
* which is what the ListModel presents. This workaround assumes that Swing
* will never dispatch more than one event at a time.
*/
private class TestTableModel extends AbstractTableModel {
TestTableModel(UIFactory uif) {
super();
colNames = new String[] { uif.getI18NString("br.list.col0.txt"),
uif.getI18NString("br.list.col1.txt") };
if (debug) {
Debug.println("TableModel constructed: ");
Debug.println(" -> " + this);
}
init();
}
// ---------- TableModel interface ----------
public int getRowCount() {
synchronized (liveData) {
return liveData.size();
}
}
public int getColumnCount() {
return COLUMN_COUNT;
}
public String getColumnName(int columnIndex) {
if (columnIndex >= colNames.length)
throw new IndexOutOfBoundsException();
else
return columnIndex == 0 ? colNames[0] : uif.getI18NString("br.runtime.show.status.mit");
}
public Object getValueAt(int row, int column) {
if (column == 0) {
synchronized (liveData) {
return liveData.get(row);
}
} else if (column == 1) {
synchronized (liveData) {
if ((liveData.get(row)) instanceof TestResult)
return getSelectedProperty(liveData.get(row));
else
// should not happen
return uif.getI18NString("br.list.notAvailable.txt");
}
} else
throw new IndexOutOfBoundsException(
"Index into filtered out table is out of range: " + row
+ ", " + column);
}
public boolean isCellEditable(int rowIndex, int colIndex) {
return false;
}
// ---------- Custom methods for this model ----------
/**
* @param suppressNotify
* Actively request that no update be scheduled.
*/
void addTest(TestResult tr, boolean suppressNotify) {
synchronized (vLock) {
// make sure this item is not already in the list
if (!inQueue.contains(tr) && !liveData.contains(tr))
inQueue.addElement(tr);
} // sync
// try not to saturate the GUI event thread
if (!suppressNotify && !isUpdateScheduled) {
TableNotifier tn = new TableNotifier(subpanelNode, this);
pendingEvents.addElement(tn);
EventQueue.invokeLater(tn);
}
}
/**
* Sorts data in the table
*
* @param v
* Current tests list
* @param column
* Number of column sorting to be applied to
* @param mode
* Indicates ascending (0) or descending (1) sorting
*/
void sortTests(LinkedList