content: /* ========================================================================
   * JCommon : a free general purpose class library for the Java(tm) platform
   * ========================================================================
   *
   * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   *
   * Project Info:  http://www.jfree.org/jcommon/index.html
   *
   * This library is free software; you can redistribute it and/or modify it
   * under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation; either version 2.1 of the License, or
   * (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
   * License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
   * USA.
   *
   * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
   * in the United States and other countries.]
   *
   * ----------------
   * ObjectTable.java
   * ----------------
   * (C) Copyright 2003, 2004, by Object Refinery Limited.
   *
   * Original Author:  David Gilbert (for Object Refinery Limited);
   * Contributor(s):   -;
   *
   * $Id: ObjectTable.java,v 1.10 2008/09/10 09:22:04 mungady Exp $
   *
   * Changes
   * -------
   * 29-Apr-2003 : Version 1, based on PaintTable class (DG);
   * 21-May-2003 : Copied the array based implementation of StrokeTable and
   *               fixed the serialisation behaviour (TM).
   */
  
  package org.jfree.util;
  
  import java.io.IOException;
  import java.io.ObjectInputStream;
  import java.io.ObjectOutputStream;
  import java.io.Serializable;
  import java.util.Arrays;
  
  /**
   * A lookup table for objects. This implementation is not synchronized, it is up
   * to the caller to synchronize it properly.
   *
   * @author Thomas Morgner
   */
  public class ObjectTable implements Serializable
  {
  
    /**
     * For serialization.
     */
    private static final long serialVersionUID = -3968322452944912066L;
  
    /**
     * The number of rows.
     */
    private int rows;
  
    /**
     * The number of columns.
     */
    private int columns;
  
    /**
     * An array of objects.  The array may contain <code>null</code> values.
     */
    private transient Object[][] data;
  
    /**
     * Defines how many object-slots get reserved each time we run out of
     * space.
     */
    private int rowIncrement;
  
    /**
     * Defines how many object-slots get reserved each time we run out of
     * space.
     */
    private int columnIncrement;
  
    /**
     * Creates a new table.
     */
    public ObjectTable()
    {
      this(5, 5);
    }
  
    /**
     * Creates a new table.
     *
     * @param increment the row and column size increment.
     */
    public ObjectTable(final int increment)
    {
      this(increment, increment);
    }
  
    /**
     * Creates a new table.
     *
     * @param rowIncrement the row size increment.
     * @param colIncrement the column size increment.
     */
    public ObjectTable(final int rowIncrement, final int colIncrement)
    {
      if (rowIncrement < 1)
      {
        throw new IllegalArgumentException("Increment must be positive.");
      }
  
      if (colIncrement < 1)
      {
        throw new IllegalArgumentException("Increment must be positive.");
      }
  
      this.rows = 0;
      this.columns = 0;
      this.rowIncrement = rowIncrement;
      this.columnIncrement = colIncrement;
  
      this.data = new Object[rowIncrement][];
    }
  
    /**
     * Returns the column size increment.
     *
     * @return the increment.
     */
    public int getColumnIncrement()
    {
      return this.columnIncrement;
    }
  
    /**
     * Returns the row size increment.
     *
     * @return the increment.
     */
    public int getRowIncrement()
    {
      return this.rowIncrement;
    }
  
    /**
     * Checks that there is storage capacity for the specified row and resizes
     * if necessary.
     *
     * @param row the row index.
     */
    protected void ensureRowCapacity(final int row)
    {
  
      // does this increase the number of rows?  if yes, create new storage
      if (row >= this.data.length)
      {
  
        final Object[][] enlarged = new Object[row + this.rowIncrement][];
        System.arraycopy(this.data, 0, enlarged, 0, this.data.length);
        // do not create empty arrays - this is more expensive than checking
        // for null-values.
        this.data = enlarged;
      }
    }
  
    /**
     * Ensures that there is storage capacity for the specified item.
     *
     * @param row    the row index.
     * @param column the column index.
     */
    public void ensureCapacity(final int row, final int column)
    {
  
      if (row < 0)
      {
        throw new IndexOutOfBoundsException("Row is invalid. " + row);
      }
      if (column < 0)
      {
        throw new IndexOutOfBoundsException("Column is invalid. " + column);
      }
  
      ensureRowCapacity(row);
  
      final Object[] current = this.data[row];
      if (current == null)
      {
        final Object[] enlarged
            = new Object[Math.max(column + 1, this.columnIncrement)];
        this.data[row] = enlarged;
      }
      else if (column >= current.length)
      {
        final Object[] enlarged = new Object[column + this.columnIncrement];
        System.arraycopy(current, 0, enlarged, 0, current.length);
        this.data[row] = enlarged;
      }
    }
  
    /**
     * Returns the number of rows in the table.
     *
     * @return The row count.
     */
    public int getRowCount()
    {
      return this.rows;
    }
  
    /**
     * Returns the number of columns in the table.
     *
     * @return The column count.
     */
    public int getColumnCount()
    {
      return this.columns;
    }
  
    /**
     * Returns the object from a particular cell in the table. Returns null, if
     * there is no object at the given position.
     * <p/>
     * Note: throws IndexOutOfBoundsException if row or column is negative.
     *
     * @param row    the row index (zero-based).
     * @param column the column index (zero-based).
     * @return The object.
     */
    protected Object getObject(final int row, final int column)
    {
  
      if (row < this.data.length)
      {
        final Object[] current = this.data[row];
        if (current == null)
        {
          return null;
        }
        if (column < current.length)
        {
          return current[column];
        }
      }
      return null;
  
    }
  
    /**
     * Sets the object for a cell in the table.  The table is expanded if
     * necessary.
     *
     * @param row    the row index (zero-based).
     * @param column the column index (zero-based).
     * @param object the object.
     */
    protected void setObject(final int row, final int column,
                             final Object object)
    {
  
      ensureCapacity(row, column);
  
      this.data[row][column] = object;
      this.rows = Math.max(this.rows, row + 1);
      this.columns = Math.max(this.columns, column + 1);
    }
  
    /**
     * Tests this paint table for equality with another object (typically also
     * an <code>ObjectTable</code>).
     *
     * @param o the other object.
     * @return A boolean.
     */
    public boolean equals(final Object o)
    {
  
      if (o == null)
      {
        return false;
      }
  
      if (this == o)
      {
        return true;
      }
  
      if ((o instanceof ObjectTable) == false)
      {
        return false;
      }
  
      final ObjectTable ot = (ObjectTable) o;
      if (getRowCount() != ot.getRowCount())
      {
        return false;
      }
  
      if (getColumnCount() != ot.getColumnCount())
      {
        return false;
      }
  
      for (int r = 0; r < getRowCount(); r++)
      {
        for (int c = 0; c < getColumnCount(); c++)
        {
          if (ObjectUtilities.equal(getObject(r, c),
              ot.getObject(r, c)) == false)
          {
            return false;
          }
        }
      }
      return true;
    }
  
    /**
     * Returns a hash code value for the object.
     *
     * @return the hashcode
     */
    public int hashCode()
    {
      int result;
      result = this.rows;
      result = 29 * result + this.columns;
      return result;
    }
  
    /**
     * Handles serialization.
     *
     * @param stream the output stream.
     * @throws java.io.IOException if there is an I/O problem.
     */
    private void writeObject(final ObjectOutputStream stream)
        throws IOException
    {
      stream.defaultWriteObject();
      final int rowCount = this.data.length;
      stream.writeInt(rowCount);
      for (int r = 0; r < rowCount; r++)
      {
        final Object[] column = this.data[r];
        stream.writeBoolean(column != null);
        if (column != null)
        {
          final int columnCount = column.length;
          stream.writeInt(columnCount);
          for (int c = 0; c < columnCount; c++)
          {
            writeSerializedData(stream, column[c]);
          }
        }
      }
    }
  
    /**
     * Handles the serialization of an single element of this table.
     *
     * @param stream the stream which should write the object
     * @param o      the object that should be serialized
     * @throws IOException if an IO error occured
     */
    protected void writeSerializedData(final ObjectOutputStream stream,
                                       final Object o)
        throws IOException
    {
      stream.writeObject(o);
    }
  
    /**
     * Restores a serialized object.
     *
     * @param stream the input stream.
     * @throws java.io.IOException    if there is an I/O problem.
     * @throws ClassNotFoundException if a class cannot be found.
     */
    private void readObject(final ObjectInputStream stream)
        throws IOException, ClassNotFoundException
    {
      stream.defaultReadObject();
      final int rowCount = stream.readInt();
      this.data = new Object[rowCount][];
      for (int r = 0; r < rowCount; r++)
      {
        final boolean isNotNull = stream.readBoolean();
        if (isNotNull)
        {
          final int columnCount = stream.readInt();
          final Object[] column = new Object[columnCount];
          this.data[r] = column;
          for (int c = 0; c < columnCount; c++)
          {
            column[c] = readSerializedData(stream);
          }
        }
      }
    }
  
    /**
     * Handles the deserialization of a single element of the table.
     *
     * @param stream the object input stream from which to read the object.
     * @return the deserialized object
     * @throws ClassNotFoundException if a class cannot be found.
     * @throws IOException            Any of the usual Input/Output related
     *                                exceptions.
     */
    protected Object readSerializedData(final ObjectInputStream stream)
        throws ClassNotFoundException, IOException
    {
      return stream.readObject();
    }
  
    /**
     * Clears the table.
     */
    public void clear()
    {
      this.rows = 0;
      this.columns = 0;
      for (int i = 0; i < this.data.length; i++)
      {
        if (this.data[i] != null)
        {
          Arrays.fill(this.data[i], null);
        }
      }
    }
  
    /**
     * Copys the contents of the old column to the new column.
     *
     * @param oldColumn the index of the old (source) column
     * @param newColumn the index of the new column
     */
    protected void copyColumn(final int oldColumn, final int newColumn)
    {
      for (int i = 0; i < getRowCount(); i++)
      {
        setObject(i, newColumn, getObject(i, oldColumn));
      }
    }
  
    /**
     * Copys the contents of the old row to the new row. This uses raw access to
     * the data and is remarkably faster than manual copying.
     *
     * @param oldRow the index of the old row
     * @param newRow the index of the new row
     */
    protected void copyRow(final int oldRow, final int newRow)
    {
      this.ensureCapacity(newRow, getColumnCount());
      final Object[] oldRowStorage = this.data[oldRow];
      if (oldRowStorage == null)
      {
        final Object[] newRowStorage = this.data[newRow];
        if (newRowStorage != null)
        {
          Arrays.fill(newRowStorage, null);
        }
      }
      else
      {
        this.data[newRow] = (Object[]) oldRowStorage.clone();
      }
    }
  
    /**
     * Sets the table data.
     *
     * @param data  the data.
     * @param colCount  the number of columns.
     */
    protected void setData(final Object[][] data, final int colCount)
    {
      if (data == null) {
        throw new NullPointerException();
      }
      if (colCount < 0) {
        throw new IndexOutOfBoundsException();
      }
  
      this.data = data;
      this.rows = data.length;
      this.columns = colCount;
    }
  
    /**
     * Returns the table data.
     *
     * @return The table data.
     */
    protected Object[][] getData()
    {
      return this.data;
    }
  }
  
  