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