001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2005, 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-2005, 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.2 2005/10/25 21:31:44 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     *
048     */
049    
050    package org.jfree.data.gantt;
051    
052    import java.io.Serializable;
053    import java.util.Iterator;
054    import java.util.List;
055    
056    import org.jfree.data.general.AbstractSeriesDataset;
057    import org.jfree.data.general.SeriesChangeEvent;
058    import org.jfree.data.time.TimePeriod;
059    import org.jfree.util.ObjectUtilities;
060    import org.jfree.util.PublicCloneable;
061    
062    /**
063     * A collection of {@link TaskSeries} objects.  This class provides one 
064     * implementation of the {@link GanttCategoryDataset} interface.
065     */
066    public class TaskSeriesCollection extends AbstractSeriesDataset
067                                      implements GanttCategoryDataset,
068                                                 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 the number of series in the collection.
093         *
094         * @return The series count.
095         */
096        public int getSeriesCount() {
097            return getRowCount();
098        }
099    
100        /**
101         * Returns the name of a series.
102         *
103         * @param series  the series index (zero-based).
104         *
105         * @return The name of a series.
106         */
107        public Comparable getSeriesKey(int series) {
108            TaskSeries ts = (TaskSeries) this.data.get(series);
109            return ts.getKey();
110        }
111    
112        /**
113         * Returns the number of rows (series) in the collection.
114         *
115         * @return The series count.
116         */
117        public int getRowCount() {
118            return this.data.size();
119        }
120    
121        /**
122         * Returns the row keys.  In this case, each series is a key.
123         *
124         * @return The row keys.
125         */
126        public List getRowKeys() {
127            return this.data;
128        }
129    
130        /**
131         * Returns the number of column in the dataset.
132         *
133         * @return The column count.
134         */
135        public int getColumnCount() {
136            return this.keys.size();
137        }
138    
139        /**
140         * Returns a list of the column keys in the dataset.
141         *
142         * @return The category list.
143         */
144        public List getColumnKeys() {
145            return this.keys;
146        }
147    
148        /**
149         * Returns a column key.
150         *
151         * @param index  the column index.
152         *
153         * @return The column key.
154         */
155        public Comparable getColumnKey(int index) {
156            return (Comparable) this.keys.get(index);
157        }
158    
159        /**
160         * Returns the column index for a column key.
161         *
162         * @param columnKey  the columnKey.
163         *
164         * @return The column index.
165         */
166        public int getColumnIndex(Comparable columnKey) {
167            return this.keys.indexOf(columnKey);
168        }
169    
170        /**
171         * Returns the row index for the given row key.
172         *
173         * @param rowKey  the row key.
174         *
175         * @return The index.
176         */
177        public int getRowIndex(Comparable rowKey) {
178            int result = -1;
179            int count = this.data.size();
180            for (int i = 0; i < count; i++) {
181                TaskSeries s = (TaskSeries) this.data.get(i);
182                if (s.getKey().equals(rowKey)) {
183                    result = i;
184                    break;
185                }
186            }
187            return result;
188        }
189    
190        /**
191         * Returns the key for a row.
192         *
193         * @param index  the row index (zero-based).
194         *
195         * @return The key.
196         */
197        public Comparable getRowKey(int index) {
198            TaskSeries series = (TaskSeries) this.data.get(index);
199            return series.getKey();
200        }
201    
202        /**
203         * Adds a series to the dataset and sends a 
204         * {@link org.jfree.data.general.DatasetChangeEvent} to all registered 
205         * listeners.
206         *
207         * @param series  the series (<code>null</code> not permitted).
208         */
209        public void add(TaskSeries series) {
210            if (series == null) {
211                throw new IllegalArgumentException("Null 'series' argument.");
212            }
213            this.data.add(series);
214            series.addChangeListener(this);
215    
216            // look for any keys that we don't already know about...
217            Iterator iterator = series.getTasks().iterator();
218            while (iterator.hasNext()) {
219                Task task = (Task) iterator.next();
220                String key = task.getDescription();
221                int index = this.keys.indexOf(key);
222                if (index < 0) {
223                    this.keys.add(key);
224                }
225            }
226            fireDatasetChanged();
227        }
228    
229        /**
230         * Removes a series from the collection and sends 
231         * a {@link org.jfree.data.general.DatasetChangeEvent}
232         * to all registered listeners.
233         *
234         * @param series  the series.
235         */
236        public void remove(TaskSeries series) {
237            if (series == null) {
238                throw new IllegalArgumentException("Null 'series' argument.");
239            }
240            if (this.data.contains(series)) {
241                series.removeChangeListener(this);
242                this.data.remove(series);
243                fireDatasetChanged();
244            }
245        }
246    
247        /**
248         * Removes a series from the collection and sends 
249         * a {@link org.jfree.data.general.DatasetChangeEvent}
250         * to all registered listeners.
251         *
252         * @param series  the series (zero based index).
253         */
254        public void remove(int series) {
255            if ((series < 0) || (series > getSeriesCount())) {
256                throw new IllegalArgumentException(
257                    "TaskSeriesCollection.remove(): index outside valid range.");
258            }
259    
260            // fetch the series, remove the change listener, then remove the series.
261            TaskSeries ts = (TaskSeries) this.data.get(series);
262            ts.removeChangeListener(this);
263            this.data.remove(series);
264            fireDatasetChanged();
265    
266        }
267    
268        /**
269         * Removes all the series from the collection and sends 
270         * a {@link org.jfree.data.general.DatasetChangeEvent}
271         * to all registered listeners.
272         */
273        public void removeAll() {
274    
275            // deregister the collection as a change listener to each series in 
276            // the collection.
277            Iterator iterator = this.data.iterator();
278            while (iterator.hasNext()) {
279                TaskSeries series = (TaskSeries) iterator.next();
280                series.removeChangeListener(this);
281            }
282    
283            // remove all the series from the collection and notify listeners.
284            this.data.clear();
285            fireDatasetChanged();
286    
287        }
288    
289        /**
290         * Returns the value for an item.
291         *
292         * @param rowKey  the row key.
293         * @param columnKey  the column key.
294         *
295         * @return The item value.
296         */
297        public Number getValue(Comparable rowKey, Comparable columnKey) {
298            return getStartValue(rowKey, columnKey);
299        }
300    
301        /**
302         * Returns the value for a task.
303         *
304         * @param row  the row index (zero-based).
305         * @param column  the column index (zero-based).
306         *
307         * @return The start value.
308         */
309        public Number getValue(int row, int column) {
310            return getStartValue(row, column);
311        }
312    
313        /**
314         * Returns the start value for a task.  This is a date/time value, measured
315         * in milliseconds since 1-Jan-1970.
316         *
317         * @param rowKey  the series.
318         * @param columnKey  the category.
319         *
320         * @return The start value (possibly <code>null</code>.
321         */
322        public Number getStartValue(Comparable rowKey, Comparable columnKey) {
323            Number result = null;
324            int row = getRowIndex(rowKey);
325            TaskSeries series = (TaskSeries) this.data.get(row);
326            Task task = series.get(columnKey.toString());
327            if (task != null) {
328                TimePeriod duration = task.getDuration();
329                if (duration != null) {
330                    result = new Long(duration.getStart().getTime());
331                }
332            }
333            return result;
334        }
335    
336        /**
337         * Returns the start value for a task.
338         *
339         * @param row  the row index (zero-based).
340         * @param column  the column index (zero-based).
341         *
342         * @return The start value.
343         */
344        public Number getStartValue(int row, int column) {
345            Comparable rowKey = getRowKey(row);
346            Comparable columnKey = getColumnKey(column);
347            return getStartValue(rowKey, columnKey);
348        }
349    
350        /**
351         * Returns the end value for a task.  This is a date/time value, measured
352         * in milliseconds since 1-Jan-1970.
353         *
354         * @param rowKey  the series.
355         * @param columnKey  the category.
356         *
357         * @return The end value (possibly <code>null</code>).
358         */
359        public Number getEndValue(Comparable rowKey, Comparable columnKey) {
360            Number result = null;
361            int row = getRowIndex(rowKey);
362            TaskSeries series = (TaskSeries) this.data.get(row);
363            Task task = series.get(columnKey.toString());
364            if (task != null) {
365                TimePeriod duration = task.getDuration();
366                if (duration != null) {
367                    result = new Long(duration.getEnd().getTime());
368                }
369            }
370            return result;
371        }
372    
373        /**
374         * Returns the end value for a task.
375         *
376         * @param row  the row index (zero-based).
377         * @param column  the column index (zero-based).
378         *
379         * @return The end value.
380         */
381        public Number getEndValue(int row, int column) {
382            Comparable rowKey = getRowKey(row);
383            Comparable columnKey = getColumnKey(column);
384            return getEndValue(rowKey, columnKey);
385        }
386    
387        /**
388         * Returns the percent complete for a given item.
389         *
390         * @param row  the row index (zero-based).
391         * @param column  the column index (zero-based).
392         *
393         * @return The percent complete (possibly <code>null</code>).
394         */
395        public Number getPercentComplete(int row, int column) {
396            Comparable rowKey = getRowKey(row);
397            Comparable columnKey = getColumnKey(column);
398            return getPercentComplete(rowKey, columnKey);
399        }
400    
401        /**
402         * Returns the percent complete for a given item.
403         *
404         * @param rowKey  the row key.
405         * @param columnKey  the column key.
406         *
407         * @return The percent complete.
408         */
409        public Number getPercentComplete(Comparable rowKey, Comparable columnKey) {
410            Number result = null;
411            int row = getRowIndex(rowKey);
412            TaskSeries series = (TaskSeries) this.data.get(row);
413            Task task = series.get(columnKey.toString());
414            if (task != null) {
415                result = task.getPercentComplete();
416            }
417            return result;
418        }
419    
420        /**
421         * Returns the number of sub-intervals for a given item.
422         *
423         * @param row  the row index (zero-based).
424         * @param column  the column index (zero-based).
425         *
426         * @return The sub-interval count.
427         */
428        public int getSubIntervalCount(int row, int column) {
429            Comparable rowKey = getRowKey(row);
430            Comparable columnKey = getColumnKey(column);
431            return getSubIntervalCount(rowKey, columnKey);
432        }
433    
434        /**
435         * Returns the number of sub-intervals for a given item.
436         *
437         * @param rowKey  the row key.
438         * @param columnKey  the column key.
439         *
440         * @return The sub-interval count.
441         */
442        public int getSubIntervalCount(Comparable rowKey, Comparable columnKey) {
443            int result = 0;
444            int row = getRowIndex(rowKey);
445            TaskSeries series = (TaskSeries) this.data.get(row);
446            Task task = series.get(columnKey.toString());
447            if (task != null) {
448                result = task.getSubtaskCount();
449            }
450            return result;
451        }
452    
453        /**
454         * Returns the start value of a sub-interval for a given item.
455         *
456         * @param row  the row index (zero-based).
457         * @param column  the column index (zero-based).
458         * @param subinterval  the sub-interval index (zero-based).
459         *
460         * @return The start value (possibly <code>null</code>).
461         */
462        public Number getStartValue(int row, int column, int subinterval) {
463            Comparable rowKey = getRowKey(row);
464            Comparable columnKey = getColumnKey(column);
465            return getStartValue(rowKey, columnKey, subinterval);
466        }
467    
468        /**
469         * Returns the start value of a sub-interval for a given item.
470         *
471         * @param rowKey  the row key.
472         * @param columnKey  the column key.
473         * @param subinterval  the subinterval.
474         *
475         * @return The start value (possibly <code>null</code>).
476         */
477        public Number getStartValue(Comparable rowKey, Comparable columnKey, 
478                                    int subinterval) {
479            Number result = null;
480            int row = getRowIndex(rowKey);
481            TaskSeries series = (TaskSeries) this.data.get(row);
482            Task task = series.get(columnKey.toString());
483            if (task != null) {
484                Task sub = task.getSubtask(subinterval);
485                if (sub != null) {
486                    TimePeriod duration = sub.getDuration();
487                    result = new Long(duration.getStart().getTime());
488                }
489            }
490            return result;
491        }
492    
493        /**
494         * Returns the end 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 subinterval.
499         *
500         * @return The end value (possibly <code>null</code>).
501         */
502        public Number getEndValue(int row, int column, int subinterval) {
503            Comparable rowKey = getRowKey(row);
504            Comparable columnKey = getColumnKey(column);
505            return getEndValue(rowKey, columnKey, subinterval);
506        }
507    
508        /**
509         * Returns the end 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 end value (possibly <code>null</code>).
516         */
517        public Number getEndValue(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.getEnd().getTime());
528                }
529            }
530            return result;
531        }
532    
533        /**
534         * Returns the percentage complete 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 sub-interval.
539         *
540         * @return The percent complete value (possibly <code>null</code>).
541         */
542        public Number getPercentComplete(int row, int column, int subinterval) {
543            Comparable rowKey = getRowKey(row);
544            Comparable columnKey = getColumnKey(column);
545            return getPercentComplete(rowKey, columnKey, subinterval);
546        }
547    
548        /**
549         * Returns the percentage complete 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 sub-interval.
554         *
555         * @return The precent complete value (possibly <code>null</code>).
556         */
557        public Number getPercentComplete(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                    result = sub.getPercentComplete();
567                }
568            }
569            return result;
570        }
571    
572        /**
573         * Called when a series belonging to the dataset changes.
574         *
575         * @param event  information about the change.
576         */
577        public void seriesChanged(SeriesChangeEvent event) {
578            refreshKeys();
579            fireDatasetChanged();
580        }
581    
582        /**
583         * Refreshes the keys.
584         */
585        private void refreshKeys() {
586    
587            this.keys.clear();
588            for (int i = 0; i < getSeriesCount(); i++) {
589                TaskSeries series = (TaskSeries) this.data.get(i);
590                // look for any keys that we don't already know about...
591                Iterator iterator = series.getTasks().iterator();
592                while (iterator.hasNext()) {
593                    Task task = (Task) iterator.next();
594                    String key = task.getDescription();
595                    int index = this.keys.indexOf(key);
596                    if (index < 0) {
597                        this.keys.add(key);
598                    }
599                }
600            }
601    
602        }
603        
604        /**
605         * Tests this instance for equality with an arbitrary object.
606         * 
607         * @param obj  the object (<code>null</code> permitted).
608         * 
609         * @return A boolean.
610         */
611        public boolean equals(Object obj) {
612            if (obj == this) {
613                return true;
614            }
615            if (!(obj instanceof TaskSeriesCollection)) {
616                return false;
617            }
618            TaskSeriesCollection that = (TaskSeriesCollection) obj;
619            if (!ObjectUtilities.equal(this.data, that.data)) {
620                return false;
621            }
622            return true;
623        }
624    
625    }