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