001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, 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-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}