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