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