001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2022, 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 * TaskSeriesCollection.java 029 * ------------------------- 030 * (C) Copyright 2002-2022, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Thomas Schuster; 034 * 035 */ 036 037package org.jfree.data.gantt; 038 039import java.io.Serializable; 040import java.util.ArrayList; 041import java.util.List; 042import java.util.Objects; 043 044import org.jfree.chart.internal.CloneUtils; 045import org.jfree.chart.internal.Args; 046import org.jfree.chart.api.PublicCloneable; 047 048import org.jfree.data.general.AbstractSeriesDataset; 049import org.jfree.data.general.SeriesChangeEvent; 050import org.jfree.data.time.TimePeriod; 051 052/** 053 * A collection of {@link TaskSeries} objects. This class provides one 054 * implementation of the {@link GanttCategoryDataset} interface. 055 */ 056public class TaskSeriesCollection<R extends Comparable<R>, C extends Comparable<C>> 057 extends AbstractSeriesDataset<R> 058 implements GanttCategoryDataset<R, C>, Cloneable, PublicCloneable, 059 Serializable { 060 061 /** For serialization. */ 062 private static final long serialVersionUID = -2065799050738449903L; 063 064 /** 065 * Storage for aggregate task keys (the task description is used as the 066 * key). 067 */ 068 private List<C> keys; 069 070 /** Storage for the series. */ 071 private List<TaskSeries<R>> data; 072 073 /** 074 * Default constructor. 075 */ 076 public TaskSeriesCollection() { 077 this.keys = new ArrayList<>(); 078 this.data = new ArrayList<>(); 079 } 080 081 /** 082 * Returns a series from the collection. 083 * 084 * @param key the series key ({@code null} not permitted). 085 * 086 * @return The series. 087 * 088 * @since 1.0.1 089 */ 090 public TaskSeries<R> getSeries(R key) { 091 Args.nullNotPermitted(key, "key"); 092 TaskSeries<R> result = null; 093 int index = getRowIndex(key); 094 if (index >= 0) { 095 result = getSeries(index); 096 } 097 return result; 098 } 099 100 /** 101 * Returns a series from the collection. 102 * 103 * @param series the series index (zero-based). 104 * 105 * @return The series. 106 * 107 * @since 1.0.1 108 */ 109 public TaskSeries<R> getSeries(int series) { 110 Args.requireInRange(series, "series", 0, this.data.size() - 1); 111 return this.data.get(series); 112 } 113 114 /** 115 * Returns the number of series in the collection. 116 * 117 * @return The series count. 118 */ 119 @Override 120 public int getSeriesCount() { 121 return getRowCount(); 122 } 123 124 /** 125 * Returns the name of a series. 126 * 127 * @param series the series index (zero-based). 128 * 129 * @return The name of a series. 130 */ 131 @Override 132 public R getSeriesKey(int series) { 133 TaskSeries<R> ts = this.data.get(series); 134 return ts.getKey(); 135 } 136 137 /** 138 * Returns the number of rows (series) in the collection. 139 * 140 * @return The series count. 141 */ 142 @Override 143 public int getRowCount() { 144 return this.data.size(); 145 } 146 147 /** 148 * Returns the row keys. In this case, each series is a key. 149 * 150 * @return The row keys. 151 */ 152 @Override 153 public List<R> getRowKeys() { 154 List<R> result = new ArrayList<>(); 155 for (TaskSeries<R> series : this.data) { 156 result.add(series.getKey()); 157 } 158 return result; 159 } 160 161 /** 162 * Returns the number of column in the dataset. 163 * 164 * @return The column count. 165 */ 166 @Override 167 public int getColumnCount() { 168 return this.keys.size(); 169 } 170 171 /** 172 * Returns a list of the column keys in the dataset. 173 * 174 * @return The category list. 175 */ 176 @Override 177 public List<C> getColumnKeys() { 178 return this.keys; 179 } 180 181 /** 182 * Returns a column key. 183 * 184 * @param index the column index. 185 * 186 * @return The column key. 187 */ 188 @Override 189 public C getColumnKey(int index) { 190 return this.keys.get(index); 191 } 192 193 /** 194 * Returns the column index for a column key. 195 * 196 * @param columnKey the column key ({@code null} not permitted). 197 * 198 * @return The column index. 199 */ 200 @Override 201 public int getColumnIndex(C columnKey) { 202 Args.nullNotPermitted(columnKey, "columnKey"); 203 return this.keys.indexOf(columnKey); 204 } 205 206 /** 207 * Returns the row index for the given row key. 208 * 209 * @param rowKey the row key. 210 * 211 * @return The index. 212 */ 213 @Override 214 public int getRowIndex(R rowKey) { 215 int result = -1; 216 int count = this.data.size(); 217 for (int i = 0; i < count; i++) { 218 TaskSeries<R> s = this.data.get(i); 219 if (s.getKey().equals(rowKey)) { 220 result = i; 221 break; 222 } 223 } 224 return result; 225 } 226 227 /** 228 * Returns the key for a row. 229 * 230 * @param index the row index (zero-based). 231 * 232 * @return The key. 233 */ 234 @Override 235 public R getRowKey(int index) { 236 TaskSeries<R> series = this.data.get(index); 237 return series.getKey(); 238 } 239 240 /** 241 * Adds a series to the dataset and sends a 242 * {@link org.jfree.data.general.DatasetChangeEvent} to all registered 243 * listeners. 244 * 245 * @param series the series ({@code null} not permitted). 246 */ 247 public void add(TaskSeries<R> series) { 248 Args.nullNotPermitted(series, "series"); 249 this.data.add(series); 250 series.addChangeListener(this); 251 252 // look for any keys that we don't already know about... 253 for (Task task : series.getTasks()) { 254 C key = (C) task.getDescription(); // FIXME 255 int index = this.keys.indexOf(key); 256 if (index < 0) { 257 this.keys.add(key); 258 } 259 } 260 fireDatasetChanged(); 261 } 262 263 /** 264 * Removes a series from the collection and sends 265 * a {@link org.jfree.data.general.DatasetChangeEvent} 266 * to all registered listeners. 267 * 268 * @param series the series. 269 */ 270 public void remove(TaskSeries<R> series) { 271 Args.nullNotPermitted(series, "series"); 272 if (this.data.contains(series)) { 273 series.removeChangeListener(this); 274 this.data.remove(series); 275 fireDatasetChanged(); 276 } 277 } 278 279 /** 280 * Removes a series from the collection and sends 281 * a {@link org.jfree.data.general.DatasetChangeEvent} 282 * to all registered listeners. 283 * 284 * @param series the series (zero based index). 285 */ 286 public void remove(int series) { 287 Args.requireInRange(series, "series", 0, this.data.size() - 1); 288 289 // fetch the series, remove the change listener, then remove the series. 290 TaskSeries<R> ts = this.data.get(series); 291 ts.removeChangeListener(this); 292 this.data.remove(series); 293 fireDatasetChanged(); 294 295 } 296 297 /** 298 * Removes all the series from the collection and sends 299 * a {@link org.jfree.data.general.DatasetChangeEvent} 300 * to all registered listeners. 301 */ 302 public void removeAll() { 303 // deregister the collection as a change listener to each series in 304 // the collection. 305 for (TaskSeries<R> series : this.data) { 306 series.removeChangeListener(this); 307 } 308 309 // remove all the series from the collection and notify listeners. 310 this.data.clear(); 311 fireDatasetChanged(); 312 } 313 314 /** 315 * Returns the value for an item. 316 * 317 * @param rowKey the row key. 318 * @param columnKey the column key. 319 * 320 * @return The item value. 321 */ 322 @Override 323 public Number getValue(R rowKey, C columnKey) { 324 return getStartValue(rowKey, columnKey); 325 } 326 327 /** 328 * Returns the value for a task. 329 * 330 * @param row the row index (zero-based). 331 * @param column the column index (zero-based). 332 * 333 * @return The start value. 334 */ 335 @Override 336 public Number getValue(int row, int column) { 337 return getStartValue(row, column); 338 } 339 340 /** 341 * Returns the start value for a task. This is a date/time value, measured 342 * in milliseconds since 1-Jan-1970. 343 * 344 * @param rowKey the series. 345 * @param columnKey the category. 346 * 347 * @return The start value (possibly {@code null}). 348 */ 349 @Override 350 public Number getStartValue(R rowKey, C columnKey) { 351 Number result = null; 352 int row = getRowIndex(rowKey); 353 TaskSeries<R> series = this.data.get(row); 354 Task task = series.get(columnKey.toString()); 355 if (task != null) { 356 TimePeriod duration = task.getDuration(); 357 if (duration != null) { 358 result = duration.getStart().getTime(); 359 } 360 } 361 return result; 362 } 363 364 /** 365 * Returns the start value for a task. 366 * 367 * @param row the row index (zero-based). 368 * @param column the column index (zero-based). 369 * 370 * @return The start value. 371 */ 372 @Override 373 public Number getStartValue(int row, int column) { 374 R rowKey = getRowKey(row); 375 C columnKey = getColumnKey(column); 376 return getStartValue(rowKey, columnKey); 377 } 378 379 /** 380 * Returns the end value for a task. This is a date/time value, measured 381 * in milliseconds since 1-Jan-1970. 382 * 383 * @param rowKey the series. 384 * @param columnKey the category. 385 * 386 * @return The end value (possibly {@code null}). 387 */ 388 @Override 389 public Number getEndValue(R rowKey, C columnKey) { 390 Number result = null; 391 int row = getRowIndex(rowKey); 392 TaskSeries<R> series = this.data.get(row); 393 Task task = series.get(columnKey.toString()); 394 if (task != null) { 395 TimePeriod duration = task.getDuration(); 396 if (duration != null) { 397 result = duration.getEnd().getTime(); 398 } 399 } 400 return result; 401 } 402 403 /** 404 * Returns the end value for a task. 405 * 406 * @param row the row index (zero-based). 407 * @param column the column index (zero-based). 408 * 409 * @return The end value. 410 */ 411 @Override 412 public Number getEndValue(int row, int column) { 413 R rowKey = getRowKey(row); 414 C columnKey = getColumnKey(column); 415 return getEndValue(rowKey, columnKey); 416 } 417 418 /** 419 * Returns the percent complete for a given item. 420 * 421 * @param row the row index (zero-based). 422 * @param column the column index (zero-based). 423 * 424 * @return The percent complete (possibly {@code null}). 425 */ 426 @Override 427 public Number getPercentComplete(int row, int column) { 428 R rowKey = getRowKey(row); 429 C columnKey = getColumnKey(column); 430 return getPercentComplete(rowKey, columnKey); 431 } 432 433 /** 434 * Returns the percent complete for a given item. 435 * 436 * @param rowKey the row key. 437 * @param columnKey the column key. 438 * 439 * @return The percent complete. 440 */ 441 @Override 442 public Number getPercentComplete(R rowKey, C columnKey) { 443 Number result = null; 444 int row = getRowIndex(rowKey); 445 TaskSeries<R> series = this.data.get(row); 446 Task task = series.get(columnKey.toString()); 447 if (task != null) { 448 result = task.getPercentComplete(); 449 } 450 return result; 451 } 452 453 /** 454 * Returns the number of sub-intervals for a given item. 455 * 456 * @param row the row index (zero-based). 457 * @param column the column index (zero-based). 458 * 459 * @return The sub-interval count. 460 */ 461 @Override 462 public int getSubIntervalCount(int row, int column) { 463 R rowKey = getRowKey(row); 464 C columnKey = getColumnKey(column); 465 return getSubIntervalCount(rowKey, columnKey); 466 } 467 468 /** 469 * Returns the number of sub-intervals for a given item. 470 * 471 * @param rowKey the row key. 472 * @param columnKey the column key. 473 * 474 * @return The sub-interval count. 475 */ 476 @Override 477 public int getSubIntervalCount(R rowKey, C columnKey) { 478 int result = 0; 479 int row = getRowIndex(rowKey); 480 TaskSeries<R> series = this.data.get(row); 481 Task task = series.get(columnKey.toString()); 482 if (task != null) { 483 result = task.getSubtaskCount(); 484 } 485 return result; 486 } 487 488 /** 489 * Returns the start value of a sub-interval for a given item. 490 * 491 * @param row the row index (zero-based). 492 * @param column the column index (zero-based). 493 * @param subinterval the sub-interval index (zero-based). 494 * 495 * @return The start value (possibly {@code null}). 496 */ 497 @Override 498 public Number getStartValue(int row, int column, int subinterval) { 499 R rowKey = getRowKey(row); 500 C columnKey = getColumnKey(column); 501 return getStartValue(rowKey, columnKey, subinterval); 502 } 503 504 /** 505 * Returns the start value of a sub-interval for a given item. 506 * 507 * @param rowKey the row key. 508 * @param columnKey the column key. 509 * @param subinterval the subinterval. 510 * 511 * @return The start value (possibly {@code null}). 512 */ 513 @Override 514 public Number getStartValue(R rowKey, C columnKey, int subinterval) { 515 Number result = null; 516 int row = getRowIndex(rowKey); 517 TaskSeries<R> series = this.data.get(row); 518 Task task = series.get(columnKey.toString()); 519 if (task != null) { 520 Task sub = task.getSubtask(subinterval); 521 if (sub != null) { 522 TimePeriod duration = sub.getDuration(); 523 if (duration != null) { 524 result = duration.getStart().getTime(); 525 } 526 } 527 } 528 return result; 529 } 530 531 /** 532 * Returns the end value of a sub-interval for a given item. 533 * 534 * @param row the row index (zero-based). 535 * @param column the column index (zero-based). 536 * @param subinterval the subinterval. 537 * 538 * @return The end value (possibly {@code null}). 539 */ 540 @Override 541 public Number getEndValue(int row, int column, int subinterval) { 542 R rowKey = getRowKey(row); 543 C columnKey = getColumnKey(column); 544 return getEndValue(rowKey, columnKey, subinterval); 545 } 546 547 /** 548 * Returns the end value of a sub-interval for a given item. 549 * 550 * @param rowKey the row key. 551 * @param columnKey the column key. 552 * @param subinterval the subinterval. 553 * 554 * @return The end value (possibly {@code null}). 555 */ 556 @Override 557 public Number getEndValue(R rowKey, C columnKey, int subinterval) { 558 Number result = null; 559 int row = getRowIndex(rowKey); 560 TaskSeries<R> series = this.data.get(row); 561 Task task = series.get(columnKey.toString()); 562 if (task != null) { 563 Task sub = task.getSubtask(subinterval); 564 if (sub != null) { 565 TimePeriod duration = sub.getDuration(); 566 if (duration != null) { 567 result = duration.getEnd().getTime(); 568 } 569 } 570 } 571 return result; 572 } 573 574 /** 575 * Returns the percentage complete value of a sub-interval for a given item. 576 * 577 * @param row the row index (zero-based). 578 * @param column the column index (zero-based). 579 * @param subinterval the sub-interval. 580 * 581 * @return The percent complete value (possibly {@code null}). 582 */ 583 @Override 584 public Number getPercentComplete(int row, int column, int subinterval) { 585 R rowKey = getRowKey(row); 586 C columnKey = getColumnKey(column); 587 return getPercentComplete(rowKey, columnKey, subinterval); 588 } 589 590 /** 591 * Returns the percentage complete value of a sub-interval for a given item. 592 * 593 * @param rowKey the row key. 594 * @param columnKey the column key. 595 * @param subinterval the sub-interval. 596 * 597 * @return The percent complete value (possibly {@code null}). 598 */ 599 @Override 600 public Number getPercentComplete(R rowKey, C columnKey, int subinterval) { 601 Number result = null; 602 int row = getRowIndex(rowKey); 603 TaskSeries<R> series = this.data.get(row); 604 Task task = series.get(columnKey.toString()); 605 if (task != null) { 606 Task sub = task.getSubtask(subinterval); 607 if (sub != null) { 608 result = sub.getPercentComplete(); 609 } 610 } 611 return result; 612 } 613 614 /** 615 * Called when a series belonging to the dataset changes. 616 * 617 * @param event information about the change. 618 */ 619 @Override 620 public void seriesChanged(SeriesChangeEvent event) { 621 refreshKeys(); 622 fireDatasetChanged(); 623 } 624 625 /** 626 * Refreshes the keys. 627 */ 628 private void refreshKeys() { 629 630 this.keys.clear(); 631 for (int i = 0; i < getSeriesCount(); i++) { 632 TaskSeries<R> series = this.data.get(i); 633 // look for any keys that we don't already know about... 634 for (Task task : series.getTasks()) { 635 C key = (C) task.getDescription(); // FIXME 636 int index = this.keys.indexOf(key); 637 if (index < 0) { 638 this.keys.add(key); 639 } 640 } 641 } 642 643 } 644 645 /** 646 * Tests this instance for equality with an arbitrary object. 647 * 648 * @param obj the object ({@code null} permitted). 649 * 650 * @return A boolean. 651 */ 652 @Override 653 public boolean equals(Object obj) { 654 if (obj == this) { 655 return true; 656 } 657 if (!(obj instanceof TaskSeriesCollection)) { 658 return false; 659 } 660 TaskSeriesCollection that = (TaskSeriesCollection) obj; 661 if (!Objects.equals(this.data, that.data)) { 662 return false; 663 } 664 return true; 665 } 666 667 @Override 668 public int hashCode(){ 669 int hash = 7; 670 hash = 89 * hash + Objects.hashCode(this.data); 671 return hash; 672 } 673 674 /** 675 * Returns an independent copy of this dataset. 676 * 677 * @return A clone of the dataset. 678 * 679 * @throws CloneNotSupportedException if there is some problem cloning 680 * the dataset. 681 */ 682 @Override 683 public Object clone() throws CloneNotSupportedException { 684 TaskSeriesCollection clone = (TaskSeriesCollection) super.clone(); 685 clone.data = CloneUtils.cloneList(this.data); 686 clone.keys = new java.util.ArrayList(this.keys); 687 return clone; 688 } 689 690}