    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     * SlidingGanttCategoryDataset.java
    029     * --------------------------------
    030     * (C) Copyright 2008, 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
    041    package org.jfree.data.gantt;
    042
    043    import java.util.Collections;
    044    import java.util.List;
    045
    046    import org.jfree.data.UnknownKeyException;
    047    import org.jfree.data.general.AbstractDataset;
    048    import org.jfree.data.general.DatasetChangeEvent;
    049    import 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     */
    059    public 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, c + this.firstCategoryIndex);
    503            }
    504            else {
    505                throw new UnknownKeyException("Unknown columnKey: " + columnKey);
    506            }
    507        }
    508
    509        /**
    510         * Returns the start value for the interval for a given series and category.
    511         *
    512         * @param row  the series (zero-based index).
    513         * @param column  the category (zero-based index).
    514         *
    515         * @return The start value (possibly <code>null</code>).
    516         *
    517         * @see #getEndValue(int, int)
    518         */
    519        public Number getStartValue(int row, int column) {
    520            return this.underlying.getStartValue(row,
    521                    column + this.firstCategoryIndex);
    522        }
    523
    524        /**
    525         * Returns the end value for the interval for a given series and category.
    526         *
    527         * @param rowKey  the series key.
    528         * @param columnKey  the category key.
    529         *
    530         * @return The end value (possibly <code>null</code>).
    531         *
    532         * @see #getStartValue(Comparable, Comparable)
    533         */
    534        public Number getEndValue(Comparable rowKey, Comparable columnKey) {
    535            int r = getRowIndex(rowKey);
    536            int c = getColumnIndex(columnKey);
    537            if (c != -1) {
    538                return this.underlying.getEndValue(r, c + this.firstCategoryIndex);
    539            }
    540            else {
    541                throw new UnknownKeyException("Unknown columnKey: " + columnKey);
    542            }
    543        }
    544
    545        /**
    546         * Returns the end value for the interval for a given series and category.
    547         *
    548         * @param series  the series (zero-based index).
    549         * @param category  the category (zero-based index).
    550         *
    551         * @return The end value (possibly <code>null</code>).
    552         */
    553        public Number getEndValue(int series, int category) {
    554            return this.underlying.getEndValue(series,
    555                    category + this.firstCategoryIndex);
    556        }
    557
    558        /**
    559         * Tests this <code>SlidingCategoryDataset</code> for equality with an
    560         * arbitrary object.
    561         *
    562         * @param obj  the object (<code>null</code> permitted).
    563         *
    564         * @return A boolean.
    565         */
    566        public boolean equals(Object obj) {
    567            if (obj == this) {
    568                return true;
    569            }
    570            if (!(obj instanceof SlidingGanttCategoryDataset)) {
    571                return false;
    572            }
    573            SlidingGanttCategoryDataset that = (SlidingGanttCategoryDataset) obj;
    574            if (this.firstCategoryIndex != that.firstCategoryIndex) {
    575                return false;
    576            }
    577            if (this.maximumCategoryCount != that.maximumCategoryCount) {
    578                return false;
    579            }
    580            if (!this.underlying.equals(that.underlying)) {
    581                return false;
    582            }
    583            return true;
    584        }
    585
    586        /**
    587         * Returns an independent copy of the dataset.  Note that:
    588         * <ul>
    589         * <li>the underlying dataset is only cloned if it implements the
    590         * {@link PublicCloneable} interface;</li>
    591         * <li>the listeners registered with this dataset are not carried over to
    592         * the cloned dataset.</li>
    593         * </ul>
    594         *
    595         * @return An independent copy of the dataset.
    596         *
    597         * @throws CloneNotSupportedException if the dataset cannot be cloned for
    598         *         any reason.
    599         */
    600        public Object clone() throws CloneNotSupportedException {
    601            SlidingGanttCategoryDataset clone
    602                    = (SlidingGanttCategoryDataset) super.clone();
    603            if (this.underlying instanceof PublicCloneable) {
    604                PublicCloneable pc = (PublicCloneable) this.underlying;
    605                clone.underlying = (GanttCategoryDataset) pc.clone();
    606            }
    607            return clone;
    608        }
    609
    610    }