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