    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
    045package org.jfree.data;
    046
    047import java.io.Serializable;
    048import java.util.Collections;
    049import java.util.Iterator;
    050import 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 */
    056public 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}