Source for org.jfree.data.DefaultKeyedValues2D

   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:  * DefaultKeyedValues2D.java
  29:  * -------------------------
  30:  * (C) Copyright 2002-2007, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Andreas Schroeder;
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 28-Oct-2002 : Version 1 (DG);
  38:  * 21-Jan-2003 : Updated Javadocs (DG);
  39:  * 13-Mar-2003 : Implemented Serializable (DG);
  40:  * 18-Aug-2003 : Implemented Cloneable (DG);
  41:  * 31-Mar-2004 : Made the rows optionally sortable by a flag (AS);
  42:  * 01-Apr-2004 : Implemented remove method (AS);
  43:  * 05-Apr-2004 : Added clear() method (DG);
  44:  * 15-Sep-2004 : Fixed clone() method (DG);
  45:  * 12-Jan-2005 : Fixed bug in getValue() method (DG);
  46:  * 23-Mar-2005 : Implemented PublicCloneable (DG);
  47:  * 09-Jun-2005 : Modified getValue() method to throw exception for unknown
  48:  *               keys (DG);
  49:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  50:  * 18-Jan-2007 : Fixed bug in getValue() method (DG);
  51:  * 30-Mar-2007 : Fixed bug 1690654, problem with removeValue() (DG);
  52:  * 21-Nov-2007 : Fixed bug (1835955) in removeColumn(Comparable) method (DG);
  53:  * 23-Nov-2007 : Added argument checks to removeRow(Comparable) to make it
  54:  *               consistent with the removeRow(Comparable) method (DG);
  55:  *
  56:  */
  57: 
  58: package org.jfree.data;
  59: 
  60: import java.io.Serializable;
  61: import java.util.Collections;
  62: import java.util.Iterator;
  63: import java.util.List;
  64: 
  65: import org.jfree.util.ObjectUtilities;
  66: import org.jfree.util.PublicCloneable;
  67: 
  68: /**
  69:  * A data structure that stores zero, one or many values, where each value 
  70:  * is associated with two keys (a 'row' key and a 'column' key).  The keys 
  71:  * should be (a) instances of {@link Comparable} and (b) immutable.  
  72:  */
  73: public class DefaultKeyedValues2D implements KeyedValues2D, 
  74:                                              PublicCloneable, Cloneable, 
  75:                                              Serializable {
  76: 
  77:     /** For serialization. */
  78:     private static final long serialVersionUID = -5514169970951994748L;
  79:     
  80:     /** The row keys. */
  81:     private List rowKeys;
  82: 
  83:     /** The column keys. */
  84:     private List columnKeys;
  85: 
  86:     /** The row data. */
  87:     private List rows;
  88:     
  89:     /** If the row keys should be sorted by their comparable order. */
  90:     private boolean sortRowKeys;
  91: 
  92:     /**
  93:      * Creates a new instance (initially empty).
  94:      */
  95:     public DefaultKeyedValues2D() {
  96:         this(false);
  97:     }
  98: 
  99:     /**
 100:      * Creates a new instance (initially empty).
 101:      * 
 102:      * @param sortRowKeys  if the row keys should be sorted.
 103:      */
 104:     public DefaultKeyedValues2D(boolean sortRowKeys) {
 105:         this.rowKeys = new java.util.ArrayList();
 106:         this.columnKeys = new java.util.ArrayList();
 107:         this.rows = new java.util.ArrayList();
 108:         this.sortRowKeys = sortRowKeys;
 109:     }
 110: 
 111:     /**
 112:      * Returns the row count.
 113:      *
 114:      * @return The row count.
 115:      * 
 116:      * @see #getColumnCount()
 117:      */
 118:     public int getRowCount() {
 119:         return this.rowKeys.size();
 120:     }
 121: 
 122:     /**
 123:      * Returns the column count.
 124:      *
 125:      * @return The column count.
 126:      * 
 127:      * @see #getRowCount()
 128:      */
 129:     public int getColumnCount() {
 130:         return this.columnKeys.size();
 131:     }
 132: 
 133:     /**
 134:      * Returns the value for a given row and column.
 135:      *
 136:      * @param row  the row index.
 137:      * @param column  the column index.
 138:      *
 139:      * @return The value.
 140:      * 
 141:      * @see #getValue(Comparable, Comparable)
 142:      */
 143:     public Number getValue(int row, int column) {
 144:         Number result = null;
 145:         DefaultKeyedValues rowData = (DefaultKeyedValues) this.rows.get(row);
 146:         if (rowData != null) {
 147:             Comparable columnKey = (Comparable) this.columnKeys.get(column);
 148:             // the row may not have an entry for this key, in which case the 
 149:             // return value is null
 150:             int index = rowData.getIndex(columnKey);
 151:             if (index >= 0) {
 152:                 result = rowData.getValue(index);
 153:             }
 154:         }
 155:         return result;
 156:     }
 157: 
 158:     /**
 159:      * Returns the key for a given row.
 160:      *
 161:      * @param row  the row index (in the range 0 to {@link #getRowCount()} - 1).
 162:      *
 163:      * @return The row key.
 164:      * 
 165:      * @see #getRowIndex(Comparable)
 166:      * @see #getColumnKey(int)
 167:      */
 168:     public Comparable getRowKey(int row) {
 169:         return (Comparable) this.rowKeys.get(row);
 170:     }
 171: 
 172:     /**
 173:      * Returns the row index for a given key.
 174:      *
 175:      * @param key  the key (<code>null</code> not permitted).
 176:      *
 177:      * @return The row index.
 178:      * 
 179:      * @see #getRowKey(int)
 180:      * @see #getColumnIndex(Comparable)
 181:      */
 182:     public int getRowIndex(Comparable key) {
 183:         if (key == null) {
 184:             throw new IllegalArgumentException("Null 'key' argument.");
 185:         }
 186:         if (this.sortRowKeys) {
 187:             return Collections.binarySearch(this.rowKeys, key);
 188:         }
 189:         else {
 190:             return this.rowKeys.indexOf(key);
 191:         }
 192:     }
 193: 
 194:     /**
 195:      * Returns the row keys in an unmodifiable list.
 196:      *
 197:      * @return The row keys.
 198:      * 
 199:      * @see #getColumnKeys()
 200:      */
 201:     public List getRowKeys() {
 202:         return Collections.unmodifiableList(this.rowKeys);
 203:     }
 204: 
 205:     /**
 206:      * Returns the key for a given column.
 207:      *
 208:      * @param column  the column (in the range 0 to {@link #getColumnCount()} 
 209:      *     - 1).
 210:      *
 211:      * @return The key.
 212:      * 
 213:      * @see #getColumnIndex(Comparable)
 214:      * @see #getRowKey(int)
 215:      */
 216:     public Comparable getColumnKey(int column) {
 217:         return (Comparable) this.columnKeys.get(column);
 218:     }
 219: 
 220:     /**
 221:      * Returns the column index for a given key.
 222:      *
 223:      * @param key  the key (<code>null</code> not permitted).
 224:      *
 225:      * @return The column index.
 226:      * 
 227:      * @see #getColumnKey(int)
 228:      * @see #getRowIndex(Comparable)
 229:      */
 230:     public int getColumnIndex(Comparable key) {
 231:         if (key == null) {
 232:             throw new IllegalArgumentException("Null 'key' argument.");
 233:         }
 234:         return this.columnKeys.indexOf(key);
 235:     }
 236: 
 237:     /**
 238:      * Returns the column keys in an unmodifiable list.
 239:      *
 240:      * @return The column keys.
 241:      * 
 242:      * @see #getRowKeys()
 243:      */
 244:     public List getColumnKeys() {
 245:         return Collections.unmodifiableList(this.columnKeys);
 246:     }
 247: 
 248:     /**
 249:      * Returns the value for the given row and column keys.  This method will
 250:      * throw an {@link UnknownKeyException} if either key is not defined in the
 251:      * data structure.
 252:      *
 253:      * @param rowKey  the row key (<code>null</code> not permitted).
 254:      * @param columnKey  the column key (<code>null</code> not permitted).
 255:      *
 256:      * @return The value (possibly <code>null</code>).
 257:      * 
 258:      * @see #addValue(Number, Comparable, Comparable)
 259:      * @see #removeValue(Comparable, Comparable)
 260:      */
 261:     public Number getValue(Comparable rowKey, Comparable columnKey) {
 262:         if (rowKey == null) {
 263:             throw new IllegalArgumentException("Null 'rowKey' argument.");
 264:         }
 265:         if (columnKey == null) {
 266:             throw new IllegalArgumentException("Null 'columnKey' argument.");
 267:         }
 268:         
 269:         // check that the column key is defined in the 2D structure
 270:         if (!(this.columnKeys.contains(columnKey))) {
 271:             throw new UnknownKeyException("Unrecognised columnKey: " 
 272:                     + columnKey);
 273:         }
 274:         
 275:         // now fetch the row data - need to bear in mind that the row
 276:         // structure may not have an entry for the column key, but that we
 277:         // have already checked that the key is valid for the 2D structure
 278:         int row = getRowIndex(rowKey);
 279:         if (row >= 0) {
 280:             DefaultKeyedValues rowData 
 281:                 = (DefaultKeyedValues) this.rows.get(row);
 282:             int col = rowData.getIndex(columnKey);
 283:             return (col >= 0 ? rowData.getValue(col) : null);
 284:         }
 285:         else {
 286:             throw new UnknownKeyException("Unrecognised rowKey: " + rowKey);
 287:         }
 288:     }
 289: 
 290:     /**
 291:      * Adds a value to the table.  Performs the same function as 
 292:      * #setValue(Number, Comparable, Comparable).
 293:      *
 294:      * @param value  the value (<code>null</code> permitted).
 295:      * @param rowKey  the row key (<code>null</code> not permitted).
 296:      * @param columnKey  the column key (<code>null</code> not permitted).
 297:      * 
 298:      * @see #setValue(Number, Comparable, Comparable)
 299:      * @see #removeValue(Comparable, Comparable)
 300:      */
 301:     public void addValue(Number value, Comparable rowKey, 
 302:                          Comparable columnKey) {
 303:         // defer argument checking
 304:         setValue(value, rowKey, columnKey);
 305:     }
 306: 
 307:     /**
 308:      * Adds or updates a value.
 309:      *
 310:      * @param value  the value (<code>null</code> permitted).
 311:      * @param rowKey  the row key (<code>null</code> not permitted).
 312:      * @param columnKey  the column key (<code>null</code> not permitted).
 313:      * 
 314:      * @see #addValue(Number, Comparable, Comparable)
 315:      * @see #removeValue(Comparable, Comparable)
 316:      */
 317:     public void setValue(Number value, Comparable rowKey, 
 318:                          Comparable columnKey) {
 319: 
 320:         DefaultKeyedValues row;
 321:         int rowIndex = getRowIndex(rowKey);
 322:         
 323:         if (rowIndex >= 0) {
 324:             row = (DefaultKeyedValues) this.rows.get(rowIndex);
 325:         }
 326:         else {
 327:             row = new DefaultKeyedValues();
 328:             if (this.sortRowKeys) {
 329:                 rowIndex = -rowIndex - 1;
 330:                 this.rowKeys.add(rowIndex, rowKey);
 331:                 this.rows.add(rowIndex, row);
 332:             }
 333:             else {
 334:                 this.rowKeys.add(rowKey);
 335:                 this.rows.add(row);
 336:             }
 337:         }
 338:         row.setValue(columnKey, value);
 339:         
 340:         int columnIndex = this.columnKeys.indexOf(columnKey);
 341:         if (columnIndex < 0) {
 342:             this.columnKeys.add(columnKey);
 343:         }
 344:     }
 345: 
 346:     /**
 347:      * Removes a value from the table by setting it to <code>null</code>.  If
 348:      * all the values in the specified row and/or column are now 
 349:      * <code>null</code>, the row and/or column is removed from the table.
 350:      *
 351:      * @param rowKey  the row key (<code>null</code> not permitted).
 352:      * @param columnKey  the column key (<code>null</code> not permitted).
 353:      * 
 354:      * @see #addValue(Number, Comparable, Comparable)
 355:      */
 356:     public void removeValue(Comparable rowKey, Comparable columnKey) {
 357:         setValue(null, rowKey, columnKey);
 358:         
 359:         // 1. check whether the row is now empty.
 360:         boolean allNull = true;
 361:         int rowIndex = getRowIndex(rowKey);
 362:         DefaultKeyedValues row = (DefaultKeyedValues) this.rows.get(rowIndex);
 363: 
 364:         for (int item = 0, itemCount = row.getItemCount(); item < itemCount; 
 365:              item++) {
 366:             if (row.getValue(item) != null) {
 367:                 allNull = false;
 368:                 break;
 369:             }
 370:         }
 371:         
 372:         if (allNull) {
 373:             this.rowKeys.remove(rowIndex);
 374:             this.rows.remove(rowIndex);
 375:         }
 376:         
 377:         // 2. check whether the column is now empty.
 378:         allNull = true;
 379:         //int columnIndex = getColumnIndex(columnKey);
 380:         
 381:         for (int item = 0, itemCount = this.rows.size(); item < itemCount; 
 382:              item++) {
 383:             row = (DefaultKeyedValues) this.rows.get(item);
 384:             int columnIndex = row.getIndex(columnKey);
 385:             if (columnIndex >= 0 && row.getValue(columnIndex) != null) {
 386:                 allNull = false;
 387:                 break;
 388:             }
 389:         }
 390:         
 391:         if (allNull) {
 392:             for (int item = 0, itemCount = this.rows.size(); item < itemCount; 
 393:                  item++) {
 394:                 row = (DefaultKeyedValues) this.rows.get(item);
 395:                 int columnIndex = row.getIndex(columnKey);
 396:                 if (columnIndex >= 0) {
 397:                     row.removeValue(columnIndex);
 398:                 }
 399:             }
 400:             this.columnKeys.remove(columnKey);
 401:         }
 402:     }
 403: 
 404:     /**
 405:      * Removes a row.
 406:      *
 407:      * @param rowIndex  the row index.
 408:      * 
 409:      * @see #removeRow(Comparable)
 410:      * @see #removeColumn(int)
 411:      */
 412:     public void removeRow(int rowIndex) {
 413:         this.rowKeys.remove(rowIndex);
 414:         this.rows.remove(rowIndex);
 415:     }
 416: 
 417:     /**
 418:      * Removes a row from the table.
 419:      *
 420:      * @param rowKey  the row key (<code>null</code> not permitted).
 421:      * 
 422:      * @see #removeRow(int)
 423:      * @see #removeColumn(Comparable)
 424:      *
 425:      * @throws UnknownKeyException if <code>rowKey</code> is not defined in the
 426:      *         table.
 427:      */
 428:     public void removeRow(Comparable rowKey) {
 429:         if (rowKey == null) {
 430:             throw new IllegalArgumentException("Null 'rowKey' argument.");
 431:         }
 432:         int index = getRowIndex(rowKey);
 433:         if (index >= 0) {
 434:             removeRow(index);
 435:         }
 436:         else {
 437:             throw new UnknownKeyException("Unknown key: " + rowKey);
 438:         }
 439:     }
 440: 
 441:     /**
 442:      * Removes a column.
 443:      *
 444:      * @param columnIndex  the column index.
 445:      * 
 446:      * @see #removeColumn(Comparable)
 447:      * @see #removeRow(int)
 448:      */
 449:     public void removeColumn(int columnIndex) {
 450:         Comparable columnKey = getColumnKey(columnIndex);
 451:         removeColumn(columnKey);
 452:     }
 453: 
 454:     /**
 455:      * Removes a column from the table.
 456:      *
 457:      * @param columnKey  the column key (<code>null</code> not permitted).
 458:      * 
 459:      * @throws UnknownKeyException if the table does not contain a column with
 460:      *     the specified key.
 461:      * @throws IllegalArgumentException if <code>columnKey</code> is 
 462:      *     <code>null</code>.
 463:      * 
 464:      * @see #removeColumn(int)
 465:      * @see #removeRow(Comparable)
 466:      */
 467:     public void removeColumn(Comparable columnKey) {
 468:         if (columnKey == null) {
 469:             throw new IllegalArgumentException("Null 'columnKey' argument.");
 470:         }
 471:         if (!this.columnKeys.contains(columnKey)) {
 472:             throw new UnknownKeyException("Unknown key: " + columnKey);
 473:         }
 474:         Iterator iterator = this.rows.iterator();
 475:         while (iterator.hasNext()) {
 476:             DefaultKeyedValues rowData = (DefaultKeyedValues) iterator.next();
 477:             int index = rowData.getIndex(columnKey);
 478:             if (index >= 0) {
 479:                 rowData.removeValue(columnKey);
 480:             }
 481:         }
 482:         this.columnKeys.remove(columnKey);
 483:     }
 484: 
 485:     /**
 486:      * Clears all the data and associated keys.
 487:      */
 488:     public void clear() {
 489:         this.rowKeys.clear();
 490:         this.columnKeys.clear();
 491:         this.rows.clear();
 492:     }
 493:     
 494:     /**
 495:      * Tests if this object is equal to another.
 496:      *
 497:      * @param o  the other object (<code>null</code> permitted).
 498:      *
 499:      * @return A boolean.
 500:      */
 501:     public boolean equals(Object o) {
 502: 
 503:         if (o == null) {
 504:             return false;
 505:         }
 506:         if (o == this) {
 507:             return true;
 508:         }
 509: 
 510:         if (!(o instanceof KeyedValues2D)) {
 511:             return false;
 512:         }
 513:         KeyedValues2D kv2D = (KeyedValues2D) o;
 514:         if (!getRowKeys().equals(kv2D.getRowKeys())) {
 515:             return false;
 516:         }
 517:         if (!getColumnKeys().equals(kv2D.getColumnKeys())) {
 518:             return false;
 519:         }
 520:         int rowCount = getRowCount();
 521:         if (rowCount != kv2D.getRowCount()) {
 522:             return false;
 523:         }
 524: 
 525:         int colCount = getColumnCount();
 526:         if (colCount != kv2D.getColumnCount()) {
 527:             return false;
 528:         }
 529: 
 530:         for (int r = 0; r < rowCount; r++) {
 531:             for (int c = 0; c < colCount; c++) {
 532:                 Number v1 = getValue(r, c);
 533:                 Number v2 = kv2D.getValue(r, c);
 534:                 if (v1 == null) {
 535:                     if (v2 != null) {
 536:                         return false;
 537:                     }
 538:                 }
 539:                 else {
 540:                     if (!v1.equals(v2)) {
 541:                         return false;
 542:                     }
 543:                 }
 544:             }
 545:         }
 546:         return true;
 547:     }
 548: 
 549:     /**
 550:      * Returns a hash code.
 551:      * 
 552:      * @return A hash code.
 553:      */
 554:     public int hashCode() {
 555:         int result;
 556:         result = this.rowKeys.hashCode();
 557:         result = 29 * result + this.columnKeys.hashCode();
 558:         result = 29 * result + this.rows.hashCode();
 559:         return result;
 560:     }
 561: 
 562:     /**
 563:      * Returns a clone.
 564:      * 
 565:      * @return A clone.
 566:      * 
 567:      * @throws CloneNotSupportedException  this class will not throw this 
 568:      *         exception, but subclasses (if any) might.
 569:      */
 570:     public Object clone() throws CloneNotSupportedException {
 571:         DefaultKeyedValues2D clone = (DefaultKeyedValues2D) super.clone();
 572:         // for the keys, a shallow copy should be fine because keys
 573:         // should be immutable...
 574:         clone.columnKeys = new java.util.ArrayList(this.columnKeys);
 575:         clone.rowKeys = new java.util.ArrayList(this.rowKeys);
 576:         
 577:         // but the row data requires a deep copy
 578:         clone.rows = (List) ObjectUtilities.deepClone(this.rows);
 579:         return clone;
 580:     }
 581: 
 582: }