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