001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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    @Override
172    public int getColumnIndex(Comparable key) {
173        int index = this.underlying.getColumnIndex(key);
174        if (index >= this.firstCategoryIndex && index <= lastCategoryIndex()) {
175            return index - this.firstCategoryIndex;
176        }
177        return -1;  // we didn't find the key
178    }
179
180    /**
181     * Returns the column key for a given index.
182     *
183     * @param column  the column index (zero-based).
184     *
185     * @return The column key.
186     *
187     * @throws IndexOutOfBoundsException if <code>row</code> is out of bounds.
188     */
189    @Override
190    public Comparable getColumnKey(int column) {
191        return this.underlying.getColumnKey(column + this.firstCategoryIndex);
192    }
193
194    /**
195     * Returns the column keys.
196     *
197     * @return The keys.
198     *
199     * @see #getColumnKey(int)
200     */
201    @Override
202    public List getColumnKeys() {
203        List result = new java.util.ArrayList();
204        int last = lastCategoryIndex();
205        for (int i = this.firstCategoryIndex; i < last; i++) {
206            result.add(this.underlying.getColumnKey(i));
207        }
208        return Collections.unmodifiableList(result);
209    }
210
211    /**
212     * Returns the row index for a given key.
213     *
214     * @param key  the row key.
215     *
216     * @return The row index, or <code>-1</code> if the key is unrecognised.
217     */
218    @Override
219    public int getRowIndex(Comparable key) {
220        return this.underlying.getRowIndex(key);
221    }
222
223    /**
224     * Returns the row key for a given index.
225     *
226     * @param row  the row index (zero-based).
227     *
228     * @return The row key.
229     *
230     * @throws IndexOutOfBoundsException if <code>row</code> is out of bounds.
231     */
232    @Override
233    public Comparable getRowKey(int row) {
234        return this.underlying.getRowKey(row);
235    }
236
237    /**
238     * Returns the row keys.
239     *
240     * @return The keys.
241     */
242    @Override
243    public List getRowKeys() {
244        return this.underlying.getRowKeys();
245    }
246
247    /**
248     * Returns the value for a pair of keys.
249     *
250     * @param rowKey  the row key (<code>null</code> not permitted).
251     * @param columnKey  the column key (<code>null</code> not permitted).
252     *
253     * @return The value (possibly <code>null</code>).
254     *
255     * @throws UnknownKeyException if either key is not defined in the dataset.
256     */
257    @Override
258    public Number getValue(Comparable rowKey, Comparable columnKey) {
259        int r = getRowIndex(rowKey);
260        int c = getColumnIndex(columnKey);
261        if (c != -1) {
262            return this.underlying.getValue(r, c + this.firstCategoryIndex);
263        }
264        else {
265            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
266        }
267    }
268
269    /**
270     * Returns the number of columns in the table.
271     *
272     * @return The column count.
273     */
274    @Override
275    public int getColumnCount() {
276        int last = lastCategoryIndex();
277        if (last == -1) {
278            return 0;
279        }
280        else {
281            return Math.max(last - this.firstCategoryIndex + 1, 0);
282        }
283    }
284
285    /**
286     * Returns the number of rows in the table.
287     *
288     * @return The row count.
289     */
290    @Override
291    public int getRowCount() {
292        return this.underlying.getRowCount();
293    }
294
295    /**
296     * Returns a value from the table.
297     *
298     * @param row  the row index (zero-based).
299     * @param column  the column index (zero-based).
300     *
301     * @return The value (possibly <code>null</code>).
302     */
303    @Override
304    public Number getValue(int row, int column) {
305        return this.underlying.getValue(row, column + this.firstCategoryIndex);
306    }
307
308    /**
309     * Returns the percent complete for a given item.
310     *
311     * @param rowKey  the row key.
312     * @param columnKey  the column key.
313     *
314     * @return The percent complete.
315     */
316    @Override
317    public Number getPercentComplete(Comparable rowKey, Comparable columnKey) {
318        int r = getRowIndex(rowKey);
319        int c = getColumnIndex(columnKey);
320        if (c != -1) {
321            return this.underlying.getPercentComplete(r,
322                    c + this.firstCategoryIndex);
323        }
324        else {
325            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
326        }
327    }
328
329    /**
330     * Returns the percentage complete value of a sub-interval for a given item.
331     *
332     * @param rowKey  the row key.
333     * @param columnKey  the column key.
334     * @param subinterval  the sub-interval.
335     *
336     * @return The percent complete value (possibly <code>null</code>).
337     *
338     * @see #getPercentComplete(int, int, int)
339     */
340    @Override
341    public Number getPercentComplete(Comparable rowKey, Comparable columnKey,
342            int subinterval) {
343        int r = getRowIndex(rowKey);
344        int c = getColumnIndex(columnKey);
345        if (c != -1) {
346            return this.underlying.getPercentComplete(r,
347                    c + this.firstCategoryIndex, subinterval);
348        }
349        else {
350            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
351        }
352    }
353
354    /**
355     * Returns the end value of a sub-interval for a given item.
356     *
357     * @param rowKey  the row key.
358     * @param columnKey  the column key.
359     * @param subinterval  the sub-interval.
360     *
361     * @return The end value (possibly <code>null</code>).
362     *
363     * @see #getStartValue(Comparable, Comparable, int)
364     */
365    @Override
366    public Number getEndValue(Comparable rowKey, Comparable columnKey,
367            int subinterval) {
368        int r = getRowIndex(rowKey);
369        int c = getColumnIndex(columnKey);
370        if (c != -1) {
371            return this.underlying.getEndValue(r,
372                    c + this.firstCategoryIndex, subinterval);
373        }
374        else {
375            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
376        }
377    }
378
379    /**
380     * Returns the end value of a sub-interval for a given item.
381     *
382     * @param row  the row index (zero-based).
383     * @param column  the column index (zero-based).
384     * @param subinterval  the sub-interval.
385     *
386     * @return The end value (possibly <code>null</code>).
387     *
388     * @see #getStartValue(int, int, int)
389     */
390    @Override
391    public Number getEndValue(int row, int column, int subinterval) {
392        return this.underlying.getEndValue(row,
393                column + this.firstCategoryIndex, subinterval);
394    }
395
396    /**
397     * Returns the percent complete for a given item.
398     *
399     * @param series  the row index (zero-based).
400     * @param category  the column index (zero-based).
401     *
402     * @return The percent complete.
403     */
404    @Override
405    public Number getPercentComplete(int series, int category) {
406        return this.underlying.getPercentComplete(series,
407                category + this.firstCategoryIndex);
408    }
409
410    /**
411     * Returns the percentage complete value of a sub-interval for a given item.
412     *
413     * @param row  the row index (zero-based).
414     * @param column  the column index (zero-based).
415     * @param subinterval  the sub-interval.
416     *
417     * @return The percent complete value (possibly <code>null</code>).
418     *
419     * @see #getPercentComplete(Comparable, Comparable, int)
420     */
421    @Override
422    public Number getPercentComplete(int row, int column, int subinterval) {
423        return this.underlying.getPercentComplete(row,
424                column + this.firstCategoryIndex, subinterval);
425    }
426
427    /**
428     * Returns the start value of a sub-interval for a given item.
429     *
430     * @param rowKey  the row key.
431     * @param columnKey  the column key.
432     * @param subinterval  the sub-interval.
433     *
434     * @return The start value (possibly <code>null</code>).
435     *
436     * @see #getEndValue(Comparable, Comparable, int)
437     */
438    @Override
439    public Number getStartValue(Comparable rowKey, Comparable columnKey,
440            int subinterval) {
441        int r = getRowIndex(rowKey);
442        int c = getColumnIndex(columnKey);
443        if (c != -1) {
444            return this.underlying.getStartValue(r,
445                    c + this.firstCategoryIndex, subinterval);
446        }
447        else {
448            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
449        }
450    }
451
452    /**
453     * Returns the start value of a sub-interval for a given item.
454     *
455     * @param row  the row index (zero-based).
456     * @param column  the column index (zero-based).
457     * @param subinterval  the sub-interval index (zero-based).
458     *
459     * @return The start value (possibly <code>null</code>).
460     *
461     * @see #getEndValue(int, int, int)
462     */
463    @Override
464    public Number getStartValue(int row, int column, int subinterval) {
465        return this.underlying.getStartValue(row,
466                column + this.firstCategoryIndex, subinterval);
467    }
468
469    /**
470     * Returns the number of sub-intervals for a given item.
471     *
472     * @param rowKey  the row key.
473     * @param columnKey  the column key.
474     *
475     * @return The sub-interval count.
476     *
477     * @see #getSubIntervalCount(int, int)
478     */
479    @Override
480    public int getSubIntervalCount(Comparable rowKey, Comparable columnKey) {
481        int r = getRowIndex(rowKey);
482        int c = getColumnIndex(columnKey);
483        if (c != -1) {
484            return this.underlying.getSubIntervalCount(r,
485                    c + this.firstCategoryIndex);
486        }
487        else {
488            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
489        }
490    }
491
492    /**
493     * Returns the number of sub-intervals for a given item.
494     *
495     * @param row  the row index (zero-based).
496     * @param column  the column index (zero-based).
497     *
498     * @return The sub-interval count.
499     *
500     * @see #getSubIntervalCount(Comparable, Comparable)
501     */
502    @Override
503    public int getSubIntervalCount(int row, int column) {
504        return this.underlying.getSubIntervalCount(row,
505                column + this.firstCategoryIndex);
506    }
507
508    /**
509     * Returns the start value for the interval for a given series and category.
510     *
511     * @param rowKey  the series key.
512     * @param columnKey  the category key.
513     *
514     * @return The start value (possibly <code>null</code>).
515     *
516     * @see #getEndValue(Comparable, Comparable)
517     */
518    @Override
519    public Number getStartValue(Comparable rowKey, Comparable columnKey) {
520        int r = getRowIndex(rowKey);
521        int c = getColumnIndex(columnKey);
522        if (c != -1) {
523            return this.underlying.getStartValue(r,
524                    c + this.firstCategoryIndex);
525        }
526        else {
527            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
528        }
529    }
530
531    /**
532     * Returns the start value for the interval for a given series and category.
533     *
534     * @param row  the series (zero-based index).
535     * @param column  the category (zero-based index).
536     *
537     * @return The start value (possibly <code>null</code>).
538     *
539     * @see #getEndValue(int, int)
540     */
541    @Override
542    public Number getStartValue(int row, int column) {
543        return this.underlying.getStartValue(row,
544                column + this.firstCategoryIndex);
545    }
546
547    /**
548     * Returns the end value for the interval for a given series and category.
549     *
550     * @param rowKey  the series key.
551     * @param columnKey  the category key.
552     *
553     * @return The end value (possibly <code>null</code>).
554     *
555     * @see #getStartValue(Comparable, Comparable)
556     */
557    @Override
558    public Number getEndValue(Comparable rowKey, Comparable columnKey) {
559        int r = getRowIndex(rowKey);
560        int c = getColumnIndex(columnKey);
561        if (c != -1) {
562            return this.underlying.getEndValue(r, c + this.firstCategoryIndex);
563        }
564        else {
565            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
566        }
567    }
568
569    /**
570     * Returns the end value for the interval for a given series and category.
571     *
572     * @param series  the series (zero-based index).
573     * @param category  the category (zero-based index).
574     *
575     * @return The end value (possibly <code>null</code>).
576     */
577    @Override
578    public Number getEndValue(int series, int category) {
579        return this.underlying.getEndValue(series,
580                category + this.firstCategoryIndex);
581    }
582
583    /**
584     * Tests this <code>SlidingCategoryDataset</code> for equality with an
585     * arbitrary object.
586     *
587     * @param obj  the object (<code>null</code> permitted).
588     *
589     * @return A boolean.
590     */
591    @Override
592    public boolean equals(Object obj) {
593        if (obj == this) {
594            return true;
595        }
596        if (!(obj instanceof SlidingGanttCategoryDataset)) {
597            return false;
598        }
599        SlidingGanttCategoryDataset that = (SlidingGanttCategoryDataset) obj;
600        if (this.firstCategoryIndex != that.firstCategoryIndex) {
601            return false;
602        }
603        if (this.maximumCategoryCount != that.maximumCategoryCount) {
604            return false;
605        }
606        if (!this.underlying.equals(that.underlying)) {
607            return false;
608        }
609        return true;
610    }
611
612    /**
613     * Returns an independent copy of the dataset.  Note that:
614     * <ul>
615     * <li>the underlying dataset is only cloned if it implements the
616     * {@link PublicCloneable} interface;</li>
617     * <li>the listeners registered with this dataset are not carried over to
618     * the cloned dataset.</li>
619     * </ul>
620     *
621     * @return An independent copy of the dataset.
622     *
623     * @throws CloneNotSupportedException if the dataset cannot be cloned for
624     *         any reason.
625     */
626    @Override
627    public Object clone() throws CloneNotSupportedException {
628        SlidingGanttCategoryDataset clone
629                = (SlidingGanttCategoryDataset) super.clone();
630        if (this.underlying instanceof PublicCloneable) {
631            PublicCloneable pc = (PublicCloneable) this.underlying;
632            clone.underlying = (GanttCategoryDataset) pc.clone();
633        }
634        return clone;
635    }
636
637}