001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, 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-2022, 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.internal.Args;
042import org.jfree.chart.api.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 CategoryDataset source;
061
062    /** The extract type. */
063    private TableOrder extract;
064
065    /** The row or column index. */
066    private 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     * @since 1.0.2
097     */
098    public CategoryDataset getUnderlyingDataset() {
099        return this.source;
100    }
101
102    /**
103     * Returns the extract type, which determines whether data is read from
104     * one row or one column of the underlying dataset.
105     *
106     * @return The extract type.
107     *
108     * @since 1.0.2
109     */
110    public TableOrder getExtractType() {
111        return this.extract;
112    }
113
114    /**
115     * Returns the index of the row or column from which to extract the data.
116     *
117     * @return The extract index.
118     *
119     * @since 1.0.2
120     */
121    public int getExtractIndex() {
122        return this.index;
123    }
124
125    /**
126     * Returns the number of items (values) in the collection.  If the
127     * underlying dataset is {@code null}, this method returns zero.
128     *
129     * @return The item count.
130     */
131    @Override
132    public int getItemCount() {
133        int result = 0;
134        if (this.source != null) {
135            if (this.extract == TableOrder.BY_ROW) {
136                result = this.source.getColumnCount();
137            }
138            else if (this.extract == TableOrder.BY_COLUMN) {
139                result = this.source.getRowCount();
140            }
141        }
142        return result;
143    }
144
145    /**
146     * Returns a value from the dataset.
147     *
148     * @param item  the item index (zero-based).
149     *
150     * @return The value (possibly {@code null}).
151     *
152     * @throws IndexOutOfBoundsException if {@code item} is not in the
153     *     range {@code 0} to {@code getItemCount() -1}.
154     */
155    @Override
156    public Number getValue(int item) {
157        Number result = null;
158        if (item < 0 || item >= getItemCount()) {
159            // this will include the case where the underlying dataset is null
160            throw new IndexOutOfBoundsException(
161                    "The 'item' index is out of bounds.");
162        }
163        if (this.extract == TableOrder.BY_ROW) {
164            result = this.source.getValue(this.index, item);
165        }
166        else if (this.extract == TableOrder.BY_COLUMN) {
167            result = this.source.getValue(item, this.index);
168        }
169        return result;
170    }
171
172    /**
173     * Returns the key at the specified index.
174     *
175     * @param index  the item index (in the range {@code 0} to
176     *     {@code getItemCount() -1}).
177     *
178     * @return The key.
179     *
180     * @throws IndexOutOfBoundsException if {@code index} is not in the
181     *     specified range.
182     */
183    @Override
184    public Comparable getKey(int index) {
185        Comparable result = null;
186        if (index < 0 || index >= getItemCount()) {
187            // this includes the case where the underlying dataset is null
188            throw new IndexOutOfBoundsException("Invalid 'index': " + index);
189        }
190        if (this.extract == TableOrder.BY_ROW) {
191            result = this.source.getColumnKey(index);
192        }
193        else if (this.extract == TableOrder.BY_COLUMN) {
194            result = this.source.getRowKey(index);
195        }
196        return result;
197    }
198
199    /**
200     * Returns the index for a given key, or {@code -1} if there is no
201     * such key.
202     *
203     * @param key  the key.
204     *
205     * @return The index for the key, or {@code -1}.
206     */
207    @Override
208    public int getIndex(Comparable key) {
209        int result = -1;
210        if (this.source != null) {
211            if (this.extract == TableOrder.BY_ROW) {
212                result = this.source.getColumnIndex(key);
213            }
214            else if (this.extract == TableOrder.BY_COLUMN) {
215                result = this.source.getRowIndex(key);
216            }
217        }
218        return result;
219    }
220
221    /**
222     * Returns the keys for the dataset.
223     * <p>
224     * If the underlying dataset is {@code null}, this method returns an
225     * empty list.
226     *
227     * @return The keys.
228     */
229    @Override
230    public List getKeys() {
231        List result = Collections.EMPTY_LIST;
232        if (this.source != null) {
233            if (this.extract == TableOrder.BY_ROW) {
234                result = this.source.getColumnKeys();
235            }
236            else if (this.extract == TableOrder.BY_COLUMN) {
237                result = this.source.getRowKeys();
238            }
239        }
240        return result;
241    }
242
243    /**
244     * Returns the value for a given key.  If the key is not recognised, the
245     * method should return {@code null} (but note that {@code null}
246     * can be associated with a valid key also).
247     *
248     * @param key  the key.
249     *
250     * @return The value (possibly {@code null}).
251     */
252    @Override
253    public Number getValue(Comparable key) {
254        Number result = null;
255        int keyIndex = getIndex(key);
256        if (keyIndex != -1) {
257            if (this.extract == TableOrder.BY_ROW) {
258                result = this.source.getValue(this.index, keyIndex);
259            }
260            else if (this.extract == TableOrder.BY_COLUMN) {
261                result = this.source.getValue(keyIndex, this.index);
262            }
263        }
264        return result;
265    }
266
267    /**
268     * Sends a {@link DatasetChangeEvent} to all registered listeners, with
269     * this (not the underlying) dataset as the source.
270     *
271     * @param event  the event (ignored, a new event with this dataset as the
272     *     source is sent to the listeners).
273     */
274    @Override
275    public void datasetChanged(DatasetChangeEvent event) {
276        fireDatasetChanged();
277    }
278
279    /**
280     * Tests this dataset for equality with an arbitrary object, returning
281     * {@code true} if {@code obj} is a dataset containing the same
282     * keys and values in the same order as this dataset.
283     *
284     * @param obj  the object to test ({@code null} permitted).
285     *
286     * @return A boolean.
287     */
288    @Override
289    public boolean equals(Object obj) {
290        if (obj == this) {
291            return true;
292        }
293        if (!(obj instanceof PieDataset)) {
294            return false;
295        }
296        PieDataset that = (PieDataset) obj;
297        int count = getItemCount();
298        if (that.getItemCount() != count) {
299            return false;
300        }
301        for (int i = 0; i < count; i++) {
302            Comparable k1 = getKey(i);
303            Comparable k2 = that.getKey(i);
304            if (!k1.equals(k2)) {
305                return false;
306            }
307
308            Number v1 = getValue(i);
309            Number v2 = that.getValue(i);
310            if (v1 == null) {
311                if (v2 != null) {
312                    return false;
313                }
314            }
315            else {
316                if (!v1.equals(v2)) {
317                    return false;
318                }
319            }
320        }
321        return true;
322    }
323
324}