001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2006, 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: * ComparableObjectSeries.java
029: * ---------------------------
030: * (C) Copyright 2006, by Object Refinery Limited.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): -;
034: *
035: * $Id: ComparableObjectSeries.java,v 1.1.2.2 2006/10/23 09:18:54 mungady Exp $
036: *
037: * Changes
038: * -------
039: * 19-Oct-2006 : New class, based on XYDataItem (DG);
040: *
041: */
042:
043: package org.jfree.data;
044:
045: import java.io.Serializable;
046: import java.util.Collections;
047: import java.util.List;
048:
049: import org.jfree.data.general.Series;
050: import org.jfree.data.general.SeriesChangeEvent;
051: import org.jfree.data.general.SeriesException;
052: import org.jfree.util.ObjectUtilities;
053:
054: /**
055: * A (possibly ordered) list of (Comparable, Object) data items.
056: *
057: * @since 1.0.3
058: */
059: public class ComparableObjectSeries extends Series implements
060: Cloneable, Serializable {
061:
062: /** Storage for the data items in the series. */
063: protected List data;
064:
065: /** The maximum number of items for the series. */
066: private int maximumItemCount = Integer.MAX_VALUE;
067:
068: /** A flag that controls whether the items are automatically sorted. */
069: private boolean autoSort;
070:
071: /** A flag that controls whether or not duplicate x-values are allowed. */
072: private boolean allowDuplicateXValues;
073:
074: /**
075: * Creates a new empty series. By default, items added to the series will
076: * be sorted into ascending order by x-value, and duplicate x-values will
077: * be allowed (these defaults can be modified with another constructor.
078: *
079: * @param key the series key (<code>null</code> not permitted).
080: */
081: public ComparableObjectSeries(Comparable key) {
082: this (key, true, true);
083: }
084:
085: /**
086: * Constructs a new series that contains no data. You can specify
087: * whether or not duplicate x-values are allowed for the series.
088: *
089: * @param key the series key (<code>null</code> not permitted).
090: * @param autoSort a flag that controls whether or not the items in the
091: * series are sorted.
092: * @param allowDuplicateXValues a flag that controls whether duplicate
093: * x-values are allowed.
094: */
095: public ComparableObjectSeries(Comparable key, boolean autoSort,
096: boolean allowDuplicateXValues) {
097: super (key);
098: this .data = new java.util.ArrayList();
099: this .autoSort = autoSort;
100: this .allowDuplicateXValues = allowDuplicateXValues;
101: }
102:
103: /**
104: * Returns the flag that controls whether the items in the series are
105: * automatically sorted. There is no setter for this flag, it must be
106: * defined in the series constructor.
107: *
108: * @return A boolean.
109: */
110: public boolean getAutoSort() {
111: return this .autoSort;
112: }
113:
114: /**
115: * Returns a flag that controls whether duplicate x-values are allowed.
116: * This flag can only be set in the constructor.
117: *
118: * @return A boolean.
119: */
120: public boolean getAllowDuplicateXValues() {
121: return this .allowDuplicateXValues;
122: }
123:
124: /**
125: * Returns the number of items in the series.
126: *
127: * @return The item count.
128: */
129: public int getItemCount() {
130: return this .data.size();
131: }
132:
133: /**
134: * Returns the maximum number of items that will be retained in the series.
135: * The default value is <code>Integer.MAX_VALUE</code>.
136: *
137: * @return The maximum item count.
138: * @see #setMaximumItemCount(int)
139: */
140: public int getMaximumItemCount() {
141: return this .maximumItemCount;
142: }
143:
144: /**
145: * Sets the maximum number of items that will be retained in the series.
146: * If you add a new item to the series such that the number of items will
147: * exceed the maximum item count, then the first element in the series is
148: * automatically removed, ensuring that the maximum item count is not
149: * exceeded.
150: * <p>
151: * Typically this value is set before the series is populated with data,
152: * but if it is applied later, it may cause some items to be removed from
153: * the series (in which case a {@link SeriesChangeEvent} will be sent to
154: * all registered listeners.
155: *
156: * @param maximum the maximum number of items for the series.
157: */
158: public void setMaximumItemCount(int maximum) {
159: this .maximumItemCount = maximum;
160: boolean dataRemoved = false;
161: while (this .data.size() > maximum) {
162: this .data.remove(0);
163: dataRemoved = true;
164: }
165: if (dataRemoved) {
166: fireSeriesChanged();
167: }
168: }
169:
170: /**
171: * Adds new data to the series and sends a {@link SeriesChangeEvent} to
172: * all registered listeners.
173: * <P>
174: * Throws an exception if the x-value is a duplicate AND the
175: * allowDuplicateXValues flag is false.
176: *
177: * @param x the x-value (<code>null</code> not permitted).
178: * @param y the y-value (<code>null</code> permitted).
179: */
180: protected void add(Comparable x, Object y) {
181: // argument checking delegated...
182: add(x, y, true);
183: }
184:
185: /**
186: * Adds new data to the series and, if requested, sends a
187: * {@link SeriesChangeEvent} to all registered listeners.
188: * <P>
189: * Throws an exception if the x-value is a duplicate AND the
190: * allowDuplicateXValues flag is false.
191: *
192: * @param x the x-value (<code>null</code> not permitted).
193: * @param y the y-value (<code>null</code> permitted).
194: * @param notify a flag the controls whether or not a
195: * {@link SeriesChangeEvent} is sent to all registered
196: * listeners.
197: */
198: protected void add(Comparable x, Object y, boolean notify) {
199: // delegate argument checking to XYDataItem...
200: ComparableObjectItem item = new ComparableObjectItem(x, y);
201: add(item, notify);
202: }
203:
204: /**
205: * Adds a data item to the series and, if requested, sends a
206: * {@link SeriesChangeEvent} to all registered listeners.
207: *
208: * @param item the (x, y) item (<code>null</code> not permitted).
209: * @param notify a flag that controls whether or not a
210: * {@link SeriesChangeEvent} is sent to all registered
211: * listeners.
212: */
213: protected void add(ComparableObjectItem item, boolean notify) {
214:
215: if (item == null) {
216: throw new IllegalArgumentException("Null 'item' argument.");
217: }
218:
219: if (this .autoSort) {
220: int index = Collections.binarySearch(this .data, item);
221: if (index < 0) {
222: this .data.add(-index - 1, item);
223: } else {
224: if (this .allowDuplicateXValues) {
225: // need to make sure we are adding *after* any duplicates
226: int size = this .data.size();
227: while (index < size
228: && item.compareTo(this .data.get(index)) == 0) {
229: index++;
230: }
231: if (index < this .data.size()) {
232: this .data.add(index, item);
233: } else {
234: this .data.add(item);
235: }
236: } else {
237: throw new SeriesException("X-value already exists.");
238: }
239: }
240: } else {
241: if (!this .allowDuplicateXValues) {
242: // can't allow duplicate values, so we need to check whether
243: // there is an item with the given x-value already
244: int index = indexOf(item.getComparable());
245: if (index >= 0) {
246: throw new SeriesException("X-value already exists.");
247: }
248: }
249: this .data.add(item);
250: }
251: if (getItemCount() > this .maximumItemCount) {
252: this .data.remove(0);
253: }
254: if (notify) {
255: fireSeriesChanged();
256: }
257: }
258:
259: /**
260: * Returns the index of the item with the specified x-value, or a negative
261: * index if the series does not contain an item with that x-value. Be
262: * aware that for an unsorted series, the index is found by iterating
263: * through all items in the series.
264: *
265: * @param x the x-value (<code>null</code> not permitted).
266: *
267: * @return The index.
268: */
269: public int indexOf(Comparable x) {
270: if (this .autoSort) {
271: return Collections.binarySearch(this .data,
272: new ComparableObjectItem(x, null));
273: } else {
274: for (int i = 0; i < this .data.size(); i++) {
275: ComparableObjectItem item = (ComparableObjectItem) this .data
276: .get(i);
277: if (item.getComparable().equals(x)) {
278: return i;
279: }
280: }
281: return -1;
282: }
283: }
284:
285: /**
286: * Updates an item in the series.
287: *
288: * @param x the x-value (<code>null</code> not permitted).
289: * @param y the y-value (<code>null</code> permitted).
290: *
291: * @throws SeriesException if there is no existing item with the specified
292: * x-value.
293: */
294: protected void update(Comparable x, Object y) {
295: int index = indexOf(x);
296: if (index < 0) {
297: throw new SeriesException("No observation for x = " + x);
298: } else {
299: ComparableObjectItem item = getDataItem(index);
300: item.setObject(y);
301: fireSeriesChanged();
302: }
303: }
304:
305: /**
306: * Updates the value of an item in the series and sends a
307: * {@link SeriesChangeEvent} to all registered listeners.
308: *
309: * @param index the item (zero based index).
310: * @param y the new value (<code>null</code> permitted).
311: */
312: protected void updateByIndex(int index, Object y) {
313: ComparableObjectItem item = getDataItem(index);
314: item.setObject(y);
315: fireSeriesChanged();
316: }
317:
318: /**
319: * Return the data item with the specified index.
320: *
321: * @param index the index.
322: *
323: * @return The data item with the specified index.
324: */
325: protected ComparableObjectItem getDataItem(int index) {
326: return (ComparableObjectItem) this .data.get(index);
327: }
328:
329: /**
330: * Deletes a range of items from the series and sends a
331: * {@link SeriesChangeEvent} to all registered listeners.
332: *
333: * @param start the start index (zero-based).
334: * @param end the end index (zero-based).
335: */
336: protected void delete(int start, int end) {
337: for (int i = start; i <= end; i++) {
338: this .data.remove(start);
339: }
340: fireSeriesChanged();
341: }
342:
343: /**
344: * Removes all data items from the series.
345: */
346: protected void clear() {
347: if (this .data.size() > 0) {
348: this .data.clear();
349: fireSeriesChanged();
350: }
351: }
352:
353: /**
354: * Removes the item at the specified index and sends a
355: * {@link SeriesChangeEvent} to all registered listeners.
356: *
357: * @param index the index.
358: *
359: * @return The item removed.
360: */
361: protected ComparableObjectItem remove(int index) {
362: ComparableObjectItem result = (ComparableObjectItem) this .data
363: .remove(index);
364: fireSeriesChanged();
365: return result;
366: }
367:
368: /**
369: * Removes the item with the specified x-value and sends a
370: * {@link SeriesChangeEvent} to all registered listeners.
371: *
372: * @param x the x-value.
373:
374: * @return The item removed.
375: */
376: public ComparableObjectItem remove(Comparable x) {
377: return remove(indexOf(x));
378: }
379:
380: /**
381: * Tests this series for equality with an arbitrary object.
382: *
383: * @param obj the object to test against for equality
384: * (<code>null</code> permitted).
385: *
386: * @return A boolean.
387: */
388: public boolean equals(Object obj) {
389: if (obj == this ) {
390: return true;
391: }
392: if (!(obj instanceof ComparableObjectSeries)) {
393: return false;
394: }
395: if (!super .equals(obj)) {
396: return false;
397: }
398: ComparableObjectSeries that = (ComparableObjectSeries) obj;
399: if (this .maximumItemCount != that.maximumItemCount) {
400: return false;
401: }
402: if (this .autoSort != that.autoSort) {
403: return false;
404: }
405: if (this .allowDuplicateXValues != that.allowDuplicateXValues) {
406: return false;
407: }
408: if (!ObjectUtilities.equal(this .data, that.data)) {
409: return false;
410: }
411: return true;
412: }
413:
414: /**
415: * Returns a hash code.
416: *
417: * @return A hash code.
418: */
419: public int hashCode() {
420: int result = super .hashCode();
421: result = 29 * result
422: + (this .data != null ? this .data.hashCode() : 0);
423: result = 29 * result + this .maximumItemCount;
424: result = 29 * result + (this .autoSort ? 1 : 0);
425: result = 29 * result + (this .allowDuplicateXValues ? 1 : 0);
426: return result;
427: }
428:
429: }
|