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