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    }