    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
    044    package org.jfree.util;
    045
    046    import java.io.IOException;
    047    import java.io.ObjectInputStream;
    048    import java.io.ObjectOutputStream;
    049    import java.io.Serializable;
    050    import 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     */
    058    public 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