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