001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, by David Gilbert 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-2022, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
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;
045import org.jfree.chart.api.PublicCloneable;
046
047import org.jfree.data.UnknownKeyException;
048import org.jfree.data.general.AbstractDataset;
049import org.jfree.data.general.DatasetChangeEvent;
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} 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}).
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} 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} 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} 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} not permitted).
251     * @param columnKey  the column key ({@code null} not permitted).
252     *
253     * @return The value (possibly {@code null}).
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            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
263        }
264        else if (r == -1) {
265            throw new UnknownKeyException("Unknown rowKey: " + rowKey);
266        }
267        else {
268            return this.underlying.getValue(r, c + this.firstCategoryIndex);
269        }
270    }
271
272    /**
273     * Returns the number of columns in the table.
274     *
275     * @return The column count.
276     */
277    @Override
278    public int getColumnCount() {
279        int last = lastCategoryIndex();
280        if (last == -1) {
281            return 0;
282        }
283        else {
284            return Math.max(last - this.firstCategoryIndex + 1, 0);
285        }
286    }
287
288    /**
289     * Returns the number of rows in the table.
290     *
291     * @return The row count.
292     */
293    @Override
294    public int getRowCount() {
295        return this.underlying.getRowCount();
296    }
297
298    /**
299     * Returns a value from the table.
300     *
301     * @param row  the row index (zero-based).
302     * @param column  the column index (zero-based).
303     *
304     * @return The value (possibly {@code null}).
305     */
306    @Override
307    public Number getValue(int row, int column) {
308        return this.underlying.getValue(row, column + this.firstCategoryIndex);
309    }
310
311    /**
312     * Returns the percent complete for a given item.
313     *
314     * @param rowKey  the row key.
315     * @param columnKey  the column key.
316     *
317     * @return The percent complete.
318     */
319    @Override
320    public Number getPercentComplete(Comparable rowKey, Comparable columnKey) {
321        int r = getRowIndex(rowKey);
322        int c = getColumnIndex(columnKey);
323        if (c == -1) {
324            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
325        }
326        else if (r == -1) {
327            throw new UnknownKeyException("Unknown rowKey: " + rowKey);
328        }
329        else {
330            return this.underlying.getPercentComplete(r,
331                    c + this.firstCategoryIndex);
332        }
333    }
334
335    /**
336     * Returns the percentage complete value of a sub-interval for a given item.
337     *
338     * @param rowKey  the row key.
339     * @param columnKey  the column key.
340     * @param subinterval  the sub-interval.
341     *
342     * @return The percent complete value (possibly {@code null}).
343     *
344     * @see #getPercentComplete(int, int, int)
345     */
346    @Override
347    public Number getPercentComplete(Comparable rowKey, Comparable columnKey,
348            int subinterval) {
349        int r = getRowIndex(rowKey);
350        int c = getColumnIndex(columnKey);
351        if (c == -1) {
352            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
353        }
354        else if (r == -1) {
355            throw new UnknownKeyException("Unknown rowKey: " + rowKey);
356        }
357        else {
358            return this.underlying.getPercentComplete(r,
359                    c + this.firstCategoryIndex, subinterval);
360        }
361    }
362
363    /**
364     * Returns the end value of a sub-interval for a given item.
365     *
366     * @param rowKey  the row key.
367     * @param columnKey  the column key.
368     * @param subinterval  the sub-interval.
369     *
370     * @return The end value (possibly {@code null}).
371     *
372     * @see #getStartValue(Comparable, Comparable, int)
373     */
374    @Override
375    public Number getEndValue(Comparable rowKey, Comparable columnKey,
376            int subinterval) {
377        int r = getRowIndex(rowKey);
378        int c = getColumnIndex(columnKey);
379        if (c == -1) {
380            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
381        }
382        else if (r == -1) {
383            throw new UnknownKeyException("Unknown rowKey: " + rowKey);
384        }
385        else {
386            return this.underlying.getEndValue(r,
387                    c + this.firstCategoryIndex, subinterval);
388        }
389    }
390
391    /**
392     * Returns the end value of a sub-interval for a given item.
393     *
394     * @param row  the row index (zero-based).
395     * @param column  the column index (zero-based).
396     * @param subinterval  the sub-interval.
397     *
398     * @return The end value (possibly {@code null}).
399     *
400     * @see #getStartValue(int, int, int)
401     */
402    @Override
403    public Number getEndValue(int row, int column, int subinterval) {
404        return this.underlying.getEndValue(row,
405                column + this.firstCategoryIndex, subinterval);
406    }
407
408    /**
409     * Returns the percent complete for a given item.
410     *
411     * @param series  the row index (zero-based).
412     * @param category  the column index (zero-based).
413     *
414     * @return The percent complete.
415     */
416    @Override
417    public Number getPercentComplete(int series, int category) {
418        return this.underlying.getPercentComplete(series,
419                category + this.firstCategoryIndex);
420    }
421
422    /**
423     * Returns the percentage complete value of a sub-interval for a given item.
424     *
425     * @param row  the row index (zero-based).
426     * @param column  the column index (zero-based).
427     * @param subinterval  the sub-interval.
428     *
429     * @return The percent complete value (possibly {@code null}).
430     *
431     * @see #getPercentComplete(Comparable, Comparable, int)
432     */
433    @Override
434    public Number getPercentComplete(int row, int column, int subinterval) {
435        return this.underlying.getPercentComplete(row,
436                column + this.firstCategoryIndex, subinterval);
437    }
438
439    /**
440     * Returns the start value of a sub-interval for a given item.
441     *
442     * @param rowKey  the row key.
443     * @param columnKey  the column key.
444     * @param subinterval  the sub-interval.
445     *
446     * @return The start value (possibly {@code null}).
447     *
448     * @see #getEndValue(Comparable, Comparable, int)
449     */
450    @Override
451    public Number getStartValue(Comparable rowKey, Comparable columnKey,
452            int subinterval) {
453        int r = getRowIndex(rowKey);
454        int c = getColumnIndex(columnKey);
455        if (c == -1) {
456            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
457        }
458        else if (r == -1) {
459            throw new UnknownKeyException("Unknown rowKey: " + rowKey);
460        }
461        else {
462            return this.underlying.getStartValue(r,
463                    c + this.firstCategoryIndex, subinterval);
464        }
465    }
466
467    /**
468     * Returns the start value of a sub-interval for a given item.
469     *
470     * @param row  the row index (zero-based).
471     * @param column  the column index (zero-based).
472     * @param subinterval  the sub-interval index (zero-based).
473     *
474     * @return The start value (possibly {@code null}).
475     *
476     * @see #getEndValue(int, int, int)
477     */
478    @Override
479    public Number getStartValue(int row, int column, int subinterval) {
480        return this.underlying.getStartValue(row,
481                column + this.firstCategoryIndex, subinterval);
482    }
483
484    /**
485     * Returns the number of sub-intervals for a given item.
486     *
487     * @param rowKey  the row key.
488     * @param columnKey  the column key.
489     *
490     * @return The sub-interval count.
491     *
492     * @see #getSubIntervalCount(int, int)
493     */
494    @Override
495    public int getSubIntervalCount(Comparable rowKey, Comparable columnKey) {
496        int r = getRowIndex(rowKey);
497        int c = getColumnIndex(columnKey);
498        if (c == -1) {
499            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
500        } else if (r == -1) {
501            throw new UnknownKeyException("Unknown rowKey: " + rowKey);
502        }
503        else {
504            return this.underlying.getSubIntervalCount(r,
505                    c + this.firstCategoryIndex);
506        }
507    }
508
509    /**
510     * Returns the number of sub-intervals for a given item.
511     *
512     * @param row  the row index (zero-based).
513     * @param column  the column index (zero-based).
514     *
515     * @return The sub-interval count.
516     *
517     * @see #getSubIntervalCount(Comparable, Comparable)
518     */
519    @Override
520    public int getSubIntervalCount(int row, int column) {
521        return this.underlying.getSubIntervalCount(row,
522                column + this.firstCategoryIndex);
523    }
524
525    /**
526     * Returns the start value for the interval for a given series and category.
527     *
528     * @param rowKey  the series key.
529     * @param columnKey  the category key.
530     *
531     * @return The start value (possibly {@code null}).
532     *
533     * @see #getEndValue(Comparable, Comparable)
534     */
535    @Override
536    public Number getStartValue(Comparable rowKey, Comparable columnKey) {
537        int r = getRowIndex(rowKey);
538        int c = getColumnIndex(columnKey);
539        if (c == -1) {
540            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
541        } else if (r == -1) {
542            throw new UnknownKeyException("Unknown rowKey: " + rowKey);
543        }
544        else {
545            return this.underlying.getStartValue(r,
546                    c + this.firstCategoryIndex);
547        }
548    }
549
550    /**
551     * Returns the start value for the interval for a given series and category.
552     *
553     * @param row  the series (zero-based index).
554     * @param column  the category (zero-based index).
555     *
556     * @return The start value (possibly {@code null}).
557     *
558     * @see #getEndValue(int, int)
559     */
560    @Override
561    public Number getStartValue(int row, int column) {
562        return this.underlying.getStartValue(row,
563                column + this.firstCategoryIndex);
564    }
565
566    /**
567     * Returns the end value for the interval for a given series and category.
568     *
569     * @param rowKey  the series key.
570     * @param columnKey  the category key.
571     *
572     * @return The end value (possibly {@code null}).
573     *
574     * @see #getStartValue(Comparable, Comparable)
575     */
576    @Override
577    public Number getEndValue(Comparable rowKey, Comparable columnKey) {
578        int r = getRowIndex(rowKey);
579        int c = getColumnIndex(columnKey);
580        if (c == -1) {
581            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
582        } else if (r == -1) {
583            throw new UnknownKeyException("Unknown rowKey: " + rowKey);
584        }
585        else {
586            return this.underlying.getEndValue(r, c + this.firstCategoryIndex);
587        }
588    }
589
590    /**
591     * Returns the end value for the interval for a given series and category.
592     *
593     * @param series  the series (zero-based index).
594     * @param category  the category (zero-based index).
595     *
596     * @return The end value (possibly {@code null}).
597     */
598    @Override
599    public Number getEndValue(int series, int category) {
600        return this.underlying.getEndValue(series,
601                category + this.firstCategoryIndex);
602    }
603
604    /**
605     * Tests this {@code SlidingGanttCategoryDataset} instance for equality 
606     * with an arbitrary object.
607     *
608     * @param obj  the object ({@code null} permitted).
609     *
610     * @return A boolean.
611     */
612    @Override
613    public boolean equals(Object obj) {
614        if (obj == this) {
615            return true;
616        }
617        if (!(obj instanceof SlidingGanttCategoryDataset)) {
618            return false;
619        }
620        SlidingGanttCategoryDataset that = (SlidingGanttCategoryDataset) obj;
621        if (this.firstCategoryIndex != that.firstCategoryIndex) {
622            return false;
623        }
624        if (this.maximumCategoryCount != that.maximumCategoryCount) {
625            return false;
626        }
627        if (!this.underlying.equals(that.underlying)) {
628            return false;
629        }
630        return true;
631    }
632
633    /**
634     * Returns an independent copy of the dataset.  Note that:
635     * <ul>
636     * <li>the underlying dataset is only cloned if it implements the
637     * {@link PublicCloneable} interface;</li>
638     * <li>the listeners registered with this dataset are not carried over to
639     * the cloned dataset.</li>
640     * </ul>
641     *
642     * @return An independent copy of the dataset.
643     *
644     * @throws CloneNotSupportedException if the dataset cannot be cloned for
645     *         any reason.
646     */
647    @Override
648    public Object clone() throws CloneNotSupportedException {
649        SlidingGanttCategoryDataset clone
650                = (SlidingGanttCategoryDataset) super.clone();
651        if (this.underlying instanceof PublicCloneable) {
652            PublicCloneable pc = (PublicCloneable) this.underlying;
653            clone.underlying = (GanttCategoryDataset) pc.clone();
654        }
655        return clone;
656    }
657
658}