001/* ========================================================================
002 * JCommon : a free general purpose class library for the Java(tm) platform
003 * ========================================================================
004 *
005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jcommon/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ----------------
028 * ObjectTable.java
029 * ----------------
030 * (C) Copyright 2003, 2004, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * $Id: ObjectTable.java,v 1.10 2008/09/10 09:22:04 mungady Exp $
036 *
037 * Changes
038 * -------
039 * 29-Apr-2003 : Version 1, based on PaintTable class (DG);
040 * 21-May-2003 : Copied the array based implementation of StrokeTable and
041 *               fixed the serialisation behaviour (TM).
042 */
043
044package org.jfree.util;
045
046import java.io.IOException;
047import java.io.ObjectInputStream;
048import java.io.ObjectOutputStream;
049import java.io.Serializable;
050import java.util.Arrays;
051
052/**
053 * A lookup table for objects. This implementation is not synchronized, it is up
054 * to the caller to synchronize it properly.
055 *
056 * @author Thomas Morgner
057 */
058public class ObjectTable implements Serializable
059{
060
061  /**
062   * For serialization.
063   */
064  private static final long serialVersionUID = -3968322452944912066L;
065
066  /**
067   * The number of rows.
068   */
069  private int rows;
070
071  /**
072   * The number of columns.
073   */
074  private int columns;
075
076  /**
077   * An array of objects.  The array may contain <code>null</code> values.
078   */
079  private transient Object[][] data;
080
081  /**
082   * Defines how many object-slots get reserved each time we run out of
083   * space.
084   */
085  private int rowIncrement;
086
087  /**
088   * Defines how many object-slots get reserved each time we run out of
089   * space.
090   */
091  private int columnIncrement;
092
093  /**
094   * Creates a new table.
095   */
096  public ObjectTable()
097  {
098    this(5, 5);
099  }
100
101  /**
102   * Creates a new table.
103   *
104   * @param increment the row and column size increment.
105   */
106  public ObjectTable(final int increment)
107  {
108    this(increment, increment);
109  }
110
111  /**
112   * Creates a new table.
113   *
114   * @param rowIncrement the row size increment.
115   * @param colIncrement the column size increment.
116   */
117  public ObjectTable(final int rowIncrement, final int colIncrement)
118  {
119    if (rowIncrement < 1)
120    {
121      throw new IllegalArgumentException("Increment must be positive.");
122    }
123
124    if (colIncrement < 1)
125    {
126      throw new IllegalArgumentException("Increment must be positive.");
127    }
128
129    this.rows = 0;
130    this.columns = 0;
131    this.rowIncrement = rowIncrement;
132    this.columnIncrement = colIncrement;
133
134    this.data = new Object[rowIncrement][];
135  }
136
137  /**
138   * Returns the column size increment.
139   *
140   * @return the increment.
141   */
142  public int getColumnIncrement()
143  {
144    return this.columnIncrement;
145  }
146
147  /**
148   * Returns the row size increment.
149   *
150   * @return the increment.
151   */
152  public int getRowIncrement()
153  {
154    return this.rowIncrement;
155  }
156
157  /**
158   * Checks that there is storage capacity for the specified row and resizes
159   * if necessary.
160   *
161   * @param row the row index.
162   */
163  protected void ensureRowCapacity(final int row)
164  {
165
166    // does this increase the number of rows?  if yes, create new storage
167    if (row >= this.data.length)
168    {
169
170      final Object[][] enlarged = new Object[row + this.rowIncrement][];
171      System.arraycopy(this.data, 0, enlarged, 0, this.data.length);
172      // do not create empty arrays - this is more expensive than checking
173      // for null-values.
174      this.data = enlarged;
175    }
176  }
177
178  /**
179   * Ensures that there is storage capacity for the specified item.
180   *
181   * @param row    the row index.
182   * @param column the column index.
183   */
184  public void ensureCapacity(final int row, final int column)
185  {
186
187    if (row < 0)
188    {
189      throw new IndexOutOfBoundsException("Row is invalid. " + row);
190    }
191    if (column < 0)
192    {
193      throw new IndexOutOfBoundsException("Column is invalid. " + column);
194    }
195
196    ensureRowCapacity(row);
197
198    final Object[] current = this.data[row];
199    if (current == null)
200    {
201      final Object[] enlarged
202          = new Object[Math.max(column + 1, this.columnIncrement)];
203      this.data[row] = enlarged;
204    }
205    else if (column >= current.length)
206    {
207      final Object[] enlarged = new Object[column + this.columnIncrement];
208      System.arraycopy(current, 0, enlarged, 0, current.length);
209      this.data[row] = enlarged;
210    }
211  }
212
213  /**
214   * Returns the number of rows in the table.
215   *
216   * @return The row count.
217   */
218  public int getRowCount()
219  {
220    return this.rows;
221  }
222
223  /**
224   * Returns the number of columns in the table.
225   *
226   * @return The column count.
227   */
228  public int getColumnCount()
229  {
230    return this.columns;
231  }
232
233  /**
234   * Returns the object from a particular cell in the table. Returns null, if
235   * there is no object at the given position.
236   * <p/>
237   * Note: throws IndexOutOfBoundsException if row or column is negative.
238   *
239   * @param row    the row index (zero-based).
240   * @param column the column index (zero-based).
241   * @return The object.
242   */
243  protected Object getObject(final int row, final int column)
244  {
245
246    if (row < this.data.length)
247    {
248      final Object[] current = this.data[row];
249      if (current == null)
250      {
251        return null;
252      }
253      if (column < current.length)
254      {
255        return current[column];
256      }
257    }
258    return null;
259
260  }
261
262  /**
263   * Sets the object for a cell in the table.  The table is expanded if
264   * necessary.
265   *
266   * @param row    the row index (zero-based).
267   * @param column the column index (zero-based).
268   * @param object the object.
269   */
270  protected void setObject(final int row, final int column,
271                           final Object object)
272  {
273
274    ensureCapacity(row, column);
275
276    this.data[row][column] = object;
277    this.rows = Math.max(this.rows, row + 1);
278    this.columns = Math.max(this.columns, column + 1);
279  }
280
281  /**
282   * Tests this paint table for equality with another object (typically also
283   * an <code>ObjectTable</code>).
284   *
285   * @param o the other object.
286   * @return A boolean.
287   */
288  public boolean equals(final Object o)
289  {
290
291    if (o == null)
292    {
293      return false;
294    }
295
296    if (this == o)
297    {
298      return true;
299    }
300
301    if ((o instanceof ObjectTable) == false)
302    {
303      return false;
304    }
305
306    final ObjectTable ot = (ObjectTable) o;
307    if (getRowCount() != ot.getRowCount())
308    {
309      return false;
310    }
311
312    if (getColumnCount() != ot.getColumnCount())
313    {
314      return false;
315    }
316
317    for (int r = 0; r < getRowCount(); r++)
318    {
319      for (int c = 0; c < getColumnCount(); c++)
320      {
321        if (ObjectUtilities.equal(getObject(r, c),
322            ot.getObject(r, c)) == false)
323        {
324          return false;
325        }
326      }
327    }
328    return true;
329  }
330
331  /**
332   * Returns a hash code value for the object.
333   *
334   * @return the hashcode
335   */
336  public int hashCode()
337  {
338    int result;
339    result = this.rows;
340    result = 29 * result + this.columns;
341    return result;
342  }
343
344  /**
345   * Handles serialization.
346   *
347   * @param stream the output stream.
348   * @throws java.io.IOException if there is an I/O problem.
349   */
350  private void writeObject(final ObjectOutputStream stream)
351      throws IOException
352  {
353    stream.defaultWriteObject();
354    final int rowCount = this.data.length;
355    stream.writeInt(rowCount);
356    for (int r = 0; r < rowCount; r++)
357    {
358      final Object[] column = this.data[r];
359      stream.writeBoolean(column != null);
360      if (column != null)
361      {
362        final int columnCount = column.length;
363        stream.writeInt(columnCount);
364        for (int c = 0; c < columnCount; c++)
365        {
366          writeSerializedData(stream, column[c]);
367        }
368      }
369    }
370  }
371
372  /**
373   * Handles the serialization of an single element of this table.
374   *
375   * @param stream the stream which should write the object
376   * @param o      the object that should be serialized
377   * @throws IOException if an IO error occured
378   */
379  protected void writeSerializedData(final ObjectOutputStream stream,
380                                     final Object o)
381      throws IOException
382  {
383    stream.writeObject(o);
384  }
385
386  /**
387   * Restores a serialized object.
388   *
389   * @param stream the input stream.
390   * @throws java.io.IOException    if there is an I/O problem.
391   * @throws ClassNotFoundException if a class cannot be found.
392   */
393  private void readObject(final ObjectInputStream stream)
394      throws IOException, ClassNotFoundException
395  {
396    stream.defaultReadObject();
397    final int rowCount = stream.readInt();
398    this.data = new Object[rowCount][];
399    for (int r = 0; r < rowCount; r++)
400    {
401      final boolean isNotNull = stream.readBoolean();
402      if (isNotNull)
403      {
404        final int columnCount = stream.readInt();
405        final Object[] column = new Object[columnCount];
406        this.data[r] = column;
407        for (int c = 0; c < columnCount; c++)
408        {
409          column[c] = readSerializedData(stream);
410        }
411      }
412    }
413  }
414
415  /**
416   * Handles the deserialization of a single element of the table.
417   *
418   * @param stream the object input stream from which to read the object.
419   * @return the deserialized object
420   * @throws ClassNotFoundException if a class cannot be found.
421   * @throws IOException            Any of the usual Input/Output related
422   *                                exceptions.
423   */
424  protected Object readSerializedData(final ObjectInputStream stream)
425      throws ClassNotFoundException, IOException
426  {
427    return stream.readObject();
428  }
429
430  /**
431   * Clears the table.
432   */
433  public void clear()
434  {
435    this.rows = 0;
436    this.columns = 0;
437    for (int i = 0; i < this.data.length; i++)
438    {
439      if (this.data[i] != null)
440      {
441        Arrays.fill(this.data[i], null);
442      }
443    }
444  }
445
446  /**
447   * Copys the contents of the old column to the new column.
448   *
449   * @param oldColumn the index of the old (source) column
450   * @param newColumn the index of the new column
451   */
452  protected void copyColumn(final int oldColumn, final int newColumn)
453  {
454    for (int i = 0; i < getRowCount(); i++)
455    {
456      setObject(i, newColumn, getObject(i, oldColumn));
457    }
458  }
459
460  /**
461   * Copys the contents of the old row to the new row. This uses raw access to
462   * the data and is remarkably faster than manual copying.
463   *
464   * @param oldRow the index of the old row
465   * @param newRow the index of the new row
466   */
467  protected void copyRow(final int oldRow, final int newRow)
468  {
469    this.ensureCapacity(newRow, getColumnCount());
470    final Object[] oldRowStorage = this.data[oldRow];
471    if (oldRowStorage == null)
472    {
473      final Object[] newRowStorage = this.data[newRow];
474      if (newRowStorage != null)
475      {
476        Arrays.fill(newRowStorage, null);
477      }
478    }
479    else
480    {
481      this.data[newRow] = (Object[]) oldRowStorage.clone();
482    }
483  }
484
485  /**
486   * Sets the table data.
487   *
488   * @param data  the data.
489   * @param colCount  the number of columns.
490   */
491  protected void setData(final Object[][] data, final int colCount)
492  {
493    if (data == null) {
494      throw new NullPointerException();
495    }
496    if (colCount < 0) {
497      throw new IndexOutOfBoundsException();
498    }
499
500    this.data = data;
501    this.rows = data.length;
502    this.columns = colCount;
503  }
504
505  /**
506   * Returns the table data.
507   *
508   * @return The table data.
509   */
510  protected Object[][] getData()
511  {
512    return this.data;
513  }
514}
515