001: /* ========================================================================
002: * JCommon : a free general purpose class library for the Java(tm) platform
003: * ========================================================================
004: *
005: * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jcommon/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: * ObjectTable.java
029: * ----------------
030: * (C) Copyright 2003, 2004, by Object Refinery Limited.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): -;
034: *
035: * $Id: ObjectTable.java,v 1.9 2006/11/05 14:34:15 taqua Exp $
036: *
037: * Changes
038: * -------
039: * 29-Apr-2003 : Version 1, based on PaintTable class (DG);
040: * 21-May-2003 : Copied the array based implementation of StrokeTable and
041: * fixed the serialisation behaviour (TM).
042: */
043:
044: package org.jfree.util;
045:
046: import java.io.IOException;
047: import java.io.ObjectInputStream;
048: import java.io.ObjectOutputStream;
049: import java.io.Serializable;
050: import java.util.Arrays;
051:
052: /**
053: * A lookup table for objects. This implementation is not synchronized, it is up
054: * to the caller to synchronize it properly.
055: *
056: * @author Thomas Morgner
057: */
058: public class ObjectTable implements Serializable {
059:
060: /**
061: * For serialization.
062: */
063: private static final long serialVersionUID = -3968322452944912066L;
064:
065: /**
066: * The number of rows.
067: */
068: private int rows;
069:
070: /**
071: * The number of columns.
072: */
073: private int columns;
074:
075: /**
076: * An array of objects. The array may contain <code>null</code> values.
077: */
078: private transient Object[][] data;
079:
080: /**
081: * Defines how many object-slots get reserved each time we run out of
082: * space.
083: */
084: private int rowIncrement;
085:
086: /**
087: * Defines how many object-slots get reserved each time we run out of
088: * space.
089: */
090: private int columnIncrement;
091:
092: /**
093: * Creates a new table.
094: */
095: public ObjectTable() {
096: this (5, 5);
097: }
098:
099: /**
100: * Creates a new table.
101: *
102: * @param increment the row and column size increment.
103: */
104: public ObjectTable(final int increment) {
105: this (increment, increment);
106: }
107:
108: /**
109: * Creates a new table.
110: *
111: * @param rowIncrement the row size increment.
112: * @param colIncrement the column size increment.
113: */
114: public ObjectTable(final int rowIncrement, final int colIncrement) {
115: if (rowIncrement < 1) {
116: throw new IllegalArgumentException(
117: "Increment must be positive.");
118: }
119:
120: if (colIncrement < 1) {
121: throw new IllegalArgumentException(
122: "Increment must be positive.");
123: }
124:
125: this .rows = 0;
126: this .columns = 0;
127: this .rowIncrement = rowIncrement;
128: this .columnIncrement = colIncrement;
129:
130: this .data = new Object[rowIncrement][];
131: }
132:
133: /**
134: * Returns the column size increment.
135: *
136: * @return the increment.
137: */
138: public int getColumnIncrement() {
139: return this .columnIncrement;
140: }
141:
142: /**
143: * Returns the row size increment.
144: *
145: * @return the increment.
146: */
147: public int getRowIncrement() {
148: return this .rowIncrement;
149: }
150:
151: /**
152: * Checks that there is storage capacity for the specified row and resizes
153: * if necessary.
154: *
155: * @param row the row index.
156: */
157: protected void ensureRowCapacity(final int row) {
158:
159: // does this increase the number of rows? if yes, create new storage
160: if (row >= this .data.length) {
161:
162: final Object[][] enlarged = new Object[row
163: + this .rowIncrement][];
164: System.arraycopy(this .data, 0, enlarged, 0,
165: this .data.length);
166: // do not create empty arrays - this is more expensive than checking
167: // for null-values.
168: this .data = enlarged;
169: }
170: }
171:
172: /**
173: * Ensures that there is storage capacity for the specified item.
174: *
175: * @param row the row index.
176: * @param column the column index.
177: */
178: public void ensureCapacity(final int row, final int column) {
179:
180: if (row < 0) {
181: throw new IndexOutOfBoundsException("Row is invalid. "
182: + row);
183: }
184: if (column < 0) {
185: throw new IndexOutOfBoundsException("Column is invalid. "
186: + column);
187: }
188:
189: ensureRowCapacity(row);
190:
191: final Object[] current = this .data[row];
192: if (current == null) {
193: final Object[] enlarged = new Object[Math.max(column + 1,
194: this .columnIncrement)];
195: this .data[row] = enlarged;
196: } else if (column >= current.length) {
197: final Object[] enlarged = new Object[column
198: + this .columnIncrement];
199: System.arraycopy(current, 0, enlarged, 0, current.length);
200: this .data[row] = enlarged;
201: }
202: }
203:
204: /**
205: * Returns the number of rows in the table.
206: *
207: * @return The row count.
208: */
209: public int getRowCount() {
210: return this .rows;
211: }
212:
213: /**
214: * Returns the number of columns in the table.
215: *
216: * @return The column count.
217: */
218: public int getColumnCount() {
219: return this .columns;
220: }
221:
222: /**
223: * Returns the object from a particular cell in the table. Returns null, if
224: * there is no object at the given position.
225: * <p/>
226: * Note: throws IndexOutOfBoundsException if row or column is negative.
227: *
228: * @param row the row index (zero-based).
229: * @param column the column index (zero-based).
230: * @return The object.
231: */
232: protected Object getObject(final int row, final int column) {
233:
234: if (row < this .data.length) {
235: final Object[] current = this .data[row];
236: if (current == null) {
237: return null;
238: }
239: if (column < current.length) {
240: return current[column];
241: }
242: }
243: return null;
244:
245: }
246:
247: /**
248: * Sets the object for a cell in the table. The table is expanded if
249: * necessary.
250: *
251: * @param row the row index (zero-based).
252: * @param column the column index (zero-based).
253: * @param object the object.
254: */
255: protected void setObject(final int row, final int column,
256: final Object object) {
257:
258: ensureCapacity(row, column);
259:
260: this .data[row][column] = object;
261: this .rows = Math.max(this .rows, row + 1);
262: this .columns = Math.max(this .columns, column + 1);
263: }
264:
265: /**
266: * Tests this paint table for equality with another object (typically also
267: * an <code>ObjectTable</code>).
268: *
269: * @param o the other object.
270: * @return A boolean.
271: */
272: public boolean equals(final Object o) {
273:
274: if (o == null) {
275: return false;
276: }
277:
278: if (this == o) {
279: return true;
280: }
281:
282: if ((o instanceof ObjectTable) == false) {
283: return false;
284: }
285:
286: final ObjectTable ot = (ObjectTable) o;
287: if (getRowCount() != ot.getRowCount()) {
288: return false;
289: }
290:
291: if (getColumnCount() != ot.getColumnCount()) {
292: return false;
293: }
294:
295: for (int r = 0; r < getRowCount(); r++) {
296: for (int c = 0; c < getColumnCount(); c++) {
297: if (ObjectUtilities.equal(getObject(r, c), ot
298: .getObject(r, c)) == false) {
299: return false;
300: }
301: }
302: }
303: return true;
304: }
305:
306: /**
307: * Returns a hash code value for the object.
308: *
309: * @return the hashcode
310: */
311: public int hashCode() {
312: int result;
313: result = this .rows;
314: result = 29 * result + this .columns;
315: return result;
316: }
317:
318: /**
319: * Handles serialization.
320: *
321: * @param stream the output stream.
322: * @throws java.io.IOException if there is an I/O problem.
323: */
324: private void writeObject(final ObjectOutputStream stream)
325: throws IOException {
326: stream.defaultWriteObject();
327: final int rowCount = this .data.length;
328: stream.writeInt(rowCount);
329: for (int r = 0; r < rowCount; r++) {
330: final Object[] column = this .data[r];
331: stream.writeBoolean(column != null);
332: if (column != null) {
333: final int columnCount = column.length;
334: stream.writeInt(columnCount);
335: for (int c = 0; c < columnCount; c++) {
336: writeSerializedData(stream, column[c]);
337: }
338: }
339: }
340: }
341:
342: /**
343: * Handles the serialization of an single element of this table.
344: *
345: * @param stream the stream which should write the object
346: * @param o the object that should be serialized
347: * @throws IOException if an IO error occured
348: */
349: protected void writeSerializedData(final ObjectOutputStream stream,
350: final Object o) throws IOException {
351: stream.writeObject(o);
352: }
353:
354: /**
355: * Restores a serialized object.
356: *
357: * @param stream the input stream.
358: * @throws java.io.IOException if there is an I/O problem.
359: * @throws ClassNotFoundException if a class cannot be found.
360: */
361: private void readObject(final ObjectInputStream stream)
362: throws IOException, ClassNotFoundException {
363: stream.defaultReadObject();
364: final int rowCount = stream.readInt();
365: this .data = new Object[rowCount][];
366: for (int r = 0; r < rowCount; r++) {
367: final boolean isNotNull = stream.readBoolean();
368: if (isNotNull) {
369: final int columnCount = stream.readInt();
370: final Object[] column = new Object[columnCount];
371: this .data[r] = column;
372: for (int c = 0; c < columnCount; c++) {
373: column[c] = readSerializedData(stream);
374: }
375: }
376: }
377: }
378:
379: /**
380: * Handles the deserialization of a single element of the table.
381: *
382: * @param stream the object input stream from which to read the object.
383: * @return the deserialized object
384: * @throws ClassNotFoundException if a class cannot be found.
385: * @throws IOException Any of the usual Input/Output related
386: * exceptions.
387: */
388: protected Object readSerializedData(final ObjectInputStream stream)
389: throws ClassNotFoundException, IOException {
390: return stream.readObject();
391: }
392:
393: /**
394: * Clears the table.
395: */
396: public void clear() {
397: this .rows = 0;
398: this .columns = 0;
399: for (int i = 0; i < this .data.length; i++) {
400: if (this .data[i] != null) {
401: Arrays.fill(this .data[i], null);
402: }
403: }
404: }
405:
406: /**
407: * Copys the contents of the old column to the new column.
408: *
409: * @param oldColumn the index of the old (source) column
410: * @param newColumn the index of the new column
411: */
412: protected void copyColumn(final int oldColumn, final int newColumn) {
413: for (int i = 0; i < getRowCount(); i++) {
414: setObject(i, newColumn, getObject(i, oldColumn));
415: }
416: }
417:
418: /**
419: * Copys the contents of the old row to the new row. This uses raw access to
420: * the data and is remarkably faster than manual copying.
421: *
422: * @param oldRow the index of the old row
423: * @param newRow the index of the new row
424: */
425: protected void copyRow(final int oldRow, final int newRow) {
426: this .ensureCapacity(newRow, getColumnCount());
427: final Object[] oldRowStorage = this .data[oldRow];
428: if (oldRowStorage == null) {
429: final Object[] newRowStorage = this .data[newRow];
430: if (newRowStorage != null) {
431: Arrays.fill(newRowStorage, null);
432: }
433: } else {
434: this .data[newRow] = (Object[]) oldRowStorage.clone();
435: }
436: }
437:
438: protected void setData(final Object[][] data, final int colCount) {
439: if (data == null) {
440: throw new NullPointerException();
441: }
442: if (colCount < 0) {
443: throw new IndexOutOfBoundsException();
444: }
445:
446: this .data = data;
447: this .rows = data.length;
448: this .columns = colCount;
449: }
450:
451: protected Object[][] getData() {
452: return data;
453: }
454: }
|