001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2009, 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     * SlidingCategoryDataset.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     * 08-May-2008 : Version 1 (DG);
038     * 15-Mar-2009 : Fixed bug in getColumnKeys() method (DG);
039     *
040     */
041    
042    package org.jfree.data.category;
043    
044    import java.util.Collections;
045    import java.util.List;
046    
047    import org.jfree.data.UnknownKeyException;
048    import org.jfree.data.general.AbstractDataset;
049    import org.jfree.data.general.DatasetChangeEvent;
050    import org.jfree.util.PublicCloneable;
051    
052    /**
053     * A {@link CategoryDataset} implementation that presents a subset of the
054     * categories in an underlying dataset.  The index of the first "visible"
055     * category can be modified, which provides a means of "sliding" through
056     * the categories in the underlying dataset.
057     *
058     * @since 1.0.10
059     */
060    public class SlidingCategoryDataset extends AbstractDataset
061            implements CategoryDataset {
062    
063        /** The underlying dataset. */
064        private CategoryDataset underlying;
065    
066        /** The index of the first category to present. */
067        private int firstCategoryIndex;
068    
069        /** The maximum number of categories to present. */
070        private int maximumCategoryCount;
071    
072        /**
073         * Creates a new instance.
074         *
075         * @param underlying  the underlying dataset (<code>null</code> not
076         *     permitted).
077         * @param firstColumn  the index of the first visible column from the
078         *     underlying dataset.
079         * @param maxColumns  the maximumColumnCount.
080         */
081        public SlidingCategoryDataset(CategoryDataset underlying, int firstColumn,
082                int maxColumns) {
083            this.underlying = underlying;
084            this.firstCategoryIndex = firstColumn;
085            this.maximumCategoryCount = maxColumns;
086        }
087    
088        /**
089         * Returns the underlying dataset that was supplied to the constructor.
090         *
091         * @return The underlying dataset (never <code>null</code>).
092         */
093        public CategoryDataset getUnderlyingDataset() {
094            return this.underlying;
095        }
096    
097        /**
098         * Returns the index of the first visible category.
099         *
100         * @return The index.
101         *
102         * @see #setFirstCategoryIndex(int)
103         */
104        public int getFirstCategoryIndex() {
105            return this.firstCategoryIndex;
106        }
107    
108        /**
109         * Sets the index of the first category that should be used from the
110         * underlying dataset, and sends a {@link DatasetChangeEvent} to all
111         * registered listeners.
112         *
113         * @param first  the index.
114         *
115         * @see #getFirstCategoryIndex()
116         */
117        public void setFirstCategoryIndex(int first) {
118            if (first < 0 || first >= this.underlying.getColumnCount()) {
119                throw new IllegalArgumentException("Invalid index.");
120            }
121            this.firstCategoryIndex = first;
122            fireDatasetChanged();
123        }
124    
125        /**
126         * Returns the maximum category count.
127         *
128         * @return The maximum category count.
129         *
130         * @see #setMaximumCategoryCount(int)
131         */
132        public int getMaximumCategoryCount() {
133            return this.maximumCategoryCount;
134        }
135    
136        /**
137         * Sets the maximum category count and sends a {@link DatasetChangeEvent}
138         * to all registered listeners.
139         *
140         * @param max  the maximum.
141         *
142         * @see #getMaximumCategoryCount()
143         */
144        public void setMaximumCategoryCount(int max) {
145            if (max < 0) {
146                throw new IllegalArgumentException("Requires 'max' >= 0.");
147            }
148            this.maximumCategoryCount = max;
149            fireDatasetChanged();
150        }
151    
152        /**
153         * Returns the index of the last column for this dataset, or -1.
154         *
155         * @return The index.
156         */
157        private int lastCategoryIndex() {
158            if (this.maximumCategoryCount == 0) {
159                return -1;
160            }
161            return Math.min(this.firstCategoryIndex + this.maximumCategoryCount,
162                    this.underlying.getColumnCount()) - 1;
163        }
164    
165        /**
166         * Returns the index for the specified column key.
167         *
168         * @param key  the key.
169         *
170         * @return The column index, or -1 if the key is not recognised.
171         */
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        public Comparable getColumnKey(int column) {
190            return this.underlying.getColumnKey(column + this.firstCategoryIndex);
191        }
192    
193        /**
194         * Returns the column keys.
195         *
196         * @return The keys.
197         *
198         * @see #getColumnKey(int)
199         */
200        public List getColumnKeys() {
201            List result = new java.util.ArrayList();
202            int last = lastCategoryIndex();
203            for (int i = this.firstCategoryIndex; i <= last; i++) {
204                result.add(this.underlying.getColumnKey(i));
205            }
206            return Collections.unmodifiableList(result);
207        }
208    
209        /**
210         * Returns the row index for a given key.
211         *
212         * @param key  the row key.
213         *
214         * @return The row index, or <code>-1</code> if the key is unrecognised.
215         */
216        public int getRowIndex(Comparable key) {
217            return this.underlying.getRowIndex(key);
218        }
219    
220        /**
221         * Returns the row key for a given index.
222         *
223         * @param row  the row index (zero-based).
224         *
225         * @return The row key.
226         *
227         * @throws IndexOutOfBoundsException if <code>row</code> is out of bounds.
228         */
229        public Comparable getRowKey(int row) {
230            return this.underlying.getRowKey(row);
231        }
232    
233        /**
234         * Returns the row keys.
235         *
236         * @return The keys.
237         */
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</code> not permitted).
246         * @param columnKey  the column key (<code>null</code> not permitted).
247         *
248         * @return The value (possibly <code>null</code>).
249         *
250         * @throws UnknownKeyException if either key is not defined in the dataset.
251         */
252        public Number getValue(Comparable rowKey, Comparable columnKey) {
253            int r = getRowIndex(rowKey);
254            int c = getColumnIndex(columnKey);
255            if (c != -1) {
256                return this.underlying.getValue(r, c + this.firstCategoryIndex);
257            }
258            else {
259                throw new UnknownKeyException("Unknown columnKey: " + columnKey);
260            }
261        }
262    
263        /**
264         * Returns the number of columns in the table.
265         *
266         * @return The column count.
267         */
268        public int getColumnCount() {
269            int last = lastCategoryIndex();
270            if (last == -1) {
271                return 0;
272            }
273            else {
274                return Math.max(last - this.firstCategoryIndex + 1, 0);
275            }
276        }
277    
278        /**
279         * Returns the number of rows in the table.
280         *
281         * @return The row count.
282         */
283        public int getRowCount() {
284            return this.underlying.getRowCount();
285        }
286    
287        /**
288         * Returns a value from the table.
289         *
290         * @param row  the row index (zero-based).
291         * @param column  the column index (zero-based).
292         *
293         * @return The value (possibly <code>null</code>).
294         */
295        public Number getValue(int row, int column) {
296            return this.underlying.getValue(row, column + this.firstCategoryIndex);
297        }
298    
299        /**
300         * Tests this <code>SlidingCategoryDataset</code> for equality with an
301         * arbitrary object.
302         *
303         * @param obj  the object (<code>null</code> permitted).
304         *
305         * @return A boolean.
306         */
307        public boolean equals(Object obj) {
308            if (obj == this) {
309                return true;
310            }
311            if (!(obj instanceof SlidingCategoryDataset)) {
312                return false;
313            }
314            SlidingCategoryDataset that = (SlidingCategoryDataset) obj;
315            if (this.firstCategoryIndex != that.firstCategoryIndex) {
316                return false;
317            }
318            if (this.maximumCategoryCount != that.maximumCategoryCount) {
319                return false;
320            }
321            if (!this.underlying.equals(that.underlying)) {
322                return false;
323            }
324            return true;
325        }
326    
327        /**
328         * Returns an independent copy of the dataset.  Note that:
329         * <ul>
330         * <li>the underlying dataset is only cloned if it implements the
331         * {@link PublicCloneable} interface;</li>
332         * <li>the listeners registered with this dataset are not carried over to
333         * the cloned dataset.</li>
334         * </ul>
335         *
336         * @return An independent copy of the dataset.
337         *
338         * @throws CloneNotSupportedException if the dataset cannot be cloned for
339         *         any reason.
340         */
341        public Object clone() throws CloneNotSupportedException {
342            SlidingCategoryDataset clone = (SlidingCategoryDataset) super.clone();
343            if (this.underlying instanceof PublicCloneable) {
344                PublicCloneable pc = (PublicCloneable) this.underlying;
345                clone.underlying = (CategoryDataset) pc.clone();
346            }
347            return clone;
348        }
349    
350    }