001/* An object for synchronization and version tracking of groups of objects.
002
003 Copyright (c) 1997-2018 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 Made _writer, _lastReader, _lastReaderRecord, and _readerRecords
028 transient so that object would be serializable. However, serialization
029 is probably not right if there are outstanding read or write permissions.
030 -- eal
031
032 */
033package ptolemy.kernel.util;
034
035import java.lang.ref.WeakReference;
036import java.util.Collections;
037import java.util.Enumeration;
038import java.util.Iterator;
039import java.util.LinkedList;
040import java.util.List;
041import java.util.WeakHashMap;
042
043///////////////////////////////////////////////////////////////////
044//// Workspace
045
046/**
047 An instance of Workspace is used for synchronization and version tracking
048 of interdependent groups of objects.  These objects are said to be in the
049 workspace. This is not the same as the <i>container</i> association
050 in Ptolemy II.  A workspace is never returned by a getContainer() method.
051 <p>
052 The workspace provides a rudimentary directory service that can
053 be used to keep track of the objects within it.  It is not required to use
054 it in order to use the workspace for synchronization. Items are added
055 to the directory by calling add().
056 The names of the items in the directory are not required to be unique.
057 <p>
058 The synchronization model of the workspace is a multiple-reader,
059 single-writer model. Any number of threads can simultaneously read the
060 workspace. Only one thread at a time can have write access to the workspace,
061 and while the write access is held, no other thread can get read access.
062 <p>
063 When reading the state of objects in the workspace, a thread must
064 ensure that no other thread is simultaneously modifying the objects in the
065 workspace. To read-synchronize on a workspace, use the following code:
066 <pre>
067 try {
068 _workspace.getReadAccess();
069 // ... code that reads
070 } finally {
071 _workspace.doneReading();
072 }
073 </pre>
074 We assume that the _workspace variable references the workspace, as for example
075 in the NamedObj class. The getReadAccess() method suspends the current thread
076 if another thread is currently modifying the workspace, and otherwise
077 returns immediately. Note that multiple readers can simultaneously have
078 read access. The finally clause is executed even if
079 an exception occurs.  This is essential because without the call
080 to doneReading(), the workspace will never again allow any thread
081 to modify it.
082 <p>
083 To make safe changes to the objects in a workspace, a thread must
084 write-synchronize using the following code:
085 <pre>
086 try {
087 _workspace.getWriteAccess();
088 // ... code that writes
089 } finally {
090 _workspace.doneWriting();
091 }
092 </pre>
093 Again, the call to doneWriting() is essential, or the workspace
094 will remain permanently locked to either reading or writing.
095 <p>
096 Note that it is not necessary to obtain a write lock just to add
097 an item to the workspace directory.  The methods for accessing
098 the directory are all synchronized, so there is no risk of any
099 thread reading an inconsistent state.
100 <p>
101 A major subtlety in using this class concerns acquiring a write
102 lock while holding a read lock. If a thread holds a read lock
103 and calls getWriteAccess(), if any other thread holds a read lock,
104 then the call to getWriteAccess() will block the calling thread
105 until those other read accesses are released. However, while
106 the thread is blocked, it yields its read permissions. This
107 prevents a deadlock from occurring, but it means that the
108 another thread may acquire write permission while the thread
109 is stalled and modify the model.  Specifically, the pattern
110 is:
111 <pre>
112     try {
113        _workspace.getReadAccess();
114        ... do things ...
115        try {
116           _workspace.getWriteAccess();
117           ... at this point, the structure of a model may have changed! ...
118           ... make my own changes knowing that the structure may have changed...
119        } finally {
120           _workspace.doneWriting();
121        }
122        ... continue doing things, knowing the model may have changed...
123     } finally {
124        _workspace.doneReading();
125     }
126  </pre>
127  Unfortunately, a user may acquire a read access and invoke a method
128  that, unknown to the user, acquires write access. For this reason, it
129  is very important to document methods that acquire write access, and
130  to avoid invoking them within blocks that hold read access. Note that
131  there is no difficulty acquiring read access from within a block
132  holding write access.
133
134 @author Edward A. Lee, Mudit Goel, Lukito Muliadi, Xiaojun Liu
135 @version $Id$
136 @since Ptolemy II 0.2
137 @Pt.ProposedRating Green (liuxj)
138 @Pt.AcceptedRating Green (liuxj)
139 */
140public final class Workspace implements Nameable {
141    // Note that Nameable extends ModelErrorHandler, so this class
142    // need not declare that it directly implements ModelErrorHandler.
143
144    // NOTE: it would make sense to have Workspace extend Observable
145    // in order to notify observers of changes (marten 29-10-2012).
146
147    /** Create a workspace with an empty string as its name.
148     */
149    public Workspace() {
150        super();
151        setName("");
152    }
153
154    /** Create a workspace with the specified name.  This name will form the
155     *  prefix of the full name of all contained objects. If the name
156     *  argument is null, then an empty string "" is used as the name.
157     *  @param name Name of the workspace.
158     */
159    public Workspace(String name) {
160        super();
161        setName(name);
162    }
163
164    ///////////////////////////////////////////////////////////////////
165    ////                         public methods                    ////
166
167    /** Add an item to the directory. The names of the objects
168     *  in the directory are not required to be unique.
169     *  Only items with no container can be added.  Items with
170     *  a container are still viewed as being within the workspace, but
171     *  they are not explicitly listed in the directory.  Instead,
172     *  their top-level container is expected to be listed (although this
173     *  is not enforced).  Increment the version number.
174     *  @param item Item to list in the directory.
175     *  @exception IllegalActionException If the item has a container, is
176     *   already in the directory, or is not in this workspace.
177     */
178    public synchronized void add(NamedObj item) throws IllegalActionException {
179        if (item.workspace() != this) {
180            throw new IllegalActionException(this, item,
181                    "Cannot add an item to the directory of a workspace "
182                            + "that it is not in.");
183        }
184
185        if (item.getContainer() != null) {
186            throw new IllegalActionException(this, item,
187                    "Cannot add an object with a container to a workspace "
188                            + "directory.");
189        }
190
191        if (_directory.indexOf(item) >= 0) {
192            throw new IllegalActionException(this, item,
193                    "Object is already listed in the workspace directory.");
194        }
195
196        _directory.add(item);
197        incrVersion();
198    }
199
200    /** Return a full description of the workspace and everything in its
201     *  directory.  This is accomplished
202     *  by calling the description method with an argument for full detail.
203     *  @return A description of the workspace.
204     *  @exception IllegalActionException If thrown while getting the
205     *  description of subcomponents.
206     */
207    @Override
208    public synchronized String description() throws IllegalActionException {
209        // NOTE: It is not strictly needed for this method to be
210        // synchronized, since _description is.  However, by making it
211        // synchronized, the documentation shows this on the public
212        // interface, not just the protected one.
213        return description(NamedObj.COMPLETE);
214    }
215
216    /** Return a description of the workspace. The level of detail depends
217     *  on the argument, which is an or-ing of the static final constants
218     *  defined in the NamedObj class.  This method returns an empty
219     *  string (not null) if there is nothing to report.  If the contents
220     *  are requested, then the items in the directory are also described.
221     *  @param detail The level of detail.
222     *  @return A description of the workspace.
223     *  @exception IllegalActionException If thrown while getting the
224     *  description of subcomponents.
225     */
226    public synchronized String description(int detail)
227            throws IllegalActionException {
228        // NOTE: It is not strictly needed for this method to be
229        // synchronized, since _description is.  However, by making it
230        // synchronized, the documentation shows this on the public
231        // interface, not just the protected one.
232        return _description(detail, 0, 0);
233    }
234
235    /** Enumerate the items in the directory, in the order in which
236     *  they were added.
237     *  @deprecated Use directoryList() instead.
238     *  @return An enumeration of NamedObj objects.
239     */
240    @Deprecated
241    public synchronized Enumeration directory() {
242        return Collections.enumeration(_directory);
243    }
244
245    /** Return an unmodifiable list of the items in the directory,
246     *  in the order in which they were added.
247     *  @return A list of instances of NamedObj.
248     */
249    public synchronized List<NamedObj> directoryList() {
250        return Collections.unmodifiableList(_directory);
251    }
252
253    /** Indicate that the calling thread is finished reading.
254     *  If this thread is completely done reading (it has no other
255     *  read access to the workspace), then notify all threads that are
256     *  waiting to get read/write access to this
257     *  workspace so that they may contend for access.
258     *
259     *  @exception InvalidStateException If this method is called
260     *   before a corresponding call to getReadAccess() by the same thread.
261     *  @see #getReadAccess()
262     */
263    public final synchronized void doneReading() {
264        Thread current = Thread.currentThread();
265        AccessRecord record = null;
266
267        record = _lastReaderRecordOrCurrentAccessRecord(current, false);
268
269        if (record == null) {
270            throw new InvalidStateException(this,
271                    "Workspace: doneReading() called without a prior "
272                            + "matching call to getReadAccess()!");
273        }
274
275        if (record.readDepth > 0) {
276            record.readDepth--;
277
278            if (record.readDepth == 0) {
279                // the current thread is no longer a reader
280                _numReaders--;
281
282                // notify waiting writers
283                // these writers may have read access, so this notification
284                // cannot be conditioned on _numReaders == 0
285                // possible condition: _writeReq >= _numReaders
286                notifyAll();
287            }
288        } else if (record.failedReadAttempts > 0) {
289            record.failedReadAttempts--;
290        } else {
291            throw new InvalidStateException(this,
292                    "Workspace: doneReading() called without a prior "
293                            + "matching call to getReadAccess()!");
294        }
295    }
296
297    /** Indicate that the calling thread is finished writing.
298     *  If this thread is completely done writing (it has no other
299     *  write access to the workspace), then notify all threads
300     *  that are waiting to get read/write access to this workspace
301     *  so that they may contend for access.
302     *  This method <b>does not</b> increment the version number
303     *  of the workspace.  This method is used to temporarily add
304     *  an attribute to the workspace without increment the version number,
305     *  or otherwise to gain exclusive access to the workspace without
306     *  changing the structure of the model.
307     *  @exception InvalidStateException If this method is called before
308     *   a corresponding call to getWriteAccess() by the same thread.
309     *  @see ptolemy.kernel.util.Attribute#Attribute(NamedObj, String, boolean)
310     *  @see #doneWriting()
311     */
312    public final synchronized void doneTemporaryWriting() {
313        _doneWriting(false);
314    }
315
316    /** Indicate that the calling thread is finished writing.
317     *  If this thread is completely done writing (it has no other
318     *  write access to the workspace), then notify all threads
319     *  that are waiting to get read/write access to this workspace
320     *  so that they may contend for access.
321     *  It also increments the version number of the workspace.
322     *  @exception InvalidStateException If this method is called before
323     *   a corresponding call to getWriteAccess() by the same thread.
324     */
325    public final synchronized void doneWriting() {
326        _doneWriting(true);
327    }
328
329    /** Get the container.  Always return null since a workspace
330     *  has no container.
331     *  @return null.
332     */
333    @Override
334    public NamedObj getContainer() {
335        return null;
336    }
337
338    /** Return a name to present to the user, which is the
339     *  same as what is returned by getName().
340     *  @return The name.
341     *  @see #getName()
342     */
343    @Override
344    public String getDisplayName() {
345        return getName();
346    }
347
348    /** Get the full name.
349     *  @return The name of the workspace.
350     */
351    @Override
352    public String getFullName() {
353        return _name;
354    }
355
356    /** Get the name.
357     *  @return The name of the workspace.
358     *  @see #setName(String)
359     */
360    @Override
361    public String getName() {
362        return _name;
363    }
364
365    /** Get the name. Since this can have no container, the relative
366     *  name is always the same as the name.
367     *  @param relativeTo This argument is ignored.
368     *  @return The name of the workspace.
369     *  @see #setName(String)
370     */
371    @Override
372    public String getName(NamedObj relativeTo) {
373        return _name;
374    }
375
376    /** Obtain permission to read objects in the workspace.
377     *  This method suspends the calling thread until read access
378     *  has been obtained. Read access is granted unless either another
379     *  thread has write access, or there are threads that
380     *  have requested write access and not gotten it yet. If this thread
381     *  already has read access, then access is granted irrespective of
382     *  other write requests.
383     *  If the calling thread is interrupted while waiting to get read
384     *  access, an InternalErrorException is thrown, and the thread does
385     *  not have read permission to the workspace.
386     *  It is essential that a call to this method is matched by a call to
387     *  doneReading(), regardless of whether this method returns normally or
388     *  an exception is thrown. This is to ensure that the workspace is in a
389     *  consistent state, otherwise write access may never again be
390     *  granted in this workspace.
391     *  If while holding read access the thread attempts to get write access
392     *  and is blocked, then upon attempting to get write access, the read
393     *  lock is released, and it is not reacquired until the write access
394     *  is granted. Therefore, upon any call to any method that gets write
395     *  access within a block holding read access, the model structure can
396     *  change. You should not assume that the model is the same prior to
397     *  the call as after.
398     *  @exception InternalErrorException If the calling thread is interrupted
399     *   while waiting to get read access.
400     *  @see #doneReading()
401     */
402    public final synchronized void getReadAccess() {
403        // This method should throw an InterruptedException when the
404        // calling thread is interrupted. InterruptedException is a
405        // checked exception, so changing this will lead to changes
406        // everywhere this method is called, which is a huge amount
407        // of work.
408        Thread current = Thread.currentThread();
409        AccessRecord record = _lastReaderRecordOrCurrentAccessRecord(current,
410                true);
411
412        if (record.readDepth > 0) {
413            // If the current thread has read permission, then grant
414            // it read permission
415            record.readDepth++;
416            return;
417        } else if (current == _writer) {
418            record.readDepth++;
419
420            // The current thread has write permission, so we grant
421            // read permission.
422            // This is a new reader, so we increment the number
423            // of readers.
424            _numReaders++;
425            return;
426        }
427
428        // Possibly need to wait for read access.
429        // First increment this to make the record not empty, so as to
430        // prevent the record from being deleted from the _readerRecords
431        // table by other threads.
432        record.failedReadAttempts++;
433
434        // Go into a loop, and at each iteration check whether the current
435        // thread can get read access. If not then do a wait() on the
436        // workspace. Otherwise, exit the loop.
437        while (_waitingWriteRequests != 0 || _writer != null) {
438            try {
439                wait();
440            } catch (InterruptedException ex) {
441                /* Throwing an exception is not correct here.
442                 * CompositeActor may interrupt this thread upon a change request.
443                 * We should just continue waiting.
444                 *
445                throw new InternalErrorException(this, ex,
446                        current.getName()
447                                + ": Thread interrupted while waiting to get "
448                                + "read access: " + ex.getMessage());
449                 */
450            }
451        }
452
453        record.failedReadAttempts--;
454
455        // Now there is no writer, and no thread waiting to get write access.
456        record.readDepth++;
457
458        // This is a new reader, so we increment the number
459        // of readers.
460        _numReaders++;
461        return;
462    }
463
464    /** Get the version number.  The version number is incremented on
465     *  each call to doneWriting() and also on calls to incrVersion().
466     *  It is meant to track changes to the objects in the workspace.
467     *  @return A non-negative long integer.
468     */
469    public synchronized final long getVersion() {
470        return _version;
471    }
472
473    /** Obtain permission to write to objects in the workspace.
474     *  Write access is granted if there are no other threads that currently
475     *  have read or write access.  In particular, it <i>is</i> granted
476     *  if this thread already has write access, or if it is the only
477     *  thread with read access. Note that if this method blocks, a side
478     *  effect is that other threads will block when trying to acquire read
479     *  access so as to ensure that write access will eventually be granted.
480     *  This makes it very risky to hold any locks while trying to acquire
481     *  read or write access.
482     *  <p>
483     *  This method suspends the calling thread until such access
484     *  has been obtained.
485     *  If the calling thread is interrupted while waiting to get write
486     *  access, an InternalErrorException is thrown, and the thread does
487     *  not have write permission to the workspace.
488     *  It is essential that a call to this method is matched by a call to
489     *  doneWriting(), regardless of whether this method returns normally or
490     *  an exception is thrown. This is to ensure that the workspace is in a
491     *  consistent state, otherwise read or write access may never again
492     *  be granted in this workspace.
493     *  @exception InternalErrorException If the calling thread is interrupted
494     *   while waiting to get write access.
495     *  @see #doneWriting()
496     */
497    public final synchronized void getWriteAccess() {
498        // This method should throw an InterruptedException when the
499        // calling thread is interrupted. InterruptedException is a
500        // checked exception, so changing this will lead to changes
501        // everywhere this method is called, which is a huge amount
502        // of work.
503        Thread current = Thread.currentThread();
504
505        if (current == _writer) {
506            // Already have write permission.
507            _writeDepth++;
508            return;
509        }
510
511        AccessRecord record = _lastReaderRecordOrCurrentAccessRecord(current,
512                true);
513
514        // Probably need to wait for write access.
515        // First increment this to make the record not empty, so as to
516        // prevent the record from being deleted from the _readerRecords
517        // table by other threads.
518        record.failedWriteAttempts++;
519
520        // Go into an infinite 'while (true)' loop and check if this thread
521        // can get a write access. If yes, then return, if not then perform
522        // a wait() on the workspace.
523        while (true) {
524            if (_writer == null) {
525                // There are no writers. Are there any readers?
526                if (_numReaders == 0
527                        || _numReaders == 1 && record.readDepth > 0) {
528                    // No readers
529                    // or the only reader is the current thread
530                    _writer = current;
531                    _writeDepth = 1;
532                    record.failedWriteAttempts--;
533                    return;
534                }
535            }
536            int depth = 0;
537            try {
538                // If there is already another waiting write request, then
539                // we have to release any read permissions that we hold or a
540                // deadlock could occur. There is no need to release those
541                // read permissions if this is the only pending write
542                // request. If another write request shows up during the
543                // wait(), then in effect this first arrived write request
544                // will have priority because it holds a read permission.
545                // If the other thread also holds a read permission, it
546                // will have to release it upon issuing the write request.
547                // Thus, the subtlety described in the class comment
548                // will only occur if the second thread to attempt a
549                // write access has the problematic
550                // pattern of acquiring write requests inside of read
551                // permission blocks. This doesn't eliminate the problems
552                // associated with this subtlety, it just makes it
553                // somewhat less likely that they will occur.
554                if (_waitingWriteRequests > 0) {
555                    // Bert Rodier suggests that we should throw an
556                    // exception in this circumstance instead of just
557                    // releasing read requests.  This would have the
558                    // advantage that the problematic cases cited in
559                    // class comment may be identified (but only if
560                    // the exception is actually thrown, which depends
561                    // on an accident of thread scheduling). I would
562                    // prefer to use static analysis to identify cases
563                    // where write permissions are accessed within
564                    // read permission blocks, and analyze those
565                    // cases for correct usage. EAL 11/12/08.
566                    depth = _releaseAllReadPermissions();
567                }
568                _waitingWriteRequests++;
569                wait();
570            } catch (InterruptedException ex) {
571                throw new InternalErrorException(this, ex,
572                        current.getName()
573                                + ": Thread interrupted while waiting to get "
574                                + "write access: " + ex.getMessage());
575            } finally {
576                _waitingWriteRequests--;
577                if (depth > 0) {
578                    _reacquireReadPermissions(depth);
579                }
580            }
581        }
582    }
583
584    /** Handle a model error by throwing the specified exception.
585     *  @param context The object in which the error occurred.
586     *  @param exception An exception that represents the error.
587     *  @return Never returns.
588     *  @exception IllegalActionException The exception passed
589     *   as an argument is always thrown.
590     *  @since Ptolemy II 2.1
591     */
592    public boolean handleModelError(NamedObj context,
593            IllegalActionException exception) throws IllegalActionException {
594        throw exception;
595    }
596
597    /** Increment the version number by one.
598     */
599    public final synchronized void incrVersion() {
600        _version++;
601    }
602
603    /** Reacquire read permission on the workspace for
604     *  the current thread. Call this after a call to
605     *  releaseReadPermissions().
606     *  @param depth The depth of the permissions to reacquire.
607     *  @see #releaseReadPermission()
608     */
609    public void reacquireReadPermission(int depth) {
610        _reacquireReadPermissions(depth);
611    }
612
613    /** Release read permission on the workspace
614     *  held by the current thread, and return the depth of the
615     *  nested calls to getReadAccess(). It is essential that
616     *  after calling this, you also call
617     *  reacquireReadPermission(int), passing it as an argument
618     *  the value returned by this method.  Hence, you should
619     *  use this as follows:
620     *  <pre>
621     *    int depth = releaseReadPermission();
622     *    try {
623     *       ... do whatever here ...
624     *    } finally {
625     *       reacquireReadPermission(depth);
626     *    }
627     *  </pre>
628     *  Note that this is done automatically by the
629     *  wait(Object) method, so if you can use that instead,
630     *  please do.
631     *  @return The depth of read permissions held by the current
632     *   thread.
633     *  @see #reacquireReadPermission(int)
634     *  @see #wait(Object)
635     *  @see #wait(Object, long)
636     */
637    public synchronized int releaseReadPermission() {
638        return _releaseAllReadPermissions();
639    }
640
641    /** Remove the specified item from the directory.
642     *  Note that that item will still refer to this workspace as
643     *  its workspace (its workspace is immutable).  If the object is
644     *  not in the directory, do nothing.
645     *  Increment the version number.
646     *  @param item The NamedObj to be removed.
647     */
648    public synchronized void remove(NamedObj item) {
649        _directory.remove(item);
650        incrVersion();
651    }
652
653    /** Remove all items from the directory.
654     *  Note that those items will still refer to this workspace as
655     *  their workspace (their workspace is immutable).
656     *  Increment the version number.
657     */
658    public synchronized void removeAll() {
659        _directory.clear();
660        incrVersion();
661    }
662
663    /** Set or change the name.  If a null argument is given the
664     *  name is set to an empty string.
665     *  Increment the version number.
666     *  @param name The new name.
667     *  @see #getName()
668     */
669    @Override
670    public synchronized void setName(String name) {
671        if (name == null) {
672            name = "";
673        }
674
675        _name = name;
676        incrVersion();
677    }
678
679    /** Return a concise description of the object.
680     *  @return The class name and name.
681     */
682    @Override
683    public String toString() {
684        return getClass().getName() + " {" + getFullName() + "}";
685    }
686
687    /** Release all the read accesses held by the current thread and suspend
688     *  the thread by calling Object.wait() on the specified object. When the
689     *  call returns, re-acquire all the read accesses held earlier by the
690     *  thread and return.
691     *  If the calling thread is interrupted while waiting to re-acquire read
692     *  accesses, an InternalErrorException is thrown, and the thread no longer
693     *  has read access to the workspace.
694     *  This method helps prevent deadlocks caused when a thread that
695     *  waits for another thread to do something prevents it from doing
696     *  that something by holding read access on the workspace.
697     *  <b>IMPORTANT</b>: The calling thread should <i>not</i> hold a lock
698     *  on <i>obj</i> when calling this method, unlike a direct call to
699     *  <i>obj</i>.wait().  Holding such a lock can lead to deadlock because
700     *  this method can block for an indeterminate amount of time while trying
701     *  to reacquire read permissions that it releases. Moreover, holding such
702     *  a lock is pointless since this method internally calls
703     *  <i>obj</i>.wait() (within its own synchronized(<i>obj</i>) block,
704     *  so the calling method cannot assume that the lock on <i>obj</i> was
705     *  held during the entire execution of this method.
706     *  If the calling thread needs to hold a lock on <i>obj</i> until
707     *  <i>obj</i>.wait() is called, then you should manually release
708     *  read permissions and release the <i>obj</i> before reacquiring them,
709     *  as follows:
710     *
711     *  <pre>
712     *    int depth = 0;
713     *    try {
714     *       synchronized(obj) {
715     *           ...
716     *           depth = releaseReadPermission();
717     *           obj.wait();
718     *        }
719     *    } finally {
720     *       if (depth &gt; 0) {
721     *          reacquireReadPermission(depth);
722     *       }
723     *    }
724     *  </pre>
725     *
726     *  @param obj The object that the thread wants to wait on.
727     *  @exception InterruptedException If the calling thread is interrupted
728     *   while waiting on the specified object and all the read accesses held
729     *   earlier by the thread are re-acquired.
730     *  @exception InternalErrorException If re-acquiring the read accesses
731     *   held earlier by the thread fails.
732     */
733    public void wait(Object obj) throws InterruptedException {
734        int depth = 0;
735        depth = _releaseAllReadPermissions();
736
737        try {
738            synchronized (obj) {
739                obj.wait();
740            }
741        } finally {
742            // NOTE: If obj != this, and this method is called
743            // inside a synchronized(obj) block, then the following
744            // call can lead to deadlock because it does a wait() on
745            // this workspace. It has to, since notification that will
746            // free the reacquire occurs on the workspace. But it holds
747            // a lock on obj during that wait. Meanwhile, another thread
748            // with write permission may attempt to acquire a lock on obj.
749            // Deadlock. Hence the warning in the method comment.
750            _reacquireReadPermissions(depth);
751        }
752    }
753
754    /** This method is equivalent to the single argument version except that
755     *  you can specify a timeout, which is in milliseconds. If value of the
756     *  timeout argument is zero, then the method is exactly equivalent
757     *  to the single argument version, and no timeout is implemented. If
758     *  the value is larger than zero, then the method returns if either
759     *  the thread is notified by another thread or the timeout expires.
760     *  <b>IMPORTANT</b>: The calling thread should <i>not</i> hold a lock
761     *  on <i>obj</i> when calling this method, unlike a direct call to
762     *  <i>obj</i>.wait().  Holding such a lock can lead to deadlock because
763     *  this method can block for an indeterminate amount of time while trying
764     *  to reacquire read permissions that it releases. Moreover, holding such
765     *  a lock is pointless since this method internally calls
766     *  <i>obj</i>.wait() (within its own synchronized(<i>obj</i>) block,
767     *  so the calling method cannot assume that the lock on <i>obj</i> was
768     *  held during the entire execution of this method.
769     *  @param obj The object that the thread wants to wait on.
770     *  @param timeout The maximum amount of time to wait, in milliseconds,
771     *   or zero to not specify a timeout.
772     *  @exception InterruptedException If the calling thread is interrupted
773     *   while waiting on the specified object and all the read accesses held
774     *   earlier by the thread are re-acquired.
775     *  @exception InternalErrorException If re-acquiring the read accesses
776     *   held earlier by the thread fails.
777     *  @see #wait(Object)
778     */
779    public void wait(Object obj, long timeout) throws InterruptedException {
780        int depth = 0;
781        depth = _releaseAllReadPermissions();
782
783        try {
784            synchronized (obj) {
785                obj.wait(timeout);
786            }
787        } finally {
788            _reacquireReadPermissions(depth);
789        }
790    }
791
792    ///////////////////////////////////////////////////////////////////
793    ////                         protected methods                 ////
794
795    /** Return a description of the workspace.  The level of detail depends
796     *  on the argument, which is an or-ing of the static final constants
797     *  defined in the NamedObj class.  If the contents are requested,
798     *  then the items in the directory are also described.
799     *  Zero, one or two brackets can be specified to surround the returned
800     *  description.  If one is specified it is the the leading bracket.
801     *  This is used by derived classes that will append to the description.
802     *  Those derived classes are responsible for the closing bracket.
803     *  An argument other than 0, 1, or 2 is taken to be equivalent to 0.
804     *  @param detail The level of detail.
805     *  @param indent The amount of indenting.
806     *  @param bracket The number of surrounding brackets (0, 1, or 2).
807     *  @return A description of the workspace.
808     *  @exception IllegalActionException If thrown while getting the
809     *  description of subcomponents.
810     */
811    protected synchronized String _description(int detail, int indent,
812            int bracket) throws IllegalActionException {
813        StringBuffer result = new StringBuffer(
814                NamedObj._getIndentPrefix(indent));
815
816        if (bracket == 1 || bracket == 2) {
817            result.append("{");
818        }
819
820        if ((detail & NamedObj.CLASSNAME) != 0) {
821            result.append(getClass().getName());
822
823            if ((detail & NamedObj.FULLNAME) != 0) {
824                result.append(" ");
825            }
826        }
827
828        if ((detail & NamedObj.FULLNAME) != 0) {
829            result.append("{" + getFullName() + "}");
830        }
831
832        if ((detail & NamedObj.CONTENTS) != 0) {
833            if ((detail & (NamedObj.CLASSNAME | NamedObj.FULLNAME)) != 0) {
834                result.append(" ");
835            }
836
837            result.append("directory {\n");
838
839            List<NamedObj> directoryList = directoryList();
840            for (NamedObj obj : directoryList) {
841
842                // If deep is not set, then zero-out the contents flag
843                // for the next round.
844                if ((detail & NamedObj.DEEP) == 0) {
845                    detail &= ~NamedObj.CONTENTS;
846                }
847
848                String description = obj._description(detail, indent + 1, 2)
849                        + "\n";
850                result.append(description);
851            }
852
853            result.append("}");
854        }
855
856        if (bracket == 2) {
857            result.append("}");
858        }
859
860        return result.toString();
861    }
862
863    ///////////////////////////////////////////////////////////////////
864    ////                         private methods                   ////
865
866    /** Indicate that the calling thread is finished writing.
867     *  If this thread is completely done writing (it has no other
868     *  write access to the workspace), then notify all threads
869     *  that are waiting to get read/write access to this workspace
870     *  so that they may contend for access.
871     *  It also increments the version number of the workspace
872     *  if the incrementWorkspaceVersion parameter is true.
873     *  @param incrementWorkspaceVersion True if we should increment
874     *  the version.  The incrementWorkspaceVersion parameter should
875     *  almost always be true, if it is set to false, then perhaps a
876     *  temporary variable is being added.
877     *  @exception InvalidStateException If this method is called before
878     *   a corresponding call to getWriteAccess() by the same thread.
879     */
880    private final synchronized void _doneWriting(
881            boolean incrementWorkspaceVersion) {
882        Thread current = Thread.currentThread();
883        AccessRecord record = _lastReaderRecordOrCurrentAccessRecord(current,
884                false);
885
886        if (incrementWorkspaceVersion) {
887            incrVersion();
888        }
889
890        if (current != _writer) {
891            if (record != null && record.failedWriteAttempts > 0) {
892                record.failedWriteAttempts--;
893            } else {
894                throw new InvalidStateException(this,
895                        "Workspace: doneWriting called without a prior "
896                                + "matching call to getWriteAccess().");
897            }
898        } else {
899            if (_writeDepth > 0) {
900                _writeDepth--;
901
902                if (_writeDepth == 0) {
903                    _writer = null;
904                    notifyAll();
905                }
906            } else {
907                throw new InvalidStateException(this,
908                        "Workspace: doneWriting called without a prior "
909                                + "matching call to getWriteAccess().");
910            }
911        }
912    }
913
914    /** If the thread is the same as the _lastReader, then return
915     *  the _lastReaderRecord.  Otherwise, get the AccessRecord for the thread.
916     *  @param thread The thread to check against _lastReader
917     *  @param createNew True if a new thread is to be created.
918     *  @return The AccessRecord
919     */
920    private final AccessRecord _lastReaderRecordOrCurrentAccessRecord(
921            Thread thread, boolean createNew) {
922        AccessRecord record = null;
923        // This is a separate method so as to avoid code duplication.
924        if (_lastReader != null && thread == _lastReader.get()) {
925            record = _lastReaderRecord;
926        } else {
927            record = _getAccessRecord(thread, createNew);
928        }
929        return record;
930    }
931
932    /** Return the AccessRecord object for the specified thread.
933     *  If the flag createNew is true and the current thread does not
934     *  have an access record, then create a new one and return it.
935     *  Set _lastReaderRecord to be the record returned.
936     */
937    private final AccessRecord _getAccessRecord(Thread current,
938            boolean createNew) {
939        //System.out.println("-- look up access record for "
940        //   + current.getName());
941        // If this object has been serialized and deserialized, then
942        // _readerRecords could be null.
943        if (_readerRecords == null) {
944            _readerRecords = new WeakHashMap<Thread, AccessRecord>();
945        }
946
947        AccessRecord record = _readerRecords.get(current);
948
949        if (record == null) {
950            // delete any record that contains no history information
951            // AND is not the last reader's record
952            Iterator records = _readerRecords.values().iterator();
953            while (records.hasNext()) {
954                AccessRecord aRecord = (AccessRecord) records.next();
955
956                if (aRecord.failedReadAttempts == 0
957                        && aRecord.failedWriteAttempts == 0
958                        && aRecord.readDepth == 0
959                        && aRecord != _lastReaderRecord) {
960                    //System.out.println("-- delete record for thread "
961                    //        + aRecord.thread
962                    //        + " in " + current.getName());
963                    records.remove();
964                }
965            }
966
967            if (createNew) {
968                record = new AccessRecord();
969                _readerRecords.put(current, record);
970            }
971        }
972
973        if (record != null) {
974            _lastReader = new WeakReference<Thread>(current);
975            _lastReaderRecord = record;
976        }
977
978        return record;
979    }
980
981    // Obtain permissions to read objects in the workspace. This obtains
982    // many permissions on the read access and should be called in
983    // conjunction with _releaseAllReadPermissions.
984    // This method suspends the calling thread until such permission
985    // has been obtained.  Permission is granted unless either another
986    // thread has write permission, or there are threads that
987    // have requested write permission and not gotten it yet.
988    // @param count This is the number of read permissions desired on the
989    //  workspace.
990    // @exception InternalErrorException If the calling thread is interrupted
991    //  while waiting to re-acquire read permissions.
992    private synchronized void _reacquireReadPermissions(int count) {
993        // If the count argument is equal to zero, which means we would like
994        // the current thread to has read depth equal to 0, i.e. not a reader,
995        // then it's already trivially done, since this method call is always
996        // preceded by _releaseAllReadPermissions.
997        if (count == 0) {
998            return;
999        }
1000
1001        Thread current = Thread.currentThread();
1002        AccessRecord record = _lastReaderRecordOrCurrentAccessRecord(current,
1003                false);
1004
1005        if (record == null || count > record.failedReadAttempts) {
1006            throw new InvalidStateException(this,
1007                    "Trying to reacquire " + "read permission not in record.");
1008        }
1009
1010        // Go into an infinite 'while (true)' loop, and each time through
1011        // the loop, check if the condition is satisfied to have the current
1012        // thread as a writer. If not, then wait on the workspace. Upon
1013        // re-awakening, iterate in the loop again to check if the condition
1014        // is now satisfied.
1015        while (true) {
1016            // If the current thread has write permission, or if there
1017            // are no pending write requests, then grant read permission.
1018            if (current == _writer
1019                    || _waitingWriteRequests == 0 && _writer == null) {
1020                _numReaders++;
1021                record.failedReadAttempts -= count;
1022                record.readDepth = count;
1023                return;
1024            }
1025
1026            try {
1027                wait();
1028            } catch (InterruptedException ex) {
1029                throw new InternalErrorException(this, ex,
1030                        current.getName()
1031                                + ": Thread interrupted while waiting to get "
1032                                + " read access: " + ex.getMessage());
1033            }
1034        }
1035    }
1036
1037    /** Frees the thread of all the readAccesses on the workspace
1038     *  held by the current thread. The method
1039     *  _reacquireAllReadAccesses should be called after this method is
1040     *  called.
1041     *  @return The number of readAccess that the thread possessed on the
1042     *  workspace
1043     */
1044    private synchronized int _releaseAllReadPermissions() {
1045        // Find the current thread.
1046        AccessRecord record = _lastReaderRecordOrCurrentAccessRecord(
1047                Thread.currentThread(), false);
1048
1049        if (record == null || record.readDepth == 0) {
1050            // current thread is not a reader
1051            return 0;
1052        } else {
1053            _numReaders--;
1054            notifyAll();
1055
1056            int result = record.readDepth;
1057            record.failedReadAttempts += result;
1058            record.readDepth = 0;
1059            return result;
1060        }
1061    }
1062
1063    ///////////////////////////////////////////////////////////////////
1064    ////                         private variables                 ////
1065
1066    /** @serial List of contained objects. */
1067    private LinkedList _directory = new LinkedList();
1068
1069    /** @serial The name. */
1070    private String _name;
1071
1072    /** @serial Version number. */
1073    private long _version = 0;
1074
1075    /** @serial The currently writing thread (if any). */
1076    private transient Thread _writer;
1077
1078    /** @serial The number of pending write requests.
1079     */
1080    private int _waitingWriteRequests = 0;
1081
1082    /** @serial The number of active write permissions
1083     *  (all to the same thread).
1084     */
1085    private int _writeDepth = 0;
1086
1087    /** @serial The last thread that acquires/releases read permission.
1088     */
1089    private transient WeakReference<Thread> _lastReader = null;
1090
1091    private transient AccessRecord _lastReaderRecord = null;
1092
1093    /** A WeakHashMap of threads to AccessRecords.
1094     *  A WeakHashMap is used because otherwise getting the effigy
1095     *  adds Manager._thread to the Map and if it is a regular
1096     *  HashMap, then when the window containing the model is closed,
1097     *  a reference to the Manager will remain.
1098     */
1099    private transient WeakHashMap<Thread, AccessRecord> _readerRecords = new WeakHashMap<Thread, AccessRecord>();
1100
1101    /** @serial The number of readers.
1102     *  The use of this field is to increment it every time we have a new
1103     *  reader and decrement it whenever a reader relinquishes ALL its read
1104     *  access.
1105     */
1106    private long _numReaders = 0;
1107
1108    ///////////////////////////////////////////////////////////////////
1109    ////                         inner classes                     ////
1110
1111    private static final class AccessRecord {
1112
1113        // FindBugs suggested making this class a static inner class:
1114        //
1115        // "This class is an inner class, but does not use its embedded
1116        // reference to the object which created it. This reference makes
1117        // the instances of the class larger, and may keep the reference
1118        // to the creator object alive longer than necessary. If
1119        // possible, the class should be made into a static inner class."
1120
1121        // the number of failed calls to getReadAccess() performed
1122        // by a thread and not yet matched by a call to doneReading()
1123        public int failedReadAttempts = 0;
1124
1125        // the number of failed calls to getWriteAccess() performed
1126        // by a thread and not yet matched by a call to doneWriting()
1127        public int failedWriteAttempts = 0;
1128
1129        // the number of successful calls to getReadAccess() performed
1130        // by a thread and not yet matched by a call to doneReading()
1131        public int readDepth = 0;
1132
1133        //public Thread thread = null;
1134        //public boolean inUse;
1135    }
1136}