/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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 Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.modules.misc.tablemodel;
import java.util.ArrayList;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
public class JoiningTableModel extends AbstractTableModel
{
private static class TablePosition
{
private TableModel tableModel;
private String prefix;
private int tableOffset;
private int columnOffset;
private TablePosition(final TableModel tableModel,
final String prefix)
{
if (tableModel == null)
{
throw new NullPointerException("Model must not be null"); //$NON-NLS-1$
}
if (prefix == null)
{
throw new NullPointerException("Prefix must not be null."); //$NON-NLS-1$
}
this.tableModel = tableModel;
this.prefix = prefix;
}
public void updateOffsets(final int tableOffset, final int columnOffset)
{
this.tableOffset = tableOffset;
this.columnOffset = columnOffset;
}
public String getPrefix()
{
return prefix;
}
public int getColumnOffset()
{
return columnOffset;
}
public TableModel getTableModel()
{
return tableModel;
}
public int getTableOffset()
{
return tableOffset;
}
}
private class TableChangeHandler implements TableModelListener
{
private TableChangeHandler()
{
}
/**
* This fine grain notification tells listeners the exact range of cells, rows, or columns that changed.
*/
public void tableChanged(final TableModelEvent e)
{
if (e.getType() == TableModelEvent.HEADER_ROW)
{
updateStructure();
}
else if (e.getType() == TableModelEvent.INSERT ||
e.getType() == TableModelEvent.DELETE)
{
updateRowCount();
}
else
{
updateData();
}
}
}
// the column names of all tables ..
private String[] columnNames;
// all column types of all tables ..
private Class[] columnTypes;
private ArrayList models;
private TableChangeHandler changeHandler;
private int rowCount;
public static final String TABLE_PREFIX_COLUMN = "TablePrefix"; //$NON-NLS-1$
public JoiningTableModel()
{
models = new ArrayList();
changeHandler = new TableChangeHandler();
}
public synchronized void addTableModel(final String prefix, final TableModel model)
{
models.add(new TablePosition(model, prefix));
model.addTableModelListener(changeHandler);
updateStructure();
}
public synchronized void removeTableModel(final TableModel model)
{
for (int i = 0; i < models.size(); i++)
{
final TablePosition position = (TablePosition) models.get(i);
if (position.getTableModel() == model)
{
models.remove(model);
model.removeTableModelListener(changeHandler);
updateStructure();
return;
}
}
}
public synchronized int getTableModelCount()
{
return models.size();
}
public synchronized TableModel getTableModel(final int pos)
{
final TablePosition position = (TablePosition) models.get(pos);
return position.getTableModel();
}
protected synchronized void updateStructure()
{
final ArrayList columnNames = new ArrayList();
final ArrayList columnTypes = new ArrayList();
columnNames.add(JoiningTableModel.TABLE_PREFIX_COLUMN);
columnTypes.add(String.class);
int columnOffset = 1;
int rowOffset = 0;
for (int i = 0; i < models.size(); i++)
{
final TablePosition pos = (TablePosition) models.get(i);
pos.updateOffsets(rowOffset, columnOffset);
final TableModel tableModel = pos.getTableModel();
rowOffset += tableModel.getRowCount();
columnOffset += tableModel.getColumnCount();
for (int c = 0; c < tableModel.getColumnCount(); c++)
{
columnNames.add(pos.getPrefix() + '.' + tableModel.getColumnName(c)); //$NON-NLS-1$
columnTypes.add(tableModel.getColumnClass(c));
}
}
this.columnNames = (String[]) columnNames.toArray(new String[columnNames.size()]);
this.columnTypes = (Class[]) columnTypes.toArray(new Class[columnTypes.size()]);
this.rowCount = rowOffset;
fireTableStructureChanged();
}
protected synchronized void updateRowCount()
{
int rowOffset = 0;
int columnOffset = 1;
for (int i = 0; i < models.size(); i++)
{
final TablePosition model = (TablePosition) models.get(i);
model.updateOffsets(rowOffset, columnOffset);
rowOffset += model.getTableModel().getRowCount();
columnOffset += model.getTableModel().getColumnCount();
}
fireTableStructureChanged();
}
protected void updateData()
{
// this is lazy, but we do not optimize for edit-speed here ...
fireTableDataChanged();
}
/**
* Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
*
* @param columnIndex the column being queried
* @return the Object.class
*/
public synchronized Class getColumnClass(final int columnIndex)
{
return columnTypes[columnIndex];
}
/**
* Returns a default name for the column using spreadsheet conventions: A, B, C, ... Z, AA, AB, etc. If
* <code>column</code> cannot be found, returns an empty string.
*
* @param column the column being queried
* @return a string containing the default name of <code>column</code>
*/
public synchronized String getColumnName(final int column)
{
return columnNames[column];
}
/**
* Returns false. JFreeReport does not like changing cells.
*
* @param rowIndex the row being queried
* @param columnIndex the column being queried
* @return false
*/
public final boolean isCellEditable(final int rowIndex, final int columnIndex)
{
return false;
}
/**
* Returns the number of columns managed by the data source object. A <B>JTable</B> uses this method to determine how
* many columns it should create and display on initialization.
*
* @return the number or columns in the model
* @see #getRowCount
*/
public synchronized int getColumnCount()
{
return columnNames.length;
}
/**
* Returns the number of records managed by the data source object. A <B>JTable</B> uses this method to determine how
* many rows it should create and display. This method should be quick, as it is call by <B>JTable</B> quite
* frequently.
*
* @return the number or rows in the model
* @see #getColumnCount
*/
public synchronized int getRowCount()
{
return rowCount;
}
/**
* Returns an attribute value for the cell at <I>columnIndex</I> and <I>rowIndex</I>.
*
* @param rowIndex the row whose value is to be looked up
* @param columnIndex the column whose value is to be looked up
* @return the value Object at the specified cell
*/
public synchronized Object getValueAt(final int rowIndex, final int columnIndex)
{
// first: find the correct table model...
final TablePosition pos = getTableModelForRow(rowIndex);
if (pos == null)
{
return null;
}
if (columnIndex == 0)
{
return pos.getPrefix();
}
final int columnOffset = pos.getColumnOffset();
if (columnIndex < columnOffset)
{
return null;
}
final TableModel tableModel = pos.getTableModel();
if (columnIndex >= (columnOffset + tableModel.getColumnCount()))
{
return null;
}
return tableModel.getValueAt
(rowIndex - pos.getTableOffset(), columnIndex - columnOffset);
}
private TablePosition getTableModelForRow(final int row)
{
// assume, that the models are in ascending order ..
for (int i = 0; i < models.size(); i++)
{
final TablePosition pos = (TablePosition) models.get(i);
final int maxRow = pos.getTableOffset() + pos.getTableModel().getRowCount();
if (row < maxRow)
{
return pos;
}
}
return null;
}
}