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