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