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
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, 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}