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 }