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 * DefaultKeyedValues2D.java
029 * -------------------------
030 * (C) Copyright 2002-2022, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Andreas Schroeder;
034 * 
035 */
036
037package org.jfree.data;
038
039import java.io.Serializable;
040import java.util.ArrayList;
041import java.util.Collections;
042import java.util.List;
043
044import org.jfree.chart.internal.CloneUtils;
045import org.jfree.chart.internal.Args;
046import org.jfree.chart.api.PublicCloneable;
047
048/**
049 * A data structure that stores zero, one or many values, where each value
050 * is associated with two keys (a 'row' key and a 'column' key).  The keys
051 * should be (a) instances of {@link Comparable} and (b) immutable.
052 */
053public class DefaultKeyedValues2D<R extends Comparable<R>, C extends Comparable<C>> 
054        implements KeyedValues2D<R, C>, PublicCloneable, Cloneable, Serializable {
055
056    /** For serialization. */
057    private static final long serialVersionUID = -5514169970951994748L;
058
059    /** The row keys. */
060    private List<R> rowKeys;
061
062    /** The column keys. */
063    private List<C> columnKeys;
064
065    /** The row data. */
066    private List<DefaultKeyedValues<C>> rows;
067
068    /** If the row keys should be sorted by their comparable order. */
069    private final boolean sortRowKeys;
070
071    /**
072     * Creates a new instance (initially empty).
073     */
074    public DefaultKeyedValues2D() {
075        this(false);
076    }
077
078    /**
079     * Creates a new instance (initially empty).
080     *
081     * @param sortRowKeys  if the row keys should be sorted.
082     */
083    public DefaultKeyedValues2D(boolean sortRowKeys) {
084        this.rowKeys = new ArrayList<>();
085        this.columnKeys = new ArrayList<>();
086        this.rows = new ArrayList<>();
087        this.sortRowKeys = sortRowKeys;
088    }
089
090    /**
091     * Returns the row count.
092     *
093     * @return The row count.
094     *
095     * @see #getColumnCount()
096     */
097    @Override
098    public int getRowCount() {
099        return this.rowKeys.size();
100    }
101
102    /**
103     * Returns the column count.
104     *
105     * @return The column count.
106     *
107     * @see #getRowCount()
108     */
109    @Override
110    public int getColumnCount() {
111        return this.columnKeys.size();
112    }
113
114    /**
115     * Returns the value for a given row and column.
116     *
117     * @param row  the row index.
118     * @param column  the column index.
119     *
120     * @return The value.
121     *
122     * @see #getValue(Comparable, Comparable)
123     */
124    @Override
125    public Number getValue(int row, int column) {
126        Number result = null;
127        DefaultKeyedValues<C> rowData = this.rows.get(row);
128        if (rowData != null) {
129            C columnKey = this.columnKeys.get(column);
130            // the row may not have an entry for this key, in which case the
131            // return value is null
132            int index = rowData.getIndex(columnKey);
133            if (index >= 0) {
134                result = rowData.getValue(index);
135            }
136        }
137        return result;
138    }
139
140    /**
141     * Returns the key for a given row.
142     *
143     * @param row  the row index (in the range 0 to {@link #getRowCount()} - 1).
144     *
145     * @return The row key.
146     *
147     * @see #getRowIndex(Comparable)
148     * @see #getColumnKey(int)
149     */
150    @Override
151    public R getRowKey(int row) {
152        return this.rowKeys.get(row);
153    }
154
155    /**
156     * Returns the row index for a given key.
157     *
158     * @param key  the key ({@code null} not permitted).
159     *
160     * @return The row index.
161     *
162     * @see #getRowKey(int)
163     * @see #getColumnIndex(Comparable)
164     */
165    @Override
166    public int getRowIndex(R key) {
167        Args.nullNotPermitted(key, "key");
168        if (this.sortRowKeys) {
169            return Collections.<R>binarySearch(this.rowKeys, key);
170        } else {
171            return this.rowKeys.indexOf(key);
172        }
173    }
174
175    /**
176     * Returns the row keys in an unmodifiable list.
177     *
178     * @return The row keys.
179     *
180     * @see #getColumnKeys()
181     */
182    @Override
183    public List<R> getRowKeys() {
184        return Collections.unmodifiableList(this.rowKeys);
185    }
186
187    /**
188     * Returns the key for a given column.
189     *
190     * @param column  the column (in the range 0 to {@link #getColumnCount()}
191     *     - 1).
192     *
193     * @return The key.
194     *
195     * @see #getColumnIndex(Comparable)
196     * @see #getRowKey(int)
197     */
198    @Override
199    public C getColumnKey(int column) {
200        return this.columnKeys.get(column);
201    }
202
203    /**
204     * Returns the column index for a given key.
205     *
206     * @param key  the key ({@code null} not permitted).
207     *
208     * @return The column index.
209     *
210     * @see #getColumnKey(int)
211     * @see #getRowIndex(Comparable)
212     */
213    @Override
214    public int getColumnIndex(C key) {
215        Args.nullNotPermitted(key, "key");
216        return this.columnKeys.indexOf(key);
217    }
218
219    /**
220     * Returns the column keys in an unmodifiable list.
221     *
222     * @return The column keys.
223     *
224     * @see #getRowKeys()
225     */
226    @Override
227    public List<C> getColumnKeys() {
228        return Collections.unmodifiableList(this.columnKeys);
229    }
230
231    /**
232     * Returns the value for the given row and column keys.  This method will
233     * throw an {@link UnknownKeyException} if either key is not defined in the
234     * data structure.
235     *
236     * @param rowKey  the row key ({@code null} not permitted).
237     * @param columnKey  the column key ({@code null} not permitted).
238     *
239     * @return The value (possibly {@code null}).
240     *
241     * @see #addValue(Number, Comparable, Comparable)
242     * @see #removeValue(Comparable, Comparable)
243     */
244    @Override
245    public Number getValue(R rowKey, C columnKey) {
246        Args.nullNotPermitted(rowKey, "rowKey");
247        Args.nullNotPermitted(columnKey, "columnKey");
248
249        // check that the column key is defined in the 2D structure
250        if (!(this.columnKeys.contains(columnKey))) {
251            throw new UnknownKeyException("Unrecognised columnKey: "
252                    + columnKey);
253        }
254
255        // now fetch the row data - need to bear in mind that the row
256        // structure may not have an entry for the column key, but that we
257        // have already checked that the key is valid for the 2D structure
258        int row = getRowIndex(rowKey);
259        if (row >= 0) {
260            DefaultKeyedValues rowData = this.rows.get(row);
261            int col = rowData.getIndex(columnKey);
262            return (col >= 0 ? rowData.getValue(col) : null);
263        }
264        else {
265            throw new UnknownKeyException("Unrecognised rowKey: " + rowKey);
266        }
267    }
268
269    /**
270     * Adds a value to the table.  Performs the same function as
271     * #setValue(Number, Comparable, Comparable).
272     *
273     * @param value  the value ({@code null} permitted).
274     * @param rowKey  the row key ({@code null} not permitted).
275     * @param columnKey  the column key ({@code null} not permitted).
276     *
277     * @see #setValue(Number, Comparable, Comparable)
278     * @see #removeValue(Comparable, Comparable)
279     */
280    public void addValue(Number value, R rowKey, C columnKey) {
281        // defer argument checking
282        setValue(value, rowKey, columnKey);
283    }
284
285    /**
286     * Adds or updates a value.
287     *
288     * @param value  the value ({@code null} permitted).
289     * @param rowKey  the row key ({@code null} not permitted).
290     * @param columnKey  the column key ({@code null} not permitted).
291     *
292     * @see #addValue(Number, Comparable, Comparable)
293     * @see #removeValue(Comparable, Comparable)
294     */
295    public void setValue(Number value, R rowKey, C columnKey) {
296
297        DefaultKeyedValues row;
298        int rowIndex = getRowIndex(rowKey);
299
300        if (rowIndex >= 0) {
301            row = this.rows.get(rowIndex);
302        }
303        else {
304            row = new DefaultKeyedValues();
305            if (this.sortRowKeys) {
306                rowIndex = -rowIndex - 1;
307                this.rowKeys.add(rowIndex, rowKey);
308                this.rows.add(rowIndex, row);
309            }
310            else {
311                this.rowKeys.add(rowKey);
312                this.rows.add(row);
313            }
314        }
315        row.setValue(columnKey, value);
316
317        int columnIndex = this.columnKeys.indexOf(columnKey);
318        if (columnIndex < 0) {
319            this.columnKeys.add(columnKey);
320        }
321    }
322
323    /**
324     * Removes a value from the table by setting it to {@code null}.  If
325     * all the values in the specified row and/or column are now
326     * {@code null}, the row and/or column is removed from the table.
327     *
328     * @param rowKey  the row key ({@code null} not permitted).
329     * @param columnKey  the column key ({@code null} not permitted).
330     *
331     * @see #addValue(Number, Comparable, Comparable)
332     */
333    public void removeValue(R rowKey, C columnKey) {
334        setValue(null, rowKey, columnKey);
335
336        // 1. check whether the row is now empty.
337        boolean allNull = true;
338        int rowIndex = getRowIndex(rowKey);
339        DefaultKeyedValues row = this.rows.get(rowIndex);
340
341        for (int item = 0, itemCount = row.getItemCount(); item < itemCount;
342             item++) {
343            if (row.getValue(item) != null) {
344                allNull = false;
345                break;
346            }
347        }
348
349        if (allNull) {
350            this.rowKeys.remove(rowIndex);
351            this.rows.remove(rowIndex);
352        }
353
354        // 2. check whether the column is now empty.
355        allNull = true;
356        //int columnIndex = getColumnIndex(columnKey);
357
358        for (int item = 0, itemCount = this.rows.size(); item < itemCount;
359             item++) {
360            row = this.rows.get(item);
361            int columnIndex = row.getIndex(columnKey);
362            if (columnIndex >= 0 && row.getValue(columnIndex) != null) {
363                allNull = false;
364                break;
365            }
366        }
367
368        if (allNull) {
369            for (int item = 0, itemCount = this.rows.size(); item < itemCount;
370                 item++) {
371                row = this.rows.get(item);
372                int columnIndex = row.getIndex(columnKey);
373                if (columnIndex >= 0) {
374                    row.removeValue(columnIndex);
375                }
376            }
377            this.columnKeys.remove(columnKey);
378        }
379    }
380
381    /**
382     * Removes a row.
383     *
384     * @param rowIndex  the row index.
385     *
386     * @see #removeRow(Comparable)
387     * @see #removeColumn(int)
388     */
389    public void removeRow(int rowIndex) {
390        this.rowKeys.remove(rowIndex);
391        this.rows.remove(rowIndex);
392    }
393
394    /**
395     * Removes a row from the table.
396     *
397     * @param rowKey  the row key ({@code null} not permitted).
398     *
399     * @see #removeRow(int)
400     * @see #removeColumn(Comparable)
401     *
402     * @throws UnknownKeyException if {@code rowKey} is not defined in the
403     *         table.
404     */
405    public void removeRow(R rowKey) {
406        Args.nullNotPermitted(rowKey, "rowKey");
407        int index = getRowIndex(rowKey);
408        if (index >= 0) {
409            removeRow(index);
410        }
411        else {
412            throw new UnknownKeyException("Unknown key: " + rowKey);
413        }
414    }
415
416    /**
417     * Removes a column.
418     *
419     * @param columnIndex  the column index.
420     *
421     * @see #removeColumn(Comparable)
422     * @see #removeRow(int)
423     */
424    public void removeColumn(int columnIndex) {
425        C columnKey = getColumnKey(columnIndex);
426        removeColumn(columnKey);
427    }
428
429    /**
430     * Removes a column from the table.
431     *
432     * @param columnKey  the column key ({@code null} not permitted).
433     *
434     * @throws UnknownKeyException if the table does not contain a column with
435     *     the specified key.
436     * @throws IllegalArgumentException if {@code columnKey} is
437     *     {@code null}.
438     *
439     * @see #removeColumn(int)
440     * @see #removeRow(Comparable)
441     */
442    public void removeColumn(C columnKey) {
443        Args.nullNotPermitted(columnKey, "columnKey");
444        if (!this.columnKeys.contains(columnKey)) {
445            throw new UnknownKeyException("Unknown key: " + columnKey);
446        }
447        for (DefaultKeyedValues rowData : this.rows) {
448            int index = rowData.getIndex(columnKey);
449            if (index >= 0) {
450                rowData.removeValue(columnKey);
451            }
452        }
453        this.columnKeys.remove(columnKey);
454    }
455
456    /**
457     * Clears all the data and associated keys.
458     */
459    public void clear() {
460        this.rowKeys.clear();
461        this.columnKeys.clear();
462        this.rows.clear();
463    }
464
465    /**
466     * Tests if this object is equal to another.
467     *
468     * @param o  the other object ({@code null} permitted).
469     *
470     * @return A boolean.
471     */
472    @Override
473    public boolean equals(Object o) {
474
475        if (o == null) {
476            return false;
477        }
478        if (o == this) {
479            return true;
480        }
481
482        if (!(o instanceof KeyedValues2D)) {
483            return false;
484        }
485        KeyedValues2D kv2D = (KeyedValues2D) o;
486        if (!getRowKeys().equals(kv2D.getRowKeys())) {
487            return false;
488        }
489        if (!getColumnKeys().equals(kv2D.getColumnKeys())) {
490            return false;
491        }
492        int rowCount = getRowCount();
493        if (rowCount != kv2D.getRowCount()) {
494            return false;
495        }
496
497        int colCount = getColumnCount();
498        if (colCount != kv2D.getColumnCount()) {
499            return false;
500        }
501
502        for (int r = 0; r < rowCount; r++) {
503            for (int c = 0; c < colCount; c++) {
504                Number v1 = getValue(r, c);
505                Number v2 = kv2D.getValue(r, c);
506                if (v1 == null) {
507                    if (v2 != null) {
508                        return false;
509                    }
510                }
511                else {
512                    if (!v1.equals(v2)) {
513                        return false;
514                    }
515                }
516            }
517        }
518        return true;
519    }
520
521    /**
522     * Returns a hash code.
523     *
524     * @return A hash code.
525     */
526    @Override
527    public int hashCode() {
528        int result;
529        result = this.rowKeys.hashCode();
530        result = 29 * result + this.columnKeys.hashCode();
531        result = 29 * result + this.rows.hashCode();
532        return result;
533    }
534
535    /**
536     * Returns a clone.
537     *
538     * @return A clone.
539     *
540     * @throws CloneNotSupportedException  this class will not throw this
541     *         exception, but subclasses (if any) might.
542     */
543    @Override
544    public Object clone() throws CloneNotSupportedException {
545        DefaultKeyedValues2D clone = (DefaultKeyedValues2D) super.clone();
546        // for the keys, a shallow copy should be fine because keys
547        // should be immutable...
548        clone.columnKeys = new java.util.ArrayList(this.columnKeys);
549        clone.rowKeys = new java.util.ArrayList(this.rowKeys);
550
551        // but the row data requires a deep copy
552        clone.rows = CloneUtils.cloneList(this.rows);
553        return clone;
554    }
555
556}