/*
* 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 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.modules.misc.tablemodel;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import java.util.ArrayList;
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.UPDATE && e.getFirstRow() == 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<TablePosition> models;
private TableChangeHandler changeHandler;
private int rowCount;
public static final String TABLE_PREFIX_COLUMN = "TablePrefix"; //$NON-NLS-1$
public JoiningTableModel() {
models = new ArrayList<TablePosition>();
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 = models.get( i );
if ( position.getTableModel() == model ) {
models.remove( position );
model.removeTableModelListener( changeHandler );
updateStructure();
return;
}
}
}
public synchronized int getTableModelCount() {
return models.size();
}
public synchronized TableModel getTableModel( final int pos ) {
final TablePosition position = models.get( pos );
return position.getTableModel();
}
protected synchronized void updateStructure() {
final ArrayList<String> columnNames = new ArrayList<String>();
final ArrayList<Class<?>> columnTypes = new ArrayList<Class<?>>();
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 = 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 = columnNames.toArray( new String[columnNames.size()] );
this.columnTypes = 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 = 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 = models.get( i );
final int maxRow = pos.getTableOffset() + pos.getTableModel().getRowCount();
if ( row < maxRow ) {
return pos;
}
}
return null;
}
}