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    }