Source for org.jfree.data.gantt.SlidingGanttCategoryDataset

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2008, 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:  * SlidingGanttCategoryDataset.java
  29:  * --------------------------------
  30:  * (C) Copyright 2008, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 09-May-2008 : Version 1 (DG);
  38:  *
  39:  */
  40: 
  41: package org.jfree.data.gantt;
  42: 
  43: import java.util.Collections;
  44: import java.util.List;
  45: 
  46: import org.jfree.data.UnknownKeyException;
  47: import org.jfree.data.general.AbstractDataset;
  48: import org.jfree.data.general.DatasetChangeEvent;
  49: import org.jfree.util.PublicCloneable;
  50: 
  51: /**
  52:  * A {@link GanttCategoryDataset} implementation that presents a subset of the
  53:  * categories in an underlying dataset.  The index of the first "visible"
  54:  * category can be modified, which provides a means of "sliding" through
  55:  * the categories in the underlying dataset.
  56:  *
  57:  * @since 1.0.10
  58:  */
  59: public class SlidingGanttCategoryDataset extends AbstractDataset
  60:         implements GanttCategoryDataset {
  61: 
  62:     /** The underlying dataset. */
  63:     private GanttCategoryDataset underlying;
  64: 
  65:     /** The index of the first category to present. */
  66:     private int firstCategoryIndex;
  67: 
  68:     /** The maximum number of categories to present. */
  69:     private int maximumCategoryCount;
  70: 
  71:     /**
  72:      * Creates a new instance.
  73:      *
  74:      * @param underlying  the underlying dataset (<code>null</code> not
  75:      *     permitted).
  76:      * @param firstColumn  the index of the first visible column from the
  77:      *     underlying dataset.
  78:      * @param maxColumns  the maximumColumnCount.
  79:      */
  80:     public SlidingGanttCategoryDataset(GanttCategoryDataset underlying,
  81:             int firstColumn, int maxColumns) {
  82:         this.underlying = underlying;
  83:         this.firstCategoryIndex = firstColumn;
  84:         this.maximumCategoryCount = maxColumns;
  85:     }
  86: 
  87:     /**
  88:      * Returns the underlying dataset that was supplied to the constructor.
  89:      *
  90:      * @return The underlying dataset (never <code>null</code>).
  91:      */
  92:     public GanttCategoryDataset getUnderlyingDataset() {
  93:         return this.underlying;
  94:     }
  95: 
  96:     /**
  97:      * Returns the index of the first visible category.
  98:      *
  99:      * @return The index.
 100:      *
 101:      * @see #setFirstCategoryIndex(int)
 102:      */
 103:     public int getFirstCategoryIndex() {
 104:         return this.firstCategoryIndex;
 105:     }
 106: 
 107:     /**
 108:      * Sets the index of the first category that should be used from the
 109:      * underlying dataset, and sends a {@link DatasetChangeEvent} to all
 110:      * registered listeners.
 111:      *
 112:      * @param first  the index.
 113:      *
 114:      * @see #getFirstCategoryIndex()
 115:      */
 116:     public void setFirstCategoryIndex(int first) {
 117:         if (first < 0 || first >= this.underlying.getColumnCount()) {
 118:             throw new IllegalArgumentException("Invalid index.");
 119:         }
 120:         this.firstCategoryIndex = first;
 121:         fireDatasetChanged();
 122:     }
 123: 
 124:     /**
 125:      * Returns the maximum category count.
 126:      *
 127:      * @return The maximum category count.
 128:      *
 129:      * @see #setMaximumCategoryCount(int)
 130:      */
 131:     public int getMaximumCategoryCount() {
 132:         return this.maximumCategoryCount;
 133:     }
 134: 
 135:     /**
 136:      * Sets the maximum category count and sends a {@link DatasetChangeEvent}
 137:      * to all registered listeners.
 138:      *
 139:      * @param max  the maximum.
 140:      *
 141:      * @see #getMaximumCategoryCount()
 142:      */
 143:     public void setMaximumCategoryCount(int max) {
 144:         if (max < 0) {
 145:             throw new IllegalArgumentException("Requires 'max' >= 0.");
 146:         }
 147:         this.maximumCategoryCount = max;
 148:         fireDatasetChanged();
 149:     }
 150: 
 151:     /**
 152:      * Returns the index of the last column for this dataset, or -1.
 153:      *
 154:      * @return The index.
 155:      */
 156:     private int lastCategoryIndex() {
 157:         if (this.maximumCategoryCount == 0) {
 158:             return -1;
 159:         }
 160:         return Math.min(this.firstCategoryIndex + this.maximumCategoryCount,
 161:                 this.underlying.getColumnCount()) - 1;
 162:     }
 163: 
 164:     /**
 165:      * Returns the index for the specified column key.
 166:      *
 167:      * @param key  the key.
 168:      *
 169:      * @return The column index, or -1 if the key is not recognised.
 170:      */
 171:     public int getColumnIndex(Comparable key) {
 172:         int index = this.underlying.getColumnIndex(key);
 173:         if (index >= this.firstCategoryIndex && index <= lastCategoryIndex()) {
 174:             return index - this.firstCategoryIndex;
 175:         }
 176:         return -1;  // we didn't find the key
 177:     }
 178: 
 179:     /**
 180:      * Returns the column key for a given index.
 181:      *
 182:      * @param column  the column index (zero-based).
 183:      *
 184:      * @return The column key.
 185:      *
 186:      * @throws IndexOutOfBoundsException if <code>row</code> is out of bounds.
 187:      */
 188:     public Comparable getColumnKey(int column) {
 189:         return this.underlying.getColumnKey(column + this.firstCategoryIndex);
 190:     }
 191: 
 192:     /**
 193:      * Returns the column keys.
 194:      *
 195:      * @return The keys.
 196:      *
 197:      * @see #getColumnKey(int)
 198:      */
 199:     public List getColumnKeys() {
 200:         List result = new java.util.ArrayList();
 201:         int last = lastCategoryIndex();
 202:         for (int i = this.firstCategoryIndex; i < last; i++) {
 203:             result.add(this.underlying.getColumnKey(i));
 204:         }
 205:         return Collections.unmodifiableList(result);
 206:     }
 207: 
 208:     /**
 209:      * Returns the row index for a given key.
 210:      *
 211:      * @param key  the row key.
 212:      *
 213:      * @return The row index, or <code>-1</code> if the key is unrecognised.
 214:      */
 215:     public int getRowIndex(Comparable key) {
 216:         return this.underlying.getRowIndex(key);
 217:     }
 218: 
 219:     /**
 220:      * Returns the row key for a given index.
 221:      *
 222:      * @param row  the row index (zero-based).
 223:      *
 224:      * @return The row key.
 225:      *
 226:      * @throws IndexOutOfBoundsException if <code>row</code> is out of bounds.
 227:      */
 228:     public Comparable getRowKey(int row) {
 229:         return this.underlying.getRowKey(row);
 230:     }
 231: 
 232:     /**
 233:      * Returns the row keys.
 234:      *
 235:      * @return The keys.
 236:      */
 237:     public List getRowKeys() {
 238:         return this.underlying.getRowKeys();
 239:     }
 240: 
 241:     /**
 242:      * Returns the value for a pair of keys.
 243:      *
 244:      * @param rowKey  the row key (<code>null</code> not permitted).
 245:      * @param columnKey  the column key (<code>null</code> not permitted).
 246:      *
 247:      * @return The value (possibly <code>null</code>).
 248:      *
 249:      * @throws UnknownKeyException if either key is not defined in the dataset.
 250:      */
 251:     public Number getValue(Comparable rowKey, Comparable columnKey) {
 252:         int r = getRowIndex(rowKey);
 253:         int c = getColumnIndex(columnKey);
 254:         if (c != -1) {
 255:             return this.underlying.getValue(r, c + this.firstCategoryIndex);
 256:         }
 257:         else {
 258:             throw new UnknownKeyException("Unknown columnKey: " + columnKey);
 259:         }
 260:     }
 261: 
 262:     /**
 263:      * Returns the number of columns in the table.
 264:      *
 265:      * @return The column count.
 266:      */
 267:     public int getColumnCount() {
 268:         int last = lastCategoryIndex();
 269:         if (last == -1) {
 270:             return 0;
 271:         }
 272:         else {
 273:             return Math.max(last - this.firstCategoryIndex + 1, 0);
 274:         }
 275:     }
 276: 
 277:     /**
 278:      * Returns the number of rows in the table.
 279:      *
 280:      * @return The row count.
 281:      */
 282:     public int getRowCount() {
 283:         return this.underlying.getRowCount();
 284:     }
 285: 
 286:     /**
 287:      * Returns a value from the table.
 288:      *
 289:      * @param row  the row index (zero-based).
 290:      * @param column  the column index (zero-based).
 291:      *
 292:      * @return The value (possibly <code>null</code>).
 293:      */
 294:     public Number getValue(int row, int column) {
 295:         return this.underlying.getValue(row, column + this.firstCategoryIndex);
 296:     }
 297: 
 298:     /**
 299:      * Returns the percent complete for a given item.
 300:      *
 301:      * @param rowKey  the row key.
 302:      * @param columnKey  the column key.
 303:      *
 304:      * @return The percent complete.
 305:      */
 306:     public Number getPercentComplete(Comparable rowKey, Comparable columnKey) {
 307:         int r = getRowIndex(rowKey);
 308:         int c = getColumnIndex(columnKey);
 309:         if (c != -1) {
 310:             return this.underlying.getPercentComplete(r,
 311:                     c + this.firstCategoryIndex);
 312:         }
 313:         else {
 314:             throw new UnknownKeyException("Unknown columnKey: " + columnKey);
 315:         }
 316:     }
 317: 
 318:     /**
 319:      * Returns the percentage complete value of a sub-interval for a given item.
 320:      *
 321:      * @param rowKey  the row key.
 322:      * @param columnKey  the column key.
 323:      * @param subinterval  the sub-interval.
 324:      *
 325:      * @return The percent complete value (possibly <code>null</code>).
 326:      *
 327:      * @see #getPercentComplete(int, int, int)
 328:      */
 329:     public Number getPercentComplete(Comparable rowKey, Comparable columnKey,
 330:             int subinterval) {
 331:         int r = getRowIndex(rowKey);
 332:         int c = getColumnIndex(columnKey);
 333:         if (c != -1) {
 334:             return this.underlying.getPercentComplete(r,
 335:                     c + this.firstCategoryIndex, subinterval);
 336:         }
 337:         else {
 338:             throw new UnknownKeyException("Unknown columnKey: " + columnKey);
 339:         }
 340:     }
 341: 
 342:     /**
 343:      * Returns the end value of a sub-interval for a given item.
 344:      *
 345:      * @param rowKey  the row key.
 346:      * @param columnKey  the column key.
 347:      * @param subinterval  the sub-interval.
 348:      *
 349:      * @return The end value (possibly <code>null</code>).
 350:      *
 351:      * @see #getStartValue(Comparable, Comparable, int)
 352:      */
 353:     public Number getEndValue(Comparable rowKey, Comparable columnKey,
 354:             int subinterval) {
 355:         int r = getRowIndex(rowKey);
 356:         int c = getColumnIndex(columnKey);
 357:         if (c != -1) {
 358:             return this.underlying.getEndValue(r,
 359:                     c + this.firstCategoryIndex, subinterval);
 360:         }
 361:         else {
 362:             throw new UnknownKeyException("Unknown columnKey: " + columnKey);
 363:         }
 364:     }
 365: 
 366:     /**
 367:      * Returns the end value of a sub-interval for a given item.
 368:      *
 369:      * @param row  the row index (zero-based).
 370:      * @param column  the column index (zero-based).
 371:      * @param subinterval  the sub-interval.
 372:      *
 373:      * @return The end value (possibly <code>null</code>).
 374:      *
 375:      * @see #getStartValue(int, int, int)
 376:      */
 377:     public Number getEndValue(int row, int column, int subinterval) {
 378:         return this.underlying.getEndValue(row,
 379:                 column + this.firstCategoryIndex, subinterval);
 380:     }
 381: 
 382:     /**
 383:      * Returns the percent complete for a given item.
 384:      *
 385:      * @param series  the row index (zero-based).
 386:      * @param category  the column index (zero-based).
 387:      *
 388:      * @return The percent complete.
 389:      */
 390:     public Number getPercentComplete(int series, int category) {
 391:         return this.underlying.getPercentComplete(series,
 392:                 category + this.firstCategoryIndex);
 393:     }
 394: 
 395:     /**
 396:      * Returns the percentage complete value of a sub-interval for a given item.
 397:      *
 398:      * @param row  the row index (zero-based).
 399:      * @param column  the column index (zero-based).
 400:      * @param subinterval  the sub-interval.
 401:      *
 402:      * @return The percent complete value (possibly <code>null</code>).
 403:      *
 404:      * @see #getPercentComplete(Comparable, Comparable, int)
 405:      */
 406:     public Number getPercentComplete(int row, int column, int subinterval) {
 407:         return this.underlying.getPercentComplete(row,
 408:                 column + this.firstCategoryIndex, subinterval);
 409:     }
 410: 
 411:     /**
 412:      * Returns the start value of a sub-interval for a given item.
 413:      *
 414:      * @param rowKey  the row key.
 415:      * @param columnKey  the column key.
 416:      * @param subinterval  the sub-interval.
 417:      *
 418:      * @return The start value (possibly <code>null</code>).
 419:      *
 420:      * @see #getEndValue(Comparable, Comparable, int)
 421:      */
 422:     public Number getStartValue(Comparable rowKey, Comparable columnKey,
 423:             int subinterval) {
 424:         int r = getRowIndex(rowKey);
 425:         int c = getColumnIndex(columnKey);
 426:         if (c != -1) {
 427:             return this.underlying.getStartValue(r,
 428:                     c + this.firstCategoryIndex, subinterval);
 429:         }
 430:         else {
 431:             throw new UnknownKeyException("Unknown columnKey: " + columnKey);
 432:         }
 433:     }
 434: 
 435:     /**
 436:      * Returns the start value of a sub-interval for a given item.
 437:      *
 438:      * @param row  the row index (zero-based).
 439:      * @param column  the column index (zero-based).
 440:      * @param subinterval  the sub-interval index (zero-based).
 441:      *
 442:      * @return The start value (possibly <code>null</code>).
 443:      *
 444:      * @see #getEndValue(int, int, int)
 445:      */
 446:     public Number getStartValue(int row, int column, int subinterval) {
 447:         return this.underlying.getStartValue(row,
 448:                 column + this.firstCategoryIndex, subinterval);
 449:     }
 450: 
 451:     /**
 452:      * Returns the number of sub-intervals for a given item.
 453:      *
 454:      * @param rowKey  the row key.
 455:      * @param columnKey  the column key.
 456:      *
 457:      * @return The sub-interval count.
 458:      *
 459:      * @see #getSubIntervalCount(int, int)
 460:      */
 461:     public int getSubIntervalCount(Comparable rowKey, Comparable columnKey) {
 462:         int r = getRowIndex(rowKey);
 463:         int c = getColumnIndex(columnKey);
 464:         if (c != -1) {
 465:             return this.underlying.getSubIntervalCount(r,
 466:                     c + this.firstCategoryIndex);
 467:         }
 468:         else {
 469:             throw new UnknownKeyException("Unknown columnKey: " + columnKey);
 470:         }
 471:     }
 472: 
 473:     /**
 474:      * Returns the number of sub-intervals for a given item.
 475:      *
 476:      * @param row  the row index (zero-based).
 477:      * @param column  the column index (zero-based).
 478:      *
 479:      * @return The sub-interval count.
 480:      *
 481:      * @see #getSubIntervalCount(Comparable, Comparable)
 482:      */
 483:     public int getSubIntervalCount(int row, int column) {
 484:         return this.underlying.getSubIntervalCount(row,
 485:                 column + this.firstCategoryIndex);
 486:     }
 487: 
 488:     /**
 489:      * Returns the start value for the interval for a given series and category.
 490:      *
 491:      * @param rowKey  the series key.
 492:      * @param columnKey  the category key.
 493:      *
 494:      * @return The start value (possibly <code>null</code>).
 495:      *
 496:      * @see #getEndValue(Comparable, Comparable)
 497:      */
 498:     public Number getStartValue(Comparable rowKey, Comparable columnKey) {
 499:         int r = getRowIndex(rowKey);
 500:         int c = getColumnIndex(columnKey);
 501:         if (c != -1) {
 502:             return this.underlying.getStartValue(r, c + this.firstCategoryIndex);
 503:         }
 504:         else {
 505:             throw new UnknownKeyException("Unknown columnKey: " + columnKey);
 506:         }
 507:     }
 508: 
 509:     /**
 510:      * Returns the start value for the interval for a given series and category.
 511:      *
 512:      * @param row  the series (zero-based index).
 513:      * @param column  the category (zero-based index).
 514:      *
 515:      * @return The start value (possibly <code>null</code>).
 516:      *
 517:      * @see #getEndValue(int, int)
 518:      */
 519:     public Number getStartValue(int row, int column) {
 520:         return this.underlying.getStartValue(row,
 521:                 column + this.firstCategoryIndex);
 522:     }
 523: 
 524:     /**
 525:      * Returns the end value for the interval for a given series and category.
 526:      *
 527:      * @param rowKey  the series key.
 528:      * @param columnKey  the category key.
 529:      *
 530:      * @return The end value (possibly <code>null</code>).
 531:      *
 532:      * @see #getStartValue(Comparable, Comparable)
 533:      */
 534:     public Number getEndValue(Comparable rowKey, Comparable columnKey) {
 535:         int r = getRowIndex(rowKey);
 536:         int c = getColumnIndex(columnKey);
 537:         if (c != -1) {
 538:             return this.underlying.getEndValue(r, c + this.firstCategoryIndex);
 539:         }
 540:         else {
 541:             throw new UnknownKeyException("Unknown columnKey: " + columnKey);
 542:         }
 543:     }
 544: 
 545:     /**
 546:      * Returns the end value for the interval for a given series and category.
 547:      *
 548:      * @param series  the series (zero-based index).
 549:      * @param category  the category (zero-based index).
 550:      *
 551:      * @return The end value (possibly <code>null</code>).
 552:      */
 553:     public Number getEndValue(int series, int category) {
 554:         return this.underlying.getEndValue(series,
 555:                 category + this.firstCategoryIndex);
 556:     }
 557: 
 558:     /**
 559:      * Tests this <code>SlidingCategoryDataset</code> for equality with an
 560:      * arbitrary object.
 561:      *
 562:      * @param obj  the object (<code>null</code> permitted).
 563:      *
 564:      * @return A boolean.
 565:      */
 566:     public boolean equals(Object obj) {
 567:         if (obj == this) {
 568:             return true;
 569:         }
 570:         if (!(obj instanceof SlidingGanttCategoryDataset)) {
 571:             return false;
 572:         }
 573:         SlidingGanttCategoryDataset that = (SlidingGanttCategoryDataset) obj;
 574:         if (this.firstCategoryIndex != that.firstCategoryIndex) {
 575:             return false;
 576:         }
 577:         if (this.maximumCategoryCount != that.maximumCategoryCount) {
 578:             return false;
 579:         }
 580:         if (!this.underlying.equals(that.underlying)) {
 581:             return false;
 582:         }
 583:         return true;
 584:     }
 585: 
 586:     /**
 587:      * Returns an independent copy of the dataset.  Note that:
 588:      * <ul>
 589:      * <li>the underlying dataset is only cloned if it implements the
 590:      * {@link PublicCloneable} interface;</li>
 591:      * <li>the listeners registered with this dataset are not carried over to
 592:      * the cloned dataset.</li>
 593:      * </ul>
 594:      *
 595:      * @return An independent copy of the dataset.
 596:      *
 597:      * @throws CloneNotSupportedException if the dataset cannot be cloned for
 598:      *         any reason.
 599:      */
 600:     public Object clone() throws CloneNotSupportedException {
 601:         SlidingGanttCategoryDataset clone
 602:                 = (SlidingGanttCategoryDataset) super.clone();
 603:         if (this.underlying instanceof PublicCloneable) {
 604:             PublicCloneable pc = (PublicCloneable) this.underlying;
 605:             clone.underlying = (GanttCategoryDataset) pc.clone();
 606:         }
 607:         return clone;
 608:     }
 609: 
 610: }