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