001 /** 002 * ======================================== 003 * JFreeReport : a free Java report library 004 * ======================================== 005 * 006 * Project Info: http://reporting.pentaho.org/ 007 * 008 * (C) Copyright 2000-2007, by Object Refinery Limited, Pentaho Corporation and Contributors. 009 * 010 * This library is free software; you can redistribute it and/or modify it under the terms 011 * of the GNU Lesser General Public License as published by the Free Software Foundation; 012 * either version 2.1 of the License, or (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 015 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 016 * See the GNU Lesser General Public License for more details. 017 * 018 * You should have received a copy of the GNU Lesser General Public License along with this 019 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, 020 * Boston, MA 02111-1307, USA. 021 * 022 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 023 * in the United States and other countries.] 024 * 025 * ------------ 026 * $Id: JoiningTableModel.java,v 1.5 2007/04/01 18:49:32 taqua Exp $ 027 * ------------ 028 * (C) Copyright 2000-2005, by Object Refinery Limited. 029 * (C) Copyright 2005-2007, by Pentaho Corporation. 030 */ 031 package org.jfree.report.modules.misc.tablemodel; 032 033 import java.util.ArrayList; 034 import javax.swing.event.TableModelEvent; 035 import javax.swing.event.TableModelListener; 036 import javax.swing.table.AbstractTableModel; 037 import javax.swing.table.TableModel; 038 039 public class JoiningTableModel extends AbstractTableModel 040 { 041 private static class TablePosition 042 { 043 private TableModel tableModel; 044 private String prefix; 045 private int tableOffset; 046 private int columnOffset; 047 048 public TablePosition (final TableModel tableModel, 049 final String prefix) 050 { 051 if (tableModel == null) 052 { 053 throw new NullPointerException("Model must not be null"); 054 } 055 if (prefix == null) 056 { 057 throw new NullPointerException("Prefix must not be null."); 058 } 059 this.tableModel = tableModel; 060 this.prefix = prefix; 061 } 062 063 public void updateOffsets (final int tableOffset, final int columnOffset) 064 { 065 this.tableOffset = tableOffset; 066 this.columnOffset = columnOffset; 067 } 068 069 public String getPrefix () 070 { 071 return prefix; 072 } 073 074 public int getColumnOffset () 075 { 076 return columnOffset; 077 } 078 079 public TableModel getTableModel () 080 { 081 return tableModel; 082 } 083 084 public int getTableOffset () 085 { 086 return tableOffset; 087 } 088 } 089 090 private class TableChangeHandler implements TableModelListener 091 { 092 public TableChangeHandler () 093 { 094 } 095 096 /** 097 * This fine grain notification tells listeners the exact range of cells, rows, or 098 * columns that changed. 099 */ 100 public void tableChanged (final TableModelEvent e) 101 { 102 if (e.getType() == TableModelEvent.HEADER_ROW) 103 { 104 updateStructure(); 105 } 106 else if (e.getType() == TableModelEvent.INSERT || 107 e.getType() == TableModelEvent.DELETE) 108 { 109 updateRowCount(); 110 } 111 else 112 { 113 updateData(); 114 } 115 } 116 } 117 118 // the column names of all tables .. 119 private String[] columnNames; 120 // all column types of all tables .. 121 private Class[] columnTypes; 122 123 private ArrayList models; 124 private TableChangeHandler changeHandler; 125 private int rowCount; 126 public static final String TABLE_PREFIX_COLUMN = "TablePrefix"; 127 128 public JoiningTableModel () 129 { 130 models = new ArrayList(); 131 changeHandler = new TableChangeHandler(); 132 } 133 134 public synchronized void addTableModel (final String prefix, final TableModel model) 135 { 136 models.add(new TablePosition(model, prefix)); 137 model.addTableModelListener(changeHandler); 138 updateStructure(); 139 } 140 141 public synchronized void removeTableModel (final TableModel model) 142 { 143 for (int i = 0; i < models.size(); i++) 144 { 145 final TablePosition position = (TablePosition) models.get(i); 146 if (position.getTableModel() == model) 147 { 148 models.remove(model); 149 model.removeTableModelListener(changeHandler); 150 updateStructure(); 151 return; 152 } 153 } 154 return; 155 } 156 157 public synchronized int getTableModelCount () 158 { 159 return models.size(); 160 } 161 162 public synchronized TableModel getTableModel (final int pos) 163 { 164 final TablePosition position = (TablePosition) models.get(pos); 165 return position.getTableModel(); 166 } 167 168 protected synchronized void updateStructure() 169 { 170 final ArrayList columnNames = new ArrayList(); 171 final ArrayList columnTypes = new ArrayList(); 172 int rowOffset = 0; 173 int columnOffset = 1; 174 175 columnNames.add(TABLE_PREFIX_COLUMN); 176 columnTypes.add(String.class); 177 178 for (int i = 0; i < models.size(); i++) 179 { 180 final TablePosition pos = (TablePosition) models.get(i); 181 pos.updateOffsets(rowOffset, columnOffset); 182 final TableModel tableModel = pos.getTableModel(); 183 rowOffset += tableModel.getRowCount(); 184 columnOffset += tableModel.getColumnCount(); 185 for (int c = 0; c < tableModel.getColumnCount(); c++) 186 { 187 columnNames.add(pos.getPrefix() + "." + tableModel.getColumnName(c)); 188 columnTypes.add(tableModel.getColumnClass(c)); 189 } 190 } 191 this.columnNames = (String[]) columnNames.toArray(new String[columnNames.size()]); 192 this.columnTypes = (Class[]) columnTypes.toArray(new Class[columnTypes.size()]); 193 this.rowCount = rowOffset; 194 fireTableStructureChanged(); 195 } 196 197 protected synchronized void updateRowCount() 198 { 199 int rowOffset = 0; 200 int columnOffset = 1; 201 for (int i = 0; i < models.size(); i++) 202 { 203 final TablePosition model = (TablePosition) models.get(i); 204 model.updateOffsets(rowOffset, columnOffset); 205 rowOffset += model.getTableModel().getRowCount(); 206 columnOffset += model.getTableModel().getColumnCount(); 207 } 208 fireTableStructureChanged(); 209 } 210 211 protected void updateData() 212 { 213 // this is lazy, but we do not optimize for edit-speed here ... 214 fireTableDataChanged(); 215 } 216 217 /** 218 * Returns <code>Object.class</code> regardless of <code>columnIndex</code>. 219 * 220 * @param columnIndex the column being queried 221 * @return the Object.class 222 */ 223 public synchronized Class getColumnClass (final int columnIndex) 224 { 225 return columnTypes[columnIndex]; 226 } 227 228 /** 229 * Returns a default name for the column using spreadsheet conventions: A, B, C, ... Z, 230 * AA, AB, etc. If <code>column</code> cannot be found, returns an empty string. 231 * 232 * @param column the column being queried 233 * @return a string containing the default name of <code>column</code> 234 */ 235 public synchronized String getColumnName (final int column) 236 { 237 return columnNames[column]; 238 } 239 240 /** 241 * Returns false. JFreeReport does not like changing cells. 242 * 243 * @param rowIndex the row being queried 244 * @param columnIndex the column being queried 245 * @return false 246 */ 247 public final boolean isCellEditable (final int rowIndex, final int columnIndex) 248 { 249 return false; 250 } 251 252 /** 253 * Returns the number of columns managed by the data source object. A <B>JTable</B> uses 254 * this method to determine how many columns it should create and display on 255 * initialization. 256 * 257 * @return the number or columns in the model 258 * 259 * @see #getRowCount 260 */ 261 public synchronized int getColumnCount () 262 { 263 return columnNames.length; 264 } 265 266 /** 267 * Returns the number of records managed by the data source object. A <B>JTable</B> uses 268 * this method to determine how many rows it should create and display. This method 269 * should be quick, as it is call by <B>JTable</B> quite frequently. 270 * 271 * @return the number or rows in the model 272 * 273 * @see #getColumnCount 274 */ 275 public synchronized int getRowCount () 276 { 277 return rowCount; 278 } 279 280 /** 281 * Returns an attribute value for the cell at <I>columnIndex</I> and <I>rowIndex</I>. 282 * 283 * @param rowIndex the row whose value is to be looked up 284 * @param columnIndex the column whose value is to be looked up 285 * @return the value Object at the specified cell 286 */ 287 public synchronized Object getValueAt (final int rowIndex, final int columnIndex) 288 { 289 // first: find the correct table model... 290 final TablePosition pos = getTableModelForRow(rowIndex); 291 if (pos == null) 292 { 293 return null; 294 } 295 296 if (columnIndex == 0) 297 { 298 return pos.getPrefix(); 299 } 300 301 final int columnOffset = pos.getColumnOffset(); 302 if (columnIndex < columnOffset) 303 { 304 return null; 305 } 306 307 final TableModel tableModel = pos.getTableModel(); 308 if (columnIndex >= (columnOffset + tableModel.getColumnCount())) 309 { 310 return null; 311 } 312 return tableModel.getValueAt 313 (rowIndex - pos.getTableOffset(), columnIndex - columnOffset); 314 } 315 316 private TablePosition getTableModelForRow (final int row) 317 { 318 // assume, that the models are in ascending order .. 319 for (int i = 0; i < models.size(); i++) 320 { 321 final TablePosition pos = (TablePosition) models.get(i); 322 final int maxRow = pos.getTableOffset() + pos.getTableModel().getRowCount(); 323 if (row < maxRow) 324 { 325 return pos; 326 } 327 } 328 return null; 329 } 330 }