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