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.5 2007/03/08 13:57:08 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 }