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