001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, 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-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier);
034 *
035 */
036
037package org.jfree.data.gantt;
038
039import java.util.Collections;
040import java.util.List;
041import java.util.Objects;
042import org.jfree.chart.util.PublicCloneable;
043
044import org.jfree.data.UnknownKeyException;
045import org.jfree.data.general.AbstractDataset;
046import org.jfree.data.general.DatasetChangeEvent;
047
048/**
049 * A {@link GanttCategoryDataset} implementation that presents a subset of the
050 * categories in an underlying dataset.  The index of the first "visible"
051 * category can be modified, which provides a means of "sliding" through
052 * the categories in the underlying dataset.
053 */
054public class SlidingGanttCategoryDataset extends AbstractDataset
055        implements GanttCategoryDataset {
056
057    /** The underlying dataset. */
058    private GanttCategoryDataset underlying;
059
060    /** The index of the first category to present. */
061    private int firstCategoryIndex;
062
063    /** The maximum number of categories to present. */
064    private int maximumCategoryCount;
065
066    /**
067     * Creates a new instance.
068     *
069     * @param underlying  the underlying dataset ({@code null} not
070     *     permitted).
071     * @param firstColumn  the index of the first visible column from the
072     *     underlying dataset.
073     * @param maxColumns  the maximumColumnCount.
074     */
075    public SlidingGanttCategoryDataset(GanttCategoryDataset underlying,
076            int firstColumn, int maxColumns) {
077        this.underlying = underlying;
078        this.firstCategoryIndex = firstColumn;
079        this.maximumCategoryCount = maxColumns;
080    }
081
082    /**
083     * Returns the underlying dataset that was supplied to the constructor.
084     *
085     * @return The underlying dataset (never {@code null}).
086     */
087    public GanttCategoryDataset getUnderlyingDataset() {
088        return this.underlying;
089    }
090
091    /**
092     * Returns the index of the first visible category.
093     *
094     * @return The index.
095     *
096     * @see #setFirstCategoryIndex(int)
097     */
098    public int getFirstCategoryIndex() {
099        return this.firstCategoryIndex;
100    }
101
102    /**
103     * Sets the index of the first category that should be used from the
104     * underlying dataset, and sends a {@link DatasetChangeEvent} to all
105     * registered listeners.
106     *
107     * @param first  the index.
108     *
109     * @see #getFirstCategoryIndex()
110     */
111    public void setFirstCategoryIndex(int first) {
112        if (first < 0 || first >= this.underlying.getColumnCount()) {
113            throw new IllegalArgumentException("Invalid index.");
114        }
115        this.firstCategoryIndex = first;
116        fireDatasetChanged();
117    }
118
119    /**
120     * Returns the maximum category count.
121     *
122     * @return The maximum category count.
123     *
124     * @see #setMaximumCategoryCount(int)
125     */
126    public int getMaximumCategoryCount() {
127        return this.maximumCategoryCount;
128    }
129
130    /**
131     * Sets the maximum category count and sends a {@link DatasetChangeEvent}
132     * to all registered listeners.
133     *
134     * @param max  the maximum.
135     *
136     * @see #getMaximumCategoryCount()
137     */
138    public void setMaximumCategoryCount(int max) {
139        if (max < 0) {
140            throw new IllegalArgumentException("Requires 'max' >= 0.");
141        }
142        this.maximumCategoryCount = max;
143        fireDatasetChanged();
144    }
145
146    /**
147     * Returns the index of the last column for this dataset, or -1.
148     *
149     * @return The index.
150     */
151    private int lastCategoryIndex() {
152        if (this.maximumCategoryCount == 0) {
153            return -1;
154        }
155        return Math.min(this.firstCategoryIndex + this.maximumCategoryCount,
156                this.underlying.getColumnCount()) - 1;
157    }
158
159    /**
160     * Returns the index for the specified column key.
161     *
162     * @param key  the key.
163     *
164     * @return The column index, or -1 if the key is not recognised.
165     */
166    @Override
167    public int getColumnIndex(Comparable key) {
168        int index = this.underlying.getColumnIndex(key);
169        if (index >= this.firstCategoryIndex && index <= lastCategoryIndex()) {
170            return index - this.firstCategoryIndex;
171        }
172        return -1;  // we didn't find the key
173    }
174
175    /**
176     * Returns the column key for a given index.
177     *
178     * @param column  the column index (zero-based).
179     *
180     * @return The column key.
181     *
182     * @throws IndexOutOfBoundsException if {@code row} is out of bounds.
183     */
184    @Override
185    public Comparable getColumnKey(int column) {
186        return this.underlying.getColumnKey(column + this.firstCategoryIndex);
187    }
188
189    /**
190     * Returns the column keys.
191     *
192     * @return The keys.
193     *
194     * @see #getColumnKey(int)
195     */
196    @Override
197    public List getColumnKeys() {
198        List result = new java.util.ArrayList();
199        int last = lastCategoryIndex();
200        for (int i = this.firstCategoryIndex; i < last; i++) {
201            result.add(this.underlying.getColumnKey(i));
202        }
203        return Collections.unmodifiableList(result);
204    }
205
206    /**
207     * Returns the row index for a given key.
208     *
209     * @param key  the row key.
210     *
211     * @return The row index, or {@code -1} if the key is unrecognised.
212     */
213    @Override
214    public int getRowIndex(Comparable key) {
215        return this.underlying.getRowIndex(key);
216    }
217
218    /**
219     * Returns the row key for a given index.
220     *
221     * @param row  the row index (zero-based).
222     *
223     * @return The row key.
224     *
225     * @throws IndexOutOfBoundsException if {@code row} is out of bounds.
226     */
227    @Override
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    @Override
238    public List getRowKeys() {
239        return this.underlying.getRowKeys();
240    }
241
242    /**
243     * Returns the value for a pair of keys.
244     *
245     * @param rowKey  the row key ({@code null} not permitted).
246     * @param columnKey  the column key ({@code null} not permitted).
247     *
248     * @return The value (possibly {@code null}).
249     *
250     * @throws UnknownKeyException if either key is not defined in the dataset.
251     */
252    @Override
253    public Number getValue(Comparable rowKey, Comparable columnKey) {
254        int r = getRowIndex(rowKey);
255        int c = getColumnIndex(columnKey);
256        if (c != -1) {
257            return this.underlying.getValue(r, c + this.firstCategoryIndex);
258        }
259        else {
260            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
261        }
262    }
263
264    /**
265     * Returns the number of columns in the table.
266     *
267     * @return The column count.
268     */
269    @Override
270    public int getColumnCount() {
271        int last = lastCategoryIndex();
272        if (last == -1) {
273            return 0;
274        }
275        else {
276            return Math.max(last - this.firstCategoryIndex + 1, 0);
277        }
278    }
279
280    /**
281     * Returns the number of rows in the table.
282     *
283     * @return The row count.
284     */
285    @Override
286    public int getRowCount() {
287        return this.underlying.getRowCount();
288    }
289
290    /**
291     * Returns a value from the table.
292     *
293     * @param row  the row index (zero-based).
294     * @param column  the column index (zero-based).
295     *
296     * @return The value (possibly {@code null}).
297     */
298    @Override
299    public Number getValue(int row, int column) {
300        return this.underlying.getValue(row, column + this.firstCategoryIndex);
301    }
302
303    /**
304     * Returns the percent complete for a given item.
305     *
306     * @param rowKey  the row key.
307     * @param columnKey  the column key.
308     *
309     * @return The percent complete.
310     */
311    @Override
312    public Number getPercentComplete(Comparable rowKey, Comparable columnKey) {
313        int r = getRowIndex(rowKey);
314        int c = getColumnIndex(columnKey);
315        if (c != -1) {
316            return this.underlying.getPercentComplete(r,
317                    c + this.firstCategoryIndex);
318        }
319        else {
320            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
321        }
322    }
323
324    /**
325     * Returns the percentage complete value of a sub-interval for a given item.
326     *
327     * @param rowKey  the row key.
328     * @param columnKey  the column key.
329     * @param subinterval  the sub-interval.
330     *
331     * @return The percent complete value (possibly {@code null}).
332     *
333     * @see #getPercentComplete(int, int, int)
334     */
335    @Override
336    public Number getPercentComplete(Comparable rowKey, Comparable columnKey,
337            int subinterval) {
338        int r = getRowIndex(rowKey);
339        int c = getColumnIndex(columnKey);
340        if (c != -1) {
341            return this.underlying.getPercentComplete(r,
342                    c + this.firstCategoryIndex, subinterval);
343        }
344        else {
345            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
346        }
347    }
348
349    /**
350     * Returns the end value of a sub-interval for a given item.
351     *
352     * @param rowKey  the row key.
353     * @param columnKey  the column key.
354     * @param subinterval  the sub-interval.
355     *
356     * @return The end value (possibly {@code null}).
357     *
358     * @see #getStartValue(Comparable, Comparable, int)
359     */
360    @Override
361    public Number getEndValue(Comparable rowKey, Comparable columnKey,
362            int subinterval) {
363        int r = getRowIndex(rowKey);
364        int c = getColumnIndex(columnKey);
365        if (c != -1) {
366            return this.underlying.getEndValue(r,
367                    c + this.firstCategoryIndex, subinterval);
368        }
369        else {
370            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
371        }
372    }
373
374    /**
375     * Returns the end value of a sub-interval for a given item.
376     *
377     * @param row  the row index (zero-based).
378     * @param column  the column index (zero-based).
379     * @param subinterval  the sub-interval.
380     *
381     * @return The end value (possibly {@code null}).
382     *
383     * @see #getStartValue(int, int, int)
384     */
385    @Override
386    public Number getEndValue(int row, int column, int subinterval) {
387        return this.underlying.getEndValue(row,
388                column + this.firstCategoryIndex, subinterval);
389    }
390
391    /**
392     * Returns the percent complete for a given item.
393     *
394     * @param series  the row index (zero-based).
395     * @param category  the column index (zero-based).
396     *
397     * @return The percent complete.
398     */
399    @Override
400    public Number getPercentComplete(int series, int category) {
401        return this.underlying.getPercentComplete(series,
402                category + this.firstCategoryIndex);
403    }
404
405    /**
406     * Returns the percentage complete value of a sub-interval for a given item.
407     *
408     * @param row  the row index (zero-based).
409     * @param column  the column index (zero-based).
410     * @param subinterval  the sub-interval.
411     *
412     * @return The percent complete value (possibly {@code null}).
413     *
414     * @see #getPercentComplete(Comparable, Comparable, int)
415     */
416    @Override
417    public Number getPercentComplete(int row, int column, int subinterval) {
418        return this.underlying.getPercentComplete(row,
419                column + this.firstCategoryIndex, subinterval);
420    }
421
422    /**
423     * Returns the start value of a sub-interval for a given item.
424     *
425     * @param rowKey  the row key.
426     * @param columnKey  the column key.
427     * @param subinterval  the sub-interval.
428     *
429     * @return The start value (possibly {@code null}).
430     *
431     * @see #getEndValue(Comparable, Comparable, int)
432     */
433    @Override
434    public Number getStartValue(Comparable rowKey, Comparable columnKey,
435            int subinterval) {
436        int r = getRowIndex(rowKey);
437        int c = getColumnIndex(columnKey);
438        if (c != -1) {
439            return this.underlying.getStartValue(r,
440                    c + this.firstCategoryIndex, subinterval);
441        }
442        else {
443            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
444        }
445    }
446
447    /**
448     * Returns the start value of a sub-interval for a given item.
449     *
450     * @param row  the row index (zero-based).
451     * @param column  the column index (zero-based).
452     * @param subinterval  the sub-interval index (zero-based).
453     *
454     * @return The start value (possibly {@code null}).
455     *
456     * @see #getEndValue(int, int, int)
457     */
458    @Override
459    public Number getStartValue(int row, int column, int subinterval) {
460        return this.underlying.getStartValue(row,
461                column + this.firstCategoryIndex, subinterval);
462    }
463
464    /**
465     * Returns the number of sub-intervals for a given item.
466     *
467     * @param rowKey  the row key.
468     * @param columnKey  the column key.
469     *
470     * @return The sub-interval count.
471     *
472     * @see #getSubIntervalCount(int, int)
473     */
474    @Override
475    public int getSubIntervalCount(Comparable rowKey, Comparable columnKey) {
476        int r = getRowIndex(rowKey);
477        int c = getColumnIndex(columnKey);
478        if (c != -1) {
479            return this.underlying.getSubIntervalCount(r,
480                    c + this.firstCategoryIndex);
481        }
482        else {
483            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
484        }
485    }
486
487    /**
488     * Returns the number of sub-intervals for a given item.
489     *
490     * @param row  the row index (zero-based).
491     * @param column  the column index (zero-based).
492     *
493     * @return The sub-interval count.
494     *
495     * @see #getSubIntervalCount(Comparable, Comparable)
496     */
497    @Override
498    public int getSubIntervalCount(int row, int column) {
499        return this.underlying.getSubIntervalCount(row,
500                column + this.firstCategoryIndex);
501    }
502
503    /**
504     * Returns the start value for the interval for a given series and category.
505     *
506     * @param rowKey  the series key.
507     * @param columnKey  the category key.
508     *
509     * @return The start value (possibly {@code null}).
510     *
511     * @see #getEndValue(Comparable, Comparable)
512     */
513    @Override
514    public Number getStartValue(Comparable rowKey, Comparable columnKey) {
515        int r = getRowIndex(rowKey);
516        int c = getColumnIndex(columnKey);
517        if (c != -1) {
518            return this.underlying.getStartValue(r,
519                    c + this.firstCategoryIndex);
520        }
521        else {
522            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
523        }
524    }
525
526    /**
527     * Returns the start value for the interval for a given series and category.
528     *
529     * @param row  the series (zero-based index).
530     * @param column  the category (zero-based index).
531     *
532     * @return The start value (possibly {@code null}).
533     *
534     * @see #getEndValue(int, int)
535     */
536    @Override
537    public Number getStartValue(int row, int column) {
538        return this.underlying.getStartValue(row,
539                column + this.firstCategoryIndex);
540    }
541
542    /**
543     * Returns the end value for the interval for a given series and category.
544     *
545     * @param rowKey  the series key.
546     * @param columnKey  the category key.
547     *
548     * @return The end value (possibly {@code null}).
549     *
550     * @see #getStartValue(Comparable, Comparable)
551     */
552    @Override
553    public Number getEndValue(Comparable rowKey, Comparable columnKey) {
554        int r = getRowIndex(rowKey);
555        int c = getColumnIndex(columnKey);
556        if (c != -1) {
557            return this.underlying.getEndValue(r, c + this.firstCategoryIndex);
558        }
559        else {
560            throw new UnknownKeyException("Unknown columnKey: " + columnKey);
561        }
562    }
563
564    /**
565     * Returns the end value for the interval for a given series and category.
566     *
567     * @param series  the series (zero-based index).
568     * @param category  the category (zero-based index).
569     *
570     * @return The end value (possibly {@code null}).
571     */
572    @Override
573    public Number getEndValue(int series, int category) {
574        return this.underlying.getEndValue(series,
575                category + this.firstCategoryIndex);
576    }
577
578    /**
579     * Tests this {@code SlidingGanttCategoryDataset} instance for equality 
580     * with an arbitrary object.
581     *
582     * @param obj  the object ({@code null} permitted).
583     *
584     * @return A boolean.
585     */
586    @Override
587    public boolean equals(Object obj) {
588        if (obj == this) {
589            return true;
590        }
591        if (!(obj instanceof SlidingGanttCategoryDataset)) {
592            return false;
593        }
594        SlidingGanttCategoryDataset that = (SlidingGanttCategoryDataset) obj;
595        if (this.firstCategoryIndex != that.firstCategoryIndex) {
596            return false;
597        }
598        if (this.maximumCategoryCount != that.maximumCategoryCount) {
599            return false;
600        }
601        if (!Objects.equals(this.underlying, that.underlying)) {
602            return false;
603        }
604        if (!that.canEqual(this)) {
605            return false;
606        }
607        return super.equals(obj);
608    }
609
610    /**
611     * Ensures symmetry between super/subclass implementations of equals. For
612     * more detail, see http://jqno.nl/equalsverifier/manual/inheritance.
613     *
614     * @param other Object
615     * 
616     * @return true ONLY if the parameter is THIS class type
617     */
618    @Override
619    public boolean canEqual(Object other) {
620        // fix the "equals not symmetric" problem
621        return (other instanceof SlidingGanttCategoryDataset);
622    }
623
624    @Override
625    public int hashCode() {
626        int hash = super.hashCode(); // equals calls superclass, hashCode must also
627        hash = 23 * hash + Objects.hashCode(this.underlying);
628        hash = 23 * hash + this.firstCategoryIndex;
629        hash = 23 * hash + this.maximumCategoryCount;
630        return hash;
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}