    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