    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    }