Source for org.jfree.util.ObjectTable

   1: /* ========================================================================
   2:  * JCommon : a free general purpose class library for the Java(tm) platform
   3:  * ========================================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  * 
   7:  * Project Info:  http://www.jfree.org/jcommon/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ----------------
  28:  * ObjectTable.java
  29:  * ----------------
  30:  * (C) Copyright 2003, 2004, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: ObjectTable.java,v 1.9 2006/11/05 14:34:15 taqua Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 29-Apr-2003 : Version 1, based on PaintTable class (DG);
  40:  * 21-May-2003 : Copied the array based implementation of StrokeTable and
  41:  *               fixed the serialisation behaviour (TM).
  42:  */
  43: 
  44: package org.jfree.util;
  45: 
  46: import java.io.IOException;
  47: import java.io.ObjectInputStream;
  48: import java.io.ObjectOutputStream;
  49: import java.io.Serializable;
  50: import java.util.Arrays;
  51: 
  52: /**
  53:  * A lookup table for objects. This implementation is not synchronized, it is up
  54:  * to the caller to synchronize it properly.
  55:  *
  56:  * @author Thomas Morgner
  57:  */
  58: public class ObjectTable implements Serializable
  59: {
  60: 
  61:   /**
  62:    * For serialization.
  63:    */
  64:   private static final long serialVersionUID = -3968322452944912066L;
  65: 
  66:   /**
  67:    * The number of rows.
  68:    */
  69:   private int rows;
  70: 
  71:   /**
  72:    * The number of columns.
  73:    */
  74:   private int columns;
  75: 
  76:   /**
  77:    * An array of objects.  The array may contain <code>null</code> values.
  78:    */
  79:   private transient Object[][] data;
  80: 
  81:   /**
  82:    * Defines how many object-slots get reserved each time we run out of
  83:    * space.
  84:    */
  85:   private int rowIncrement;
  86: 
  87:   /**
  88:    * Defines how many object-slots get reserved each time we run out of
  89:    * space.
  90:    */
  91:   private int columnIncrement;
  92: 
  93:   /**
  94:    * Creates a new table.
  95:    */
  96:   public ObjectTable()
  97:   {
  98:     this(5, 5);
  99:   }
 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:   protected void setData(final Object[][] data, final int colCount)
 486:   {
 487:     if (data == null) {
 488:       throw new NullPointerException();
 489:     }
 490:     if (colCount < 0) {
 491:       throw new IndexOutOfBoundsException();
 492:     }
 493: 
 494:     this.data = data;
 495:     this.rows = data.length;
 496:     this.columns = colCount;
 497:   }
 498: 
 499:   protected Object[][] getData()
 500:   {
 501:     return data;
 502:   }
 503: }