001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * -------------------------------- 028 * SlidingGanttCategoryDataset.java 029 * -------------------------------- 030 * (C) Copyright 2008-present, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); 034 * 035 */ 036 037package org.jfree.data.gantt; 038 039import java.util.Collections; 040import java.util.List; 041import java.util.Objects; 042import org.jfree.chart.util.PublicCloneable; 043 044import org.jfree.data.UnknownKeyException; 045import org.jfree.data.general.AbstractDataset; 046import org.jfree.data.general.DatasetChangeEvent; 047 048/** 049 * A {@link GanttCategoryDataset} implementation that presents a subset of the 050 * categories in an underlying dataset. The index of the first "visible" 051 * category can be modified, which provides a means of "sliding" through 052 * the categories in the underlying dataset. 053 */ 054public class SlidingGanttCategoryDataset extends AbstractDataset 055 implements GanttCategoryDataset { 056 057 /** The underlying dataset. */ 058 private GanttCategoryDataset underlying; 059 060 /** The index of the first category to present. */ 061 private int firstCategoryIndex; 062 063 /** The maximum number of categories to present. */ 064 private int maximumCategoryCount; 065 066 /** 067 * Creates a new instance. 068 * 069 * @param underlying the underlying dataset ({@code null} not 070 * permitted). 071 * @param firstColumn the index of the first visible column from the 072 * underlying dataset. 073 * @param maxColumns the maximumColumnCount. 074 */ 075 public SlidingGanttCategoryDataset(GanttCategoryDataset underlying, 076 int firstColumn, int maxColumns) { 077 this.underlying = underlying; 078 this.firstCategoryIndex = firstColumn; 079 this.maximumCategoryCount = maxColumns; 080 } 081 082 /** 083 * Returns the underlying dataset that was supplied to the constructor. 084 * 085 * @return The underlying dataset (never {@code null}). 086 */ 087 public GanttCategoryDataset getUnderlyingDataset() { 088 return this.underlying; 089 } 090 091 /** 092 * Returns the index of the first visible category. 093 * 094 * @return The index. 095 * 096 * @see #setFirstCategoryIndex(int) 097 */ 098 public int getFirstCategoryIndex() { 099 return this.firstCategoryIndex; 100 } 101 102 /** 103 * Sets the index of the first category that should be used from the 104 * underlying dataset, and sends a {@link DatasetChangeEvent} to all 105 * registered listeners. 106 * 107 * @param first the index. 108 * 109 * @see #getFirstCategoryIndex() 110 */ 111 public void setFirstCategoryIndex(int first) { 112 if (first < 0 || first >= this.underlying.getColumnCount()) { 113 throw new IllegalArgumentException("Invalid index."); 114 } 115 this.firstCategoryIndex = first; 116 fireDatasetChanged(); 117 } 118 119 /** 120 * Returns the maximum category count. 121 * 122 * @return The maximum category count. 123 * 124 * @see #setMaximumCategoryCount(int) 125 */ 126 public int getMaximumCategoryCount() { 127 return this.maximumCategoryCount; 128 } 129 130 /** 131 * Sets the maximum category count and sends a {@link DatasetChangeEvent} 132 * to all registered listeners. 133 * 134 * @param max the maximum. 135 * 136 * @see #getMaximumCategoryCount() 137 */ 138 public void setMaximumCategoryCount(int max) { 139 if (max < 0) { 140 throw new IllegalArgumentException("Requires 'max' >= 0."); 141 } 142 this.maximumCategoryCount = max; 143 fireDatasetChanged(); 144 } 145 146 /** 147 * Returns the index of the last column for this dataset, or -1. 148 * 149 * @return The index. 150 */ 151 private int lastCategoryIndex() { 152 if (this.maximumCategoryCount == 0) { 153 return -1; 154 } 155 return Math.min(this.firstCategoryIndex + this.maximumCategoryCount, 156 this.underlying.getColumnCount()) - 1; 157 } 158 159 /** 160 * Returns the index for the specified column key. 161 * 162 * @param key the key. 163 * 164 * @return The column index, or -1 if the key is not recognised. 165 */ 166 @Override 167 public int getColumnIndex(Comparable key) { 168 int index = this.underlying.getColumnIndex(key); 169 if (index >= this.firstCategoryIndex && index <= lastCategoryIndex()) { 170 return index - this.firstCategoryIndex; 171 } 172 return -1; // we didn't find the key 173 } 174 175 /** 176 * Returns the column key for a given index. 177 * 178 * @param column the column index (zero-based). 179 * 180 * @return The column key. 181 * 182 * @throws IndexOutOfBoundsException if {@code row} is out of bounds. 183 */ 184 @Override 185 public Comparable getColumnKey(int column) { 186 return this.underlying.getColumnKey(column + this.firstCategoryIndex); 187 } 188 189 /** 190 * Returns the column keys. 191 * 192 * @return The keys. 193 * 194 * @see #getColumnKey(int) 195 */ 196 @Override 197 public List getColumnKeys() { 198 List result = new java.util.ArrayList(); 199 int last = lastCategoryIndex(); 200 for (int i = this.firstCategoryIndex; i < last; i++) { 201 result.add(this.underlying.getColumnKey(i)); 202 } 203 return Collections.unmodifiableList(result); 204 } 205 206 /** 207 * Returns the row index for a given key. 208 * 209 * @param key the row key. 210 * 211 * @return The row index, or {@code -1} if the key is unrecognised. 212 */ 213 @Override 214 public int getRowIndex(Comparable key) { 215 return this.underlying.getRowIndex(key); 216 } 217 218 /** 219 * Returns the row key for a given index. 220 * 221 * @param row the row index (zero-based). 222 * 223 * @return The row key. 224 * 225 * @throws IndexOutOfBoundsException if {@code row} is out of bounds. 226 */ 227 @Override 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 @Override 238 public List getRowKeys() { 239 return this.underlying.getRowKeys(); 240 } 241 242 /** 243 * Returns the value for a pair of keys. 244 * 245 * @param rowKey the row key ({@code null} not permitted). 246 * @param columnKey the column key ({@code null} not permitted). 247 * 248 * @return The value (possibly {@code null}). 249 * 250 * @throws UnknownKeyException if either key is not defined in the dataset. 251 */ 252 @Override 253 public Number getValue(Comparable rowKey, Comparable columnKey) { 254 int r = getRowIndex(rowKey); 255 int c = getColumnIndex(columnKey); 256 if (c != -1) { 257 return this.underlying.getValue(r, c + this.firstCategoryIndex); 258 } 259 else { 260 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 261 } 262 } 263 264 /** 265 * Returns the number of columns in the table. 266 * 267 * @return The column count. 268 */ 269 @Override 270 public int getColumnCount() { 271 int last = lastCategoryIndex(); 272 if (last == -1) { 273 return 0; 274 } 275 else { 276 return Math.max(last - this.firstCategoryIndex + 1, 0); 277 } 278 } 279 280 /** 281 * Returns the number of rows in the table. 282 * 283 * @return The row count. 284 */ 285 @Override 286 public int getRowCount() { 287 return this.underlying.getRowCount(); 288 } 289 290 /** 291 * Returns a value from the table. 292 * 293 * @param row the row index (zero-based). 294 * @param column the column index (zero-based). 295 * 296 * @return The value (possibly {@code null}). 297 */ 298 @Override 299 public Number getValue(int row, int column) { 300 return this.underlying.getValue(row, column + this.firstCategoryIndex); 301 } 302 303 /** 304 * Returns the percent complete for a given item. 305 * 306 * @param rowKey the row key. 307 * @param columnKey the column key. 308 * 309 * @return The percent complete. 310 */ 311 @Override 312 public Number getPercentComplete(Comparable rowKey, Comparable columnKey) { 313 int r = getRowIndex(rowKey); 314 int c = getColumnIndex(columnKey); 315 if (c != -1) { 316 return this.underlying.getPercentComplete(r, 317 c + this.firstCategoryIndex); 318 } 319 else { 320 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 321 } 322 } 323 324 /** 325 * Returns the percentage complete value of a sub-interval for a given item. 326 * 327 * @param rowKey the row key. 328 * @param columnKey the column key. 329 * @param subinterval the sub-interval. 330 * 331 * @return The percent complete value (possibly {@code null}). 332 * 333 * @see #getPercentComplete(int, int, int) 334 */ 335 @Override 336 public Number getPercentComplete(Comparable rowKey, Comparable columnKey, 337 int subinterval) { 338 int r = getRowIndex(rowKey); 339 int c = getColumnIndex(columnKey); 340 if (c != -1) { 341 return this.underlying.getPercentComplete(r, 342 c + this.firstCategoryIndex, subinterval); 343 } 344 else { 345 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 346 } 347 } 348 349 /** 350 * Returns the end value of a sub-interval for a given item. 351 * 352 * @param rowKey the row key. 353 * @param columnKey the column key. 354 * @param subinterval the sub-interval. 355 * 356 * @return The end value (possibly {@code null}). 357 * 358 * @see #getStartValue(Comparable, Comparable, int) 359 */ 360 @Override 361 public Number getEndValue(Comparable rowKey, Comparable columnKey, 362 int subinterval) { 363 int r = getRowIndex(rowKey); 364 int c = getColumnIndex(columnKey); 365 if (c != -1) { 366 return this.underlying.getEndValue(r, 367 c + this.firstCategoryIndex, subinterval); 368 } 369 else { 370 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 371 } 372 } 373 374 /** 375 * Returns the end value of a sub-interval for a given item. 376 * 377 * @param row the row index (zero-based). 378 * @param column the column index (zero-based). 379 * @param subinterval the sub-interval. 380 * 381 * @return The end value (possibly {@code null}). 382 * 383 * @see #getStartValue(int, int, int) 384 */ 385 @Override 386 public Number getEndValue(int row, int column, int subinterval) { 387 return this.underlying.getEndValue(row, 388 column + this.firstCategoryIndex, subinterval); 389 } 390 391 /** 392 * Returns the percent complete for a given item. 393 * 394 * @param series the row index (zero-based). 395 * @param category the column index (zero-based). 396 * 397 * @return The percent complete. 398 */ 399 @Override 400 public Number getPercentComplete(int series, int category) { 401 return this.underlying.getPercentComplete(series, 402 category + this.firstCategoryIndex); 403 } 404 405 /** 406 * Returns the percentage complete value of a sub-interval for a given item. 407 * 408 * @param row the row index (zero-based). 409 * @param column the column index (zero-based). 410 * @param subinterval the sub-interval. 411 * 412 * @return The percent complete value (possibly {@code null}). 413 * 414 * @see #getPercentComplete(Comparable, Comparable, int) 415 */ 416 @Override 417 public Number getPercentComplete(int row, int column, int subinterval) { 418 return this.underlying.getPercentComplete(row, 419 column + this.firstCategoryIndex, subinterval); 420 } 421 422 /** 423 * Returns the start value of a sub-interval for a given item. 424 * 425 * @param rowKey the row key. 426 * @param columnKey the column key. 427 * @param subinterval the sub-interval. 428 * 429 * @return The start value (possibly {@code null}). 430 * 431 * @see #getEndValue(Comparable, Comparable, int) 432 */ 433 @Override 434 public Number getStartValue(Comparable rowKey, Comparable columnKey, 435 int subinterval) { 436 int r = getRowIndex(rowKey); 437 int c = getColumnIndex(columnKey); 438 if (c != -1) { 439 return this.underlying.getStartValue(r, 440 c + this.firstCategoryIndex, subinterval); 441 } 442 else { 443 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 444 } 445 } 446 447 /** 448 * Returns the start value of a sub-interval for a given item. 449 * 450 * @param row the row index (zero-based). 451 * @param column the column index (zero-based). 452 * @param subinterval the sub-interval index (zero-based). 453 * 454 * @return The start value (possibly {@code null}). 455 * 456 * @see #getEndValue(int, int, int) 457 */ 458 @Override 459 public Number getStartValue(int row, int column, int subinterval) { 460 return this.underlying.getStartValue(row, 461 column + this.firstCategoryIndex, subinterval); 462 } 463 464 /** 465 * Returns the number of sub-intervals for a given item. 466 * 467 * @param rowKey the row key. 468 * @param columnKey the column key. 469 * 470 * @return The sub-interval count. 471 * 472 * @see #getSubIntervalCount(int, int) 473 */ 474 @Override 475 public int getSubIntervalCount(Comparable rowKey, Comparable columnKey) { 476 int r = getRowIndex(rowKey); 477 int c = getColumnIndex(columnKey); 478 if (c != -1) { 479 return this.underlying.getSubIntervalCount(r, 480 c + this.firstCategoryIndex); 481 } 482 else { 483 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 484 } 485 } 486 487 /** 488 * Returns the number of sub-intervals for a given item. 489 * 490 * @param row the row index (zero-based). 491 * @param column the column index (zero-based). 492 * 493 * @return The sub-interval count. 494 * 495 * @see #getSubIntervalCount(Comparable, Comparable) 496 */ 497 @Override 498 public int getSubIntervalCount(int row, int column) { 499 return this.underlying.getSubIntervalCount(row, 500 column + this.firstCategoryIndex); 501 } 502 503 /** 504 * Returns the start value for the interval for a given series and category. 505 * 506 * @param rowKey the series key. 507 * @param columnKey the category key. 508 * 509 * @return The start value (possibly {@code null}). 510 * 511 * @see #getEndValue(Comparable, Comparable) 512 */ 513 @Override 514 public Number getStartValue(Comparable rowKey, Comparable columnKey) { 515 int r = getRowIndex(rowKey); 516 int c = getColumnIndex(columnKey); 517 if (c != -1) { 518 return this.underlying.getStartValue(r, 519 c + this.firstCategoryIndex); 520 } 521 else { 522 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 523 } 524 } 525 526 /** 527 * Returns the start value for the interval for a given series and category. 528 * 529 * @param row the series (zero-based index). 530 * @param column the category (zero-based index). 531 * 532 * @return The start value (possibly {@code null}). 533 * 534 * @see #getEndValue(int, int) 535 */ 536 @Override 537 public Number getStartValue(int row, int column) { 538 return this.underlying.getStartValue(row, 539 column + this.firstCategoryIndex); 540 } 541 542 /** 543 * Returns the end value for the interval for a given series and category. 544 * 545 * @param rowKey the series key. 546 * @param columnKey the category key. 547 * 548 * @return The end value (possibly {@code null}). 549 * 550 * @see #getStartValue(Comparable, Comparable) 551 */ 552 @Override 553 public Number getEndValue(Comparable rowKey, Comparable columnKey) { 554 int r = getRowIndex(rowKey); 555 int c = getColumnIndex(columnKey); 556 if (c != -1) { 557 return this.underlying.getEndValue(r, c + this.firstCategoryIndex); 558 } 559 else { 560 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 561 } 562 } 563 564 /** 565 * Returns the end value for the interval for a given series and category. 566 * 567 * @param series the series (zero-based index). 568 * @param category the category (zero-based index). 569 * 570 * @return The end value (possibly {@code null}). 571 */ 572 @Override 573 public Number getEndValue(int series, int category) { 574 return this.underlying.getEndValue(series, 575 category + this.firstCategoryIndex); 576 } 577 578 /** 579 * Tests this {@code SlidingGanttCategoryDataset} instance for equality 580 * with an arbitrary object. 581 * 582 * @param obj the object ({@code null} permitted). 583 * 584 * @return A boolean. 585 */ 586 @Override 587 public boolean equals(Object obj) { 588 if (obj == this) { 589 return true; 590 } 591 if (!(obj instanceof SlidingGanttCategoryDataset)) { 592 return false; 593 } 594 SlidingGanttCategoryDataset that = (SlidingGanttCategoryDataset) obj; 595 if (this.firstCategoryIndex != that.firstCategoryIndex) { 596 return false; 597 } 598 if (this.maximumCategoryCount != that.maximumCategoryCount) { 599 return false; 600 } 601 if (!Objects.equals(this.underlying, that.underlying)) { 602 return false; 603 } 604 if (!that.canEqual(this)) { 605 return false; 606 } 607 return super.equals(obj); 608 } 609 610 /** 611 * Ensures symmetry between super/subclass implementations of equals. For 612 * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. 613 * 614 * @param other Object 615 * 616 * @return true ONLY if the parameter is THIS class type 617 */ 618 @Override 619 public boolean canEqual(Object other) { 620 // fix the "equals not symmetric" problem 621 return (other instanceof SlidingGanttCategoryDataset); 622 } 623 624 @Override 625 public int hashCode() { 626 int hash = super.hashCode(); // equals calls superclass, hashCode must also 627 hash = 23 * hash + Objects.hashCode(this.underlying); 628 hash = 23 * hash + this.firstCategoryIndex; 629 hash = 23 * hash + this.maximumCategoryCount; 630 return hash; 631 } 632 633 /** 634 * Returns an independent copy of the dataset. Note that: 635 * <ul> 636 * <li>the underlying dataset is only cloned if it implements the 637 * {@link PublicCloneable} interface;</li> 638 * <li>the listeners registered with this dataset are not carried over to 639 * the cloned dataset.</li> 640 * </ul> 641 * 642 * @return An independent copy of the dataset. 643 * 644 * @throws CloneNotSupportedException if the dataset cannot be cloned for 645 * any reason. 646 */ 647 @Override 648 public Object clone() throws CloneNotSupportedException { 649 SlidingGanttCategoryDataset clone 650 = (SlidingGanttCategoryDataset) super.clone(); 651 if (this.underlying instanceof PublicCloneable) { 652 PublicCloneable pc = (PublicCloneable) this.underlying; 653 clone.underlying = (GanttCategoryDataset) pc.clone(); 654 } 655 return clone; 656 } 657 658}