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 }