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