    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2011, 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     * [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     * KeyedObject2D.java
    029     * ------------------
    030     * (C) Copyright 2003-2008, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * Changes
    036     * -------
    037     * 05-Feb-2003 : Version 1 (DG);
    038     * 01-Mar-2004 : Added equals() and clone() methods and implemented
    039     *               Serializable (DG);
    040     * 03-Oct-2007 : Updated getObject() to handle modified behaviour in
    041     *               KeyedObjects class, added clear() method (DG);
    042     *
    043     */
    044
    045    package org.jfree.data;
    046
    047    import java.io.Serializable;
    048    import java.util.Collections;
    049    import java.util.Iterator;
    050    import java.util.List;
    051
    052    /**
    053     * A data structure that stores zero, one or many objects, where each object is
    054     * associated with two keys (a 'row' key and a 'column' key).
    055     */
    056    public class KeyedObjects2D implements Cloneable, Serializable {
    057
    058        /** For serialization. */
    059        private static final long serialVersionUID = -1015873563138522374L;
    060
    061        /** The row keys. */
    062        private List rowKeys;
    063
    064        /** The column keys. */
    065        private List columnKeys;
    066
    067        /** The row data. */
    068        private List rows;
    069
    070        /**
    071         * Creates a new instance (initially empty).
    072         */
    073        public KeyedObjects2D() {
    074            this.rowKeys = new java.util.ArrayList();
    075            this.columnKeys = new java.util.ArrayList();
    076            this.rows = new java.util.ArrayList();
    077        }
    078
    079        /**
    080         * Returns the row count.
    081         *
    082         * @return The row count.
    083         *
    084         * @see #getColumnCount()
    085         */
    086        public int getRowCount() {
    087            return this.rowKeys.size();
    088        }
    089
    090        /**
    091         * Returns the column count.
    092         *
    093         * @return The column count.
    094         *
    095         * @see #getRowCount()
    096         */
    097        public int getColumnCount() {
    098            return this.columnKeys.size();
    099        }
    100
    101        /**
    102         * Returns the object for a given row and column.
    103         *
    104         * @param row  the row index (in the range 0 to getRowCount() - 1).
    105         * @param column  the column index (in the range 0 to getColumnCount() - 1).
    106         *
    107         * @return The object (possibly <code>null</code>).
    108         *
    109         * @see #getObject(Comparable, Comparable)
    110         */
    111        public Object getObject(int row, int column) {
    112            Object result = null;
    113            KeyedObjects rowData = (KeyedObjects) this.rows.get(row);
    114            if (rowData != null) {
    115                Comparable columnKey = (Comparable) this.columnKeys.get(column);
    116                if (columnKey != null) {
    117                    int index = rowData.getIndex(columnKey);
    118                    if (index >= 0) {
    119                        result = rowData.getObject(columnKey);
    120                    }
    121                }
    122            }
    123            return result;
    124        }
    125
    126        /**
    127         * Returns the key for a given row.
    128         *
    129         * @param row  the row index (zero based).
    130         *
    131         * @return The row index.
    132         *
    133         * @see #getRowIndex(Comparable)
    134         */
    135        public Comparable getRowKey(int row) {
    136            return (Comparable) this.rowKeys.get(row);
    137        }
    138
    139        /**
    140         * Returns the row index for a given key, or <code>-1</code> if the key
    141         * is not recognised.
    142         *
    143         * @param key  the key (<code>null</code> not permitted).
    144         *
    145         * @return The row index.
    146         *
    147         * @see #getRowKey(int)
    148         */
    149        public int getRowIndex(Comparable key) {
    150            if (key == null) {
    151                throw new IllegalArgumentException("Null 'key' argument.");
    152            }
    153            return this.rowKeys.indexOf(key);
    154        }
    155
    156        /**
    157         * Returns the row keys.
    158         *
    159         * @return The row keys (never <code>null</code>).
    160         *
    161         * @see #getRowKeys()
    162         */
    163        public List getRowKeys() {
    164            return Collections.unmodifiableList(this.rowKeys);
    165        }
    166
    167        /**
    168         * Returns the key for a given column.
    169         *
    170         * @param column  the column.
    171         *
    172         * @return The key.
    173         *
    174         * @see #getColumnIndex(Comparable)
    175         */
    176        public Comparable getColumnKey(int column) {
    177            return (Comparable) this.columnKeys.get(column);
    178        }
    179
    180        /**
    181         * Returns the column index for a given key, or <code>-1</code> if the key
    182         * is not recognised.
    183         *
    184         * @param key  the key (<code>null</code> not permitted).
    185         *
    186         * @return The column index.
    187         *
    188         * @see #getColumnKey(int)
    189         */
    190        public int getColumnIndex(Comparable key) {
    191            if (key == null) {
    192                throw new IllegalArgumentException("Null 'key' argument.");
    193            }
    194            return this.columnKeys.indexOf(key);
    195        }
    196
    197        /**
    198         * Returns the column keys.
    199         *
    200         * @return The column keys (never <code>null</code>).
    201         *
    202         * @see #getRowKeys()
    203         */
    204        public List getColumnKeys() {
    205            return Collections.unmodifiableList(this.columnKeys);
    206        }
    207
    208        /**
    209         * Returns the object for the given row and column keys.
    210         *
    211         * @param rowKey  the row key (<code>null</code> not permitted).
    212         * @param columnKey  the column key (<code>null</code> not permitted).
    213         *
    214         * @return The object (possibly <code>null</code>).
    215         *
    216         * @throws IllegalArgumentException if <code>rowKey</code> or
    217         *         <code>columnKey</code> is <code>null</code>.
    218         * @throws UnknownKeyException if <code>rowKey</code> or
    219         *         <code>columnKey</code> is not recognised.
    220         */
    221        public Object getObject(Comparable rowKey, Comparable columnKey) {
    222            if (rowKey == null) {
    223                throw new IllegalArgumentException("Null 'rowKey' argument.");
    224            }
    225            if (columnKey == null) {
    226                throw new IllegalArgumentException("Null 'columnKey' argument.");
    227            }
    228            int row = this.rowKeys.indexOf(rowKey);
    229            if (row < 0) {
    230                throw new UnknownKeyException("Row key (" + rowKey
    231                        + ") not recognised.");
    232            }
    233            int column = this.columnKeys.indexOf(columnKey);
    234            if (column < 0) {
    235                throw new UnknownKeyException("Column key (" + columnKey
    236                        + ") not recognised.");
    237            }
    238            KeyedObjects rowData = (KeyedObjects) this.rows.get(row);
    239            int index = rowData.getIndex(columnKey);
    240            if (index >= 0) {
    241                return rowData.getObject(index);
    242            }
    243            else {
    244                return null;
    245            }
    246        }
    247
    248        /**
    249         * Adds an object to the table.  Performs the same function as setObject().
    250         *
    251         * @param object  the object.
    252         * @param rowKey  the row key (<code>null</code> not permitted).
    253         * @param columnKey  the column key (<code>null</code> not permitted).
    254         */
    255        public void addObject(Object object, Comparable rowKey,
    256                Comparable columnKey) {
    257            setObject(object, rowKey, columnKey);
    258        }
    259
    260        /**
    261         * Adds or updates an object.
    262         *
    263         * @param object  the object.
    264         * @param rowKey  the row key (<code>null</code> not permitted).
    265         * @param columnKey  the column key (<code>null</code> not permitted).
    266         */
    267        public void setObject(Object object, Comparable rowKey,
    268                Comparable columnKey) {
    269
    270            if (rowKey == null) {
    271                throw new IllegalArgumentException("Null 'rowKey' argument.");
    272            }
    273            if (columnKey == null) {
    274                throw new IllegalArgumentException("Null 'columnKey' argument.");
    275            }
    276            KeyedObjects row;
    277            int rowIndex = this.rowKeys.indexOf(rowKey);
    278            if (rowIndex >= 0) {
    279                row = (KeyedObjects) this.rows.get(rowIndex);
    280            }
    281            else {
    282                this.rowKeys.add(rowKey);
    283                row = new KeyedObjects();
    284                this.rows.add(row);
    285            }
    286            row.setObject(columnKey, object);
    287            int columnIndex = this.columnKeys.indexOf(columnKey);
    288            if (columnIndex < 0) {
    289                this.columnKeys.add(columnKey);
    290            }
    291
    292        }
    293
    294        /**
    295         * Removes an object from the table by setting it to <code>null</code>.  If
    296         * all the objects in the specified row and/or column are now
    297         * <code>null</code>, the row and/or column is removed from the table.
    298         *
    299         * @param rowKey  the row key (<code>null</code> not permitted).
    300         * @param columnKey  the column key (<code>null</code> not permitted).
    301         *
    302         * @see #addObject(Object, Comparable, Comparable)
    303         */
    304        public void removeObject(Comparable rowKey, Comparable columnKey) {
    305            int rowIndex = getRowIndex(rowKey);
    306            if (rowIndex < 0) {
    307                throw new UnknownKeyException("Row key (" + rowKey
    308                        + ") not recognised.");
    309            }
    310            int columnIndex = getColumnIndex(columnKey);
    311            if (columnIndex < 0) {
    312                throw new UnknownKeyException("Column key (" + columnKey
    313                        + ") not recognised.");
    314            }
    315            setObject(null, rowKey, columnKey);
    316
    317            // 1. check whether the row is now empty.
    318            boolean allNull = true;
    319            KeyedObjects row = (KeyedObjects) this.rows.get(rowIndex);
    320
    321            for (int item = 0, itemCount = row.getItemCount(); item < itemCount;
    322                 item++) {
    323                if (row.getObject(item) != null) {
    324                    allNull = false;
    325                    break;
    326                }
    327            }
    328
    329            if (allNull) {
    330                this.rowKeys.remove(rowIndex);
    331                this.rows.remove(rowIndex);
    332            }
    333
    334            // 2. check whether the column is now empty.
    335            allNull = true;
    336
    337            for (int item = 0, itemCount = this.rows.size(); item < itemCount;
    338                 item++) {
    339                row = (KeyedObjects) this.rows.get(item);
    340                int colIndex = row.getIndex(columnKey);
    341                if (colIndex >= 0 && row.getObject(colIndex) != null) {
    342                    allNull = false;
    343                    break;
    344                }
    345            }
    346
    347            if (allNull) {
    348                for (int item = 0, itemCount = this.rows.size(); item < itemCount;
    349                     item++) {
    350                    row = (KeyedObjects) this.rows.get(item);
    351                    int colIndex = row.getIndex(columnKey);
    352                    if (colIndex >= 0) {
    353                        row.removeValue(colIndex);
    354                    }
    355                }
    356                this.columnKeys.remove(columnKey);
    357            }
    358        }
    359
    360        /**
    361         * Removes an entire row from the table.
    362         *
    363         * @param rowIndex  the row index.
    364         *
    365         * @see #removeColumn(int)
    366         */
    367        public void removeRow(int rowIndex) {
    368            this.rowKeys.remove(rowIndex);
    369            this.rows.remove(rowIndex);
    370        }
    371
    372        /**
    373         * Removes an entire row from the table.
    374         *
    375         * @param rowKey  the row key (<code>null</code> not permitted).
    376         *
    377         * @throws UnknownKeyException if <code>rowKey</code> is not recognised.
    378         *
    379         * @see #removeColumn(Comparable)
    380         */
    381        public void removeRow(Comparable rowKey) {
    382            int index = getRowIndex(rowKey);
    383            if (index < 0) {
    384                throw new UnknownKeyException("Row key (" + rowKey
    385                        + ") not recognised.");
    386            }
    387            removeRow(index);
    388        }
    389
    390        /**
    391         * Removes an entire column from the table.
    392         *
    393         * @param columnIndex  the column index.
    394         *
    395         * @see #removeRow(int)
    396         */
    397        public void removeColumn(int columnIndex) {
    398            Comparable columnKey = getColumnKey(columnIndex);
    399            removeColumn(columnKey);
    400        }
    401
    402        /**
    403         * Removes an entire column from the table.
    404         *
    405         * @param columnKey  the column key (<code>null</code> not permitted).
    406         *
    407         * @throws UnknownKeyException if <code>rowKey</code> is not recognised.
    408         *
    409         * @see #removeRow(Comparable)
    410         */
    411        public void removeColumn(Comparable columnKey) {
    412            int index = getColumnIndex(columnKey);
    413            if (index < 0) {
    414                throw new UnknownKeyException("Column key (" + columnKey
    415                        + ") not recognised.");
    416            }
    417            Iterator iterator = this.rows.iterator();
    418            while (iterator.hasNext()) {
    419                KeyedObjects rowData = (KeyedObjects) iterator.next();
    420                int i = rowData.getIndex(columnKey);
    421                if (i >= 0) {
    422                    rowData.removeValue(i);
    423                }
    424            }
    425            this.columnKeys.remove(columnKey);
    426        }
    427
    428        /**
    429         * Clears all the data and associated keys.
    430         *
    431         * @since 1.0.7
    432         */
    433        public void clear() {
    434            this.rowKeys.clear();
    435            this.columnKeys.clear();
    436            this.rows.clear();
    437        }
    438
    439        /**
    440         * Tests this object for equality with an arbitrary object.
    441         *
    442         * @param obj  the object to test (<code>null</code> permitted).
    443         *
    444         * @return A boolean.
    445         */
    446        public boolean equals(Object obj) {
    447            if (obj == this) {
    448                return true;
    449            }
    450            if (!(obj instanceof KeyedObjects2D)) {
    451                return false;
    452            }
    453
    454            KeyedObjects2D that = (KeyedObjects2D) obj;
    455            if (!getRowKeys().equals(that.getRowKeys())) {
    456                return false;
    457            }
    458            if (!getColumnKeys().equals(that.getColumnKeys())) {
    459                return false;
    460            }
    461            int rowCount = getRowCount();
    462            if (rowCount != that.getRowCount()) {
    463                return false;
    464            }
    465            int colCount = getColumnCount();
    466            if (colCount != that.getColumnCount()) {
    467                return false;
    468            }
    469            for (int r = 0; r < rowCount; r++) {
    470                for (int c = 0; c < colCount; c++) {
    471                    Object v1 = getObject(r, c);
    472                    Object v2 = that.getObject(r, c);
    473                    if (v1 == null) {
    474                        if (v2 != null) {
    475                            return false;
    476                        }
    477                    }
    478                    else {
    479                        if (!v1.equals(v2)) {
    480                            return false;
    481                        }
    482                    }
    483                }
    484            }
    485            return true;
    486        }
    487
    488        /**
    489         * Returns a hashcode for this object.
    490         *
    491         * @return A hashcode.
    492         */
    493        public int hashCode() {
    494            int result;
    495            result = this.rowKeys.hashCode();
    496            result = 29 * result + this.columnKeys.hashCode();
    497            result = 29 * result + this.rows.hashCode();
    498            return result;
    499        }
    500
    501        /**
    502         * Returns a clone.
    503         *
    504         * @return A clone.
    505         *
    506         * @throws CloneNotSupportedException  this class will not throw this
    507         *         exception, but subclasses (if any) might.
    508         */
    509        public Object clone() throws CloneNotSupportedException {
    510            KeyedObjects2D clone = (KeyedObjects2D) super.clone();
    511            clone.columnKeys = new java.util.ArrayList(this.columnKeys);
    512            clone.rowKeys = new java.util.ArrayList(this.rowKeys);
    513            clone.rows = new java.util.ArrayList(this.rows.size());
    514            Iterator iterator = this.rows.iterator();
    515            while (iterator.hasNext()) {
    516                KeyedObjects row = (KeyedObjects) iterator.next();
    517                clone.rows.add(row.clone());
    518            }
    519            return clone;
    520        }
    521
    522    }