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