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