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}