001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2011, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * ------------------------- 028 * OHLCSeriesCollection.java 029 * ------------------------- 030 * (C) Copyright 2006-2009, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes 036 * ------- 037 * 04-Dec-2006 : Version 1 (DG); 038 * 10-Jul-2008 : Added accessor methods for xPosition attribute (DG); 039 * 23-May-2009 : Added hashCode() implementation (DG); 040 * 26-Jun-2009 : Added removeSeries() methods (DG); 041 * 042 */ 043 044 package org.jfree.data.time.ohlc; 045 046 import java.io.Serializable; 047 import java.util.List; 048 049 import org.jfree.chart.HashUtilities; 050 import org.jfree.data.general.DatasetChangeEvent; 051 import org.jfree.data.time.RegularTimePeriod; 052 import org.jfree.data.time.TimePeriodAnchor; 053 import org.jfree.data.xy.AbstractXYDataset; 054 import org.jfree.data.xy.OHLCDataset; 055 import org.jfree.data.xy.XYDataset; 056 import org.jfree.util.ObjectUtilities; 057 058 /** 059 * A collection of {@link OHLCSeries} objects. 060 * 061 * @since 1.0.4 062 * 063 * @see OHLCSeries 064 */ 065 public class OHLCSeriesCollection extends AbstractXYDataset 066 implements OHLCDataset, Serializable { 067 068 /** Storage for the data series. */ 069 private List data; 070 071 private TimePeriodAnchor xPosition = TimePeriodAnchor.MIDDLE; 072 073 /** 074 * Creates a new instance of <code>OHLCSeriesCollection</code>. 075 */ 076 public OHLCSeriesCollection() { 077 this.data = new java.util.ArrayList(); 078 } 079 080 /** 081 * Returns the position within each time period that is used for the X 082 * value when the collection is used as an {@link XYDataset}. 083 * 084 * @return The anchor position (never <code>null</code>). 085 * 086 * @since 1.0.11 087 */ 088 public TimePeriodAnchor getXPosition() { 089 return this.xPosition; 090 } 091 092 /** 093 * Sets the position within each time period that is used for the X values 094 * when the collection is used as an {@link XYDataset}, then sends a 095 * {@link DatasetChangeEvent} is sent to all registered listeners. 096 * 097 * @param anchor the anchor position (<code>null</code> not permitted). 098 * 099 * @since 1.0.11 100 */ 101 public void setXPosition(TimePeriodAnchor anchor) { 102 if (anchor == null) { 103 throw new IllegalArgumentException("Null 'anchor' argument."); 104 } 105 this.xPosition = anchor; 106 notifyListeners(new DatasetChangeEvent(this, this)); 107 } 108 109 /** 110 * Adds a series to the collection and sends a {@link DatasetChangeEvent} 111 * to all registered listeners. 112 * 113 * @param series the series (<code>null</code> not permitted). 114 */ 115 public void addSeries(OHLCSeries series) { 116 if (series == null) { 117 throw new IllegalArgumentException("Null 'series' argument."); 118 } 119 this.data.add(series); 120 series.addChangeListener(this); 121 fireDatasetChanged(); 122 } 123 124 /** 125 * Returns the number of series in the collection. 126 * 127 * @return The series count. 128 */ 129 public int getSeriesCount() { 130 return this.data.size(); 131 } 132 133 /** 134 * Returns a series from the collection. 135 * 136 * @param series the series index (zero-based). 137 * 138 * @return The series. 139 * 140 * @throws IllegalArgumentException if <code>series</code> is not in the 141 * range <code>0</code> to <code>getSeriesCount() - 1</code>. 142 */ 143 public OHLCSeries getSeries(int series) { 144 if ((series < 0) || (series >= getSeriesCount())) { 145 throw new IllegalArgumentException("Series index out of bounds"); 146 } 147 return (OHLCSeries) this.data.get(series); 148 } 149 150 /** 151 * Returns the key for a series. 152 * 153 * @param series the series index (in the range <code>0</code> to 154 * <code>getSeriesCount() - 1</code>). 155 * 156 * @return The key for a series. 157 * 158 * @throws IllegalArgumentException if <code>series</code> is not in the 159 * specified range. 160 */ 161 public Comparable getSeriesKey(int series) { 162 // defer argument checking 163 return getSeries(series).getKey(); 164 } 165 166 /** 167 * Returns the number of items in the specified series. 168 * 169 * @param series the series (zero-based index). 170 * 171 * @return The item count. 172 * 173 * @throws IllegalArgumentException if <code>series</code> is not in the 174 * range <code>0</code> to <code>getSeriesCount() - 1</code>. 175 */ 176 public int getItemCount(int series) { 177 // defer argument checking 178 return getSeries(series).getItemCount(); 179 } 180 181 /** 182 * Returns the x-value for a time period. 183 * 184 * @param period the time period (<code>null</code> not permitted). 185 * 186 * @return The x-value. 187 */ 188 protected synchronized long getX(RegularTimePeriod period) { 189 long result = 0L; 190 if (this.xPosition == TimePeriodAnchor.START) { 191 result = period.getFirstMillisecond(); 192 } 193 else if (this.xPosition == TimePeriodAnchor.MIDDLE) { 194 result = period.getMiddleMillisecond(); 195 } 196 else if (this.xPosition == TimePeriodAnchor.END) { 197 result = period.getLastMillisecond(); 198 } 199 return result; 200 } 201 202 /** 203 * Returns the x-value for an item within a series. 204 * 205 * @param series the series index. 206 * @param item the item index. 207 * 208 * @return The x-value. 209 */ 210 public double getXValue(int series, int item) { 211 OHLCSeries s = (OHLCSeries) this.data.get(series); 212 OHLCItem di = (OHLCItem) s.getDataItem(item); 213 RegularTimePeriod period = di.getPeriod(); 214 return getX(period); 215 } 216 217 /** 218 * Returns the x-value for an item within a series. 219 * 220 * @param series the series index. 221 * @param item the item index. 222 * 223 * @return The x-value. 224 */ 225 public Number getX(int series, int item) { 226 return new Double(getXValue(series, item)); 227 } 228 229 /** 230 * Returns the y-value for an item within a series. 231 * 232 * @param series the series index. 233 * @param item the item index. 234 * 235 * @return The y-value. 236 */ 237 public Number getY(int series, int item) { 238 OHLCSeries s = (OHLCSeries) this.data.get(series); 239 OHLCItem di = (OHLCItem) s.getDataItem(item); 240 return new Double(di.getYValue()); 241 } 242 243 /** 244 * Returns the open-value for an item within a series. 245 * 246 * @param series the series index. 247 * @param item the item index. 248 * 249 * @return The open-value. 250 */ 251 public double getOpenValue(int series, int item) { 252 OHLCSeries s = (OHLCSeries) this.data.get(series); 253 OHLCItem di = (OHLCItem) s.getDataItem(item); 254 return di.getOpenValue(); 255 } 256 257 /** 258 * Returns the open-value for an item within a series. 259 * 260 * @param series the series index. 261 * @param item the item index. 262 * 263 * @return The open-value. 264 */ 265 public Number getOpen(int series, int item) { 266 return new Double(getOpenValue(series, item)); 267 } 268 269 /** 270 * Returns the close-value for an item within a series. 271 * 272 * @param series the series index. 273 * @param item the item index. 274 * 275 * @return The close-value. 276 */ 277 public double getCloseValue(int series, int item) { 278 OHLCSeries s = (OHLCSeries) this.data.get(series); 279 OHLCItem di = (OHLCItem) s.getDataItem(item); 280 return di.getCloseValue(); 281 } 282 283 /** 284 * Returns the close-value for an item within a series. 285 * 286 * @param series the series index. 287 * @param item the item index. 288 * 289 * @return The close-value. 290 */ 291 public Number getClose(int series, int item) { 292 return new Double(getCloseValue(series, item)); 293 } 294 295 /** 296 * Returns the high-value for an item within a series. 297 * 298 * @param series the series index. 299 * @param item the item index. 300 * 301 * @return The high-value. 302 */ 303 public double getHighValue(int series, int item) { 304 OHLCSeries s = (OHLCSeries) this.data.get(series); 305 OHLCItem di = (OHLCItem) s.getDataItem(item); 306 return di.getHighValue(); 307 } 308 309 /** 310 * Returns the high-value for an item within a series. 311 * 312 * @param series the series index. 313 * @param item the item index. 314 * 315 * @return The high-value. 316 */ 317 public Number getHigh(int series, int item) { 318 return new Double(getHighValue(series, item)); 319 } 320 321 /** 322 * Returns the low-value for an item within a series. 323 * 324 * @param series the series index. 325 * @param item the item index. 326 * 327 * @return The low-value. 328 */ 329 public double getLowValue(int series, int item) { 330 OHLCSeries s = (OHLCSeries) this.data.get(series); 331 OHLCItem di = (OHLCItem) s.getDataItem(item); 332 return di.getLowValue(); 333 } 334 335 /** 336 * Returns the low-value for an item within a series. 337 * 338 * @param series the series index. 339 * @param item the item index. 340 * 341 * @return The low-value. 342 */ 343 public Number getLow(int series, int item) { 344 return new Double(getLowValue(series, item)); 345 } 346 347 /** 348 * Returns <code>null</code> always, because this dataset doesn't record 349 * any volume data. 350 * 351 * @param series the series index (ignored). 352 * @param item the item index (ignored). 353 * 354 * @return <code>null</code>. 355 */ 356 public Number getVolume(int series, int item) { 357 return null; 358 } 359 360 /** 361 * Returns <code>Double.NaN</code> always, because this dataset doesn't 362 * record any volume data. 363 * 364 * @param series the series index (ignored). 365 * @param item the item index (ignored). 366 * 367 * @return <code>Double.NaN</code>. 368 */ 369 public double getVolumeValue(int series, int item) { 370 return Double.NaN; 371 } 372 373 /** 374 * Removes the series with the specified index and sends a 375 * {@link DatasetChangeEvent} to all registered listeners. 376 * 377 * @param index the series index. 378 * 379 * @since 1.0.14 380 */ 381 public void removeSeries(int index) { 382 OHLCSeries series = getSeries(index); 383 if (series != null) { 384 removeSeries(series); 385 } 386 } 387 388 /** 389 * Removes the specified series from the dataset and sends a 390 * {@link DatasetChangeEvent} to all registered listeners. 391 * 392 * @param series the series (<code>null</code> not permitted). 393 * 394 * @return <code>true</code> if the series was removed, and 395 * <code>false</code> otherwise. 396 * 397 * @since 1.0.14 398 */ 399 public boolean removeSeries(OHLCSeries series) { 400 if (series == null) { 401 throw new IllegalArgumentException("Null 'series' argument."); 402 } 403 boolean removed = this.data.remove(series); 404 if (removed) { 405 series.removeChangeListener(this); 406 fireDatasetChanged(); 407 } 408 return removed; 409 } 410 411 /** 412 * Removes all the series from the collection and sends a 413 * {@link DatasetChangeEvent} to all registered listeners. 414 * 415 * @since 1.0.14 416 */ 417 public void removeAllSeries() { 418 419 if (this.data.size() == 0) { 420 return; // nothing to do 421 } 422 423 // deregister the collection as a change listener to each series in the 424 // collection 425 for (int i = 0; i < this.data.size(); i++) { 426 OHLCSeries series = (OHLCSeries) this.data.get(i); 427 series.removeChangeListener(this); 428 } 429 430 // remove all the series from the collection and notify listeners. 431 this.data.clear(); 432 fireDatasetChanged(); 433 434 } 435 436 /** 437 * Tests this instance for equality with an arbitrary object. 438 * 439 * @param obj the object (<code>null</code> permitted). 440 * 441 * @return A boolean. 442 */ 443 public boolean equals(Object obj) { 444 if (obj == this) { 445 return true; 446 } 447 if (!(obj instanceof OHLCSeriesCollection)) { 448 return false; 449 } 450 OHLCSeriesCollection that = (OHLCSeriesCollection) obj; 451 if (!this.xPosition.equals(that.xPosition)) { 452 return false; 453 } 454 return ObjectUtilities.equal(this.data, that.data); 455 } 456 457 /** 458 * Returns a hash code for this instance. 459 * 460 * @return A hash code. 461 */ 462 public int hashCode() { 463 int result = 137; 464 result = HashUtilities.hashCode(result, this.xPosition); 465 for (int i = 0; i < this.data.size(); i++) { 466 result = HashUtilities.hashCode(result, this.data.get(i)); 467 } 468 return result; 469 } 470 471 /** 472 * Returns a clone of this instance. 473 * 474 * @return A clone. 475 * 476 * @throws CloneNotSupportedException if there is a problem. 477 */ 478 public Object clone() throws CloneNotSupportedException { 479 OHLCSeriesCollection clone 480 = (OHLCSeriesCollection) super.clone(); 481 clone.data = (List) ObjectUtilities.deepClone(this.data); 482 return clone; 483 } 484 485 }