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