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.5 2007/03/30 09:53:10 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: * 30-Mar-2007 : Fixed bug 1690654, problem with removeValue() (DG);
054: *
055: */
056:
057: package org.jfree.data;
058:
059: import java.io.Serializable;
060: import java.util.Collections;
061: import java.util.Iterator;
062: import java.util.List;
063:
064: import org.jfree.util.ObjectUtilities;
065: import org.jfree.util.PublicCloneable;
066:
067: /**
068: * A data structure that stores zero, one or many values, where each value
069: * is associated with two keys (a 'row' key and a 'column' key). The keys
070: * should be (a) instances of {@link Comparable} and (b) immutable.
071: */
072: public class DefaultKeyedValues2D implements KeyedValues2D,
073: PublicCloneable, Cloneable, 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
144: .get(row);
145: if (rowData != null) {
146: Comparable columnKey = (Comparable) this .columnKeys
147: .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: } 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(
263: "Null 'rowKey' argument.");
264: }
265: if (columnKey == null) {
266: throw new IllegalArgumentException(
267: "Null 'columnKey' argument.");
268: }
269:
270: // check that the column key is defined in the 2D structure
271: if (!(this .columnKeys.contains(columnKey))) {
272: throw new UnknownKeyException("Unrecognised columnKey: "
273: + columnKey);
274: }
275:
276: // now fetch the row data - need to bear in mind that the row
277: // structure may not have an entry for the column key, but that we
278: // have already checked that the key is valid for the 2D structure
279: int row = getRowIndex(rowKey);
280: if (row >= 0) {
281: DefaultKeyedValues rowData = (DefaultKeyedValues) this .rows
282: .get(row);
283: int col = rowData.getIndex(columnKey);
284: return (col >= 0 ? rowData.getValue(col) : null);
285: } else {
286: throw new UnknownKeyException("Unrecognised rowKey: "
287: + rowKey);
288: }
289: }
290:
291: /**
292: * Adds a value to the table. Performs the same function as
293: * #setValue(Number, Comparable, Comparable).
294: *
295: * @param value the value (<code>null</code> permitted).
296: * @param rowKey the row key (<code>null</code> not permitted).
297: * @param columnKey the column key (<code>null</code> not permitted).
298: *
299: * @see #setValue(Number, Comparable, Comparable)
300: * @see #removeValue(Comparable, Comparable)
301: */
302: public void addValue(Number value, Comparable rowKey,
303: Comparable columnKey) {
304: // defer argument checking
305: setValue(value, rowKey, columnKey);
306: }
307:
308: /**
309: * Adds or updates a value.
310: *
311: * @param value the value (<code>null</code> permitted).
312: * @param rowKey the row key (<code>null</code> not permitted).
313: * @param columnKey the column key (<code>null</code> not permitted).
314: *
315: * @see #addValue(Number, Comparable, Comparable)
316: * @see #removeValue(Comparable, Comparable)
317: */
318: public void setValue(Number value, Comparable rowKey,
319: Comparable columnKey) {
320:
321: DefaultKeyedValues row;
322: int rowIndex = getRowIndex(rowKey);
323:
324: if (rowIndex >= 0) {
325: row = (DefaultKeyedValues) this .rows.get(rowIndex);
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: } 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
362: .get(rowIndex);
363:
364: for (int item = 0, itemCount = row.getItemCount(); item < itemCount; 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; item++) {
381: row = (DefaultKeyedValues) this .rows.get(item);
382: int columnIndex = row.getIndex(columnKey);
383: if (columnIndex >= 0 && row.getValue(columnIndex) != null) {
384: allNull = false;
385: break;
386: }
387: }
388:
389: if (allNull) {
390: for (int item = 0, itemCount = this .rows.size(); item < itemCount; item++) {
391: row = (DefaultKeyedValues) this .rows.get(item);
392: int columnIndex = row.getIndex(columnKey);
393: if (columnIndex >= 0) {
394: row.removeValue(columnIndex);
395: }
396: }
397: this .columnKeys.remove(columnKey);
398: }
399: }
400:
401: /**
402: * Removes a row.
403: *
404: * @param rowIndex the row index.
405: *
406: * @see #removeRow(Comparable)
407: * @see #removeColumn(int)
408: */
409: public void removeRow(int rowIndex) {
410: this .rowKeys.remove(rowIndex);
411: this .rows.remove(rowIndex);
412: }
413:
414: /**
415: * Removes a row.
416: *
417: * @param rowKey the row key (<code>null</code> not permitted).
418: *
419: * @see #removeRow(int)
420: * @see #removeColumn(Comparable)
421: */
422: public void removeRow(Comparable rowKey) {
423: removeRow(getRowIndex(rowKey));
424: }
425:
426: /**
427: * Removes a column.
428: *
429: * @param columnIndex the column index.
430: *
431: * @see #removeColumn(Comparable)
432: * @see #removeRow(int)
433: */
434: public void removeColumn(int columnIndex) {
435: Comparable columnKey = getColumnKey(columnIndex);
436: removeColumn(columnKey);
437: }
438:
439: /**
440: * Removes a column.
441: *
442: * @param columnKey the column key (<code>null</code> not permitted).
443: *
444: * @see #removeColumn(int)
445: * @see #removeRow(Comparable)
446: */
447: public void removeColumn(Comparable columnKey) {
448: Iterator iterator = this .rows.iterator();
449: while (iterator.hasNext()) {
450: DefaultKeyedValues rowData = (DefaultKeyedValues) iterator
451: .next();
452: rowData.removeValue(columnKey);
453: }
454: this .columnKeys.remove(columnKey);
455: }
456:
457: /**
458: * Clears all the data and associated keys.
459: */
460: public void clear() {
461: this .rowKeys.clear();
462: this .columnKeys.clear();
463: this .rows.clear();
464: }
465:
466: /**
467: * Tests if this object is equal to another.
468: *
469: * @param o the other object (<code>null</code> permitted).
470: *
471: * @return A boolean.
472: */
473: public boolean equals(Object o) {
474:
475: if (o == null) {
476: return false;
477: }
478: if (o == this ) {
479: return true;
480: }
481:
482: if (!(o instanceof KeyedValues2D)) {
483: return false;
484: }
485: KeyedValues2D kv2D = (KeyedValues2D) o;
486: if (!getRowKeys().equals(kv2D.getRowKeys())) {
487: return false;
488: }
489: if (!getColumnKeys().equals(kv2D.getColumnKeys())) {
490: return false;
491: }
492: int rowCount = getRowCount();
493: if (rowCount != kv2D.getRowCount()) {
494: return false;
495: }
496:
497: int colCount = getColumnCount();
498: if (colCount != kv2D.getColumnCount()) {
499: return false;
500: }
501:
502: for (int r = 0; r < rowCount; r++) {
503: for (int c = 0; c < colCount; c++) {
504: Number v1 = getValue(r, c);
505: Number v2 = kv2D.getValue(r, c);
506: if (v1 == null) {
507: if (v2 != null) {
508: return false;
509: }
510: } else {
511: if (!v1.equals(v2)) {
512: return false;
513: }
514: }
515: }
516: }
517: return true;
518: }
519:
520: /**
521: * Returns a hash code.
522: *
523: * @return A hash code.
524: */
525: public int hashCode() {
526: int result;
527: result = this .rowKeys.hashCode();
528: result = 29 * result + this .columnKeys.hashCode();
529: result = 29 * result + this .rows.hashCode();
530: return result;
531: }
532:
533: /**
534: * Returns a clone.
535: *
536: * @return A clone.
537: *
538: * @throws CloneNotSupportedException this class will not throw this
539: * exception, but subclasses (if any) might.
540: */
541: public Object clone() throws CloneNotSupportedException {
542: DefaultKeyedValues2D clone = (DefaultKeyedValues2D) super
543: .clone();
544: // for the keys, a shallow copy should be fine because keys
545: // should be immutable...
546: clone.columnKeys = new java.util.ArrayList(this .columnKeys);
547: clone.rowKeys = new java.util.ArrayList(this .rowKeys);
548:
549: // but the row data requires a deep copy
550: clone.rows = (List) ObjectUtilities.deepClone(this.rows);
551: return clone;
552: }
553:
554: }
|