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