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 3525 2007-10-16 11:43:48Z tmorgner $ 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 private 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 private 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 } 155 156 public synchronized int getTableModelCount () 157 { 158 return models.size(); 159 } 160 161 public synchronized TableModel getTableModel (final int pos) 162 { 163 final TablePosition position = (TablePosition) models.get(pos); 164 return position.getTableModel(); 165 } 166 167 protected synchronized void updateStructure() 168 { 169 final ArrayList columnNames = new ArrayList(); 170 final ArrayList columnTypes = new ArrayList(); 171 172 columnNames.add(TABLE_PREFIX_COLUMN); 173 columnTypes.add(String.class); 174 175 int columnOffset = 1; 176 int rowOffset = 0; 177 for (int i = 0; i < models.size(); i++) 178 { 179 final TablePosition pos = (TablePosition) models.get(i); 180 pos.updateOffsets(rowOffset, columnOffset); 181 final TableModel tableModel = pos.getTableModel(); 182 rowOffset += tableModel.getRowCount(); 183 columnOffset += tableModel.getColumnCount(); 184 for (int c = 0; c < tableModel.getColumnCount(); c++) 185 { 186 columnNames.add(pos.getPrefix() + "." + tableModel.getColumnName(c)); 187 columnTypes.add(tableModel.getColumnClass(c)); 188 } 189 } 190 this.columnNames = (String[]) columnNames.toArray(new String[columnNames.size()]); 191 this.columnTypes = (Class[]) columnTypes.toArray(new Class[columnTypes.size()]); 192 this.rowCount = rowOffset; 193 fireTableStructureChanged(); 194 } 195 196 protected synchronized void updateRowCount() 197 { 198 int rowOffset = 0; 199 int columnOffset = 1; 200 for (int i = 0; i < models.size(); i++) 201 { 202 final TablePosition model = (TablePosition) models.get(i); 203 model.updateOffsets(rowOffset, columnOffset); 204 rowOffset += model.getTableModel().getRowCount(); 205 columnOffset += model.getTableModel().getColumnCount(); 206 } 207 fireTableStructureChanged(); 208 } 209 210 protected void updateData() 211 { 212 // this is lazy, but we do not optimize for edit-speed here ... 213 fireTableDataChanged(); 214 } 215 216 /** 217 * Returns <code>Object.class</code> regardless of <code>columnIndex</code>. 218 * 219 * @param columnIndex the column being queried 220 * @return the Object.class 221 */ 222 public synchronized Class getColumnClass (final int columnIndex) 223 { 224 return columnTypes[columnIndex]; 225 } 226 227 /** 228 * Returns a default name for the column using spreadsheet conventions: A, B, C, ... Z, 229 * AA, AB, etc. If <code>column</code> cannot be found, returns an empty string. 230 * 231 * @param column the column being queried 232 * @return a string containing the default name of <code>column</code> 233 */ 234 public synchronized String getColumnName (final int column) 235 { 236 return columnNames[column]; 237 } 238 239 /** 240 * Returns false. JFreeReport does not like changing cells. 241 * 242 * @param rowIndex the row being queried 243 * @param columnIndex the column being queried 244 * @return false 245 */ 246 public final boolean isCellEditable (final int rowIndex, final int columnIndex) 247 { 248 return false; 249 } 250 251 /** 252 * Returns the number of columns managed by the data source object. A <B>JTable</B> uses 253 * this method to determine how many columns it should create and display on 254 * initialization. 255 * 256 * @return the number or columns in the model 257 * 258 * @see #getRowCount 259 */ 260 public synchronized int getColumnCount () 261 { 262 return columnNames.length; 263 } 264 265 /** 266 * Returns the number of records managed by the data source object. A <B>JTable</B> uses 267 * this method to determine how many rows it should create and display. This method 268 * should be quick, as it is call by <B>JTable</B> quite frequently. 269 * 270 * @return the number or rows in the model 271 * 272 * @see #getColumnCount 273 */ 274 public synchronized int getRowCount () 275 { 276 return rowCount; 277 } 278 279 /** 280 * Returns an attribute value for the cell at <I>columnIndex</I> and <I>rowIndex</I>. 281 * 282 * @param rowIndex the row whose value is to be looked up 283 * @param columnIndex the column whose value is to be looked up 284 * @return the value Object at the specified cell 285 */ 286 public synchronized Object getValueAt (final int rowIndex, final int columnIndex) 287 { 288 // first: find the correct table model... 289 final TablePosition pos = getTableModelForRow(rowIndex); 290 if (pos == null) 291 { 292 return null; 293 } 294 295 if (columnIndex == 0) 296 { 297 return pos.getPrefix(); 298 } 299 300 final int columnOffset = pos.getColumnOffset(); 301 if (columnIndex < columnOffset) 302 { 303 return null; 304 } 305 306 final TableModel tableModel = pos.getTableModel(); 307 if (columnIndex >= (columnOffset + tableModel.getColumnCount())) 308 { 309 return null; 310 } 311 return tableModel.getValueAt 312 (rowIndex - pos.getTableOffset(), columnIndex - columnOffset); 313 } 314 315 private TablePosition getTableModelForRow (final int row) 316 { 317 // assume, that the models are in ascending order .. 318 for (int i = 0; i < models.size(); i++) 319 { 320 final TablePosition pos = (TablePosition) models.get(i); 321 final int maxRow = pos.getTableOffset() + pos.getTableModel().getRowCount(); 322 if (row < maxRow) 323 { 324 return pos; 325 } 326 } 327 return null; 328 } 329 }