001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2006, 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-2006, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Thomas Schuster; 034 * 035 * $Id: TaskSeriesCollection.java,v 1.9.2.4 2006/01/26 14:51:20 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 06-Jun-2002 : Version 1 (DG); 040 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG); 041 * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 042 * CategoryToolTipGenerator interface (DG); 043 * 10-Jan-2003 : Renamed GanttSeriesCollection --> TaskSeriesCollection (DG); 044 * 04-Sep-2003 : Fixed bug 800324 (DG); 045 * 16-Sep-2003 : Implemented GanttCategoryDataset (DG); 046 * 12-Jan-2005 : Fixed bug 1099331 (DG); 047 * 18-Jan-2006 : Added new methods getSeries(int) and 048 * getSeries(Comparable) (DG); 049 * 050 */ 051 052 package org.jfree.data.gantt; 053 054 import java.io.Serializable; 055 import java.util.Iterator; 056 import java.util.List; 057 058 import org.jfree.data.general.AbstractSeriesDataset; 059 import org.jfree.data.general.SeriesChangeEvent; 060 import org.jfree.data.time.TimePeriod; 061 import org.jfree.util.ObjectUtilities; 062 import org.jfree.util.PublicCloneable; 063 064 /** 065 * A collection of {@link TaskSeries} objects. This class provides one 066 * implementation of the {@link GanttCategoryDataset} interface. 067 */ 068 public class TaskSeriesCollection extends AbstractSeriesDataset 069 implements GanttCategoryDataset, 070 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 public int getSeriesCount() { 136 return getRowCount(); 137 } 138 139 /** 140 * Returns the name of a series. 141 * 142 * @param series the series index (zero-based). 143 * 144 * @return The name of a series. 145 */ 146 public Comparable getSeriesKey(int series) { 147 TaskSeries ts = (TaskSeries) this.data.get(series); 148 return ts.getKey(); 149 } 150 151 /** 152 * Returns the number of rows (series) in the collection. 153 * 154 * @return The series count. 155 */ 156 public int getRowCount() { 157 return this.data.size(); 158 } 159 160 /** 161 * Returns the row keys. In this case, each series is a key. 162 * 163 * @return The row keys. 164 */ 165 public List getRowKeys() { 166 return this.data; 167 } 168 169 /** 170 * Returns the number of column in the dataset. 171 * 172 * @return The column count. 173 */ 174 public int getColumnCount() { 175 return this.keys.size(); 176 } 177 178 /** 179 * Returns a list of the column keys in the dataset. 180 * 181 * @return The category list. 182 */ 183 public List getColumnKeys() { 184 return this.keys; 185 } 186 187 /** 188 * Returns a column key. 189 * 190 * @param index the column index. 191 * 192 * @return The column key. 193 */ 194 public Comparable getColumnKey(int index) { 195 return (Comparable) this.keys.get(index); 196 } 197 198 /** 199 * Returns the column index for a column key. 200 * 201 * @param columnKey the columnKey. 202 * 203 * @return The column index. 204 */ 205 public int getColumnIndex(Comparable columnKey) { 206 return this.keys.indexOf(columnKey); 207 } 208 209 /** 210 * Returns the row index for the given row key. 211 * 212 * @param rowKey the row key. 213 * 214 * @return The index. 215 */ 216 public int getRowIndex(Comparable rowKey) { 217 int result = -1; 218 int count = this.data.size(); 219 for (int i = 0; i < count; i++) { 220 TaskSeries s = (TaskSeries) this.data.get(i); 221 if (s.getKey().equals(rowKey)) { 222 result = i; 223 break; 224 } 225 } 226 return result; 227 } 228 229 /** 230 * Returns the key for a row. 231 * 232 * @param index the row index (zero-based). 233 * 234 * @return The key. 235 */ 236 public Comparable getRowKey(int index) { 237 TaskSeries series = (TaskSeries) this.data.get(index); 238 return series.getKey(); 239 } 240 241 /** 242 * Adds a series to the dataset and sends a 243 * {@link org.jfree.data.general.DatasetChangeEvent} to all registered 244 * listeners. 245 * 246 * @param series the series (<code>null</code> not permitted). 247 */ 248 public void add(TaskSeries series) { 249 if (series == null) { 250 throw new IllegalArgumentException("Null 'series' argument."); 251 } 252 this.data.add(series); 253 series.addChangeListener(this); 254 255 // look for any keys that we don't already know about... 256 Iterator iterator = series.getTasks().iterator(); 257 while (iterator.hasNext()) { 258 Task task = (Task) iterator.next(); 259 String key = task.getDescription(); 260 int index = this.keys.indexOf(key); 261 if (index < 0) { 262 this.keys.add(key); 263 } 264 } 265 fireDatasetChanged(); 266 } 267 268 /** 269 * Removes a series from the collection and sends 270 * a {@link org.jfree.data.general.DatasetChangeEvent} 271 * to all registered listeners. 272 * 273 * @param series the series. 274 */ 275 public void remove(TaskSeries series) { 276 if (series == null) { 277 throw new IllegalArgumentException("Null 'series' argument."); 278 } 279 if (this.data.contains(series)) { 280 series.removeChangeListener(this); 281 this.data.remove(series); 282 fireDatasetChanged(); 283 } 284 } 285 286 /** 287 * Removes a series from the collection and sends 288 * a {@link org.jfree.data.general.DatasetChangeEvent} 289 * to all registered listeners. 290 * 291 * @param series the series (zero based index). 292 */ 293 public void remove(int series) { 294 if ((series < 0) || (series > getSeriesCount())) { 295 throw new IllegalArgumentException( 296 "TaskSeriesCollection.remove(): index outside valid range."); 297 } 298 299 // fetch the series, remove the change listener, then remove the series. 300 TaskSeries ts = (TaskSeries) this.data.get(series); 301 ts.removeChangeListener(this); 302 this.data.remove(series); 303 fireDatasetChanged(); 304 305 } 306 307 /** 308 * Removes all the series from the collection and sends 309 * a {@link org.jfree.data.general.DatasetChangeEvent} 310 * to all registered listeners. 311 */ 312 public void removeAll() { 313 314 // deregister the collection as a change listener to each series in 315 // the collection. 316 Iterator iterator = this.data.iterator(); 317 while (iterator.hasNext()) { 318 TaskSeries series = (TaskSeries) iterator.next(); 319 series.removeChangeListener(this); 320 } 321 322 // remove all the series from the collection and notify listeners. 323 this.data.clear(); 324 fireDatasetChanged(); 325 326 } 327 328 /** 329 * Returns the value for an item. 330 * 331 * @param rowKey the row key. 332 * @param columnKey the column key. 333 * 334 * @return The item value. 335 */ 336 public Number getValue(Comparable rowKey, Comparable columnKey) { 337 return getStartValue(rowKey, columnKey); 338 } 339 340 /** 341 * Returns the value for a task. 342 * 343 * @param row the row index (zero-based). 344 * @param column the column index (zero-based). 345 * 346 * @return The start value. 347 */ 348 public Number getValue(int row, int column) { 349 return getStartValue(row, column); 350 } 351 352 /** 353 * Returns the start value for a task. This is a date/time value, measured 354 * in milliseconds since 1-Jan-1970. 355 * 356 * @param rowKey the series. 357 * @param columnKey the category. 358 * 359 * @return The start value (possibly <code>null</code>). 360 */ 361 public Number getStartValue(Comparable rowKey, Comparable columnKey) { 362 Number result = null; 363 int row = getRowIndex(rowKey); 364 TaskSeries series = (TaskSeries) this.data.get(row); 365 Task task = series.get(columnKey.toString()); 366 if (task != null) { 367 TimePeriod duration = task.getDuration(); 368 if (duration != null) { 369 result = new Long(duration.getStart().getTime()); 370 } 371 } 372 return result; 373 } 374 375 /** 376 * Returns the start value for a task. 377 * 378 * @param row the row index (zero-based). 379 * @param column the column index (zero-based). 380 * 381 * @return The start value. 382 */ 383 public Number getStartValue(int row, int column) { 384 Comparable rowKey = getRowKey(row); 385 Comparable columnKey = getColumnKey(column); 386 return getStartValue(rowKey, columnKey); 387 } 388 389 /** 390 * Returns the end value for a task. This is a date/time value, measured 391 * in milliseconds since 1-Jan-1970. 392 * 393 * @param rowKey the series. 394 * @param columnKey the category. 395 * 396 * @return The end value (possibly <code>null</code>). 397 */ 398 public Number getEndValue(Comparable rowKey, Comparable columnKey) { 399 Number result = null; 400 int row = getRowIndex(rowKey); 401 TaskSeries series = (TaskSeries) this.data.get(row); 402 Task task = series.get(columnKey.toString()); 403 if (task != null) { 404 TimePeriod duration = task.getDuration(); 405 if (duration != null) { 406 result = new Long(duration.getEnd().getTime()); 407 } 408 } 409 return result; 410 } 411 412 /** 413 * Returns the end value for a task. 414 * 415 * @param row the row index (zero-based). 416 * @param column the column index (zero-based). 417 * 418 * @return The end value. 419 */ 420 public Number getEndValue(int row, int column) { 421 Comparable rowKey = getRowKey(row); 422 Comparable columnKey = getColumnKey(column); 423 return getEndValue(rowKey, columnKey); 424 } 425 426 /** 427 * Returns the percent complete for a given item. 428 * 429 * @param row the row index (zero-based). 430 * @param column the column index (zero-based). 431 * 432 * @return The percent complete (possibly <code>null</code>). 433 */ 434 public Number getPercentComplete(int row, int column) { 435 Comparable rowKey = getRowKey(row); 436 Comparable columnKey = getColumnKey(column); 437 return getPercentComplete(rowKey, columnKey); 438 } 439 440 /** 441 * Returns the percent complete for a given item. 442 * 443 * @param rowKey the row key. 444 * @param columnKey the column key. 445 * 446 * @return The percent complete. 447 */ 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 public int getSubIntervalCount(int row, int column) { 468 Comparable rowKey = getRowKey(row); 469 Comparable columnKey = getColumnKey(column); 470 return getSubIntervalCount(rowKey, columnKey); 471 } 472 473 /** 474 * Returns the number of sub-intervals for a given item. 475 * 476 * @param rowKey the row key. 477 * @param columnKey the column key. 478 * 479 * @return The sub-interval count. 480 */ 481 public int getSubIntervalCount(Comparable rowKey, Comparable columnKey) { 482 int result = 0; 483 int row = getRowIndex(rowKey); 484 TaskSeries series = (TaskSeries) this.data.get(row); 485 Task task = series.get(columnKey.toString()); 486 if (task != null) { 487 result = task.getSubtaskCount(); 488 } 489 return result; 490 } 491 492 /** 493 * Returns the start value of a sub-interval for a given item. 494 * 495 * @param row the row index (zero-based). 496 * @param column the column index (zero-based). 497 * @param subinterval the sub-interval index (zero-based). 498 * 499 * @return The start value (possibly <code>null</code>). 500 */ 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</code>). 515 */ 516 public Number getStartValue(Comparable rowKey, Comparable columnKey, 517 int subinterval) { 518 Number result = null; 519 int row = getRowIndex(rowKey); 520 TaskSeries series = (TaskSeries) this.data.get(row); 521 Task task = series.get(columnKey.toString()); 522 if (task != null) { 523 Task sub = task.getSubtask(subinterval); 524 if (sub != null) { 525 TimePeriod duration = sub.getDuration(); 526 result = new Long(duration.getStart().getTime()); 527 } 528 } 529 return result; 530 } 531 532 /** 533 * Returns the end value of a sub-interval for a given item. 534 * 535 * @param row the row index (zero-based). 536 * @param column the column index (zero-based). 537 * @param subinterval the subinterval. 538 * 539 * @return The end value (possibly <code>null</code>). 540 */ 541 public Number getEndValue(int row, int column, int subinterval) { 542 Comparable rowKey = getRowKey(row); 543 Comparable 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</code>). 555 */ 556 public Number getEndValue(Comparable rowKey, Comparable columnKey, 557 int subinterval) { 558 Number result = null; 559 int row = getRowIndex(rowKey); 560 TaskSeries series = (TaskSeries) 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 result = new Long(duration.getEnd().getTime()); 567 } 568 } 569 return result; 570 } 571 572 /** 573 * Returns the percentage complete value of a sub-interval for a given item. 574 * 575 * @param row the row index (zero-based). 576 * @param column the column index (zero-based). 577 * @param subinterval the sub-interval. 578 * 579 * @return The percent complete value (possibly <code>null</code>). 580 */ 581 public Number getPercentComplete(int row, int column, int subinterval) { 582 Comparable rowKey = getRowKey(row); 583 Comparable columnKey = getColumnKey(column); 584 return getPercentComplete(rowKey, columnKey, subinterval); 585 } 586 587 /** 588 * Returns the percentage complete value of a sub-interval for a given item. 589 * 590 * @param rowKey the row key. 591 * @param columnKey the column key. 592 * @param subinterval the sub-interval. 593 * 594 * @return The precent complete value (possibly <code>null</code>). 595 */ 596 public Number getPercentComplete(Comparable rowKey, Comparable columnKey, 597 int subinterval) { 598 Number result = null; 599 int row = getRowIndex(rowKey); 600 TaskSeries series = (TaskSeries) this.data.get(row); 601 Task task = series.get(columnKey.toString()); 602 if (task != null) { 603 Task sub = task.getSubtask(subinterval); 604 if (sub != null) { 605 result = sub.getPercentComplete(); 606 } 607 } 608 return result; 609 } 610 611 /** 612 * Called when a series belonging to the dataset changes. 613 * 614 * @param event information about the change. 615 */ 616 public void seriesChanged(SeriesChangeEvent event) { 617 refreshKeys(); 618 fireDatasetChanged(); 619 } 620 621 /** 622 * Refreshes the keys. 623 */ 624 private void refreshKeys() { 625 626 this.keys.clear(); 627 for (int i = 0; i < getSeriesCount(); i++) { 628 TaskSeries series = (TaskSeries) this.data.get(i); 629 // look for any keys that we don't already know about... 630 Iterator iterator = series.getTasks().iterator(); 631 while (iterator.hasNext()) { 632 Task task = (Task) iterator.next(); 633 String key = task.getDescription(); 634 int index = this.keys.indexOf(key); 635 if (index < 0) { 636 this.keys.add(key); 637 } 638 } 639 } 640 641 } 642 643 /** 644 * Tests this instance for equality with an arbitrary object. 645 * 646 * @param obj the object (<code>null</code> permitted). 647 * 648 * @return A boolean. 649 */ 650 public boolean equals(Object obj) { 651 if (obj == this) { 652 return true; 653 } 654 if (!(obj instanceof TaskSeriesCollection)) { 655 return false; 656 } 657 TaskSeriesCollection that = (TaskSeriesCollection) obj; 658 if (!ObjectUtilities.equal(this.data, that.data)) { 659 return false; 660 } 661 return true; 662 } 663 664 }