001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert 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 * Series.java 029 * ----------- 030 * (C) Copyright 2001-present, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); 034 * 035 */ 036 037package org.jfree.data.general; 038 039import org.jfree.chart.util.Args; 040 041import javax.swing.event.EventListenerList; 042import java.beans.*; 043import java.io.IOException; 044import java.io.ObjectInputStream; 045import java.io.ObjectOutputStream; 046import java.io.Serializable; 047import java.util.Objects; 048 049/** 050 * Base class representing a data series. Subclasses are left to implement the 051 * actual data structures. 052 * <P> 053 * The series has two properties ("Key" and "Description") for which you can 054 * register a {@code PropertyChangeListener}. 055 * <P> 056 * You can also register a {@link SeriesChangeListener} to receive notification 057 * of changes to the series data. 058 */ 059public abstract class Series implements Cloneable, Serializable { 060 061 /** For serialization. */ 062 private static final long serialVersionUID = -6906561437538683581L; 063 064 /** The key for the series. */ 065 private Comparable key; 066 067 /** A description of the series. */ 068 private String description; 069 070 /** Storage for registered change listeners. */ 071 private transient EventListenerList listeners; 072 073 /** Object to support property change notification. */ 074 private transient PropertyChangeSupport propertyChangeSupport; 075 076 /** Object to support property change notification. */ 077 private transient VetoableChangeSupport vetoableChangeSupport; 078 079 /** A flag that controls whether changes are notified. */ 080 private boolean notify; 081 082 /** 083 * Creates a new series with the specified key. 084 * 085 * @param key the series key ({@code null} not permitted). 086 */ 087 protected Series(Comparable key) { 088 this(key, null); 089 } 090 091 /** 092 * Creates a new series with the specified key and description. 093 * 094 * @param key the series key ({@code null} NOT permitted). 095 * @param description the series description ({@code null} permitted). 096 */ 097 protected Series(Comparable key, String description) { 098 Args.nullNotPermitted(key, "key"); 099 this.key = key; 100 this.description = description; 101 this.listeners = new EventListenerList(); 102 this.propertyChangeSupport = new PropertyChangeSupport(this); 103 this.vetoableChangeSupport = new VetoableChangeSupport(this); 104 this.notify = true; 105 } 106 107 /** 108 * Returns the key for the series. 109 * 110 * @return The series key (never {@code null}). 111 * 112 * @see #setKey(Comparable) 113 */ 114 public Comparable getKey() { 115 return this.key; 116 } 117 118 /** 119 * Sets the key for the series and sends a {@code VetoableChangeEvent} 120 * (with the property name "Key") to all registered listeners. For 121 * backwards compatibility, this method also fires a regular 122 * {@code PropertyChangeEvent}. If the key change is vetoed this 123 * method will throw an IllegalArgumentException. 124 * 125 * This implementation is not very robust when cloning or deserialising 126 * series collections, so you should not rely upon it for that purpose. 127 * In future releases, the series key will be made immutable. 128 * 129 * @param key the key ({@code null} not permitted). 130 * 131 * @see #getKey() 132 * @deprecated In future releases the series key will be immutable. 133 */ 134 @Deprecated 135 public void setKey(Comparable key) { 136 Args.nullNotPermitted(key, "key"); 137 Comparable old = this.key; 138 try { 139 // if this series belongs to a dataset, the dataset might veto the 140 // change if it results in two series within the dataset having the 141 // same key 142 this.vetoableChangeSupport.fireVetoableChange("Key", old, key); 143 this.key = key; 144 // prior to 1.0.14, we just fired a PropertyChange - so we need to 145 // keep doing this 146 this.propertyChangeSupport.firePropertyChange("Key", old, key); 147 } catch (PropertyVetoException e) { 148 throw new IllegalArgumentException(e.getMessage()); 149 } 150 } 151 152 /** 153 * Returns a description of the series. 154 * 155 * @return The series description (possibly {@code null}). 156 * 157 * @see #setDescription(String) 158 */ 159 public String getDescription() { 160 return this.description; 161 } 162 163 /** 164 * Sets the description of the series and sends a 165 * {@code PropertyChangeEvent} to all registered listeners. 166 * 167 * @param description the description ({@code null} permitted). 168 * 169 * @see #getDescription() 170 */ 171 public void setDescription(String description) { 172 String old = this.description; 173 this.description = description; 174 this.propertyChangeSupport.firePropertyChange("Description", old, 175 description); 176 } 177 178 /** 179 * Returns the flag that controls whether or not change events are sent to 180 * registered listeners. 181 * 182 * @return A boolean. 183 * 184 * @see #setNotify(boolean) 185 */ 186 public boolean getNotify() { 187 return this.notify; 188 } 189 190 /** 191 * Sets the flag that controls whether or not change events are sent to 192 * registered listeners. 193 * 194 * @param notify the new value of the flag. 195 * 196 * @see #getNotify() 197 */ 198 public void setNotify(boolean notify) { 199 if (this.notify != notify) { 200 this.notify = notify; 201 fireSeriesChanged(); 202 } 203 } 204 205 /** 206 * Returns {@code true} if the series contains no data items, and 207 * {@code false} otherwise. 208 * 209 * @return A boolean. 210 */ 211 public boolean isEmpty() { 212 return (getItemCount() == 0); 213 } 214 215 /** 216 * Returns the number of data items in the series. 217 * 218 * @return The number of data items in the series. 219 */ 220 public abstract int getItemCount(); 221 222 /** 223 * Returns a clone of the series. 224 * <P> 225 * Notes: 226 * <ul> 227 * <li>No need to clone the name or description, since String object is 228 * immutable.</li> 229 * <li>We set the listener list to empty, since the listeners did not 230 * register with the clone.</li> 231 * <li>Same applies to the PropertyChangeSupport instance.</li> 232 * </ul> 233 * 234 * @return A clone of the series. 235 * 236 * @throws CloneNotSupportedException not thrown by this class, but 237 * subclasses may differ. 238 */ 239 @Override 240 public Object clone() throws CloneNotSupportedException { 241 Series clone = (Series) super.clone(); 242 clone.listeners = new EventListenerList(); 243 clone.propertyChangeSupport = new PropertyChangeSupport(clone); 244 clone.vetoableChangeSupport = new VetoableChangeSupport(clone); 245 return clone; 246 } 247 248 /** 249 * Tests the series for equality with another object. 250 * 251 * @param obj the object ({@code null} permitted). 252 * 253 * @return {@code true} or {@code false}. 254 */ 255 @Override 256 public boolean equals(Object obj) { 257 if (obj == this) { 258 return true; 259 } 260 if (!(obj instanceof Series)) { 261 return false; 262 } 263 Series that = (Series) obj; 264 if (!Objects.equals(this.key, that.key)) { 265 return false; 266 } 267 if (!Objects.equals(this.description, that.description)) { 268 return false; 269 } 270 if (!that.canEqual(this)) { 271 return false; 272 } 273 return true; 274 } 275 276 /** 277 * Ensures symmetry between super/subclass implementations of equals. For 278 * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. 279 * 280 * @param other Object 281 * 282 * @return true ONLY if the parameter is THIS class type 283 */ 284 public boolean canEqual(Object other) { 285 // fix the "equals not symmetric" problem 286 return (other instanceof Series); 287 } 288 289 /** 290 * Returns a hash code. 291 * 292 * @return A hash code. 293 */ 294 @Override 295 public int hashCode() { 296 int hash = 5; 297 hash = 53 * hash + Objects.hashCode(this.key); 298 hash = 53 * hash + Objects.hashCode(this.description); 299 return hash; 300 } 301 302 /** 303 * Registers an object with this series, to receive notification whenever 304 * the series changes. 305 * <P> 306 * Objects being registered must implement the {@link SeriesChangeListener} 307 * interface. 308 * 309 * @param listener the listener to register. 310 */ 311 public void addChangeListener(SeriesChangeListener listener) { 312 this.listeners.add(SeriesChangeListener.class, listener); 313 } 314 315 /** 316 * Deregisters an object, so that it not longer receives notification 317 * whenever the series changes. 318 * 319 * @param listener the listener to deregister. 320 */ 321 public void removeChangeListener(SeriesChangeListener listener) { 322 this.listeners.remove(SeriesChangeListener.class, listener); 323 } 324 325 /** 326 * General method for signalling to registered listeners that the series 327 * has been changed. 328 */ 329 public void fireSeriesChanged() { 330 if (this.notify) { 331 notifyListeners(new SeriesChangeEvent(this)); 332 } 333 } 334 335 /** 336 * Sends a change event to all registered listeners. 337 * 338 * @param event contains information about the event that triggered the 339 * notification. 340 */ 341 protected void notifyListeners(SeriesChangeEvent event) { 342 343 Object[] listenerList = this.listeners.getListenerList(); 344 for (int i = listenerList.length - 2; i >= 0; i -= 2) { 345 if (listenerList[i] == SeriesChangeListener.class) { 346 ((SeriesChangeListener) listenerList[i + 1]).seriesChanged( 347 event); 348 } 349 } 350 351 } 352 353 /** 354 * Adds a property change listener to the series. 355 * 356 * @param listener the listener. 357 */ 358 public void addPropertyChangeListener(PropertyChangeListener listener) { 359 this.propertyChangeSupport.addPropertyChangeListener(listener); 360 } 361 362 /** 363 * Removes a property change listener from the series. 364 * 365 * @param listener the listener. 366 */ 367 public void removePropertyChangeListener(PropertyChangeListener listener) { 368 this.propertyChangeSupport.removePropertyChangeListener(listener); 369 } 370 371 /** 372 * Fires a property change event. 373 * 374 * @param property the property key. 375 * @param oldValue the old value. 376 * @param newValue the new value. 377 */ 378 protected void firePropertyChange(String property, Object oldValue, 379 Object newValue) { 380 this.propertyChangeSupport.firePropertyChange(property, oldValue, 381 newValue); 382 } 383 384 /** 385 * Adds a vetoable property change listener to the series. 386 * 387 * @param listener the listener. 388 */ 389 public void addVetoableChangeListener(VetoableChangeListener listener) { 390 this.vetoableChangeSupport.addVetoableChangeListener(listener); 391 } 392 393 /** 394 * Removes a vetoable property change listener from the series. 395 * 396 * @param listener the listener. 397 */ 398 public void removeVetoableChangeListener(VetoableChangeListener listener) { 399 this.vetoableChangeSupport.removeVetoableChangeListener(listener); 400 } 401 402 /** 403 * Fires a vetoable property change event. 404 * 405 * @param property the property key. 406 * @param oldValue the old value. 407 * @param newValue the new value. 408 * 409 * @throws PropertyVetoException if the change was vetoed. 410 */ 411 protected void fireVetoableChange(String property, Object oldValue, 412 Object newValue) throws PropertyVetoException { 413 this.vetoableChangeSupport.fireVetoableChange(property, oldValue, 414 newValue); 415 } 416 /** 417 * Provides serialization support. 418 * 419 * @param stream the output stream ({@code null} not permitted). 420 * 421 * @throws IOException if there is an I/O error. 422 */ 423 private void writeObject(ObjectOutputStream stream) throws IOException { 424 stream.defaultWriteObject(); 425 } 426 427 /** 428 * Provides serialization support. 429 * 430 * @param stream the input stream ({@code null} not permitted). 431 * 432 * @throws IOException if there is an I/O error. 433 * @throws ClassNotFoundException if there is a classpath problem. 434 */ 435 private void readObject(ObjectInputStream stream) 436 throws IOException, ClassNotFoundException { 437 stream.defaultReadObject(); 438 this.listeners = new EventListenerList(); 439 this.propertyChangeSupport = new PropertyChangeSupport(this); 440 this.vetoableChangeSupport = new VetoableChangeSupport(this); 441 } 442 443}