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