    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 * SlidingGanttCategoryDataset.java
    029 * --------------------------------
    030 * (C) Copyright 2008, 2009, by Object Refinery Limited.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   -;
    034 *
    035 * Changes
    036 * -------
    037 * 09-May-2008 : Version 1 (DG);
    038 *
    039 */
    040
    041package org.jfree.data.gantt;
    042
    043import java.util.Collections;
    044import java.util.List;
    045
    046import org.jfree.data.UnknownKeyException;
    047import org.jfree.data.general.AbstractDataset;
    048import org.jfree.data.general.DatasetChangeEvent;
    049import org.jfree.util.PublicCloneable;
    050
    051/**
    052 * A {@link GanttCategoryDataset} implementation that presents a subset of the
    053 * categories in an underlying dataset.  The index of the first "visible"
    054 * category can be modified, which provides a means of "sliding" through
    055 * the categories in the underlying dataset.
    056 *
    057 * @since 1.0.10
    058 */
    059public class SlidingGanttCategoryDataset extends AbstractDataset
    060        implements GanttCategoryDataset {
    061
    062    /** The underlying dataset. */
    063    private GanttCategoryDataset underlying;
    064
    065    /** The index of the first category to present. */
    066    private int firstCategoryIndex;
    067
    068    /** The maximum number of categories to present. */
    069    private int maximumCategoryCount;
    070
    071    /**
    072     * Creates a new instance.
    073     *
    074     * @param underlying  the underlying dataset (<code>null</code> not
    075     *     permitted).
    076     * @param firstColumn  the index of the first visible column from the
    077     *     underlying dataset.
    078     * @param maxColumns  the maximumColumnCount.
    079     */
    080    public SlidingGanttCategoryDataset(GanttCategoryDataset underlying,
    081            int firstColumn, int maxColumns) {
    082        this.underlying = underlying;
    083        this.firstCategoryIndex = firstColumn;
    084        this.maximumCategoryCount = maxColumns;
    085    }
    086
    087    /**
    088     * Returns the underlying dataset that was supplied to the constructor.
    089     *
    090     * @return The underlying dataset (never <code>null</code>).
    091     */
    092    public GanttCategoryDataset getUnderlyingDataset() {
    093        return this.underlying;
    094    }
    095
    096    /**
    097     * Returns the index of the first visible category.
    098     *
    099     * @return The index.
    100     *
    101     * @see #setFirstCategoryIndex(int)
    102     */
    103    public int getFirstCategoryIndex() {
    104        return this.firstCategoryIndex;
    105    }
    106
    107    /**
    108     * Sets the index of the first category that should be used from the
    109     * underlying dataset, and sends a {@link DatasetChangeEvent} to all
    110     * registered listeners.
    111     *
    112     * @param first  the index.
    113     *
    114     * @see #getFirstCategoryIndex()
    115     */
    116    public void setFirstCategoryIndex(int first) {
    117        if (first < 0 || first >= this.underlying.getColumnCount()) {
    118            throw new IllegalArgumentException("Invalid index.");
    119        }
    120        this.firstCategoryIndex = first;
    121        fireDatasetChanged();
    122    }
    123
    124    /**
    125     * Returns the maximum category count.
    126     *
    127     * @return The maximum category count.
    128     *
    129     * @see #setMaximumCategoryCount(int)
    130     */
    131    public int getMaximumCategoryCount() {
    132        return this.maximumCategoryCount;
    133    }
    134
    135    /**
    136     * Sets the maximum category count and sends a {@link DatasetChangeEvent}
    137     * to all registered listeners.
    138     *
    139     * @param max  the maximum.
    140     *
    141     * @see #getMaximumCategoryCount()
    142     */
    143    public void setMaximumCategoryCount(int max) {
    144        if (max < 0) {
    145            throw new IllegalArgumentException("Requires 'max' >= 0.");
    146        }
    147        this.maximumCategoryCount = max;
    148        fireDatasetChanged();
    149    }
    150
    151    /**
    152     * Returns the index of the last column for this dataset, or -1.
    153     *
    154     * @return The index.
    155     */
    156    private int lastCategoryIndex() {
    157        if (this.maximumCategoryCount == 0) {
    158            return -1;
    159        }
    160        return Math.min(this.firstCategoryIndex + this.maximumCategoryCount,
    161                this.underlying.getColumnCount()) - 1;
    162    }
    163
    164    /**
    165     * Returns the index for the specified column key.
    166     *
    167     * @param key  the key.
    168     *
    169     * @return The column index, or -1 if the key is not recognised.
    170     */
    171    public int getColumnIndex(Comparable key) {
    172        int index = this.underlying.getColumnIndex(key);
    173        if (index >= this.firstCategoryIndex && index <= lastCategoryIndex()) {
    174            return index - this.firstCategoryIndex;
    175        }
    176        return -1;  // we didn't find the key
    177    }
    178
    179    /**
    180     * Returns the column key for a given index.
    181     *
    182     * @param column  the column index (zero-based).
    183     *
    184     * @return The column key.
    185     *
    186     * @throws IndexOutOfBoundsException if <code>row</code> is out of bounds.
    187     */
    188    public Comparable getColumnKey(int column) {
    189        return this.underlying.getColumnKey(column + this.firstCategoryIndex);
    190    }
    191
    192    /**
    193     * Returns the column keys.
    194     *
    195     * @return The keys.
    196     *
    197     * @see #getColumnKey(int)
    198     */
    199    public List getColumnKeys() {
    200        List result = new java.util.ArrayList();
    201        int last = lastCategoryIndex();
    202        for (int i = this.firstCategoryIndex; i < last; i++) {
    203            result.add(this.underlying.getColumnKey(i));
    204        }
    205        return Collections.unmodifiableList(result);
    206    }
    207
    208    /**
    209     * Returns the row index for a given key.
    210     *
    211     * @param key  the row key.
    212     *
    213     * @return The row index, or <code>-1</code> if the key is unrecognised.
    214     */
    215    public int getRowIndex(Comparable key) {
    216        return this.underlying.getRowIndex(key);
    217    }
    218
    219    /**
    220     * Returns the row key for a given index.
    221     *
    222     * @param row  the row index (zero-based).
    223     *
    224     * @return The row key.
    225     *
    226     * @throws IndexOutOfBoundsException if <code>row</code> is out of bounds.
    227     */
    228    public Comparable getRowKey(int row) {
    229        return this.underlying.getRowKey(row);
    230    }
    231
    232    /**
    233     * Returns the row keys.
    234     *
    235     * @return The keys.
    236     */
    237    public List getRowKeys() {
    238        return this.underlying.getRowKeys();
    239    }
    240
    241    /**
    242     * Returns the value for a pair of keys.
    243     *
    244     * @param rowKey  the row key (<code>null</code> not permitted).
    245     * @param columnKey  the column key (<code>null</code> not permitted).
    246     *
    247     * @return The value (possibly <code>null</code>).
    248     *
    249     * @throws UnknownKeyException if either key is not defined in the dataset.
    250     */
    251    public Number getValue(Comparable rowKey, Comparable columnKey) {
    252        int r = getRowIndex(rowKey);
    253        int c = getColumnIndex(columnKey);
    254        if (c != -1) {
    255            return this.underlying.getValue(r, c + this.firstCategoryIndex);
    256        }
    257        else {
    258            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
    259        }
    260    }
    261
    262    /**
    263     * Returns the number of columns in the table.
    264     *
    265     * @return The column count.
    266     */
    267    public int getColumnCount() {
    268        int last = lastCategoryIndex();
    269        if (last == -1) {
    270            return 0;
    271        }
    272        else {
    273            return Math.max(last - this.firstCategoryIndex + 1, 0);
    274        }
    275    }
    276
    277    /**
    278     * Returns the number of rows in the table.
    279     *
    280     * @return The row count.
    281     */
    282    public int getRowCount() {
    283        return this.underlying.getRowCount();
    284    }
    285
    286    /**
    287     * Returns a value from the table.
    288     *
    289     * @param row  the row index (zero-based).
    290     * @param column  the column index (zero-based).
    291     *
    292     * @return The value (possibly <code>null</code>).
    293     */
    294    public Number getValue(int row, int column) {
    295        return this.underlying.getValue(row, column + this.firstCategoryIndex);
    296    }
    297
    298    /**
    299     * Returns the percent complete for a given item.
    300     *
    301     * @param rowKey  the row key.
    302     * @param columnKey  the column key.
    303     *
    304     * @return The percent complete.
    305     */
    306    public Number getPercentComplete(Comparable rowKey, Comparable columnKey) {
    307        int r = getRowIndex(rowKey);
    308        int c = getColumnIndex(columnKey);
    309        if (c != -1) {
    310            return this.underlying.getPercentComplete(r,
    311                    c + this.firstCategoryIndex);
    312        }
    313        else {
    314            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
    315        }
    316    }
    317
    318    /**
    319     * Returns the percentage complete value of a sub-interval for a given item.
    320     *
    321     * @param rowKey  the row key.
    322     * @param columnKey  the column key.
    323     * @param subinterval  the sub-interval.
    324     *
    325     * @return The percent complete value (possibly <code>null</code>).
    326     *
    327     * @see #getPercentComplete(int, int, int)
    328     */
    329    public Number getPercentComplete(Comparable rowKey, Comparable columnKey,
    330            int subinterval) {
    331        int r = getRowIndex(rowKey);
    332        int c = getColumnIndex(columnKey);
    333        if (c != -1) {
    334            return this.underlying.getPercentComplete(r,
    335                    c + this.firstCategoryIndex, subinterval);
    336        }
    337        else {
    338            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
    339        }
    340    }
    341
    342    /**
    343     * Returns the end value of a sub-interval for a given item.
    344     *
    345     * @param rowKey  the row key.
    346     * @param columnKey  the column key.
    347     * @param subinterval  the sub-interval.
    348     *
    349     * @return The end value (possibly <code>null</code>).
    350     *
    351     * @see #getStartValue(Comparable, Comparable, int)
    352     */
    353    public Number getEndValue(Comparable rowKey, Comparable columnKey,
    354            int subinterval) {
    355        int r = getRowIndex(rowKey);
    356        int c = getColumnIndex(columnKey);
    357        if (c != -1) {
    358            return this.underlying.getEndValue(r,
    359                    c + this.firstCategoryIndex, subinterval);
    360        }
    361        else {
    362            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
    363        }
    364    }
    365
    366    /**
    367     * Returns the end value of a sub-interval for a given item.
    368     *
    369     * @param row  the row index (zero-based).
    370     * @param column  the column index (zero-based).
    371     * @param subinterval  the sub-interval.
    372     *
    373     * @return The end value (possibly <code>null</code>).
    374     *
    375     * @see #getStartValue(int, int, int)
    376     */
    377    public Number getEndValue(int row, int column, int subinterval) {
    378        return this.underlying.getEndValue(row,
    379                column + this.firstCategoryIndex, subinterval);
    380    }
    381
    382    /**
    383     * Returns the percent complete for a given item.
    384     *
    385     * @param series  the row index (zero-based).
    386     * @param category  the column index (zero-based).
    387     *
    388     * @return The percent complete.
    389     */
    390    public Number getPercentComplete(int series, int category) {
    391        return this.underlying.getPercentComplete(series,
    392                category + this.firstCategoryIndex);
    393    }
    394
    395    /**
    396     * Returns the percentage complete value of a sub-interval for a given item.
    397     *
    398     * @param row  the row index (zero-based).
    399     * @param column  the column index (zero-based).
    400     * @param subinterval  the sub-interval.
    401     *
    402     * @return The percent complete value (possibly <code>null</code>).
    403     *
    404     * @see #getPercentComplete(Comparable, Comparable, int)
    405     */
    406    public Number getPercentComplete(int row, int column, int subinterval) {
    407        return this.underlying.getPercentComplete(row,
    408                column + this.firstCategoryIndex, subinterval);
    409    }
    410
    411    /**
    412     * Returns the start value of a sub-interval for a given item.
    413     *
    414     * @param rowKey  the row key.
    415     * @param columnKey  the column key.
    416     * @param subinterval  the sub-interval.
    417     *
    418     * @return The start value (possibly <code>null</code>).
    419     *
    420     * @see #getEndValue(Comparable, Comparable, int)
    421     */
    422    public Number getStartValue(Comparable rowKey, Comparable columnKey,
    423            int subinterval) {
    424        int r = getRowIndex(rowKey);
    425        int c = getColumnIndex(columnKey);
    426        if (c != -1) {
    427            return this.underlying.getStartValue(r,
    428                    c + this.firstCategoryIndex, subinterval);
    429        }
    430        else {
    431            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
    432        }
    433    }
    434
    435    /**
    436     * Returns the start value of a sub-interval for a given item.
    437     *
    438     * @param row  the row index (zero-based).
    439     * @param column  the column index (zero-based).
    440     * @param subinterval  the sub-interval index (zero-based).
    441     *
    442     * @return The start value (possibly <code>null</code>).
    443     *
    444     * @see #getEndValue(int, int, int)
    445     */
    446    public Number getStartValue(int row, int column, int subinterval) {
    447        return this.underlying.getStartValue(row,
    448                column + this.firstCategoryIndex, subinterval);
    449    }
    450
    451    /**
    452     * Returns the number of sub-intervals for a given item.
    453     *
    454     * @param rowKey  the row key.
    455     * @param columnKey  the column key.
    456     *
    457     * @return The sub-interval count.
    458     *
    459     * @see #getSubIntervalCount(int, int)
    460     */
    461    public int getSubIntervalCount(Comparable rowKey, Comparable columnKey) {
    462        int r = getRowIndex(rowKey);
    463        int c = getColumnIndex(columnKey);
    464        if (c != -1) {
    465            return this.underlying.getSubIntervalCount(r,
    466                    c + this.firstCategoryIndex);
    467        }
    468        else {
    469            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
    470        }
    471    }
    472
    473    /**
    474     * Returns the number of sub-intervals for a given item.
    475     *
    476     * @param row  the row index (zero-based).
    477     * @param column  the column index (zero-based).
    478     *
    479     * @return The sub-interval count.
    480     *
    481     * @see #getSubIntervalCount(Comparable, Comparable)
    482     */
    483    public int getSubIntervalCount(int row, int column) {
    484        return this.underlying.getSubIntervalCount(row,
    485                column + this.firstCategoryIndex);
    486    }
    487
    488    /**
    489     * Returns the start value for the interval for a given series and category.
    490     *
    491     * @param rowKey  the series key.
    492     * @param columnKey  the category key.
    493     *
    494     * @return The start value (possibly <code>null</code>).
    495     *
    496     * @see #getEndValue(Comparable, Comparable)
    497     */
    498    public Number getStartValue(Comparable rowKey, Comparable columnKey) {
    499        int r = getRowIndex(rowKey);
    500        int c = getColumnIndex(columnKey);
    501        if (c != -1) {
    502            return this.underlying.getStartValue(r,
    503                    c + this.firstCategoryIndex);
    504        }
    505        else {
    506            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
    507        }
    508    }
    509
    510    /**
    511     * Returns the start value for the interval for a given series and category.
    512     *
    513     * @param row  the series (zero-based index).
    514     * @param column  the category (zero-based index).
    515     *
    516     * @return The start value (possibly <code>null</code>).
    517     *
    518     * @see #getEndValue(int, int)
    519     */
    520    public Number getStartValue(int row, int column) {
    521        return this.underlying.getStartValue(row,
    522                column + this.firstCategoryIndex);
    523    }
    524
    525    /**
    526     * Returns the end value for the interval for a given series and category.
    527     *
    528     * @param rowKey  the series key.
    529     * @param columnKey  the category key.
    530     *
    531     * @return The end value (possibly <code>null</code>).
    532     *
    533     * @see #getStartValue(Comparable, Comparable)
    534     */
    535    public Number getEndValue(Comparable rowKey, Comparable columnKey) {
    536        int r = getRowIndex(rowKey);
    537        int c = getColumnIndex(columnKey);
    538        if (c != -1) {
    539            return this.underlying.getEndValue(r, c + this.firstCategoryIndex);
    540        }
    541        else {
    542            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
    543        }
    544    }
    545
    546    /**
    547     * Returns the end value for the interval for a given series and category.
    548     *
    549     * @param series  the series (zero-based index).
    550     * @param category  the category (zero-based index).
    551     *
    552     * @return The end value (possibly <code>null</code>).
    553     */
    554    public Number getEndValue(int series, int category) {
    555        return this.underlying.getEndValue(series,
    556                category + this.firstCategoryIndex);
    557    }
    558
    559    /**
    560     * Tests this <code>SlidingCategoryDataset</code> for equality with an
    561     * arbitrary object.
    562     *
    563     * @param obj  the object (<code>null</code> permitted).
    564     *
    565     * @return A boolean.
    566     */
    567    public boolean equals(Object obj) {
    568        if (obj == this) {
    569            return true;
    570        }
    571        if (!(obj instanceof SlidingGanttCategoryDataset)) {
    572            return false;
    573        }
    574        SlidingGanttCategoryDataset that = (SlidingGanttCategoryDataset) obj;
    575        if (this.firstCategoryIndex != that.firstCategoryIndex) {
    576            return false;
    577        }
    578        if (this.maximumCategoryCount != that.maximumCategoryCount) {
    579            return false;
    580        }
    581        if (!this.underlying.equals(that.underlying)) {
    582            return false;
    583        }
    584        return true;
    585    }
    586
    587    /**
    588     * Returns an independent copy of the dataset.  Note that:
    589     * <ul>
    590     * <li>the underlying dataset is only cloned if it implements the
    591     * {@link PublicCloneable} interface;</li>
    592     * <li>the listeners registered with this dataset are not carried over to
    593     * the cloned dataset.</li>
    594     * </ul>
    595     *
    596     * @return An independent copy of the dataset.
    597     *
    598     * @throws CloneNotSupportedException if the dataset cannot be cloned for
    599     *         any reason.
    600     */
    601    public Object clone() throws CloneNotSupportedException {
    602        SlidingGanttCategoryDataset clone
    603                = (SlidingGanttCategoryDataset) super.clone();
    604        if (this.underlying instanceof PublicCloneable) {
    605            PublicCloneable pc = (PublicCloneable) this.underlying;
    606            clone.underlying = (GanttCategoryDataset) pc.clone();
    607        }
    608        return clone;
    609    }
    610
    611}