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: */package org.jfree.report.modules.misc.tablemodel;
031:
032: import java.util.ArrayList;
033: import javax.swing.event.TableModelEvent;
034: import javax.swing.event.TableModelListener;
035: import javax.swing.table.AbstractTableModel;
036: import javax.swing.table.TableModel;
037:
038: public class JoiningTableModel extends AbstractTableModel {
039: private static class TablePosition {
040: private TableModel tableModel;
041: private String prefix;
042: private int tableOffset;
043: private int columnOffset;
044:
045: private TablePosition(final TableModel tableModel,
046: final String prefix) {
047: if (tableModel == null) {
048: throw new NullPointerException("Model must not be null");
049: }
050: if (prefix == null) {
051: throw new NullPointerException(
052: "Prefix must not be null.");
053: }
054: this .tableModel = tableModel;
055: this .prefix = prefix;
056: }
057:
058: public void updateOffsets(final int tableOffset,
059: final int columnOffset) {
060: this .tableOffset = tableOffset;
061: this .columnOffset = columnOffset;
062: }
063:
064: public String getPrefix() {
065: return prefix;
066: }
067:
068: public int getColumnOffset() {
069: return columnOffset;
070: }
071:
072: public TableModel getTableModel() {
073: return tableModel;
074: }
075:
076: public int getTableOffset() {
077: return tableOffset;
078: }
079: }
080:
081: private class TableChangeHandler implements TableModelListener {
082: private TableChangeHandler() {
083: }
084:
085: /**
086: * This fine grain notification tells listeners the exact range of cells, rows, or
087: * columns that changed.
088: */
089: public void tableChanged(final TableModelEvent e) {
090: if (e.getType() == TableModelEvent.HEADER_ROW) {
091: updateStructure();
092: } else if (e.getType() == TableModelEvent.INSERT
093: || e.getType() == TableModelEvent.DELETE) {
094: updateRowCount();
095: } else {
096: updateData();
097: }
098: }
099: }
100:
101: // the column names of all tables ..
102: private String[] columnNames;
103: // all column types of all tables ..
104: private Class[] columnTypes;
105:
106: private ArrayList models;
107: private TableChangeHandler changeHandler;
108: private int rowCount;
109: public static final String TABLE_PREFIX_COLUMN = "TablePrefix";
110:
111: public JoiningTableModel() {
112: models = new ArrayList();
113: changeHandler = new TableChangeHandler();
114: }
115:
116: public synchronized void addTableModel(final String prefix,
117: final TableModel model) {
118: models.add(new TablePosition(model, prefix));
119: model.addTableModelListener(changeHandler);
120: updateStructure();
121: }
122:
123: public synchronized void removeTableModel(final TableModel model) {
124: for (int i = 0; i < models.size(); i++) {
125: final TablePosition position = (TablePosition) models
126: .get(i);
127: if (position.getTableModel() == model) {
128: models.remove(model);
129: model.removeTableModelListener(changeHandler);
130: updateStructure();
131: return;
132: }
133: }
134: }
135:
136: public synchronized int getTableModelCount() {
137: return models.size();
138: }
139:
140: public synchronized TableModel getTableModel(final int pos) {
141: final TablePosition position = (TablePosition) models.get(pos);
142: return position.getTableModel();
143: }
144:
145: protected synchronized void updateStructure() {
146: final ArrayList columnNames = new ArrayList();
147: final ArrayList columnTypes = new ArrayList();
148:
149: columnNames.add(TABLE_PREFIX_COLUMN);
150: columnTypes.add(String.class);
151:
152: int columnOffset = 1;
153: int rowOffset = 0;
154: for (int i = 0; i < models.size(); i++) {
155: final TablePosition pos = (TablePosition) models.get(i);
156: pos.updateOffsets(rowOffset, columnOffset);
157: final TableModel tableModel = pos.getTableModel();
158: rowOffset += tableModel.getRowCount();
159: columnOffset += tableModel.getColumnCount();
160: for (int c = 0; c < tableModel.getColumnCount(); c++) {
161: columnNames.add(pos.getPrefix() + "."
162: + tableModel.getColumnName(c));
163: columnTypes.add(tableModel.getColumnClass(c));
164: }
165: }
166: this .columnNames = (String[]) columnNames
167: .toArray(new String[columnNames.size()]);
168: this .columnTypes = (Class[]) columnTypes
169: .toArray(new Class[columnTypes.size()]);
170: this .rowCount = rowOffset;
171: fireTableStructureChanged();
172: }
173:
174: protected synchronized void updateRowCount() {
175: int rowOffset = 0;
176: int columnOffset = 1;
177: for (int i = 0; i < models.size(); i++) {
178: final TablePosition model = (TablePosition) models.get(i);
179: model.updateOffsets(rowOffset, columnOffset);
180: rowOffset += model.getTableModel().getRowCount();
181: columnOffset += model.getTableModel().getColumnCount();
182: }
183: fireTableStructureChanged();
184: }
185:
186: protected void updateData() {
187: // this is lazy, but we do not optimize for edit-speed here ...
188: fireTableDataChanged();
189: }
190:
191: /**
192: * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
193: *
194: * @param columnIndex the column being queried
195: * @return the Object.class
196: */
197: public synchronized Class getColumnClass(final int columnIndex) {
198: return columnTypes[columnIndex];
199: }
200:
201: /**
202: * Returns a default name for the column using spreadsheet conventions: A, B, C, ... Z,
203: * AA, AB, etc. If <code>column</code> cannot be found, returns an empty string.
204: *
205: * @param column the column being queried
206: * @return a string containing the default name of <code>column</code>
207: */
208: public synchronized String getColumnName(final int column) {
209: return columnNames[column];
210: }
211:
212: /**
213: * Returns false. JFreeReport does not like changing cells.
214: *
215: * @param rowIndex the row being queried
216: * @param columnIndex the column being queried
217: * @return false
218: */
219: public final boolean isCellEditable(final int rowIndex,
220: final int columnIndex) {
221: return false;
222: }
223:
224: /**
225: * Returns the number of columns managed by the data source object. A <B>JTable</B> uses
226: * this method to determine how many columns it should create and display on
227: * initialization.
228: *
229: * @return the number or columns in the model
230: *
231: * @see #getRowCount
232: */
233: public synchronized int getColumnCount() {
234: return columnNames.length;
235: }
236:
237: /**
238: * Returns the number of records managed by the data source object. A <B>JTable</B> uses
239: * this method to determine how many rows it should create and display. This method
240: * should be quick, as it is call by <B>JTable</B> quite frequently.
241: *
242: * @return the number or rows in the model
243: *
244: * @see #getColumnCount
245: */
246: public synchronized int getRowCount() {
247: return rowCount;
248: }
249:
250: /**
251: * Returns an attribute value for the cell at <I>columnIndex</I> and <I>rowIndex</I>.
252: *
253: * @param rowIndex the row whose value is to be looked up
254: * @param columnIndex the column whose value is to be looked up
255: * @return the value Object at the specified cell
256: */
257: public synchronized Object getValueAt(final int rowIndex,
258: final int columnIndex) {
259: // first: find the correct table model...
260: final TablePosition pos = getTableModelForRow(rowIndex);
261: if (pos == null) {
262: return null;
263: }
264:
265: if (columnIndex == 0) {
266: return pos.getPrefix();
267: }
268:
269: final int columnOffset = pos.getColumnOffset();
270: if (columnIndex < columnOffset) {
271: return null;
272: }
273:
274: final TableModel tableModel = pos.getTableModel();
275: if (columnIndex >= (columnOffset + tableModel.getColumnCount())) {
276: return null;
277: }
278: return tableModel.getValueAt(rowIndex - pos.getTableOffset(),
279: columnIndex - columnOffset);
280: }
281:
282: private TablePosition getTableModelForRow(final int row) {
283: // assume, that the models are in ascending order ..
284: for (int i = 0; i < models.size(); i++) {
285: final TablePosition pos = (TablePosition) models.get(i);
286: final int maxRow = pos.getTableOffset()
287: + pos.getTableModel().getRowCount();
288: if (row < maxRow) {
289: return pos;
290: }
291: }
292: return null;
293: }
294: }
|