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