001/* Abstract base class for change requests. 002 003 Copyright (c) 1999-2013 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.kernel.util; 029 030import java.lang.ref.WeakReference; 031import java.util.Iterator; 032import java.util.LinkedList; 033import java.util.List; 034 035/////////////////////////////////////////////////////////////////// 036//// ChangeRequest 037 038/** 039 Abstract base class for change requests. A change request is any 040 modification to a model that might be performed during execution of the 041 model, but where there might only be certain phases of execution during 042 which it is safe to make the modification. Such changes are also called 043 <i>mutations</i>. 044 <p> 045 A typical use of this class is to define an anonymous inner class that 046 implements the _execute() method to bring about the desired change. 047 The instance of that anonymous inner class is then queued with 048 an instance of NamedObj using its requestChange() method. 049 The execute() method must be called only once; attempting to call 050 it multiple times will trigger an exception. 051 <p> 052 Concrete derived classes can be defined to implement 053 mutations of a certain kind or in a certain way. Instances of these 054 classes should be queued with a NamedObj, just like an anonymous 055 inner class extending this class. MoMLChangeRequest is such a concrete 056 derived class, where the mutation is specified as MoML code. 057 058 @author Edward A. Lee, Contributor: Bert Rodiers 059 @version $Id$ 060 @since Ptolemy II 1.0 061 @Pt.ProposedRating Green (eal) 062 @Pt.AcceptedRating Green (neuendor) 063 @see ChangeListener 064 */ 065public abstract class ChangeRequest { 066 /** Construct a request with the specified source and description. 067 * The description is a string that is used to report the change, 068 * typically to the user in a debugging environment. 069 * The source is the object that requested this change request. 070 * A listener to changes will probably want to check the source 071 * so that when it is notified of errors or successful completion 072 * of changes, it can tell whether the change is one it requested. 073 * This constructor is here for backwards compatibility. The 074 * constructor <i>ChangeRequest(Object source, String description, 075 * boolean isStructuralChange) </i> has an extra parameter that allows 076 * you to specify whether this change is a structural change. If it isn't 077 * one, no complete repaints will be done of the visual model. 078 * By using this constructor the repaints will be done if there are 079 * AbstractBasicGraphModel listeners listening to this ChangeRequest. 080 * @param source The source of the change request. 081 * @param description A description of the change request. 082 * @see #ChangeRequest(Object, String, boolean) 083 */ 084 public ChangeRequest(Object source, String description) { 085 this(source, description, true); 086 } 087 088 /** Construct a request with the specified source and description. 089 * The description is a string that is used to report the change, 090 * typically to the user in a debugging environment. 091 * The source is the object that requested this change request. 092 * A listener to changes will probably want to check the source 093 * so that when it is notified of errors or successful completion 094 * of changes, it can tell whether the change is one it requested. 095 * This constructor is here for backwards compatibility. 096 * isStructuralChange specifies whether the change is structural 097 * or not. If it isn't a structural change (isStructuralChange equal 098 * to false), no complete repaints will be done of the visual model. 099 * If isStructuralChange equals true, repaints will be done if there 100 * are AbstractBasicGraphModel listeners listening to this ChangeRequest. 101 * @param source The source of the change request. 102 * @param description A description of the change request. 103 * @param isStructuralChange Specifies whether this change is a structural 104 * one. 105 */ 106 public ChangeRequest(Object source, String description, 107 boolean isStructuralChange) { 108 _source = source; 109 _description = description; 110 _errorReported = false; 111 _isStructuralChange = isStructuralChange; 112 } 113 114 /////////////////////////////////////////////////////////////////// 115 //// public methods //// 116 117 /** Add a new change listener to this request. The listener will get 118 * notified when the change is executed, or the change fails. This 119 * listener is notified first, and then any listeners that were 120 * given by setListeners. This listener is also notified before 121 * other listeners that have been previously registered with this 122 * object. 123 * @param listener The listener to add. 124 * @see #removeChangeListener(ChangeListener) 125 */ 126 public void addChangeListener(ChangeListener listener) { 127 if (_localListeners == null) { 128 _localListeners = new LinkedList<ChangeListener>(); 129 } 130 131 // NOTE: We do not use weak references for these 132 // listeners because an instance of ChangeRequest 133 // is typically a transitory object. 134 if (!_localListeners.contains(listener)) { 135 _localListeners.add(0, listener); 136 } 137 } 138 139 /** Execute the change. This method invokes the protected method 140 * _execute(), takes care of reporting execution to any listeners 141 * and then wakes up any threads that might be waiting in a call to 142 * waitForCompletion(). Listeners that are attached directly to this 143 * object (using the addChangeListener() and removeChangeListener() 144 * methods) are notified first of the status of the request, followed 145 * by global listeners that were set using the setListeners() method. 146 * If the change failed because an exception was thrown, and the 147 * exception was not reported to any global listeners, then 148 * we throw an InternalErrorException because it is a bug to 149 * not have a listener in this case. 150 * <p> 151 * This method should be called exactly once, by the object that 152 * the change request was queued with. Attempting to call this 153 * method more than once will throw an exception. 154 */ 155 public final synchronized void execute() { 156 if (!_pending) { 157 throw new InternalErrorException( 158 "Attempted to execute a change request " 159 + "that had already been executed."); 160 } 161 162 _exception = null; 163 164 // This flag is set if an exception is caught. If the exception 165 // is reported to any listeners set with setListeners, then 166 // the flag is reset to false. If we get to the end and the 167 // flag is still true, then we write out to standard error. 168 boolean needToReport = false; 169 170 try { 171 _execute(); 172 } catch (Exception ex) { 173 needToReport = true; 174 _exception = ex; 175 } 176 177 if (_localListeners != null) { 178 179 for (ChangeListener listener : _localListeners) { 180 if (_exception == null) { 181 listener.changeExecuted(this); 182 } else { 183 // note that local listeners do not prevent an exception 184 // from being seen globally. This is weird. 185 listener.changeFailed(this, _exception); 186 } 187 } 188 } 189 190 if (_listeners != null) { 191 Iterator<?> listeners = _listeners.iterator(); 192 193 while (listeners.hasNext()) { 194 Object listener = listeners.next(); 195 196 if (listener instanceof WeakReference) { 197 listener = ((WeakReference) listener).get(); 198 } 199 200 if (listener instanceof ChangeListener) { 201 if (_exception == null) { 202 ((ChangeListener) listener).changeExecuted(this); 203 } else { 204 needToReport = false; 205 ((ChangeListener) listener).changeFailed(this, 206 _exception); 207 } 208 } 209 } 210 } 211 212 // If there is no ChangeListener, and the ChangeRequest throws 213 // an exception, make sure we set _pending to false so that we 214 // don't execute the ChangeRequest twice. This is in 215 // keeping with the policy where we remove ChangeRequests 216 // from the list before we execute them. 217 218 try { 219 if (needToReport) { 220 if (_exception != null) { 221 // We used to print to stderr, but printing to 222 // stderr is a bug if we have a UI, so we throw an 223 // InternalError. If the _source is a Nameable, 224 // we use it in the Exception. 225 Nameable object = null; 226 227 if (_source instanceof Nameable) { 228 object = (Nameable) _source; 229 } 230 231 throw new InternalErrorException(object, _exception, 232 "ChangeRequest failed (NOTE: there is no " 233 + "ChangeListener):\n" + _description); 234 } 235 } 236 } finally { 237 _pending = false; 238 notifyAll(); 239 } 240 } 241 242 /** Get the description that was specified in the constructor. 243 * @return The description of the change. 244 * @see #setDescription(String) 245 */ 246 public String getDescription() { 247 return _description; 248 } 249 250 /** If a change is localized to a particular object and objects 251 * that it contains, then that object should be returned by 252 * this method. In this base class, this method returns null 253 * to indicate that the change is not localized (or at least, 254 * is not known to be localized). Derived classes that make 255 * changes that they know to be local should return non-null. 256 * @return Null to indicate that the change is not localized. 257 */ 258 public NamedObj getLocality() { 259 return null; 260 } 261 262 /** Get the source that was specified in the constructor. 263 * @return The source of the change. 264 */ 265 public Object getSource() { 266 return _source; 267 } 268 269 /** Return true if setErrorReported() has been called with a true 270 * argument. This is used by listeners to avoid reporting an 271 * error repeatedly. By convention, a listener that reports the 272 * error to the user, in a dialog box for example, should call 273 * this method to determine whether the error has already been 274 * reported. If it reports the error, then it should call 275 * setErrorReported() with a true argument. 276 * @return True if an error has already been reported. 277 */ 278 public boolean isErrorReported() { 279 return _errorReported; 280 } 281 282 /** Return false if the change represented by this request has been 283 * asserted to be non-persistent by calling setPersistent(false), 284 * and return true otherwise. That is, return false if the change 285 * has been asserted to not affect the 286 * MoML representation of the model. This might be used, for 287 * example, by a user interface, to determine whether to mark 288 * the model "modified," and hence, whether to prompt the user 289 * to save the model if a window is closed. 290 * <p> 291 * This method returns <i>true</i> unless setPersistent() 292 * has been called with an argument <i>false</i>. It is up 293 * to the creator of the change request to call that method 294 * to ensure that the change is not persistent. There is 295 * no automatic detection of whether the change is persistent. 296 * 297 * @see #setPersistent(boolean) 298 * @return True if the change represented by this request is 299 * persistent. 300 */ 301 public boolean isPersistent() { 302 return _persistent; 303 } 304 305 /** Return whether this change request is a structural change request. 306 * This is used to determine whether a complete repaint of the model 307 * is necessary. 308 * @return True this change request is a structural one. 309 */ 310 public boolean isStructuralChange() { 311 return _isStructuralChange; 312 } 313 314 /** Remove the given change listener from this request. 315 * The listener will no longer be 316 * notified when the change is executed, or the change fails. 317 * @param listener The listener to be removed. 318 * @see #addChangeListener(ChangeListener) 319 */ 320 public void removeChangeListener(ChangeListener listener) { 321 if (_localListeners != null) { 322 _localListeners.remove(listener); 323 } 324 } 325 326 /** Set the description. 327 * @param description The description. 328 * @since Ptolemy II 3.1 329 * @see #getDescription() 330 */ 331 public void setDescription(String description) { 332 _description = description; 333 } 334 335 /** Call with a true argument to indicate that an error has been 336 * reported to the user. This is used by listeners to avoid reporting an 337 * error repeatedly. By convention, a listener that reports the 338 * error to the user, in a dialog box for example, should call 339 * this method after reporting an error. It should call 340 * isErrorReported() to determine whether it is necessary to report 341 * the error. 342 * @param reported True if an error has been reported. 343 */ 344 public void setErrorReported(boolean reported) { 345 _errorReported = reported; 346 } 347 348 /** Specify a list of listeners to be notified when changes are 349 * successfully executed, or when an attempt to execute them results 350 * in an exception. The next time that execute() is called, all 351 * listeners on the specified list will be notified. 352 * This class has this method, in addition to the usual 353 * addChangeListener() and removeChangeListener() because it is 354 * assumed that the primary list of listeners is being maintained 355 * in another class, specifically the top-level named object in the 356 * hierarchy. The listeners set with this method are notified 357 * after the listeners that are attached directly to this object. 358 * <p> 359 * The class copies the given list, so that in the process of handling 360 * notifications from this class, more listeners can be added to 361 * the list of listeners in the top-level object. 362 * <p> 363 * Note that an alternative to using listeners is to call 364 * waitForCompletion(), although this may cause undesirable 365 * synchronization between the different threads. 366 * 367 * @param listeners A list of instances of ChangeListener or 368 * instances of WeakReference referring to instances of 369 * ChangeListener. 370 * @see ChangeListener 371 * @see NamedObj 372 */ 373 public void setListeners(List listeners) { 374 if (listeners != null) { 375 _listeners = new LinkedList(listeners); 376 } 377 } 378 379 /** Assert whether the change represented by this request is 380 * persistent. Call this method with argument <i>false</i> to 381 * assert that the change does not affect the MoML representation 382 * of the model. This might, for example, guide a user 383 * interface, to determine whether to mark the model "modified," 384 * and hence, whether to prompt the user to save the model if a 385 * window is closed. 386 * <p> 387 * It is up to the creator of the change request to call this 388 * method to assure that the change is not persistent. Calling 389 * this method with a <i>false</i> argument does not make the 390 * change non-persistent. It merely asserts that it is. There is 391 * no automatic detection of whether the change is persistent. 392 * By default, the change is assumed to be persistent, so unless 393 * this is called with argument <i>false</i>, a UI will likely 394 * mark the model modified upon execution of the change request. 395 * 396 * @see #isPersistent() 397 * @param persistent False to indicate that the change represented 398 * by this request is not persistent. 399 */ 400 public void setPersistent(boolean persistent) { 401 _persistent = persistent; 402 } 403 404 /** Wait for execution (or failure) of this change request. 405 * The calling thread is suspended until the execute() method 406 * completes. If an exception occurs processing the request, 407 * then this method will throw that exception. 408 * <p> 409 * Note that using this method may cause the model to deadlock 410 * and not be able to proceed. This is especially true if it 411 * is called from the Swing thread, and any actors in the 412 * model (such as plotters) wait for swing events. 413 * @exception Exception If the execution of the change request 414 * throws it. 415 */ 416 public final synchronized void waitForCompletion() throws Exception { 417 while (_pending) { 418 wait(); 419 } 420 421 if (_exception != null) { 422 // Note the use of fillInStackTrace, so that the exception 423 // appears to come from within the change request. 424 throw (Exception) _exception.fillInStackTrace(); 425 } 426 } 427 428 /////////////////////////////////////////////////////////////////// 429 //// protected methods //// 430 431 /** Execute the change. Derived classes must implement this method. 432 * Any exception may be thrown if the change fails. 433 * @exception Exception If the change fails. 434 */ 435 protected abstract void _execute() throws Exception; 436 437 /////////////////////////////////////////////////////////////////// 438 //// private variables //// 439 // A description of the change. 440 private String _description; 441 442 // A flag indicating whether the error has been reported. 443 private boolean _errorReported; 444 445 // The exception thrown by the most recent call to execute(), if any. 446 private Exception _exception; 447 448 // A flag indicating whether this change is a structural one. 449 private boolean _isStructuralChange; 450 451 // A list of listeners or weak references to listeners 452 // that are given in setListeners(). 453 private List _listeners; 454 455 // A list of listeners that are maintained locally. 456 private List<ChangeListener> _localListeners; 457 458 // A flag indicating that this request has not been executed yet. 459 private boolean _pending = true; 460 461 // A flag indicating that this change is persistent. 462 private boolean _persistent = true; 463 464 // The source of the change request. 465 private Object _source; 466}