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