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