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