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