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