Source for org.jfree.data.time.TimePeriodValues

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ---------------------
  28:  * TimePeriodValues.java
  29:  * ---------------------
  30:  * (C) Copyright 2003-2005, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: TimePeriodValues.java,v 1.8.2.1 2005/10/25 21:35:24 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 22-Apr-2003 : Version 1 (DG);
  40:  * 30-Jul-2003 : Added clone and equals methods while testing (DG);
  41:  * 11-Mar-2005 : Fixed bug in bounds recalculation - see bug report 
  42:  *               1161329 (DG);
  43:  *
  44:  */
  45: 
  46: package org.jfree.data.time;
  47: 
  48: import java.io.Serializable;
  49: import java.util.ArrayList;
  50: import java.util.List;
  51: 
  52: import org.jfree.data.general.Series;
  53: import org.jfree.data.general.SeriesException;
  54: 
  55: /**
  56:  * A structure containing zero, one or many {@link TimePeriodValue} instances.  
  57:  * The time periods can overlap, and are maintained in the order that they are 
  58:  * added to the collection.
  59:  * <p>
  60:  * This is similar to the {@link TimeSeries} class, except that the time 
  61:  * periods can have irregular lengths.
  62:  */
  63: public class TimePeriodValues extends Series implements Serializable {
  64: 
  65:     /** For serialization. */
  66:     static final long serialVersionUID = -2210593619794989709L;
  67:     
  68:     /** Default value for the domain description. */
  69:     protected static final String DEFAULT_DOMAIN_DESCRIPTION = "Time";
  70: 
  71:     /** Default value for the range description. */
  72:     protected static final String DEFAULT_RANGE_DESCRIPTION = "Value";
  73: 
  74:     /** A description of the domain. */
  75:     private String domain;
  76: 
  77:     /** A description of the range. */
  78:     private String range;
  79: 
  80:     /** The list of data pairs in the series. */
  81:     private List data;
  82: 
  83:     /** Index of the time period with the minimum start milliseconds. */
  84:     private int minStartIndex = -1;
  85:     
  86:     /** Index of the time period with the maximum start milliseconds. */
  87:     private int maxStartIndex = -1;
  88:     
  89:     /** Index of the time period with the minimum middle milliseconds. */
  90:     private int minMiddleIndex = -1;
  91:     
  92:     /** Index of the time period with the maximum middle milliseconds. */
  93:     private int maxMiddleIndex = -1;
  94:     
  95:     /** Index of the time period with the minimum end milliseconds. */
  96:     private int minEndIndex = -1;
  97:     
  98:     /** Index of the time period with the maximum end milliseconds. */
  99:     private int maxEndIndex = -1;
 100: 
 101:     /**
 102:      * Creates a new (empty) collection of time period values.
 103:      *
 104:      * @param name  the name of the series.
 105:      */
 106:     public TimePeriodValues(String name) {
 107:         this(name, DEFAULT_DOMAIN_DESCRIPTION, DEFAULT_RANGE_DESCRIPTION);
 108:     }
 109: 
 110:     /**
 111:      * Creates a new time series that contains no data.
 112:      * <P>
 113:      * Descriptions can be specified for the domain and range.  One situation
 114:      * where this is helpful is when generating a chart for the time series -
 115:      * axis labels can be taken from the domain and range description.
 116:      *
 117:      * @param name  the name of the series.
 118:      * @param domain  the domain description.
 119:      * @param range  the range description.
 120:      */
 121:     public TimePeriodValues(String name, String domain, String range) {
 122:         super(name);
 123:         this.domain = domain;
 124:         this.range = range;
 125:         this.data = new ArrayList();
 126:     }
 127: 
 128:     /**
 129:      * Returns the domain description.
 130:      *
 131:      * @return The domain description.
 132:      */
 133:     public String getDomainDescription() {
 134:         return this.domain;
 135:     }
 136: 
 137:     /**
 138:      * Sets the domain description and fires a property change event.
 139:      *
 140:      * @param description  the new description.
 141:      */
 142:     public void setDomainDescription(String description) {
 143:         String old = this.domain;
 144:         this.domain = description;
 145:         firePropertyChange("Domain", old, description);
 146:     }
 147: 
 148:     /**
 149:      * Returns the range description.
 150:      *
 151:      * @return The range description.
 152:      */
 153:     public String getRangeDescription() {
 154:         return this.range;
 155:     }
 156: 
 157:     /**
 158:      * Sets the range description and fires a property change event.
 159:      *
 160:      * @param description  the new description.
 161:      */
 162:     public void setRangeDescription(String description) {
 163:         String old = this.range;
 164:         this.range = description;
 165:         firePropertyChange("Range", old, description);
 166:     }
 167: 
 168:     /**
 169:      * Returns the number of items in the series.
 170:      *
 171:      * @return The item count.
 172:      */
 173:     public int getItemCount() {
 174:         return this.data.size();
 175:     }
 176: 
 177:     /**
 178:      * Returns one data item for the series.
 179:      *
 180:      * @param index  the item index (zero-based).
 181:      *
 182:      * @return One data item for the series.
 183:      */
 184:     public TimePeriodValue getDataItem(int index) {
 185:         return (TimePeriodValue) this.data.get(index);
 186:     }
 187: 
 188:     /**
 189:      * Returns the time period at the specified index.
 190:      *
 191:      * @param index  the index of the data pair.
 192:      *
 193:      * @return The time period at the specified index.
 194:      */
 195:     public TimePeriod getTimePeriod(int index) {
 196:         return getDataItem(index).getPeriod();
 197:     }
 198: 
 199:     /**
 200:      * Returns the value at the specified index.
 201:      *
 202:      * @param index  index of a value.
 203:      *
 204:      * @return The value at the specified index.
 205:      */
 206:     public Number getValue(int index) {
 207:         return getDataItem(index).getValue();
 208:     }
 209: 
 210:     /**
 211:      * Adds a data item to the series.
 212:      *
 213:      * @param item  the (timeperiod, value) pair.
 214:      */
 215:     public void add(TimePeriodValue item) {
 216: 
 217:         // check arguments...
 218:         if (item == null) {
 219:             throw new IllegalArgumentException("Null item not allowed.");
 220:         }
 221: 
 222:         // make the change
 223:         this.data.add(item);
 224:         updateBounds(item.getPeriod(), this.data.size() - 1);
 225: 
 226:     }
 227:     
 228:     /**
 229:      * Update the index values for the maximum and minimum bounds.
 230:      * 
 231:      * @param period  the time period.
 232:      * @param index  the index of the time period.
 233:      */
 234:     private void updateBounds(TimePeriod period, int index) {
 235:         
 236:         long start = period.getStart().getTime();
 237:         long end = period.getEnd().getTime();
 238:         long middle = start + ((end - start) / 2);
 239: 
 240:         if (this.minStartIndex >= 0) {
 241:             long minStart = getDataItem(this.minStartIndex).getPeriod()
 242:                 .getStart().getTime();
 243:             if (start < minStart) {
 244:                 this.minStartIndex = index;           
 245:             }
 246:         }
 247:         else {
 248:             this.minStartIndex = index;
 249:         }
 250:         
 251:         if (this.maxStartIndex >= 0) {
 252:             long maxStart = getDataItem(this.maxStartIndex).getPeriod()
 253:                 .getStart().getTime();
 254:             if (start > maxStart) {
 255:                 this.maxStartIndex = index;           
 256:             }
 257:         }
 258:         else {
 259:             this.maxStartIndex = index;
 260:         }
 261:         
 262:         if (this.minMiddleIndex >= 0) {
 263:             long s = getDataItem(this.minMiddleIndex).getPeriod().getStart()
 264:                 .getTime();
 265:             long e = getDataItem(this.minMiddleIndex).getPeriod().getEnd()
 266:                 .getTime();
 267:             long minMiddle = s + (e - s) / 2;
 268:             if (middle < minMiddle) {
 269:                 this.minMiddleIndex = index;           
 270:             }
 271:         }
 272:         else {
 273:             this.minMiddleIndex = index;
 274:         }
 275:         
 276:         if (this.maxMiddleIndex >= 0) {
 277:             long s = getDataItem(this.minMiddleIndex).getPeriod().getStart()
 278:                 .getTime();
 279:             long e = getDataItem(this.minMiddleIndex).getPeriod().getEnd()
 280:                 .getTime();
 281:             long maxMiddle = s + (e - s) / 2;
 282:             if (middle > maxMiddle) {
 283:                 this.maxMiddleIndex = index;           
 284:             }
 285:         }
 286:         else {
 287:             this.maxMiddleIndex = index;
 288:         }
 289:         
 290:         if (this.minEndIndex >= 0) {
 291:             long minEnd = getDataItem(this.minEndIndex).getPeriod().getEnd()
 292:                 .getTime();
 293:             if (end < minEnd) {
 294:                 this.minEndIndex = index;           
 295:             }
 296:         }
 297:         else {
 298:             this.minEndIndex = index;
 299:         }
 300:        
 301:         if (this.maxEndIndex >= 0) {
 302:             long maxEnd = getDataItem(this.maxEndIndex).getPeriod().getEnd()
 303:                 .getTime();
 304:             if (end > maxEnd) {
 305:                 this.maxEndIndex = index;           
 306:             }
 307:         }
 308:         else {
 309:             this.maxEndIndex = index;
 310:         }
 311:         
 312:     }
 313:     
 314:     /**
 315:      * Recalculates the bounds for the collection of items.
 316:      */
 317:     private void recalculateBounds() {
 318:         this.minStartIndex = -1;
 319:         this.minMiddleIndex = -1;
 320:         this.minEndIndex = -1;
 321:         this.maxStartIndex = -1;
 322:         this.maxMiddleIndex = -1;
 323:         this.maxEndIndex = -1;
 324:         for (int i = 0; i < this.data.size(); i++) {
 325:             TimePeriodValue tpv = (TimePeriodValue) this.data.get(i);
 326:             updateBounds(tpv.getPeriod(), i);
 327:         }
 328:     }
 329: 
 330:     /**
 331:      * Adds a new data item to the series.
 332:      *
 333:      * @param period  the time period.
 334:      * @param value  the value.
 335:      */
 336:     public void add(TimePeriod period, double value) {
 337:         TimePeriodValue item = new TimePeriodValue(period, value);
 338:         add(item);
 339:     }
 340: 
 341:     /**
 342:      * Adds a new data item to the series.
 343:      *
 344:      * @param period  the time period.
 345:      * @param value  the value.
 346:      */
 347:     public void add(TimePeriod period, Number value) {
 348:         TimePeriodValue item = new TimePeriodValue(period, value);
 349:         add(item);
 350:     }
 351: 
 352:     /**
 353:      * Updates (changes) the value of a data item.
 354:      *
 355:      * @param index  the index of the data item to update.
 356:      * @param value  the new value.
 357:      */
 358:     public void update(int index, Number value) {
 359:         TimePeriodValue item = getDataItem(index);
 360:         item.setValue(value);
 361:         fireSeriesChanged();
 362:     }
 363: 
 364:     /**
 365:      * Deletes data from start until end index (end inclusive).
 366:      *
 367:      * @param start  the index of the first period to delete.
 368:      * @param end  the index of the last period to delete.
 369:      */
 370:     public void delete(int start, int end) {
 371:         for (int i = 0; i <= (end - start); i++) {
 372:             this.data.remove(start);
 373:         }
 374:         recalculateBounds();
 375:         fireSeriesChanged();
 376:     }
 377:     
 378:     /**
 379:      * Tests the series for equality with another object.
 380:      *
 381:      * @param obj  the object.
 382:      *
 383:      * @return <code>true</code> or <code>false</code>.
 384:      */
 385:     public boolean equals(Object obj) {
 386:         
 387:         if (obj == this) {
 388:             return true;
 389:         }
 390: 
 391:         if (!(obj instanceof TimePeriodValues)) {
 392:             return false;
 393:         }
 394: 
 395:         if (!super.equals(obj)) {
 396:             return false;
 397:         }
 398: 
 399:         TimePeriodValues that = (TimePeriodValues) obj;
 400:         if (!getDomainDescription().equals(that.getDomainDescription())) {
 401:             return false;
 402:         }
 403:         if (!getRangeDescription().equals(that.getRangeDescription())) {
 404:             return false;
 405:         }
 406: 
 407:         int count = getItemCount();
 408:         if (count != that.getItemCount()) {
 409:             return false;
 410:         }
 411:         for (int i = 0; i < count; i++) {
 412:             if (!getDataItem(i).equals(that.getDataItem(i))) {
 413:                 return false;
 414:             }
 415:         }
 416:         return true;
 417: 
 418:     }
 419: 
 420:     /**
 421:      * Returns a hash code value for the object.
 422:      *
 423:      * @return The hashcode
 424:      */
 425:     public int hashCode() {
 426:         int result;
 427:         result = (this.domain != null ? this.domain.hashCode() : 0);
 428:         result = 29 * result + (this.range != null ? this.range.hashCode() : 0);
 429:         result = 29 * result + this.data.hashCode();
 430:         result = 29 * result + this.minStartIndex;
 431:         result = 29 * result + this.maxStartIndex;
 432:         result = 29 * result + this.minMiddleIndex;
 433:         result = 29 * result + this.maxMiddleIndex;
 434:         result = 29 * result + this.minEndIndex;
 435:         result = 29 * result + this.maxEndIndex;
 436:         return result;
 437:     }
 438: 
 439:     /**
 440:      * Returns a clone of the collection.
 441:      * <P>
 442:      * Notes:
 443:      * <ul>
 444:      *   <li>no need to clone the domain and range descriptions, since String 
 445:      *       object is immutable;</li>
 446:      *   <li>we pass over to the more general method createCopy(start, end).
 447:      *   </li>
 448:      * </ul>
 449:      *
 450:      * @return A clone of the time series.
 451:      * 
 452:      * @throws CloneNotSupportedException if there is a cloning problem.
 453:      */
 454:     public Object clone() throws CloneNotSupportedException {
 455:         Object clone = createCopy(0, getItemCount() - 1);
 456:         return clone;
 457:     }
 458: 
 459:     /**
 460:      * Creates a new instance by copying a subset of the data in this 
 461:      * collection.
 462:      *
 463:      * @param start  the index of the first item to copy.
 464:      * @param end  the index of the last item to copy.
 465:      *
 466:      * @return A copy of a subset of the items.
 467:      * 
 468:      * @throws CloneNotSupportedException if there is a cloning problem.
 469:      */
 470:     public TimePeriodValues createCopy(int start, int end) 
 471:         throws CloneNotSupportedException {
 472: 
 473:         TimePeriodValues copy = (TimePeriodValues) super.clone();
 474: 
 475:         copy.data = new ArrayList();
 476:         if (this.data.size() > 0) {
 477:             for (int index = start; index <= end; index++) {
 478:                 TimePeriodValue item = (TimePeriodValue) this.data.get(index);
 479:                 TimePeriodValue clone = (TimePeriodValue) item.clone();
 480:                 try {
 481:                     copy.add(clone);
 482:                 }
 483:                 catch (SeriesException e) {
 484:                     System.err.println("Failed to add cloned item.");
 485:                 }
 486:             }
 487:         }
 488:         return copy;
 489: 
 490:     }
 491:     
 492:     /**
 493:      * Returns the index of the time period with the minimum start milliseconds.
 494:      * 
 495:      * @return The index.
 496:      */
 497:     public int getMinStartIndex() {
 498:         return this.minStartIndex;
 499:     }
 500:     
 501:     /**
 502:      * Returns the index of the time period with the maximum start milliseconds.
 503:      * 
 504:      * @return The index.
 505:      */
 506:     public int getMaxStartIndex() {
 507:         return this.maxStartIndex;
 508:     }
 509: 
 510:     /**
 511:      * Returns the index of the time period with the minimum middle 
 512:      * milliseconds.
 513:      * 
 514:      * @return The index.
 515:      */
 516:     public int getMinMiddleIndex() {
 517:         return this.minMiddleIndex;
 518:     }
 519:     
 520:     /**
 521:      * Returns the index of the time period with the maximum middle 
 522:      * milliseconds.
 523:      * 
 524:      * @return The index.
 525:      */
 526:     public int getMaxMiddleIndex() {
 527:         return this.maxMiddleIndex;
 528:     }
 529: 
 530:     /**
 531:      * Returns the index of the time period with the minimum end milliseconds.
 532:      * 
 533:      * @return The index.
 534:      */
 535:     public int getMinEndIndex() {
 536:         return this.minEndIndex;
 537:     }
 538:     
 539:     /**
 540:      * Returns the index of the time period with the maximum end milliseconds.
 541:      * 
 542:      * @return The index.
 543:      */
 544:     public int getMaxEndIndex() {
 545:         return this.maxEndIndex;
 546:     }
 547: 
 548: }