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