    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.8 2005/10/18 13:24:19 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,
    054     * it is up to the caller to synchronize it properly.
    055     *
    056     * @author Thomas Morgner
    057     */
    058    public class ObjectTable implements Serializable {
    059
    060        /** For serialization. */
    061        private static final long serialVersionUID = -3968322452944912066L;
    062
    063        /** The number of rows. */
    064        private int rows;
    065
    066        /** The number of columns. */
    067        private int columns;
    068
    069        /** An array of objects.  The array may contain <code>null</code> values. */
    070        private transient Object[][] data;
    071
    072        /**
    073         * Defines how many object-slots get reserved each time we run out of
    074         * space.
    075         */
    076        private int rowIncrement;
    077
    078        /**
    079         * Defines how many object-slots get reserved each time we run out of
    080         * space.
    081         */
    082        private int columnIncrement;
    083
    084        /**
    085         * Creates a new table.
    086         */
    087        public ObjectTable() {
    088            this (5, 5);
    089        }
    090
    091        /**
    092         * Creates a new table.
    093         *
    094         * @param increment  the row and column size increment.
    095         */
    096        public ObjectTable(final int increment) {
    097            this (increment, increment);
    098        }
    099        /**
    100         * Creates a new table.
    101         *
    102         * @param rowIncrement  the row size increment.
    103         * @param colIncrement  the column size increment.
    104         */
    105        public ObjectTable(final int rowIncrement, final int colIncrement) {
    106            if (rowIncrement < 1) {
    107                throw new IllegalArgumentException("Increment must be positive.");
    108            }
    109
    110            if (colIncrement < 1) {
    111                throw new IllegalArgumentException("Increment must be positive.");
    112            }
    113
    114            this.rows = 0;
    115            this.columns = 0;
    116            this.rowIncrement = rowIncrement;
    117            this.columnIncrement = colIncrement;
    118
    119            this.data = new Object[rowIncrement][];
    120        }
    121
    122        /**
    123         * Returns the column size increment.
    124         *
    125         * @return the increment.
    126         */
    127        public int getColumnIncrement() {
    128            return this.columnIncrement;
    129        }
    130
    131        /**
    132         * Returns the row size increment.
    133         *
    134         * @return the increment.
    135         */
    136        public int getRowIncrement() {
    137            return this.rowIncrement;
    138        }
    139
    140        /**
    141         * Checks that there is storage capacity for the specified row and resizes
    142         * if necessary.
    143         *
    144         * @param row  the row index.
    145         */
    146        protected void ensureRowCapacity (final int row) {
    147
    148            // does this increase the number of rows?  if yes, create new storage
    149            if (row >= this.data.length) {
    150
    151                final Object[][] enlarged = new Object[row + this.rowIncrement][];
    152                System.arraycopy(this.data, 0, enlarged, 0, this.data.length);
    153                // do not create empty arrays - this is more expensive than checking
    154                // for null-values.
    155                this.data = enlarged;
    156            }
    157        }
    158
    159        /**
    160         * Ensures that there is storage capacity for the specified item.
    161         *
    162         * @param row  the row index.
    163         * @param column  the column index.
    164         */
    165        public void ensureCapacity (final int row, final int column) {
    166
    167            if (row < 0) {
    168                throw new IndexOutOfBoundsException("Row is invalid. " + row);
    169            }
    170            if (column < 0) {
    171                throw new IndexOutOfBoundsException("Column is invalid. " + column);
    172            }
    173
    174            ensureRowCapacity(row);
    175
    176            final Object[] current = this.data[row];
    177            if (current == null) {
    178                final Object[] enlarged
    179                    = new Object[Math.max (column + 1, this.columnIncrement)];
    180                this.data[row] = enlarged;
    181            }
    182            else if (column >= current.length) {
    183                final Object[] enlarged = new Object[column + this.columnIncrement];
    184                System.arraycopy(current, 0, enlarged, 0, current.length);
    185                this.data[row] = enlarged;
    186            }
    187        }
    188
    189        /**
    190         * Returns the number of rows in the table.
    191         *
    192         * @return The row count.
    193         */
    194        public int getRowCount() {
    195            return this.rows;
    196        }
    197
    198        /**
    199         * Returns the number of columns in the table.
    200         *
    201         * @return The column count.
    202         */
    203        public int getColumnCount() {
    204            return this.columns;
    205        }
    206
    207        /**
    208         * Returns the object from a particular cell in the table.
    209         * Returns null, if there is no object at the given position.
    210         * <P>
    211         * Note: throws IndexOutOfBoundsException if row or column is negative.
    212         *
    213         * @param row  the row index (zero-based).
    214         * @param column  the column index (zero-based).
    215         *
    216         * @return The object.
    217         */
    218        protected Object getObject(final int row, final int column) {
    219
    220            if (row < this.data.length) {
    221                final Object[] current = this.data[row];
    222                if (current == null) {
    223                    return null;
    224                }
    225                if (column < current.length) {
    226                    return current[column];
    227                }
    228            }
    229            return null;
    230
    231        }
    232
    233        /**
    234         * Sets the object for a cell in the table.  The table is expanded if
    235         * necessary.
    236         *
    237         * @param row  the row index (zero-based).
    238         * @param column  the column index (zero-based).
    239         * @param object  the object.
    240         */
    241        protected void setObject(final int row, final int column,
    242                                 final Object object) {
    243
    244            ensureCapacity(row, column);
    245
    246            this.data[row][column] = object;
    247            this.rows = Math.max (this.rows, row + 1);
    248            this.columns = Math.max (this.columns, column + 1);
    249        }
    250
    251        /**
    252         * Tests this paint table for equality with another object (typically also
    253         * an <code>ObjectTable</code>).
    254         *
    255         * @param o  the other object.
    256         *
    257         * @return A boolean.
    258         */
    259        public boolean equals(final Object o) {
    260
    261            if (o == null) {
    262                return false;
    263            }
    264
    265            if (this == o) {
    266                return true;
    267            }
    268
    269            if ((o instanceof ObjectTable) == false) {
    270                return false;
    271            }
    272
    273            final ObjectTable ot = (ObjectTable) o;
    274            if (getRowCount() != ot.getRowCount()) {
    275                return false;
    276            }
    277
    278            if (getColumnCount() != ot.getColumnCount()) {
    279                return false;
    280            }
    281
    282            for (int r = 0; r < getRowCount(); r++) {
    283                for (int c = 0; c < getColumnCount(); c++) {
    284                    if (ObjectUtilities.equal(getObject(r, c),
    285                        ot.getObject(r, c)) == false) {
    286                        return false;
    287                    }
    288                }
    289            }
    290            return true;
    291        }
    292
    293        /**
    294         * Returns a hash code value for the object.
    295         *
    296         * @return the hashcode
    297         */
    298        public int hashCode() {
    299            int result;
    300            result = this.rows;
    301            result = 29 * result + this.columns;
    302            return result;
    303        }
    304
    305        /**
    306         * Handles serialization.
    307         *
    308         * @param stream  the output stream.
    309         *
    310         * @throws java.io.IOException if there is an I/O problem.
    311         */
    312        private void writeObject(final ObjectOutputStream stream)
    313                throws IOException {
    314            stream.defaultWriteObject();
    315            final int rowCount = this.data.length;
    316            stream.writeInt(rowCount);
    317            for (int r = 0; r < rowCount; r++) {
    318                final Object[] column = this.data[r];
    319                stream.writeBoolean(column != null);
    320                if (column != null) {
    321                    final int columnCount = column.length;
    322                    stream.writeInt(columnCount);
    323                    for (int c = 0; c < columnCount; c++) {
    324                        writeSerializedData(stream, column[c]);
    325                    }
    326                }
    327            }
    328        }
    329
    330        /**
    331         * Handles the serialization of an single element of this table.
    332         *
    333         * @param stream the stream which should write the object
    334         * @param o the object that should be serialized
    335         * @throws IOException if an IO error occured
    336         */
    337        protected void writeSerializedData(final ObjectOutputStream stream,
    338                                           final Object o)
    339            throws IOException {
    340            stream.writeObject(o);
    341        }
    342
    343        /**
    344         * Restores a serialized object.
    345         *
    346         * @param stream  the input stream.
    347         *
    348         * @throws java.io.IOException if there is an I/O problem.
    349         * @throws ClassNotFoundException if a class cannot be found.
    350         */
    351        private void readObject(final ObjectInputStream stream)
    352                throws IOException, ClassNotFoundException {
    353            stream.defaultReadObject();
    354            final int rowCount = stream.readInt();
    355            this.data = new Object[rowCount][];
    356            for (int r = 0; r < rowCount; r++) {
    357                final boolean isNotNull = stream.readBoolean();
    358                if (isNotNull) {
    359                    final int columnCount = stream.readInt();
    360                    final Object[] column = new Object[columnCount];
    361                    this.data[r] = column;
    362                    for (int c = 0; c < columnCount; c++) {
    363                        column[c] = readSerializedData(stream);
    364                    }
    365                }
    366            }
    367        }
    368
    369        /**
    370         * Handles the deserialization of a single element of the table.
    371         *
    372         * @param stream the object input stream from which to read the object.
    373         *
    374         * @return the deserialized object
    375         *
    376         * @throws ClassNotFoundException if a class cannot be found.
    377         * @throws IOException Any of the usual Input/Output related exceptions.
    378         */
    379        protected Object readSerializedData(final ObjectInputStream stream)
    380            throws ClassNotFoundException, IOException {
    381            return stream.readObject();
    382        }
    383
    384        /**
    385         * Clears the table.
    386         */
    387        public void clear () {
    388            this.rows = 0;
    389            this.columns = 0;
    390            for (int i = 0; i < this.data.length; i++) {
    391                if (this.data[i] != null) {
    392                    Arrays.fill(this.data[i], null);
    393                }
    394            }
    395        }
    396
    397      /**
    398       * Copys the contents of the old column to the new column.
    399       *
    400       * @param oldColumn the index of the old (source) column
    401       * @param newColumn the index of the new column
    402       */
    403      protected void copyColumn (final int oldColumn, final int newColumn)
    404      {
    405        for (int i = 0; i < getRowCount(); i++)
    406        {
    407          setObject(i, newColumn, getObject(i, oldColumn));
    408        }
    409      }
    410
    411      /**
    412       * Copys the contents of the old row to the new row. This uses raw access
    413       * to the data and is remarkably faster than manual copying.
    414       *
    415       * @param oldRow the index of the old row
    416       * @param newRow the index of the new row
    417       */
    418      protected void copyRow (final int oldRow, final int newRow)
    419      {
    420        this.ensureCapacity(newRow, getColumnCount());
    421        final Object[] oldRowStorage = this.data[oldRow];
    422        if (oldRowStorage == null)
    423        {
    424          final Object[] newRowStorage = this.data[newRow];
    425          if (newRowStorage != null)
    426          {
    427            Arrays.fill(newRowStorage, null);
    428          }
    429        }
    430        else
    431        {
    432          this.data[newRow] = (Object[]) oldRowStorage.clone();
    433        }
    434      }
    435    }
    436