Somewhere lost in Oracles
clean_up to the trash (old Suns tutorials),
This project was called ChristmastTree
, is about JTable
& Performance
,
Standard Java code before crazy & messy SwingWorker
invoked from black hole called Executor
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.*;
import javax.swing.table.*;
/**
* CTTable extends JTable doing the following: <ul> <li>The UI is forced to be
* CTTableUI so that a customer CellRendererPane can be installed.
* <li>getCellRenderer is overriden to return the TableCellRenderer passed into
* the constructor. <li>tableChanged is overriden to pass the call to super only
* if the cell is visible. </ul>
*/
public class CTTable extends JTable {
private static final long serialVersionUID = 1L;
private CTTableCellRenderer renderer;
public CTTable(CTTableCellRenderer renderer) {
super();
this.renderer = renderer;
renderer.setFont(getFont());
}
@Override
public void updateUI() {
super.updateUI();
//Force the UI to be an instanceof CTTableUI. This approach will not work
//if you need to support more than one look and feel in your application.
setUI(new CTTableUI());
}
@Override
public void setFont(Font font) {
super.setFont(font);
if (renderer != null) {
renderer.setFont(font);
}
}
@Override
public TableCellRenderer getCellRenderer(int row, int column) {
return renderer;
}
@Override
public void tableChanged(TableModelEvent e) {
if (e instanceof VisibleTableModelEvent && !((VisibleTableModelEvent) e).isVisible(this)) {
return;// Do nothing if this cell isn't visible.
}
super.tableChanged(e);
}
private static class CTTableUI extends BasicTableUI {
@Override
public void installUI(JComponent c) {
super.installUI(c);// Overriden to install our own CellRendererPane
c.remove(rendererPane);
rendererPane = new CTCellRendererPane();
c.add(rendererPane);
}
}
/**
* CTCellRendererPane overrides paintComponent to NOT clone the Graphics
* passed in and NOT validate the Component passed in. This will NOT work if
* the painting code of the Component clobbers the graphics (scales it,
* installs a Paint on it...) and will NOT work if the Component needs to be
* validated before being painted.
*/
private static class CTCellRendererPane extends CellRendererPane {
private static final long serialVersionUID = 1L;
private Rectangle tmpRect = new Rectangle();
@Override
public void repaint() {
// We can safely ignore this because we shouldn't be visible
}
@Override
public void repaint(int x, int y, int width, int height) {
}
@Override
public void paintComponent(Graphics g, Component c, Container p, int x,
int y, int w, int h, boolean shouldValidate) {
if (c == null) {
if (p != null) {
Color oldColor = g.getColor();
g.setColor(p.getBackground());
g.fillRect(x, y, w, h);
g.setColor(oldColor);
}
return;
}
if (c.getParent() != this) {
this.add(c);
}
c.setBounds(x, y, w, h);
// As we are only interested in using a JLabel as the renderer,
//which does nothing in validate we can override this to do nothing,
//if you need to support components that can do layout, this will
//need to be commented out, or conditionally validate.
shouldValidate = false;
if (shouldValidate) {
c.validate();
}
boolean wasDoubleBuffered = false;
JComponent jc = (c instanceof JComponent) ? (JComponent) c : null;
if (jc != null && jc.isDoubleBuffered()) {
wasDoubleBuffered = true;
jc.setDoubleBuffered(false);
}//Don't create a new Graphics, reset the clip and translate the origin.
Rectangle clip = g.getClipBounds(tmpRect);
g.clipRect(x, y, w, h);
g.translate(x, y);
c.paint(g);
g.translate(-x, -y);
g.setClip(clip.x, clip.y, clip.width, clip.height);
if (wasDoubleBuffered) {
jc.setDoubleBuffered(true);
}
c.setBounds(-w, -h, 0, 0);
}
}
}
.
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
/**
* A custom TableCellRenderer that overrides a handful of methods: <ul>
* <li>isOpaque and setBackground are overridden to avoid filling the
* background, if possible. <li>firePropertyChange is overridden to do nothing.
* If you need to support HTML text in the renderer than this should NOT be
* overridden. <li>paint is overridden to forward the call directly to the UI,
* avoiding the creation of a Graphics. This will NOT work if you need the
* renderer to contain other childre or the Graphics is clobbered as part of
* painting the UI. </ul>
*/
public class CTTableCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
private Color background;
private Color foreground;
private Color editableForeground;
private Color editableBackground;
private Border focusBorder;
public CTTableCellRenderer() {
focusBorder = UIManager.getBorder("Table.focusCellHighlightBorder");
editableForeground = UIManager.getColor("Table.focusCellForeground");
editableBackground = UIManager.getColor("Table.focusCellBackground");
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
boolean negative = (value != null && ((Integer) value).intValue() < 0);
// Reset the background based on the sign of the value.
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
if (!negative) {
setBackground(null);
} else {
setBackground(Color.red);
}
}//NOTICE that we do NOT set the font here, because CTTable knows about
//us, it will set the font as appropriate.
if (hasFocus) {
setBorder(focusBorder);
if (table.isCellEditable(row, column)) {
setForeground(editableForeground);
setBackground(editableBackground);
}
} else {
setBorder(noFocusBorder);
}
setValue(value);
return this;
}
@Override
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
// As long as you don't have any HTML text, this override is ok.
}
@Override// This override is only appropriate if this will never contain
// any children AND the Graphics is not clobbered during painting.
public void paint(Graphics g) {
ui.update(g, this);
}
@Override
public void setBackground(Color c) {
this.background = c;
}
@Override
public Color getBackground() {
return background;
}
@Override
public void setForeground(Color c) {
this.foreground = c;
}
@Override
public Color getForeground() {
return foreground;
}
@Override
public boolean isOpaque() {
return (background != null);
}
@Override // This is generally ok for non-Composite components (like Labels)
public void invalidate() {
}
@Override // Can be ignored, we don't exist in the containment hierarchy.
public void repaint() {
}
}
.
import javax.swing.table.*;
import java.util.*;
/**
* CTTableModel, a TableModel, models a set of Datas as the rows. The data is
* stored in a List of Lists. As the changes come in against a particular Data
* object we also contain a map from Data to row. This can obviously be made
* faster by pushing the row to the Data, but this may not be feasable in
* applications of this sort.
*/
public class CTTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
/**
* Maps from Data to an integer id giving the row of the data.
*/
private Map rowMap;
/**
* Number of columns to display.
*/
private int columns;
/**
* A List of Lists.
*/
private java.util.List rowData;
/**
* If true, batch cell updates using sharedModelEvent.
*/
private boolean batchChange;
/**
* Shared model event.
*/
private VisibleTableModelEvent sharedModelEvent;
public CTTableModel(int columns) {
this.columns = columns;
// Notice how they are not synchronized, we do NOT access this class
// from another thread, and therefore do not have to worry about
// synchronization.
rowData = new ArrayList();
rowMap = new HashMap();
}
public void addRow(Data rowID) {
int row = rowData.size();
rowMap.put(rowID, new Integer(row));
ArrayList colData = new ArrayList();
for (int counter = 0; counter < columns; counter++) {
colData.add(null);
}
rowData.add(colData);
fireTableRowsInserted(row, row);
}
/**
* Toggles batch updates. When true and model changes are notified using a
* VisibleTableModelEvent.
*
* @param batch
*/
public void setBatchUpdates(boolean batch) {
this.batchChange = batch;
if (sharedModelEvent == null) {
sharedModelEvent = new VisibleTableModelEvent(this);
}
sharedModelEvent.reset();
}
public boolean getBatchUpdates() {
return batchChange;
}
/**
* Sets the display value for a particular Data item at a particular cell.
* If notify is true listeners are notified, otherwise no listeners are
* notified.
*
* @param rowID
* @param col
* @param data
* @param notify
*/
public void set(Data rowID, int col, Object data, boolean notify) {
int row = ((Integer) rowMap.get(rowID)).intValue();
((java.util.List) rowData.get(row)).set(col, data);
if (notify) {
if (batchChange) {
sharedModelEvent.set(row, col);
fireTableChanged(sharedModelEvent);
} else {
fireTableCellUpdated(row, col);
}
}
}
@Override
public int getRowCount() {
return rowData.size();
}
@Override
public int getColumnCount() {
return columns;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return ((java.util.List) rowData.get(rowIndex)).get(columnIndex);
}
}
.
/**
* Unique ID for the data.
*/
public class Data {
/**
* This is overriden to remind developers they should have an intelligent
* equals and hashCode. You do not need to override either of them, but if
* you override one you need to override the other. Additionaly, because
* they are used extensively to map the data that has changed to the table,
* equals and hashCode MUST be fast, cache data if you need to!
*
* @param x
*/
@Override
public boolean equals(Object x) {
return (this == x);
}
/**
* This is overriden to remind developers they should have an intelligent
* equals and hashCode. You do not need to override either of them, but if
* you override one you need to override the other. Additionaly, because
* they are used extensively to map the data that has changed to the table,
* equals and hashCode MUST be fast, cache data if you need to!
*/
@Override
public int hashCode() {
return super.hashCode();
}
}
.
import java.util.ArrayList;
/**
* DataChange is used to associate a Data Object with a column identifier that
* has changed. To avoid loads of garbage per update DataChanges are cached and
* reused.
*/
public class DataChange {
private static ArrayList sharedDataChanges = new ArrayList();
private Data data;
private int col;
private int hashCode;
/**
* Obtains a DataChange for the specified Data and column.
*
* @param data
* @param col
* @return
*/
public static DataChange getDataChange(Data data, int col) {
synchronized (sharedDataChanges) {
int size = sharedDataChanges.size();
if (size > 0) {
DataChange change = (DataChange) sharedDataChanges.remove(size - 1);
change.data = data;
change.col = col;
return change;
}
}
return new DataChange(data, col);
}
/**
* Indicates the DataChange is no longer needed and can be reused.
*
* @param change
*/
public static void releaseDataChange(DataChange change) {
synchronized (sharedDataChanges) {
sharedDataChanges.add(change);
}
}
DataChange(Data data, int col) {
this.data = data;
this.col = col;
hashCode = (data.hashCode() | col);
}
public Data getData() {
return data;
}
public int getColumn() {
return col;
}
@Override
public int hashCode() {
return hashCode;
}
public boolean equals(DataChange dc) {
if (dc == this) {
return true;
}
DataChange o = (DataChange) dc;
return (o.data == data && o.col == col);
}
}
.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
/**
* The Main controller, responsible for wiring everything together. Pressing
* return in any of the fields will trigger recreation of everything.
*/
public class Main implements ActionListener {
// properties: columnCount, rowCount, updateSleepTime, eqSleepTime,
// threshold, generateSleep, generatorBatchCount
private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
private JTextField columnCount;
private JTextField rowCount;
private JTextField updateSleepTime;
private JTextField eqSleepTime;
private JTextField threshold;
private JTextField generateSleep;
private JTextField generatorBatchCount;
private JFrame frame;
static JLabel totalUpdateTime;
static JLabel notifyTime;
static JLabel paintTime;
static JLabel updateCount;
private JTable table;
private UpdateThread updateThread;
private GeneratorThread generatorThread;
private CTTableModel tableModel;
private static int NUM_COLUMNS = 40;// Initial values for the 7 properties.
private static int NUM_ROWS = 3000;
private static int UPDATE_SLEEP_TIME = 500;
private static int EQ_SLEEP_TIME = 10;
private static int UPDATE_ALL_THRESHOLD = 400000;
private static int GENERATOR_SLEEP_TIME = 40;
private static int BATCH_SIZE = 1000;
Main() {
frame = new JFrame();
frame.getContentPane().setLayout(new GridBagLayout());
columnCount = add("Columns: ", NUM_COLUMNS);
rowCount = add("Rows: ", NUM_ROWS);
updateSleepTime = add("Update Sleep: ", UPDATE_SLEEP_TIME);
eqSleepTime = add("EQ Sleep: ", EQ_SLEEP_TIME);
threshold = add("Update All Threshold: ", UPDATE_ALL_THRESHOLD);
generateSleep = add("Generator Sleep: ", GENERATOR_SLEEP_TIME);
generatorBatchCount = add("Batch Size: ", BATCH_SIZE);
table = new CTTable(new CTTableCellRenderer());
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
JScrollPane sp = new JScrollPane(table);
frame.getContentPane().add(sp, new GridBagConstraints(0, 3, 6, 1, 1, 1,
GridBagConstraints.WEST, GridBagConstraints.BOTH, EMPTY_INSETS, 0, 0));
ChangeListener changeListener = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
BoundedRangeModel m = (BoundedRangeModel) (e.getSource());
if (updateThread != null) {
updateThread.setUpdatesEnabled(!(m.getValueIsAdjusting()));
}
}
};
sp.getVerticalScrollBar().getModel().addChangeListener(changeListener);
sp.getHorizontalScrollBar().getModel().addChangeListener(changeListener);
totalUpdateTime = new JLabel(" ");
notifyTime = new JLabel(" ");
paintTime = new JLabel(" ");
updateCount = new JLabel(" ");
JPanel statusPanel = new JPanel(new GridBagLayout());
frame.getContentPane().add(statusPanel, new GridBagConstraints(0, 4, 6, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
statusPanel.add(totalUpdateTime, new GridBagConstraints(0, 0, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
statusPanel.add(notifyTime, new GridBagConstraints(1, 0, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
statusPanel.add(paintTime, new GridBagConstraints(2, 0, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
statusPanel.add(updateCount, new GridBagConstraints(3, 0, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
frame.setTitle("Christmas Tree Demo Application");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(0, 0, 1000, 800);
frame.setVisible(true);
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
}
reset();
}
@Override
public void actionPerformed(ActionEvent ae) {
reset();
}
private JTextField add(String name, int defaultValue) {
Container parent = frame.getContentPane();
int row = parent.getComponentCount() / 6;
int col = parent.getComponentCount() % 6;
parent.add(new JLabel(name), new GridBagConstraints(col, row, 1, 1, 0, 0,
GridBagConstraints.WEST, 0, EMPTY_INSETS, 0, 0));
JTextField tf = new JTextField(Integer.toString(defaultValue));
tf.addActionListener(this);
parent.add(tf, new GridBagConstraints(col + 1, row, 1, 1, 1, 0,
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, EMPTY_INSETS, 0, 0));
return tf;
}
private void reset() {
System.out.println("Columns: " + getInt(columnCount));
System.out.println("Rows: " + getInt(rowCount));
System.out.println("Update Sleep: " + getInt(updateSleepTime));
System.out.println("EQ Sleep: " + getInt(eqSleepTime));
System.out.println("Update All Threshold: " + getInt(threshold));
System.out.println("Generator Sleep: " + getInt(generateSleep));
System.out.println("Batch Size: " + getInt(generatorBatchCount));
if (updateThread != null) {
System.out.println("interrupting!");
updateThread.interrupt();
generatorThread.interrupt();
}
int cols = getInt(columnCount);
tableModel = new CTTableModel(cols);
ArrayList<Data> data = new ArrayList<Data>();
for (int counter = getInt(rowCount) - 1; counter >= 0; counter--) {
Data dataID = new Data();
data.add(dataID);
tableModel.addRow(dataID);
for (int colCounter = 0; colCounter < cols; colCounter++) {
if (colCounter % 2 == 0) {
tableModel.set(dataID, colCounter,
new Integer(counter * 100 + colCounter), false);
} else {
tableModel.set(dataID, colCounter,
new Integer(counter * -100 + colCounter), false);
}
}
}
table.setModel(tableModel);
generatorThread = new GeneratorThread(data, getInt(generateSleep),
getInt(generatorBatchCount), getInt(columnCount));
updateThread = new UpdateThread(generatorThread, tableModel,
getInt(updateSleepTime), getInt(eqSleepTime), getInt(threshold));
generatorThread.start();
updateThread.start();
}
private int getInt(JTextField tf) {
try {
return Integer.parseInt(tf.getText());
} catch (NumberFormatException nfe) {
System.out.println("exception getting int: " + nfe);
}
return 0;
}
public static void main(String[] args) {
Main main = new Main();
}
}
... will be continue
SwingWorker
, for example. – Quininejavax.swing.Timer
is better for polling. – Quinine