Source for org.jfree.data.KeyedObjects2D

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/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:  * KeyedObject2D.java
  29:  * ------------------
  30:  * (C) Copyright 2003-2007, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 05-Feb-2003 : Version 1 (DG);
  38:  * 01-Mar-2004 : Added equals() and clone() methods and implemented 
  39:  *               Serializable (DG);
  40:  * 03-Oct-2007 : Updated getObject() to handle modified behaviour in 
  41:  *               KeyedObjects class, added clear() method (DG);
  42:  *
  43:  */
  44: 
  45: package org.jfree.data;
  46: 
  47: import java.io.Serializable;
  48: import java.util.Collections;
  49: import java.util.Iterator;
  50: import java.util.List;
  51: 
  52: /**
  53:  * A data structure that stores zero, one or many objects, where each object is
  54:  * associated with two keys (a 'row' key and a 'column' key).
  55:  */
  56: public class KeyedObjects2D implements Cloneable, Serializable {
  57: 
  58:     /** For serialization. */
  59:     private static final long serialVersionUID = -1015873563138522374L;
  60:     
  61:     /** The row keys. */
  62:     private List rowKeys;
  63: 
  64:     /** The column keys. */
  65:     private List columnKeys;
  66: 
  67:     /** The row data. */
  68:     private List rows;
  69: 
  70:     /**
  71:      * Creates a new instance (initially empty).
  72:      */
  73:     public KeyedObjects2D() {
  74:         this.rowKeys = new java.util.ArrayList();
  75:         this.columnKeys = new java.util.ArrayList();
  76:         this.rows = new java.util.ArrayList();
  77:     }
  78: 
  79:     /**
  80:      * Returns the row count.
  81:      *
  82:      * @return The row count.
  83:      * 
  84:      * @see #getColumnCount()
  85:      */
  86:     public int getRowCount() {
  87:         return this.rowKeys.size();
  88:     }
  89: 
  90:     /**
  91:      * Returns the column count.
  92:      *
  93:      * @return The column count.
  94:      * 
  95:      * @see #getRowCount()
  96:      */
  97:     public int getColumnCount() {
  98:         return this.columnKeys.size();
  99:     }
 100: 
 101:     /**
 102:      * Returns the object for a given row and column.
 103:      *
 104:      * @param row  the row index (in the range 0 to getRowCount() - 1).
 105:      * @param column  the column index (in the range 0 to getColumnCount() - 1).
 106:      *
 107:      * @return The object (possibly <code>null</code>).
 108:      * 
 109:      * @see #getObject(Comparable, Comparable)
 110:      */
 111:     public Object getObject(int row, int column) {
 112:         Object result = null;
 113:         KeyedObjects rowData = (KeyedObjects) this.rows.get(row);
 114:         if (rowData != null) {
 115:             Comparable columnKey = (Comparable) this.columnKeys.get(column);
 116:             if (columnKey != null) {
 117:                 int index = rowData.getIndex(columnKey);
 118:                 if (index >= 0) {
 119:                     result = rowData.getObject(columnKey);
 120:                 }
 121:             }
 122:         }
 123:         return result;
 124:     }
 125: 
 126:     /**
 127:      * Returns the key for a given row.
 128:      *
 129:      * @param row  the row index (zero based).
 130:      *
 131:      * @return The row index.
 132:      * 
 133:      * @see #getRowIndex(Comparable)
 134:      */
 135:     public Comparable getRowKey(int row) {
 136:         return (Comparable) this.rowKeys.get(row);
 137:     }
 138: 
 139:     /**
 140:      * Returns the row index for a given key, or <code>-1</code> if the key
 141:      * is not recognised.
 142:      *
 143:      * @param key  the key (<code>null</code> not permitted).
 144:      *
 145:      * @return The row index.
 146:      * 
 147:      * @see #getRowKey(int)
 148:      */
 149:     public int getRowIndex(Comparable key) {
 150:         if (key == null) {
 151:             throw new IllegalArgumentException("Null 'key' argument.");
 152:         }
 153:         return this.rowKeys.indexOf(key);
 154:     }
 155: 
 156:     /**
 157:      * Returns the row keys.
 158:      *
 159:      * @return The row keys (never <code>null</code>).
 160:      * 
 161:      * @see #getRowKeys()
 162:      */
 163:     public List getRowKeys() {
 164:         return Collections.unmodifiableList(this.rowKeys);
 165:     }
 166: 
 167:     /**
 168:      * Returns the key for a given column.
 169:      *
 170:      * @param column  the column.
 171:      *
 172:      * @return The key.
 173:      * 
 174:      * @see #getColumnIndex(Comparable)
 175:      */
 176:     public Comparable getColumnKey(int column) {
 177:         return (Comparable) this.columnKeys.get(column);
 178:     }
 179: 
 180:     /**
 181:      * Returns the column index for a given key, or <code>-1</code> if the key
 182:      * is not recognised.
 183:      *
 184:      * @param key  the key (<code>null</code> not permitted).
 185:      *
 186:      * @return The column index.
 187:      * 
 188:      * @see #getColumnKey(int)
 189:      */
 190:     public int getColumnIndex(Comparable key) {
 191:         if (key == null) {
 192:             throw new IllegalArgumentException("Null 'key' argument.");
 193:         }
 194:         return this.columnKeys.indexOf(key);
 195:     }
 196: 
 197:     /**
 198:      * Returns the column keys.
 199:      *
 200:      * @return The column keys (never <code>null</code>).
 201:      * 
 202:      * @see #getRowKeys()
 203:      */
 204:     public List getColumnKeys() {
 205:         return Collections.unmodifiableList(this.columnKeys);
 206:     }
 207: 
 208:     /**
 209:      * Returns the object for the given row and column keys.
 210:      *
 211:      * @param rowKey  the row key (<code>null</code> not permitted).
 212:      * @param columnKey  the column key (<code>null</code> not permitted).
 213:      *
 214:      * @return The object (possibly <code>null</code>).
 215:      * 
 216:      * @throws IllegalArgumentException if <code>rowKey<code> or 
 217:      *         <code>columnKey</code> is <code>null</code>.
 218:      * @throws UnknownKeyException if <code>rowKey</code> or 
 219:      *         <code>columnKey</code> is not recognised.
 220:      */
 221:     public Object getObject(Comparable rowKey, Comparable columnKey) {
 222:         if (rowKey == null) {
 223:             throw new IllegalArgumentException("Null 'rowKey' argument.");
 224:         }
 225:         if (columnKey == null) {
 226:             throw new IllegalArgumentException("Null 'columnKey' argument.");
 227:         }
 228:         int row = this.rowKeys.indexOf(rowKey);
 229:         if (row < 0) {
 230:             throw new UnknownKeyException("Row key (" + rowKey 
 231:                     + ") not recognised.");
 232:         }
 233:         int column = this.columnKeys.indexOf(columnKey);
 234:         if (column < 0) {
 235:             throw new UnknownKeyException("Column key (" + columnKey 
 236:                     + ") not recognised.");
 237:         }
 238:         KeyedObjects rowData = (KeyedObjects) this.rows.get(row);
 239:         int index = rowData.getIndex(columnKey);
 240:         if (index >= 0) {
 241:             return rowData.getObject(index);
 242:         }
 243:         else {
 244:             return null;
 245:         }
 246:     }
 247: 
 248:     /**
 249:      * Adds an object to the table.  Performs the same function as setObject().
 250:      *
 251:      * @param object  the object.
 252:      * @param rowKey  the row key (<code>null</code> not permitted).
 253:      * @param columnKey  the column key (<code>null</code> not permitted).
 254:      */
 255:     public void addObject(Object object, Comparable rowKey, 
 256:             Comparable columnKey) {
 257:         setObject(object, rowKey, columnKey);
 258:     }
 259: 
 260:     /**
 261:      * Adds or updates an object.
 262:      *
 263:      * @param object  the object.
 264:      * @param rowKey  the row key (<code>null</code> not permitted).
 265:      * @param columnKey  the column key (<code>null</code> not permitted).
 266:      */
 267:     public void setObject(Object object, Comparable rowKey, 
 268:             Comparable columnKey) {
 269: 
 270:         if (rowKey == null) {
 271:             throw new IllegalArgumentException("Null 'rowKey' argument.");
 272:         }
 273:         if (columnKey == null) {
 274:             throw new IllegalArgumentException("Null 'columnKey' argument.");
 275:         }
 276:         KeyedObjects row;
 277:         int rowIndex = this.rowKeys.indexOf(rowKey);
 278:         if (rowIndex >= 0) {
 279:             row = (KeyedObjects) this.rows.get(rowIndex);
 280:         }
 281:         else {
 282:             this.rowKeys.add(rowKey);
 283:             row = new KeyedObjects();
 284:             this.rows.add(row);
 285:         }
 286:         row.setObject(columnKey, object);
 287:         int columnIndex = this.columnKeys.indexOf(columnKey);
 288:         if (columnIndex < 0) {
 289:             this.columnKeys.add(columnKey);
 290:         }
 291: 
 292:     }
 293: 
 294:     /**
 295:      * Removes an object from the table by setting it to <code>null</code>.  If
 296:      * all the objects in the specified row and/or column are now 
 297:      * <code>null</code>, the row and/or column is removed from the table.
 298:      *
 299:      * @param rowKey  the row key (<code>null</code> not permitted).
 300:      * @param columnKey  the column key (<code>null</code> not permitted).
 301:      * 
 302:      * @see #addObject(Object, Comparable, Comparable)
 303:      */
 304:     public void removeObject(Comparable rowKey, Comparable columnKey) {
 305:         int rowIndex = getRowIndex(rowKey);
 306:         if (rowIndex < 0) {
 307:             throw new UnknownKeyException("Row key (" + rowKey 
 308:                     + ") not recognised.");
 309:         }
 310:         int columnIndex = getColumnIndex(columnKey);
 311:         if (columnIndex < 0) {
 312:             throw new UnknownKeyException("Column key (" + columnKey 
 313:                     + ") not recognised.");
 314:         }
 315:         setObject(null, rowKey, columnKey);
 316:         
 317:         // 1. check whether the row is now empty.
 318:         boolean allNull = true;
 319:         KeyedObjects row = (KeyedObjects) this.rows.get(rowIndex);
 320: 
 321:         for (int item = 0, itemCount = row.getItemCount(); item < itemCount; 
 322:              item++) {
 323:             if (row.getObject(item) != null) {
 324:                 allNull = false;
 325:                 break;
 326:             }
 327:         }
 328:         
 329:         if (allNull) {
 330:             this.rowKeys.remove(rowIndex);
 331:             this.rows.remove(rowIndex);
 332:         }
 333:         
 334:         // 2. check whether the column is now empty.
 335:         allNull = true;
 336:         
 337:         for (int item = 0, itemCount = this.rows.size(); item < itemCount; 
 338:              item++) {
 339:             row = (KeyedObjects) this.rows.get(item);
 340:             int colIndex = row.getIndex(columnKey);
 341:             if (colIndex >= 0 && row.getObject(colIndex) != null) {
 342:                 allNull = false;
 343:                 break;
 344:             }
 345:         }
 346:         
 347:         if (allNull) {
 348:             for (int item = 0, itemCount = this.rows.size(); item < itemCount; 
 349:                  item++) {
 350:                 row = (KeyedObjects) this.rows.get(item);
 351:                 int colIndex = row.getIndex(columnKey);
 352:                 if (colIndex >= 0) {
 353:                     row.removeValue(colIndex);
 354:                 }
 355:             }
 356:             this.columnKeys.remove(columnKey);
 357:         }
 358:     }
 359: 
 360:     /**
 361:      * Removes an entire row from the table.
 362:      *
 363:      * @param rowIndex  the row index.
 364:      * 
 365:      * @see #removeColumn(int)
 366:      */
 367:     public void removeRow(int rowIndex) {
 368:         this.rowKeys.remove(rowIndex);
 369:         this.rows.remove(rowIndex);
 370:     }
 371: 
 372:     /**
 373:      * Removes an entire row from the table.
 374:      *
 375:      * @param rowKey  the row key (<code>null</code> not permitted).
 376:      * 
 377:      * @throws UnknownKeyException if <code>rowKey</code> is not recognised.
 378:      * 
 379:      * @see #removeColumn(Comparable)
 380:      */
 381:     public void removeRow(Comparable rowKey) {
 382:         int index = getRowIndex(rowKey);
 383:         if (index < 0) {
 384:             throw new UnknownKeyException("Row key (" + rowKey 
 385:                     + ") not recognised.");
 386:         }
 387:         removeRow(index);
 388:     }
 389: 
 390:     /**
 391:      * Removes an entire column from the table.
 392:      *
 393:      * @param columnIndex  the column index.
 394:      * 
 395:      * @see #removeRow(int)
 396:      */
 397:     public void removeColumn(int columnIndex) {
 398:         Comparable columnKey = getColumnKey(columnIndex);
 399:         removeColumn(columnKey);
 400:     }
 401: 
 402:     /**
 403:      * Removes an entire column from the table.
 404:      *
 405:      * @param columnKey  the column key (<code>null</code> not permitted).
 406:      * 
 407:      * @throws UnknownKeyException if <code>rowKey</code> is not recognised.
 408:      * 
 409:      * @see #removeRow(Comparable)
 410:      */
 411:     public void removeColumn(Comparable columnKey) {
 412:         int index = getColumnIndex(columnKey);
 413:         if (index < 0) {
 414:             throw new UnknownKeyException("Column key (" + columnKey 
 415:                     + ") not recognised.");
 416:         }
 417:         Iterator iterator = this.rows.iterator();
 418:         while (iterator.hasNext()) {
 419:             KeyedObjects rowData = (KeyedObjects) iterator.next();
 420:             int i = rowData.getIndex(columnKey);
 421:             if (i >= 0) {
 422:                 rowData.removeValue(i);
 423:             }
 424:         }
 425:         this.columnKeys.remove(columnKey);
 426:     }
 427: 
 428:     /**
 429:      * Clears all the data and associated keys.
 430:      * 
 431:      * @since 1.0.7
 432:      */
 433:     public void clear() {
 434:         this.rowKeys.clear();
 435:         this.columnKeys.clear();
 436:         this.rows.clear();
 437:     }
 438:     
 439:     /**
 440:      * Tests this object for equality with an arbitrary object.
 441:      *
 442:      * @param obj  the object to test (<code>null</code> permitted).
 443:      *
 444:      * @return A boolean.
 445:      */
 446:     public boolean equals(Object obj) {
 447:         if (obj == this) {
 448:             return true;
 449:         }
 450:         if (!(obj instanceof KeyedObjects2D)) {
 451:             return false;
 452:         }
 453:         
 454:         KeyedObjects2D that = (KeyedObjects2D) obj;
 455:         if (!getRowKeys().equals(that.getRowKeys())) {
 456:             return false;
 457:         }
 458:         if (!getColumnKeys().equals(that.getColumnKeys())) {
 459:             return false;
 460:         }
 461:         int rowCount = getRowCount();
 462:         if (rowCount != that.getRowCount()) {
 463:             return false;
 464:         }
 465:         int colCount = getColumnCount();
 466:         if (colCount != that.getColumnCount()) {
 467:             return false;
 468:         }
 469:         for (int r = 0; r < rowCount; r++) {
 470:             for (int c = 0; c < colCount; c++) {
 471:                 Object v1 = getObject(r, c);
 472:                 Object v2 = that.getObject(r, c);
 473:                 if (v1 == null) {
 474:                     if (v2 != null) {
 475:                         return false;
 476:                     }
 477:                 }
 478:                 else {
 479:                     if (!v1.equals(v2)) {
 480:                         return false;
 481:                     }
 482:                 }
 483:             }
 484:         }
 485:         return true;
 486:     }
 487: 
 488:     /**
 489:      * Returns a hashcode for this object.
 490:      * 
 491:      * @return A hashcode.
 492:      */
 493:     public int hashCode() {
 494:         int result;
 495:         result = this.rowKeys.hashCode();
 496:         result = 29 * result + this.columnKeys.hashCode();
 497:         result = 29 * result + this.rows.hashCode();
 498:         return result;
 499:     }
 500: 
 501:     /**
 502:      * Returns a clone.
 503:      * 
 504:      * @return A clone.
 505:      * 
 506:      * @throws CloneNotSupportedException  this class will not throw this 
 507:      *         exception, but subclasses (if any) might.
 508:      */
 509:     public Object clone() throws CloneNotSupportedException {
 510:         KeyedObjects2D clone = (KeyedObjects2D) super.clone();
 511:         clone.columnKeys = new java.util.ArrayList(this.columnKeys);
 512:         clone.rowKeys = new java.util.ArrayList(this.rowKeys);
 513:         clone.rows = new java.util.ArrayList(this.rows.size());
 514:         Iterator iterator = this.rows.iterator();
 515:         while (iterator.hasNext()) {
 516:             KeyedObjects row = (KeyedObjects) iterator.next();
 517:             clone.rows.add(row.clone());
 518:         }
 519:         return clone;
 520:     }
 521: 
 522: }