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