001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, 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-2007, 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 *
048 */
049
050 package org.jfree.data.gantt;
051
052 import java.io.Serializable;
053 import java.util.Iterator;
054 import java.util.List;
055
056 import org.jfree.data.general.AbstractSeriesDataset;
057 import org.jfree.data.general.SeriesChangeEvent;
058 import org.jfree.data.time.TimePeriod;
059 import org.jfree.util.ObjectUtilities;
060 import org.jfree.util.PublicCloneable;
061
062 /**
063 * A collection of {@link TaskSeries} objects. This class provides one
064 * implementation of the {@link GanttCategoryDataset} interface.
065 */
066 public class TaskSeriesCollection extends AbstractSeriesDataset
067 implements GanttCategoryDataset,
068 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 precent 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 }