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
051package org.jfree.data.gantt;
052
053import java.io.Serializable;
054import java.util.Iterator;
055import java.util.List;
056
057import org.jfree.data.general.AbstractSeriesDataset;
058import org.jfree.data.general.SeriesChangeEvent;
059import org.jfree.data.time.TimePeriod;
060import org.jfree.util.ObjectUtilities;
061import 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 */
067public 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}