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