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    }