001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2006, 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     * CategoryToPieDataset.java
029     * -------------------------
030     * (C) Copyright 2003-2006, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Christian W. Zuckschwerdt;
034     *
035     * $Id: CategoryToPieDataset.java,v 1.4.2.2 2006/07/26 10:28:00 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 23-Jan-2003 : Version 1 (DG);
040     * 30-Jul-2003 : Pass through DatasetChangeEvent (CZ);
041     * 29-Jan-2004 : Replaced 'extract' int with TableOrder (DG);
042     * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0 
043     *               release (DG);
044     * ------------- JFREECHART 1.0.0 RELEASED ------------------------------------
045     * 26-Jul-2006 : Added serialVersionUID, changed constructor to allow null
046     *               for source, and added getSource(), getExtractType() and
047     *               getExtractIndex() methods - see feature request 1477915 (DG);
048     * 
049     */
050    
051    package org.jfree.data.category;
052    
053    import java.util.Collections;
054    import java.util.List;
055    
056    import org.jfree.data.general.AbstractDataset;
057    import org.jfree.data.general.DatasetChangeEvent;
058    import org.jfree.data.general.DatasetChangeListener;
059    import org.jfree.data.general.PieDataset;
060    import org.jfree.util.TableOrder;
061    
062    /**
063     * A {@link PieDataset} implementation that obtains its data from one row or 
064     * column of a {@link CategoryDataset}.
065     */
066    public class CategoryToPieDataset extends AbstractDataset 
067                                      implements PieDataset, DatasetChangeListener {
068    
069        static final long serialVersionUID = 5516396319762189617L;
070    
071        /** The source. */
072        private CategoryDataset source;
073    
074        /** The extract type. */
075        private TableOrder extract;
076    
077        /** The row or column index. */
078        private int index;
079    
080        /**
081         * An adaptor class that converts any {@link CategoryDataset} into a 
082         * {@link PieDataset}, by taking the values from a single row or column.
083         * <p>
084         * If <code>source</code> is <code>null</code>, the created dataset will 
085         * be empty.
086         *
087         * @param source  the source dataset (<code>null</code> permitted).
088         * @param extract  extract data from rows or columns? (<code>null</code> 
089         *                 not permitted).
090         * @param index  the row or column index.
091         */
092        public CategoryToPieDataset(CategoryDataset source, 
093                                    TableOrder extract, 
094                                    int index) {
095            if (extract == null) {
096                throw new IllegalArgumentException("Null 'extract' argument.");
097            }
098            this.source = source;
099            if (this.source != null) {
100                this.source.addChangeListener(this);
101            }
102            this.extract = extract;
103            this.index = index;
104        }
105        
106        /**
107         * Returns the underlying dataset.
108         * 
109         * @return The underlying dataset (possibly <code>null</code>).
110         * 
111         * @since 1.0.2
112         */
113        public CategoryDataset getUnderlyingDataset() {
114            return this.source;
115        }
116        
117        /**
118         * Returns the extract type, which determines whether data is read from
119         * one row or one column of the underlying dataset.
120         * 
121         * @return The extract type.
122         * 
123         * @since 1.0.2
124         */
125        public TableOrder getExtractType() {
126            return this.extract;
127        }
128        
129        /**
130         * Returns the index of the row or column from which to extract the data.
131         * 
132         * @return The extract index.
133         * 
134         * @since 1.0.2
135         */
136        public int getExtractIndex() {
137            return this.index;
138        }
139    
140        /**
141         * Returns the number of items (values) in the collection.  If the 
142         * underlying dataset is <code>null</code>, this method returns zero.
143         *
144         * @return The item count.
145         */
146        public int getItemCount() {
147            int result = 0;
148            if (this.source != null) {
149                if (this.extract == TableOrder.BY_ROW) {
150                    result = this.source.getColumnCount();
151                }
152                else if (this.extract == TableOrder.BY_COLUMN) {
153                    result = this.source.getRowCount();
154                }
155            }
156            return result;
157        }
158    
159        /**
160         * Returns a value from the dataset.
161         *
162         * @param item  the item index (zero-based).
163         *
164         * @return The value (possibly <code>null</code>).
165         * 
166         * @throws IndexOutOfBoundsException if <code>item</code> is not in the 
167         *     range <code>0</code> to <code>getItemCount() - 1</code>.
168         */
169        public Number getValue(int item) {
170            Number result = null;
171            if (item < 0 || item >= getItemCount()) {
172                // this will include the case where the underlying dataset is null
173                throw new IndexOutOfBoundsException(
174                        "The 'item' index is out of bounds.");
175            }
176            if (this.extract == TableOrder.BY_ROW) {
177                result = this.source.getValue(this.index, item);
178            }
179            else if (this.extract == TableOrder.BY_COLUMN) {
180                result = this.source.getValue(item, this.index);
181            }
182            return result;
183        }
184    
185        /**
186         * Returns the key at the specified index.
187         *
188         * @param index  the item index (in the range <code>0</code> to 
189         *     <code>getItemCount() - 1</code>).
190         *
191         * @return The key.
192         * 
193         * @throws IndexOutOfBoundsException if <code>index</code> is not in the 
194         *     specified range.
195         */
196        public Comparable getKey(int index) {
197            Comparable result = null;
198            if (index < 0 || index >= getItemCount()) {
199                // this includes the case where the underlying dataset is null
200                throw new IndexOutOfBoundsException("Invalid 'index': " + index);
201            }
202            if (this.extract == TableOrder.BY_ROW) {
203                result = this.source.getColumnKey(index);
204            }
205            else if (this.extract == TableOrder.BY_COLUMN) {
206                result = this.source.getRowKey(index);
207            }
208            return result;
209        }
210    
211        /**
212         * Returns the index for a given key, or <code>-1</code> if there is no
213         * such key.
214         *
215         * @param key  the key.
216         *
217         * @return The index for the key, or <code>-1</code>.
218         */
219        public int getIndex(Comparable key) {
220            int result = -1;
221            if (this.source != null) {
222                if (this.extract == TableOrder.BY_ROW) {
223                    result = this.source.getColumnIndex(key);
224                }
225                else if (this.extract == TableOrder.BY_COLUMN) {
226                    result = this.source.getRowIndex(key);
227                }
228            }
229            return result;
230        }
231    
232        /**
233         * Returns the keys for the dataset.
234         * <p>
235         * If the underlying dataset is <code>null</code>, this method returns an
236         * empty list.
237         *
238         * @return The keys.
239         */
240        public List getKeys() {
241            List result = Collections.EMPTY_LIST;
242            if (this.source != null) {
243                if (this.extract == TableOrder.BY_ROW) {
244                    result = this.source.getColumnKeys();
245                }
246                else if (this.extract == TableOrder.BY_COLUMN) {
247                    result = this.source.getRowKeys();
248                }
249            }
250            return result;
251        }
252    
253        /**
254         * Returns the value for a given key.  If the key is not recognised, the 
255         * method should return <code>null</code> (but note that <code>null</code> 
256         * can be associated with a valid key also).
257         *
258         * @param key  the key.
259         *
260         * @return The value (possibly <code>null</code>).
261         */
262        public Number getValue(Comparable key) {
263            Number result = null;
264            int keyIndex = getIndex(key);
265            if (keyIndex != -1) {
266                if (this.extract == TableOrder.BY_ROW) {
267                    result = this.source.getValue(this.index, keyIndex);
268                }
269                else if (this.extract == TableOrder.BY_COLUMN) {
270                    result = this.source.getValue(keyIndex, this.index);
271                }
272            }
273            return result;
274        }
275        
276        /**
277         * Sends a {@link DatasetChangeEvent} to all registered listeners, with
278         * this (not the underlying) dataset as the source.
279         * 
280         * @param event  the event (ignored, a new event with this dataset as the
281         *     source is sent to the listeners).
282         */
283        public void datasetChanged(DatasetChangeEvent event) {
284            fireDatasetChanged();
285        }
286        
287        /**
288         * Tests this dataset for equality with an arbitrary object, returning
289         * <code>true</code> if <code>obj</code> is a dataset containing the same
290         * keys and values in the same order as this dataset.
291         * 
292         * @param obj  the object to test (<code>null</code> permitted).
293         * 
294         * @return A boolean.
295         */
296        public boolean equals(Object obj) {
297            if (obj == this) {
298                return true;
299            }
300            if (!(obj instanceof PieDataset)) {
301                return false;
302            }
303            PieDataset that = (PieDataset) obj;
304            int count = getItemCount();
305            if (that.getItemCount() != count) {
306                return false;
307            }
308            for (int i = 0; i < count; i++) {
309                Comparable k1 = getKey(i);
310                Comparable k2 = that.getKey(i);
311                if (!k1.equals(k2)) {
312                    return false;
313                }
314    
315                Number v1 = getValue(i);
316                Number v2 = that.getValue(i);
317                if (v1 == null) {
318                    if (v2 != null) {
319                        return false;
320                    }
321                }
322                else {
323                    if (!v1.equals(v2)) {
324                        return false;
325                    }
326                }
327            }
328            return true;
329        }
330         
331    }