Source for org.jfree.data.DefaultKeyedValues2D

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