    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
    006 *
    007 * Project Info:  http://www.jfree.org/jfreechart/index.html
    008 *
    009 * This library is free software; you can redistribute it and/or modify it
    010 * under the terms of the GNU Lesser General Public License as published by
    011 * the Free Software Foundation; either version 2.1 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This library is distributed in the hope that it will be useful, but
    015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017 * License for more details.
    018 *
    019 * You should have received a copy of the GNU Lesser General Public
    020 * License along with this library; if not, write to the Free Software
    021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022 * USA.
    023 *
    024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * -------------------------
    028 * TaskSeriesCollection.java
    029 * -------------------------
    030 * (C) Copyright 2002-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
    051package org.jfree.data.gantt;
    052
    053import java.io.Serializable;
    054import java.util.Iterator;
    055import java.util.List;
    056
    057import org.jfree.data.general.AbstractSeriesDataset;
    058import org.jfree.data.general.SeriesChangeEvent;
    059import org.jfree.data.time.TimePeriod;
    060import org.jfree.util.ObjectUtilities;
    061import 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 */
    067public 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}