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