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