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 * KeyedObject2D.java
029 * ------------------
030 * (C) Copyright 2003-2013, 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 * 03-Jul-2013 : Use ParamChecks (DG);
043 *
044 */
045
046package org.jfree.data;
047
048import java.io.Serializable;
049import java.util.Collections;
050import java.util.Iterator;
051import java.util.List;
052import org.jfree.chart.util.ParamChecks;
053
054/**
055 * A data structure that stores zero, one or many objects, where each object is
056 * associated with two keys (a 'row' key and a 'column' key).
057 */
058public class KeyedObjects2D implements Cloneable, Serializable {
059
060    /** For serialization. */
061    private static final long serialVersionUID = -1015873563138522374L;
062
063    /** The row keys. */
064    private List rowKeys;
065
066    /** The column keys. */
067    private List columnKeys;
068
069    /** The row data. */
070    private List rows;
071
072    /**
073     * Creates a new instance (initially empty).
074     */
075    public KeyedObjects2D() {
076        this.rowKeys = new java.util.ArrayList();
077        this.columnKeys = new java.util.ArrayList();
078        this.rows = new java.util.ArrayList();
079    }
080
081    /**
082     * Returns the row count.
083     *
084     * @return The row count.
085     *
086     * @see #getColumnCount()
087     */
088    public int getRowCount() {
089        return this.rowKeys.size();
090    }
091
092    /**
093     * Returns the column count.
094     *
095     * @return The column count.
096     *
097     * @see #getRowCount()
098     */
099    public int getColumnCount() {
100        return this.columnKeys.size();
101    }
102
103    /**
104     * Returns the object for a given row and column.
105     *
106     * @param row  the row index (in the range 0 to getRowCount() - 1).
107     * @param column  the column index (in the range 0 to getColumnCount() - 1).
108     *
109     * @return The object (possibly <code>null</code>).
110     *
111     * @see #getObject(Comparable, Comparable)
112     */
113    public Object getObject(int row, int column) {
114        Object result = null;
115        KeyedObjects rowData = (KeyedObjects) this.rows.get(row);
116        if (rowData != null) {
117            Comparable columnKey = (Comparable) this.columnKeys.get(column);
118            if (columnKey != null) {
119                int index = rowData.getIndex(columnKey);
120                if (index >= 0) {
121                    result = rowData.getObject(columnKey);
122                }
123            }
124        }
125        return result;
126    }
127
128    /**
129     * Returns the key for a given row.
130     *
131     * @param row  the row index (zero based).
132     *
133     * @return The row index.
134     *
135     * @see #getRowIndex(Comparable)
136     */
137    public Comparable getRowKey(int row) {
138        return (Comparable) this.rowKeys.get(row);
139    }
140
141    /**
142     * Returns the row index for a given key, or <code>-1</code> if the key
143     * is not recognised.
144     *
145     * @param key  the key (<code>null</code> not permitted).
146     *
147     * @return The row index.
148     *
149     * @see #getRowKey(int)
150     */
151    public int getRowIndex(Comparable key) {
152        ParamChecks.nullNotPermitted(key, "key");
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        ParamChecks.nullNotPermitted(key, "key");
192        return this.columnKeys.indexOf(key);
193    }
194
195    /**
196     * Returns the column keys.
197     *
198     * @return The column keys (never <code>null</code>).
199     *
200     * @see #getRowKeys()
201     */
202    public List getColumnKeys() {
203        return Collections.unmodifiableList(this.columnKeys);
204    }
205
206    /**
207     * Returns the object for the given row and column keys.
208     *
209     * @param rowKey  the row key (<code>null</code> not permitted).
210     * @param columnKey  the column key (<code>null</code> not permitted).
211     *
212     * @return The object (possibly <code>null</code>).
213     *
214     * @throws IllegalArgumentException if <code>rowKey</code> or
215     *         <code>columnKey</code> is <code>null</code>.
216     * @throws UnknownKeyException if <code>rowKey</code> or
217     *         <code>columnKey</code> is not recognised.
218     */
219    public Object getObject(Comparable rowKey, Comparable columnKey) {
220        ParamChecks.nullNotPermitted(rowKey, "rowKey");
221        ParamChecks.nullNotPermitted(columnKey, "columnKey");
222        int row = this.rowKeys.indexOf(rowKey);
223        if (row < 0) {
224            throw new UnknownKeyException("Row key (" + rowKey
225                    + ") not recognised.");
226        }
227        int column = this.columnKeys.indexOf(columnKey);
228        if (column < 0) {
229            throw new UnknownKeyException("Column key (" + columnKey
230                    + ") not recognised.");
231        }
232        KeyedObjects rowData = (KeyedObjects) this.rows.get(row);
233        int index = rowData.getIndex(columnKey);
234        if (index >= 0) {
235            return rowData.getObject(index);
236        }
237        else {
238            return null;
239        }
240    }
241
242    /**
243     * Adds an object to the table.  Performs the same function as setObject().
244     *
245     * @param object  the object.
246     * @param rowKey  the row key (<code>null</code> not permitted).
247     * @param columnKey  the column key (<code>null</code> not permitted).
248     */
249    public void addObject(Object object, Comparable rowKey,
250            Comparable columnKey) {
251        setObject(object, rowKey, columnKey);
252    }
253
254    /**
255     * Adds or updates an object.
256     *
257     * @param object  the object.
258     * @param rowKey  the row key (<code>null</code> not permitted).
259     * @param columnKey  the column key (<code>null</code> not permitted).
260     */
261    public void setObject(Object object, Comparable rowKey,
262            Comparable columnKey) {
263        ParamChecks.nullNotPermitted(rowKey, "rowKey");
264        ParamChecks.nullNotPermitted(columnKey, "columnKey");
265        KeyedObjects row;
266        int rowIndex = this.rowKeys.indexOf(rowKey);
267        if (rowIndex >= 0) {
268            row = (KeyedObjects) this.rows.get(rowIndex);
269        }
270        else {
271            this.rowKeys.add(rowKey);
272            row = new KeyedObjects();
273            this.rows.add(row);
274        }
275        row.setObject(columnKey, object);
276        int columnIndex = this.columnKeys.indexOf(columnKey);
277        if (columnIndex < 0) {
278            this.columnKeys.add(columnKey);
279        }
280    }
281
282    /**
283     * Removes an object from the table by setting it to <code>null</code>.  If
284     * all the objects in the specified row and/or column are now
285     * <code>null</code>, the row and/or column is removed from the table.
286     *
287     * @param rowKey  the row key (<code>null</code> not permitted).
288     * @param columnKey  the column key (<code>null</code> not permitted).
289     *
290     * @see #addObject(Object, Comparable, Comparable)
291     */
292    public void removeObject(Comparable rowKey, Comparable columnKey) {
293        int rowIndex = getRowIndex(rowKey);
294        if (rowIndex < 0) {
295            throw new UnknownKeyException("Row key (" + rowKey
296                    + ") not recognised.");
297        }
298        int columnIndex = getColumnIndex(columnKey);
299        if (columnIndex < 0) {
300            throw new UnknownKeyException("Column key (" + columnKey
301                    + ") not recognised.");
302        }
303        setObject(null, rowKey, columnKey);
304
305        // 1. check whether the row is now empty.
306        boolean allNull = true;
307        KeyedObjects row = (KeyedObjects) this.rows.get(rowIndex);
308
309        for (int item = 0, itemCount = row.getItemCount(); item < itemCount;
310             item++) {
311            if (row.getObject(item) != null) {
312                allNull = false;
313                break;
314            }
315        }
316
317        if (allNull) {
318            this.rowKeys.remove(rowIndex);
319            this.rows.remove(rowIndex);
320        }
321
322        // 2. check whether the column is now empty.
323        allNull = true;
324
325        for (int item = 0, itemCount = this.rows.size(); item < itemCount;
326             item++) {
327            row = (KeyedObjects) this.rows.get(item);
328            int colIndex = row.getIndex(columnKey);
329            if (colIndex >= 0 && row.getObject(colIndex) != null) {
330                allNull = false;
331                break;
332            }
333        }
334
335        if (allNull) {
336            for (int item = 0, itemCount = this.rows.size(); item < itemCount;
337                 item++) {
338                row = (KeyedObjects) this.rows.get(item);
339                int colIndex = row.getIndex(columnKey);
340                if (colIndex >= 0) {
341                    row.removeValue(colIndex);
342                }
343            }
344            this.columnKeys.remove(columnKey);
345        }
346    }
347
348    /**
349     * Removes an entire row from the table.
350     *
351     * @param rowIndex  the row index.
352     *
353     * @see #removeColumn(int)
354     */
355    public void removeRow(int rowIndex) {
356        this.rowKeys.remove(rowIndex);
357        this.rows.remove(rowIndex);
358    }
359
360    /**
361     * Removes an entire row from the table.
362     *
363     * @param rowKey  the row key (<code>null</code> not permitted).
364     *
365     * @throws UnknownKeyException if <code>rowKey</code> is not recognised.
366     *
367     * @see #removeColumn(Comparable)
368     */
369    public void removeRow(Comparable rowKey) {
370        int index = getRowIndex(rowKey);
371        if (index < 0) {
372            throw new UnknownKeyException("Row key (" + rowKey
373                    + ") not recognised.");
374        }
375        removeRow(index);
376    }
377
378    /**
379     * Removes an entire column from the table.
380     *
381     * @param columnIndex  the column index.
382     *
383     * @see #removeRow(int)
384     */
385    public void removeColumn(int columnIndex) {
386        Comparable columnKey = getColumnKey(columnIndex);
387        removeColumn(columnKey);
388    }
389
390    /**
391     * Removes an entire column from the table.
392     *
393     * @param columnKey  the column key (<code>null</code> not permitted).
394     *
395     * @throws UnknownKeyException if <code>rowKey</code> is not recognised.
396     *
397     * @see #removeRow(Comparable)
398     */
399    public void removeColumn(Comparable columnKey) {
400        int index = getColumnIndex(columnKey);
401        if (index < 0) {
402            throw new UnknownKeyException("Column key (" + columnKey
403                    + ") not recognised.");
404        }
405        Iterator iterator = this.rows.iterator();
406        while (iterator.hasNext()) {
407            KeyedObjects rowData = (KeyedObjects) iterator.next();
408            int i = rowData.getIndex(columnKey);
409            if (i >= 0) {
410                rowData.removeValue(i);
411            }
412        }
413        this.columnKeys.remove(columnKey);
414    }
415
416    /**
417     * Clears all the data and associated keys.
418     *
419     * @since 1.0.7
420     */
421    public void clear() {
422        this.rowKeys.clear();
423        this.columnKeys.clear();
424        this.rows.clear();
425    }
426
427    /**
428     * Tests this object for equality with an arbitrary object.
429     *
430     * @param obj  the object to test (<code>null</code> permitted).
431     *
432     * @return A boolean.
433     */
434    @Override
435    public boolean equals(Object obj) {
436        if (obj == this) {
437            return true;
438        }
439        if (!(obj instanceof KeyedObjects2D)) {
440            return false;
441        }
442
443        KeyedObjects2D that = (KeyedObjects2D) obj;
444        if (!getRowKeys().equals(that.getRowKeys())) {
445            return false;
446        }
447        if (!getColumnKeys().equals(that.getColumnKeys())) {
448            return false;
449        }
450        int rowCount = getRowCount();
451        if (rowCount != that.getRowCount()) {
452            return false;
453        }
454        int colCount = getColumnCount();
455        if (colCount != that.getColumnCount()) {
456            return false;
457        }
458        for (int r = 0; r < rowCount; r++) {
459            for (int c = 0; c < colCount; c++) {
460                Object v1 = getObject(r, c);
461                Object v2 = that.getObject(r, c);
462                if (v1 == null) {
463                    if (v2 != null) {
464                        return false;
465                    }
466                }
467                else {
468                    if (!v1.equals(v2)) {
469                        return false;
470                    }
471                }
472            }
473        }
474        return true;
475    }
476
477    /**
478     * Returns a hashcode for this object.
479     *
480     * @return A hashcode.
481     */
482    @Override
483    public int hashCode() {
484        int result;
485        result = this.rowKeys.hashCode();
486        result = 29 * result + this.columnKeys.hashCode();
487        result = 29 * result + this.rows.hashCode();
488        return result;
489    }
490
491    /**
492     * Returns a clone.
493     *
494     * @return A clone.
495     *
496     * @throws CloneNotSupportedException  this class will not throw this
497     *         exception, but subclasses (if any) might.
498     */
499    @Override
500    public Object clone() throws CloneNotSupportedException {
501        KeyedObjects2D clone = (KeyedObjects2D) super.clone();
502        clone.columnKeys = new java.util.ArrayList(this.columnKeys);
503        clone.rowKeys = new java.util.ArrayList(this.rowKeys);
504        clone.rows = new java.util.ArrayList(this.rows.size());
505        Iterator iterator = this.rows.iterator();
506        while (iterator.hasNext()) {
507            KeyedObjects row = (KeyedObjects) iterator.next();
508            clone.rows.add(row.clone());
509        }
510        return clone;
511    }
512
513}