You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@river.apache.org by ni...@apache.org on 2008/12/10 06:13:55 UTC

svn commit: r724979 [4/14] - in /incubator/river/jtsk/skunk/niclas1/services/outrigger: ./ src/ src/main/ src/main/java/ src/main/java/com/ src/main/java/com/sun/ src/main/java/com/sun/jini/ src/main/java/com/sun/jini/outrigger/ src/main/java/com/sun/j...

Added: incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/FastList.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/FastList.java?rev=724979&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/FastList.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/FastList.java Tue Dec  9 21:13:53 2008
@@ -0,0 +1,1083 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.sun.jini.outrigger;
+
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * This class provides a low-synchronization list of objects.  It is
+ * designed to allow proper traversal of the list while objects are
+ * being added and/or removed, and to allow simultaneous adds and
+ * removals.  Traversals must be done in a single thread; no fair handing
+ * off a <code>Node</code> pointer to another thread to continue the
+ * traversal!  A single thread cannot perform two or more simultaneous
+ * traversals, even if traversing different lists.
+ * <p>
+ * All this means that users of the list will occasionally see a
+ * removed node in the list, because it may be marked removed after it
+ * has been reached while traversing the list.  This will be rare, but
+ * it is up to the user of the list to skip over nodes that have been
+ * removed if necessary, and to handle or avoid attempted re-removal of
+ * a node.  This is why <code>remove</code> returns a boolean -- if the
+ * node has already been removed by the time the attempt to remove it
+ * happens, <code>remove</code> will return <code>false</code>.  This
+ * is done synchronizing on the node itself.  A typical piece of code
+ * to remove would look like this:
+ *
+ * <pre>
+ *  public synchronized Value findMatch(Value matchFor) {
+ *      for (Value val = list.head(); val != null; val = val.next()) {
+ *          if (matchFor.matches(val)) {
+ *              if (!list.remove(n))   // oh, well -- someone got to it first
+ *                  continue;          // keep looking
+ *              return val;            // it's ours to take!
+ *          }
+ *      }
+ *      return null;
+ *  }
+ * </pre>
+ *
+ * This code assumes that getting a removed node will be rare enough
+ * that there is no point in testing for it until a match is found.
+ * If testing for a match was expensive, one might
+ * <code>synchronize</code> on the node object and test it for being
+ * removed and then try for matching, thus preventing any other
+ * thread from attempting a match at the same time.  In any case, the
+ * <code>remove</code> test is itself atomic -- either it succeeds, or
+ * it fails because someone got there first.
+ *
+ * <p>
+ * <i>The Java(TM) Language Specification<i> details a memory model based on
+ * local and global memories, apparently intended as a model for
+ * multiprocessor caches.  The specified memory model should be
+ * supplemented by William Pugh's {@link
+ * http://www.cs.umd.edu/~pugh/java/memoryModel/ work} on the Java
+ * programming language memory model and JSR 133.
+ * <p>
+ * In particular, the <code>volatile</code> keyword offers no practical
+ * guarantees.  The lock and unlock operations underlying the
+ * <code>synchronized</code> language construct are the primary mechanisms
+ * for communicating between threads.  A lock operation forces stale values
+ * to be flushed from the locking thread's cache: any values subsequently
+ * read by the thread are either read from main memory or are cached values
+ * no older than the time the lock was acquired.  An unlock operation forces
+ * dirty cache entries to be written to main memory: any values written by
+ * the thread up to then must be force-written to main memory before the
+ * unlock can complete.
+ * <p>
+ * In every other respect, access to shared memory by threads is
+ * arbitrary.  Updates can happen at any time and in any order.  In
+ * particular, if one thread initializes a new Node and adds it to the end
+ * of the list, it's possible for a different thread to see the pointer to
+ * the new Node but read uninitialized (stale) values for the fields of the
+ * new Node (including, ominously, the instance's <code>vptr</code>),
+ * because writes can be seen in a different order by another thread.  Even
+ * on a uniprocessor, compiler optimizations can cause similar surprises.
+ * <p>
+ * The removal of a node is decoupled from the unlinking of that node from
+ * the list.  The <code>next</code> method unlinks some nodes that have been
+ * removed; with reaping, the number of nodes which remain in a list for a
+ * long time will be less than the number of threads in the VM.
+ * <p>
+ * No node can be a member of more than one list.  Nodes which have been
+ * added to a list can never be added to another list, even if they are
+ * removed from the first list.
+ * <p>
+ * Never synchronize on an instance of this class - it could lead to
+ * deadlock. (See the paragraph on locking order below for explanation.)
+ * <p>
+ * You can synchronize on an instance of a <code>Node</code> subclass
+ * provided that the synchronizing thread does not already hold a lock on
+ * another <code>Node</code> instance taken from the same
+ * <code>FastList</code>. Note, many of the methods on <code>FastList</code>
+ * synchronize on <code>Node</code>s in the
+ * list, thus you should not call one of these methods if you
+ * already hold a lock on a <code>Node</code> in the list. Similarly
+ * for methods on <code>Node</code>.
+ * <p>
+ * The remainder of this comment discusses the internal structure
+ * and assumptions of this class.
+ * <p>
+ * The design is to have a list of <code>FastList.Node</code> nodes,
+ * each of which has a <code>next</code> reference, a <code>removed</code>
+ * flag, and a <code>guardSet</code> (indicating which traversals
+ * depend on a future synchronization on that node).
+ * <p>
+ * There is a special sublist which is important to assuring the integrity
+ * of this data structure.  The chain of nodes starting at <code>head</code>
+ * and continuing to the <code>tail</code> via <code>Node.next</code>
+ * references is called the <dfn>main sequence</dfn>.  The fundamental
+ * constraints on the main sequence are: <ul>
+ * <li> The <code>tail</code> is always reachable from the <code>head</code>.
+ * <li> Every <code>Node</code> for which <code>removed==false</code> is
+ * an element of the main sequence.
+ * <li> Every <code>Node</code> for which
+ * <code>guardSet.isEmpty()==false</code> is an element of the main sequence
+ * (regardless of <code>removed</code>).
+ * <li> <code>tail.next==null</code> whenever <code>tail != null</code>.
+ * <li> <code>tail==null</code> if and only if <code>head==null</code>.
+ * </ul>
+ * <p>
+ * Note that removed non-guard nodes can still be in the main sequence:
+ * they do not have to be unlinked immediately.
+ * <p>
+ * We say <q><var>Y</var> is <dfn>reachable</dfn> from <var>X</var></q>
+ * when <var>X</var> equals <var>Y</var> or when <var>Y</var> is
+ * reachable from <code><var>X</var>.next</code>.
+ * <p>
+ * New nodes are added at the tail, requiring synchronization on the list.
+ * A node is said to be <dfn>removed<dfn> if its <code>removed</code>
+ * field is <code>true</code>.  Such a node may still exist and be
+ * part of the main sequence.  Removed nodes will eventually be unlinked
+ * from their neighbors, if the <code>reap</code> method is called
+ * occasionally.
+ * <p>
+ * Any element can become removed (by the <code>remove</code> method),
+ * but once that happens, that element can never revert to being unremoved.
+ * This means that the <code>removed</code> field can be read without
+ * synchronization: a <code>true</code> value indicates that the node has
+ * been removed; a <code>false</code> value is inconclusive.
+ * This in turn means that a removed element cannot be added to a
+ * <code>FastList</code>, because some other threads may read a stale cached
+ * value for its <code>removed</code> field.  (It may be possible, one day,
+ * to liberate removed nodes from this restriction, given the guarantees of
+ * guarded traversal.)
+ * <p>
+ * The list can be traversed without much synchronization.
+ * When a traversal begins, a node near the end of the main sequence is
+ * specially marked (<i>guarded</i>) in order that the traversal will
+ * stop and synchronize when it reaches that node.  When the guard node is
+ * reached, and there are more nodes in the list to traverse, the
+ * procedure is repeated: the (new) last node in the list
+ * is marked, and traversal continues.  This procedure ends when the
+ * traversal reaches its guard node but finds out that it is still the
+ * last node in the list.  In this way, much of the list can be traversed
+ * without synchronization.
+ * <p>
+ * It is possible for a traversal to find itself on an orphaned branch
+ * (one from which the <code>tail</code> is not reachable).  This can
+ * happen when the <code>restart</code> method is called by a different
+ * thread.  Even a node on an orphaned branch is still useful for
+ * traversal: every unremoved node which was inserted after the orphaned
+ * node but before the traversal originally began is still reachable
+ * from the orphaned node.
+ * <p>
+ * Because every traversal has a guard, a list with at least
+ * one node can not become empty by traversal alone, even if the node
+ * is removed.  The <code>reap</code> method can remove such nodes.
+ * <p>
+ * Main-sequence nodes are stored in FIFO order.
+ * <p>
+ * Synchronized locks are acquired in accordance with the following
+ * partial order, in order to avoid deadlocks.
+ * <ul><li>Earlier nodes precede later nodes (i.e. where a thread holds
+ * the lock on a node <var>X</var>, it must not wait for a lock on any
+ * node <var>Y</var> unless <var>Y</var> is reachable from
+ * <var>X</var>).</li>
+ * <li>Every node precedes the list (i.e. where a thread holds the lock on
+ * the <code>FastList</code> itself, it must not wait for a lock on any node
+ * which has been added to that list).</li></ul>
+ * <p>
+ * Each node contain a field (<code>guardSet</code>) which
+ * indicates how many different threads are using the node as a guard node.
+ * If the field's value is <code>null</code>, the number is zero and the node
+ * is not a guard node.  If the field's value is nonnull, the value is a
+ * <code>WeakHashMap</code> whose <code>keySet</code> is the set of threads
+ * using the node as a guard node.  If that <code>keySet</code> is empty,
+ * the node is not a guard node.
+ * <p>
+ * There is a global table (actually a ThreadLocal) called
+ * <code>DynamicGuard</code> which is used to
+ * retrieve the guard node for the current thread.  The number of entries
+ * in a guard node's <code>guardSet</code> generally corresponds to the
+ * number of times that node appears in the global table (the
+ * <code>guardSet</code> size temporarily becomes greater
+ * than the number of table entries, but it can never be less).
+ * The list need not be synchronized while the global table is being accessed.
+ * <p>
+ * Unsynchronized access may be made to the <code>guardSet</code>,
+ * <code>removed</code>, and <code>next</code> fields.  In the case of
+ * <code>next</code>, the field may be modified so as to unlink any removed
+ * non-guard nodes.  This action does not invalidate the main-sequence
+ * invariant, and can only be done by a thread which is holding a lock on
+ * the node being unlinked.
+ * <p>
+ * In the case of <code>guardSet</code> and <code>removed</code>, the fields
+ * are read as an optimization.  If the values read from the fields might
+ * be stale, then synchronization is done and the old values are not
+ * relied on.  The value of <code>guardSet</code> is read without
+ * synchronization to see if it's null or not, as part of a check to detect
+ * the traversing thread's guard node.  The unsynchronized value of
+ * <code>removed</code> is untrusted when it's <code>false</code>; however,
+ * a <code>true</code> value is trustworthy, since such a value could only
+ * be read after the node is irrevocably marked removed.
+ *
+ * @author Sun Microsystems, Inc.
+ */
+
+class FastList
+{
+
+    /**
+     * A single node in a <code>FastList</code>.
+     */
+    static abstract class Node
+    {
+        /**
+         * The next youngest Node in the list.
+         * If this node is the last in the list, this field is null.
+         * Otherwise, it's a pointer to the next Node that was added
+         * to the list (and not unlinked in the meantime).
+         * <p>
+         * This field may be changed only while synchronized on
+         * <code>this</code>, or when unlinking a node from the middle
+         * of the list.  This field may be read without
+         * synchronization if the reading thread has a guard node which
+         * is not <code>this</code>.
+         */
+        private volatile Node next;
+
+        /**
+         * True only if this node has been removed.
+         * Every node starts out unremoved.  Once this field becomes
+         * true, it can never become false again.  You must be
+         * synchronized on this before you can modify this field.
+         * You can read this field without synchronization if you don't
+         * mind reading an old <code>false</code> instead of a more
+         * recent <code>true</code>.
+         */
+        private boolean removed;
+
+        /**
+         * The list containing this node.
+         * Every node in a list has the same value for this field.
+         * This field must not be changed after the node is added to
+         * a list.  The field can be read without synchronization
+         * provided that the reading thread has first synchronized on
+         * something since <code>this</code> node was added to its list
+         * (that is, during a traversal).
+         */
+        private FastList list;
+
+        /**
+         * Set of threads for which this is a guard.
+         * This is a weak map from Thread to nulls, and the key set is
+         * the important set of threads.  The values are unimportant.
+         * You must be synchronized on this node to access the map.
+         * This field may be <code>null</code>, which is semantically
+         * equivalent to the empty set.
+         * <p>
+         * This field is read without synchronization in <code>next</code>.
+         * That method is searching for a particular node where the value
+         * of this field is known to be continuously nonnull since the last
+         * synchronization by the reading thread, so it is safe.
+         * <p>
+         * Note to maintainer: it may be better to use a reference count
+         * and rely on the user to use a finally-clause to effect the
+         * necessary decrement.  In that case, head() should verify that
+         * any previous traversal has been properly terminated.
+         */
+        private transient volatile WeakHashMap guardSet;
+
+        /**
+         * Default constructor.  This is called by subclasses.
+         */
+        protected Node()
+        {
+            this.removed = false;
+            this.guardSet = null;
+            this.list = null;    // will be filled in by FastList.add()
+            this.next = null;
+        }
+
+        /**
+         * Traverse to the next node.  Note that between the time that the
+         * next node is returned and the time you look at it someone may
+         * have removed it.  Returns <code>null</code> if there are no more
+         * nodes.
+         * <p>
+         * Although this method attempts to skip removed nodes, callers
+         * <em>must not</em> assume that removed nodes have been skipped.
+         * Use the {@link #removed() removed} method to tell when a node
+         * returned by this method is in fact removed, and
+         * <code>synchronize</code> on the node if you want to prevent
+         * anyone else from removing the node while you are in the
+         * synchronized block.
+         * <p>
+         * Do not call this method twice on the same node unless each call is
+         * part of a separate traversal; a traversal starts with a call to
+         * <code>List.head</code>).
+         * <p>
+         * Note this method should not be called by threads that
+         * hold locks on <code>Node</code>s that appear in the list
+         * after this <code>Node</code>.
+         */
+        public Node next()
+        {
+
+            this.list.checkPanic();    // abort if malfunctioned
+
+            // Ensure the list has a guard and it is for THIS list
+            Node traversalGuard = (Node) FastList.DynamicGuard.get();
+            if( traversalGuard == null )
+            {
+                this.list.panic( "Unguarded FastList traversal - 345" );
+            }
+
+            if( traversalGuard.list != this.list )
+            {
+                this.list.panic( "Illegal traversal of 2 FastLists" );
+            }
+
+            /* When this thread last synchronized, Node.guardSet on
+            * the thread's guard node was nonempty because the
+            * traversing thread was added to guardSet by newGuardFor.
+            * Since then, it cannot have been emptied, and therefore
+            * cannot have been replaced with null.  Therefore the
+            * guard node must appear to have a nonnull Node.guardSet
+            * field, even if accessed without locking.  This means we
+            * can safely conclude, without locking, that a Node whose
+            * guardSet field is null is not the guard node for the
+            * current thread.
+            */
+            if( this.guardSet != null )
+            {
+                /* This may or may not be the guard node; lock it
+             * so we can be sure that reading `next' is safe:
+             */
+                synchronized( this )
+                {
+                    if( this.guardSet != null && traversalGuard == this )
+                    {
+                        /* This is the guard node for the current
+                   * thread; drop it and return null.
+                   * (We don't need to find nodes added
+                   *  after the traversal was started)
+                   */
+                        FastList.DynamicGuard.set( null );
+                        traversalGuard.unguard( Thread.currentThread() );
+                        return null;
+
+                        /* If we did want nodes that were added
+            * since the traversal started we
+            * want to try and get a new guard node
+            * with code like this :
+            *
+            * if (this.next != null) {
+            *    if (!this.list.newGuardFor(this)) {
+            *	  // If it returned false, newGuardFor()
+            *	  // has already cleaned up--we just return
+            *  	  return null;	// traversal is over
+            *    } else {
+            *	// We can extend the traversal : proceed!
+            *    }
+            * } else {
+            *    // We're at the end of the list. Clean up.
+            *    FastList.DynamicGuard.set(null);
+            *    traversalGuard.unguard(
+                        *        Thread.currentThread());
+            *    return null;
+            * }
+                        */
+                    }
+                }
+            }
+
+            // At this point, there is a guard node reachable from this.next.
+
+            /* This loop repeatedly looks at this.next to see if there's a
+            * possibility of unlinking the next node.  It stops at the
+            * first sign of trouble (a guard node, an unremoved node, or
+            * a concurrency conflict):
+            */
+            Node n = this.next;
+            while( true )
+            {
+                if( n == null )
+                {
+                    // We're on an orphaned branch.  Remove our guard.
+                    traversalGuard = (Node) FastList.DynamicGuard.get();
+                    if( traversalGuard != null )
+                    {
+                        FastList.DynamicGuard.set( null );
+                        traversalGuard.unguard( Thread.currentThread() );
+                    }
+                    return null;
+                }
+
+                // We can only unlink a removed node which is not a guard:
+                if( n.guardSet != null || !n.removed )
+                {
+                    break;
+                }
+
+                /* Lock n and see if we can find an excuse to unlink it.
+             * While n is locked, no other thread can change this.next.
+             */
+                synchronized( n )
+                {
+                    // Break if this.next changed.
+                    if( this.next != n )
+                    {
+                        break;
+                    }
+                    // Verify that n is not a guard:
+                    if( n.guardSet != null && !n.guardSet.isEmpty() )
+                    {
+                        break;
+                    }
+                    // reduce empty set to null
+                    n.guardSet = null;
+                }
+                /* n is removed and not a guard, so it's a candidate
+             * for removal.  If we got this far, then we have a
+             * guard node.  Therefore, n is not the last node in
+             * the main sequence (although it may be the last node
+             * on an orphaned branch).
+             */
+                this.next = n = n.next;
+            }
+
+            return n;
+        }
+
+        /**
+         * Resume a traversal abandoned by another thread.
+         * This is used by remote iterators.  When a traversal was
+         * started (and abandoned) by thread A, and a thread B wants
+         * to continue the traversal from where A left off, then B
+         * must call this method on the most recent <code>Node</code>
+         * traversed by thread A.  After this method returns normally,
+         * B can call the <code>next</code> method on the same
+         * <code>Node</code> to continue the traversal.
+         * <p>
+         * Note this method should not be called by threads that
+         * hold locks on <code>Node</code>s that appear in the list
+         * after this <code>Node</code>.
+         */
+        public synchronized void restart()
+        {
+
+            this.list.checkPanic();    // abort if malfunctioned.
+
+            /* If the current thread still has a guard from an earlier
+            * abandoned traversal, then clean up that guard.
+            */
+            Node oldguard = (Node) FastList.DynamicGuard.get();
+            if( oldguard != null )
+            {
+                FastList.DynamicGuard.set( null );
+                oldguard.unguard( Thread.currentThread() );
+            }
+
+            /* There must be a synchronized block here before calling
+            * newGuardFor, because newGuardFor reads FastList.tail
+            * without synchronization.  That's why this whole method
+            * is synchronized.
+            */
+
+            /* Attempt to get a guard.  If newGuardFor returns false, no
+            * guard has been allocated, but that isn't a problem because
+            * it also means that this node is on an orphaned branch where
+            * a guard node is not necessary.
+            */
+            this.list.newGuardFor( null );
+        }
+
+        /**
+         * Return <code>true</code> if the object has been removed
+         * from the list.  If called while synchronized on
+         * <code>this</code>, this will return true if the object has
+         * been removed and false otherwise.
+         */
+        public boolean removed()
+        {
+
+            this.list.checkPanic();    // abort if malfunctioned.
+            return removed;
+        }
+
+        /**
+         * Remove the node from the list.  This will be unsuccessful if the
+         * node was removed by someone else after you got your hands on
+         * it.  This must be synchronized because the test to check if it
+         * is already removed and the following decision to remove it if it
+         * hasn't been removed must be unitary.
+         */
+        private boolean remove()
+        {
+
+            synchronized( this )
+            {
+                if( removed )
+                {
+                    return false;        // beaten to it
+                }
+
+                removed = true;        // mark first, unlink later
+            }
+
+            this.list.reapable();
+
+            return true;
+        }
+
+        /**
+         * Remove a thread from guardSet.  The indicated thread is
+         * removed from the guardSet of the node, and the guardSet is
+         * reduced to <code>null</code> if it's empty.
+         *
+         * @param traverser the Thread to use as a key
+         */
+        private synchronized void unguard( Thread traverser )
+        {
+            if( this.guardSet == null )
+            {
+                return;
+            }
+
+            this.guardSet.remove( traverser );
+            if( this.guardSet.isEmpty() )
+            {
+                this.guardSet = null;
+            }
+        }
+
+        final Node dump( java.io.PrintWriter out )
+        { //DEBUG
+            if( this.removed ) //DEBUG
+            {
+                out.print( '!' ); //DEBUG
+            }
+            out.print( toString() ); //DEBUG
+            Map g = this.guardSet; //DEBUG
+            if( g != null ) //DEBUG
+            {
+                out.print( "[g=" + g.size() + "]" ); //DEBUG
+            }
+            return this.next; //DEBUG
+        } //DEBUG
+    } // end class Node
+
+    /**
+     * The guard node for the calling thread's traversal, if any.
+     * The value is <tt>null</tt> for a thread which is not traversing
+     * anything, and is a <code>Node</code> for other threads.  No
+     * thread can be involved in two concurrent traversals.  This field
+     * can be accessed without synchronization since it's thread-local.
+     */
+    private static final ThreadLocal DynamicGuard = new ThreadLocal();
+
+    /**
+     * Flag to indicate that removed elements exist in the list and
+     * that reaping is not necessarily a waste of time.  This is set
+     * in <code>remove</code> and cleared in <code>reap</code>.
+     */
+    private boolean reapable;
+
+    /**
+     * The pointer to the last element in the list.
+     */
+    private Node tail; // synchronize on list to read or write
+
+    /**
+     * The pointer to the first element in the list.
+     */
+    private Node head; // synchronize on list to read or write
+
+    /**
+     * An Error that indicated a fundamental malfunction in the list.
+     */
+    private transient Error firstPanic = null;
+
+    /**
+     * Create an new empty list.
+     */
+    public FastList()
+    {
+        this.reapable = false;
+        this.tail = null;
+        this.head = null;
+        this.firstPanic = null;
+    }
+
+    /**
+     * Add a given node to the end of the list. Note, this method
+     * generally attempts to lock <code>Node</code>s in the list and
+     * should not be called from a thread that holds a lock on a node
+     * already in the list - it is ok if the calling thread holds the
+     * lock on <code>node</code>.
+     */
+    public void add( Node node )
+    {
+
+        this.checkPanic();    // abort if malfunctioned.
+        if( node.removed )
+        {
+            throw new IllegalArgumentException(
+                "FastList.add cannot accept a removed node" );
+        }
+        if( node.list != null )
+        {
+            throw new IllegalArgumentException(
+                "FastList.add cannot accept a node from another FastList" );
+        }
+
+        node.list = this;
+
+        while( true )
+        {
+            Node t = this.tail;
+            if( t == null )
+            {
+                // List appears to be empty - lock list and insert first node
+                synchronized( this )
+                {
+                    if( this.tail != null )
+                    {
+                        //System.err.print('$'); //DBG
+                        continue;    // try again
+                    }
+                    this.tail = node;
+                    this.head = node;
+                }
+            }
+            else
+            {
+                /* List is not empty - lock tail & list and insert the
+             * node at the end.
+             */
+                synchronized( t )
+                {
+                    if( t != this.tail )
+                    { // optimization
+                        //System.err.print('#'); //DBG
+                        continue;    // try again
+                    }
+                    synchronized( this )
+                    {
+                        if( t != this.tail )
+                        {
+                            //System.err.print('&'); //DBG
+                            continue;    // try again
+                        }
+                        t.next = node;
+                        this.tail = node;
+                    }
+                }
+            }
+            return;
+        }
+    }
+
+    /**
+     * Allocate a new guard node for a traversing thread.
+     * This method selects a guard node on the main sequence, as close as
+     * practically possible to the tail.  Returns true iff a guard was
+     * allocated, and false if the list appeared to contain no elements
+     * after the oldGuard.  This routine must acquire a new guard at
+     * the same time as or before releasing the old one, in order to
+     * guarantee that the new guard will be reachable from the old.
+     * <p>
+     * Because this method reads <code>FastList.tail</code> without
+     * synchronization, the guard could be stale.  It's important that
+     * it not be too stale, or else nodes added before the traversal
+     * began might not appear in the traversal.  For this reason, the
+     * caller must have recently acquired a lock (i.e. synchronized on
+     * any object).  The guard allocated by this method will have been the
+     * tail at least once since that lock was acquired.
+     * <p>
+     * Note: the oldGuard will always be dropped as the guard node for
+     * this thread. Further, should this method return <code>false</code>
+     * the DynamicGuard will be set to <code>null</code>.
+     */
+    private boolean newGuardFor( Node oldGuard )
+    {
+
+        /* tGood becomes true when it's determined that a given node
+       * is good enough to be a guard node.  "Good enough" means
+       * that it's reachable from the head, and that it was the tail
+       * at least once since this method was entered.
+       */
+
+        Thread current = Thread.currentThread();
+
+        while( true )
+        {
+            boolean tGood = false;        // is tGood a useable guard?
+            boolean noMoreElements = false;    // is oldGuard the tail node?
+            Node t = this.tail;
+
+            /* A new guard is not necessary for an empty list.
+            * Further, an empty list has no guard nodes (or indeed
+            * any nodes) so there is no need to call unguard() on
+            * anything -- even the parameter "oldGuard".
+            */
+            if( t == null )
+            {
+                return false;
+            }
+
+            /* Optimistically presume that t will be good enough, and add
+            * to its guardSet.  If the optimism isn't justified, then the
+            * addition will be undone later.  If t is on the main sequence
+            * and after oldGuard, then it's OK for use as a new guard.
+            * This is because traversals must be guaranteed to hit the
+            * guard node, and the easiest way to do that is to ensure
+            * that the guard node is on the main sequence.
+            */
+            synchronized( t )
+            {
+                if( ( t.guardSet != null && !t.guardSet.isEmpty() )
+                    || !t.removed() )
+                {
+                    if( t != oldGuard )        // can't use same guard again
+                    {
+                        tGood = true;
+                    }
+                }
+                // Make sure t can't be unlinked after we release the lock.
+                if( t.guardSet == null )
+                {
+                    t.guardSet = new WeakHashMap();
+                }
+                t.guardSet.put( current, null );
+            }
+
+            /* If t isn't good enough, then we have to try the real tail.  If
+            * the tail isn't good enough, then we're at the end of the list,
+            * and no guard is required.
+            */
+            if( !tGood )
+            {
+                synchronized( this )
+                {
+                    if( this.tail == t )
+                    {
+                        if( t != oldGuard )
+                        {  // t is reachable from oldGuard
+                            tGood = true;
+                        }
+                        else
+                        {          // oldGuard == this.tail
+                            noMoreElements = true;
+                        }
+                    }
+                }
+            }
+
+            if( tGood )
+            {
+                DynamicGuard.set( t );
+                if( oldGuard != null )
+                {
+                    oldGuard.unguard( current );
+                }
+                return true;
+            }
+
+            //System.err.print('*'); //DBG
+
+            // Undo the optimistic work if it turns out t isn't good enough.
+            t.unguard( current );
+
+            if( noMoreElements )
+            {
+                DynamicGuard.set( null );
+                if( oldGuard != null && oldGuard != t )
+                {
+                    oldGuard.unguard( current );
+                }
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Return the start of the list. Returns null if the list is
+     * empty.  Note, this method generally attempts to lock
+     * <code>Node</code>s in the list and should not be called from a
+     * thread that holds a lock on a node already in the list.
+     */
+    public Node head()
+    {
+
+        this.checkPanic();    // Abort if malfunctioned.
+        /* If the current thread still has a guard from an earlier abandoned
+       * traversal, then clean up that guard.
+       */
+        Node oldguard = (Node) DynamicGuard.get();
+        if( oldguard != null )
+        {
+            DynamicGuard.set( null );
+            oldguard.unguard( Thread.currentThread() );
+        }
+
+        Node h;
+        Node t;
+
+        synchronized( this )
+        {
+            h = this.head;
+            t = this.tail;
+            if( h == null )
+            {
+                return null;        // nothing in list
+            }
+        }
+
+        // There must be a synchronized block here before calling
+        // newGuardFor, because newGuardFor reads FastList.tail
+        // without synchronization.
+
+        // Every traversal needs a guard.
+        if( !newGuardFor( null ) )
+        {
+            return null;        // nothing in list, suddenly
+        }
+
+        // Make an attempt to unlink removed non-guard nodes from the head
+        while( h.removed() )
+        {
+            synchronized( h )
+            {
+                /* 
+                 * We stop if we find any node that is a guard node. Of 
+                 * course, eventually we'll reach the node which is OUR 
+                 * guard node and at that point we'll break from this loop.
+                 */
+                if( h.guardSet != null && !h.guardSet.isEmpty() )
+                {
+                    break;        // can't unlink guard nodes
+                }
+
+                synchronized( this )
+                {
+                    if( h != this.head )
+                    {
+                        return this.head;    // someone else just did it
+                    }
+                    this.head = h = h.next;
+                }
+            }
+        }
+
+        return h;
+    }
+
+    /**
+     * Remove the given node from this list.
+     */
+    public boolean remove( Node node )
+    {
+
+        this.checkPanic();    // Abort if malfunctioned.
+        return node.remove();
+    }
+
+    /**
+     * Remember that there's something for the reaper to do.
+     */
+    private void reapable()
+    {
+        this.reapable = true;
+    }
+
+    /**
+     * Perform a normal traversal to help clear out some removed
+     * nodes.  It also moves the tail. Note, this method generally
+     * attempts to lock <code>Node</code>s in the list and should not
+     * be called from a thread that holds a lock on a node already in
+     * the list.
+     */
+    public void reap()
+    {
+
+        if( !this.reapable )
+        {
+            return;
+        }
+
+        this.reapable = false;
+        // reapable could be concurrently set to true, which is OK.
+
+        reapCnt++;
+
+        // Normal traversal (unlinks some nodes as a side effect).
+        Node last = null;
+        Node secondLast = null;
+        for( Node n = this.head(); n != null; n = n.next() )
+        {
+            secondLast = last;
+            last = n;
+        }
+
+        // Clear out the last node only if it's removed and not a guard
+        if( last == null || !last.removed || last.guardSet != null )
+        {
+            return;
+        }
+
+        if( secondLast == null )
+        {
+            // probably the only one in the list
+            synchronized( last )
+            {
+                if( last.next != null )
+                {
+                    return;    // changed while waiting
+                }
+                if( last.guardSet != null && !last.guardSet.isEmpty() )
+                {
+                    return;    // last became a guard while we waited
+                }
+                synchronized( this )
+                {
+                    if( last != this.tail || last != this.head )
+                    {
+                        return;    // a node was added while we waited
+                    }
+                    // remove the only node from the list
+                    this.head = this.tail = null;
+                }
+            }
+        }
+        else
+        {
+            /* Move the tail to secondLast.  We can only do this if
+            * secondLast is on the main sequence and last is both
+            * removed and not a guard.
+            */
+            if( secondLast.removed && secondLast.guardSet == null )
+            {
+                return;        // not sure secondLast is on main sequence
+            }
+            synchronized( secondLast )
+            {
+                if( secondLast.removed )
+                {
+                    if( secondLast.guardSet == null )
+                    {
+                        return;    // not sure secondLast is on main sequence
+                    }
+                    if( secondLast.guardSet.isEmpty() )
+                    {
+                        secondLast.guardSet = null;
+                        return;    // ditto
+                    }
+                }
+                // We're sure now that secondLast is on the main sequence.
+                synchronized( last )
+                {
+                    if( secondLast.next != last )
+                    {
+                        return;    // last has already been unlinked
+                    }
+                    if( last.next != null )
+                    {
+                        return;    // a node was added while we waited
+                    }
+                    if( last.guardSet != null && !last.guardSet.isEmpty() )
+                    {
+                        return;    // last is a guard
+                    }
+                    // We tested last.removed and it must still be true now.
+                    synchronized( this )
+                    {
+                        if( last != this.tail )
+                        {
+                            return; // shouldn't happen
+                        }
+                        // Unlink last (secondLast is the new tail):
+                        secondLast.next = null;
+                        this.tail = secondLast;
+                    }
+                }
+            }
+        }
+    } // end reap
+
+    int reapCnt = 0; //DEBUG
+
+    public final int reapCnt()
+    {
+        return reapCnt;
+    } //DEBUG
+
+
+    public void dump( java.io.PrintWriter out )
+    { //DEBUG
+        Node n = this.head; //DEBUG
+        while( n != null )
+        { //DEBUG
+            if( this.tail == n ) //DEBUG
+            {
+                out.print( "[tail]" ); //DEBUG
+            }
+            n = n.dump( out ); //DEBUG
+            out.print( ' ' ); //DEBUG
+        } //DEBUG
+        out.print( "null" ); //DEBUG
+    } //DEBUG
+
+    /**
+     * Shut down the list due to an error.  The argument is used to
+     * construct an Error which is thrown.  The FastList then becomes
+     * unusable because every public method calls checkPanic which
+     * then throws another exception.
+     *
+     * @param condition identifying string which will help debugging
+     */
+    private void panic( String condition )
+        throws Error
+    {
+        InternalError err = new InternalError( condition );
+
+        // Record only the first panic so that subsequent ones don't confuse:
+        if( firstPanic == null )
+        {
+            synchronized( this )
+            {
+                if( firstPanic == null )
+                {
+                    firstPanic = err;
+                }
+            }
+        }
+
+        throw err;
+    }
+
+    /**
+     * Abort if the list has been shut down.  This does nothing in
+     * normal operation.  If the list has undergone a panic, then this
+     * throws an exception which says so.
+     */
+    private void checkPanic()
+        throws Error
+    {
+        if( firstPanic == null )
+        {
+            return;
+        }
+        throw new InternalError( "FastList panicked; original error was:" +
+                                 firstPanic );
+    }
+
+}

Added: incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/IfExistsWatcher.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/IfExistsWatcher.java?rev=724979&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/IfExistsWatcher.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/IfExistsWatcher.java Tue Dec  9 21:13:53 2008
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.sun.jini.outrigger;
+
+/**
+ * Mix-in interface for <code>QueryWatcher</code>s associated
+ * with ifExists queries.
+ */
+interface IfExistsWatcher
+{
+    /**
+     * If the query has been resolved by the locked entry set
+     * becoming empty returns <code>true</code>, otherwise
+     * return <code>false</code>.
+     *
+     * @return <code>true</code> if locked entry set
+     *         associated with this query is empty.
+     * @throws IllegalStateException if the query has not
+     *                               be resolved.
+     */
+    public boolean isLockedEntrySetEmpty();
+
+    /**
+     * Called after the last transition that was recored
+     * during the initial search has been processed. This
+     * means that any entry that replaced an entry that
+     * was removed before the initial search had gotten to
+     * it has been seen by the watcher. The query should now be
+     * resolved with a <code>null</code> if the locked
+     * entry set goes empty.
+     */
+    public void caughtUp();
+}

Added: incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/IteratorProxy.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/IteratorProxy.java?rev=724979&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/IteratorProxy.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/IteratorProxy.java Tue Dec  9 21:13:53 2008
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.sun.jini.outrigger;
+
+import java.rmi.RemoteException;
+import net.jini.core.entry.Entry;
+import net.jini.core.entry.UnusableEntryException;
+import net.jini.id.Uuid;
+
+/**
+ * The iterator that is returned to the client.
+ * <code>IteratorProxy</code> uses a <code>RemoteIter</code> reference
+ * to talk to the server.  The <code>RemoteIter</code> object is its
+ * partner on the server side.
+ * <p>
+ * Note, because there is no way to duplicate one of these objects, or
+ * get a new reference to the underlying iteration one (a situation
+ * that is unlikely to change since part of the iteration is local), we
+ * don't need to do anything special for equality. The default
+ * implementations of <code>equals</code> and <code>hashCode</code>
+ * work fine.
+ */
+class IteratorProxy implements AdminIterator
+{
+    /**
+     * The <code>Uuid</code> of the iteration this
+     * proxy is associated with.
+     */
+    private final Uuid iterationUuid;
+
+    /**
+     * Reference to the server.
+     * Only assigned by this class.
+     */
+    OutriggerAdmin server;
+
+    /**
+     * Last set of reps we got from the server
+     */
+    private EntryRep[] reps;
+
+    /**
+     * Index of the next entry in rep to return.  If <code>delete()</code>
+     * we will call <code>iter.delete()</code> next - 1
+     */
+    private int next = -1;
+
+    /**
+     * How many entries to ask for each time we go to the server
+     */
+    private final int fetchSize;
+
+    /**
+     * ID of last entry we got from server
+     */
+    private Uuid lastId = null;
+
+    /**
+     * Create client side iterator proxy.
+     *
+     * @param iterationUuid The identity of the iteration this proxy is for.
+     * @param server        reference to remote server for the space.
+     * @param fetchSize     Number of entries to ask for when it goes to the
+     *                      server
+     * @throws NullPointerException if <code>server</code> or
+     *                              <code>iterationUuid</code> is <code>null</code>.
+     */
+    IteratorProxy( Uuid iterationUuid, OutriggerAdmin server, int fetchSize )
+    {
+        if( iterationUuid == null )
+        {
+            throw new NullPointerException( "iterationUuid must be non-null" );
+        }
+
+        if( server == null )
+        {
+            throw new NullPointerException( "server must be non-null" );
+        }
+
+        if( fetchSize <= 0 && fetchSize != JavaSpaceAdmin.USE_DEFAULT )
+        {
+            throw new IllegalArgumentException
+                ( "fetchSize must be postive or JavaSpaceAdmin.USE_DEFAULT" );
+        }
+
+        this.iterationUuid = iterationUuid;
+        this.server = server;
+        this.fetchSize = fetchSize;
+    }
+
+    // purposefully inherit doc comment from supertype
+    public Entry next() throws UnusableEntryException, RemoteException
+    {
+        assertOpen();
+
+        if( next < 0 || next >= reps.length )
+        {
+            // Need to get more Entries
+            reps = server.nextReps( iterationUuid, fetchSize, lastId );
+
+            if( reps == null )
+            {
+                // Finished
+                close();
+                return null;
+            }
+
+            lastId = reps[ reps.length - 1 ].id();
+
+            next = 0;
+        }
+
+        // This may throw UnusableEntryException, but that's
+        // the right thing
+        return reps[ next++ ].entry();
+    }
+
+    // purposefully inherit doc comment from supertype
+    public void delete() throws RemoteException
+    {
+        if( next < 0 )
+        {
+            throw new IllegalStateException( "AdminIterator:Can't call " +
+                                             "delete before calling next()" );
+        }
+
+        assertOpen();
+        server.delete( iterationUuid, reps[ next - 1 ].id() );
+    }
+
+    // purposefully inherit doc comment from supertype
+    public void close() throws RemoteException
+    {
+        if( server != null )
+        {
+            // always set to null, and then try to use the remote method,
+            // which is actually optional
+            OutriggerAdmin it = server;
+            server = null;
+            reps = null;
+            it.close( iterationUuid );
+        }
+    }
+
+    /**
+     * Throw <code>IllegalStateException</code> if this iterator
+     * has been closed; otherwise just return.
+     */
+    private void assertOpen() throws IllegalStateException
+    {
+        if( server == null )
+        {
+            throw new IllegalStateException( "closed AdminIterator" );
+        }
+    }
+
+    public String toString()
+    {
+        return getClass().getName() + " for " + iterationUuid +
+               " (through " + server + ")";
+    }
+}

Added: incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/JavaSpaceAdmin.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/JavaSpaceAdmin.java?rev=724979&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/JavaSpaceAdmin.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/JavaSpaceAdmin.java Tue Dec  9 21:13:53 2008
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.sun.jini.outrigger;
+
+import com.sun.jini.admin.DestroyAdmin;
+import java.rmi.RemoteException;
+import net.jini.admin.JoinAdmin;
+import net.jini.core.entry.Entry;
+import net.jini.core.transaction.Transaction;
+import net.jini.core.transaction.TransactionException;
+import net.jini.space.JavaSpace;
+import net.jini.space.JavaSpace05;
+
+/**
+ * This interface contains all the administrative methods that Outrigger
+ * provides to control its JavaSpaces<sup><font size=-2>TM</font></sup>
+ * service. <p>
+ *
+ * @author Sun Microsystems, Inc.
+ * @deprecated Invoke the {@link JoinAdmin} and {@link
+ *             DestroyAdmin} methods though those interfaces.  A view
+ *             of the space contents can be obtained through the
+ *             {@link JavaSpace05#contents JavaSpace05.contents}
+ *             method.
+ */
+public interface JavaSpaceAdmin extends JoinAdmin, DestroyAdmin
+{
+    /**
+     * Can be passed to <code>contents</code> to indicate
+     * no preference for the fetch size.
+     */
+    int USE_DEFAULT = -1;
+
+    /**
+     * Return the space that this administrative object governs.
+     *
+     * @throws RemoteException if communications with the
+     *                         server is necessary and it can not be completed.
+     */
+    JavaSpace space() throws RemoteException;
+
+    /**
+     * Return an <code>AdminIterator</code> that will iterate over all
+     * the entries in the space that match the given template and are
+     * visible under the given transaction.
+     * <p>
+     * The interactions between other operations on the space and
+     * the returned iterator are undefined
+     * <p>
+     *
+     * @param tmpl The iterator should return only entries that match
+     *             tmpl
+     * @param txn  The iterator should return only entries that match
+     *             this transaction
+     * @throws RemoteException      if communications with the
+     *                              server is necessary and it can not be completed.
+     * @throws TransactionException if there is a problem with
+     *                              <code>txn</code>.
+     * @throws SecurityException    If the space is performing
+     *                              access control and it can not be confirmed
+     *                              that the subject making this call has permission
+     *                              to create an <code>AdminIterator</code> with
+     *                              the specified template and transaction.
+     */
+    AdminIterator contents( Entry tmpl, Transaction txn )
+        throws TransactionException, RemoteException;
+
+    /**
+     * Return an <code>AdminIterator</code> that will iterate over all
+     * the entries in the space that match the given template and are
+     * visible under the given transaction.
+     * <p>
+     * The interactions between other operations on the space and
+     * the returned iterator are undefined
+     * <p>
+     *
+     * @param tmpl      The iterator should return only entries that match
+     *                  tmpl
+     * @param txn       The iterator should return only entries that match
+     *                  this transaction
+     * @param fetchSize advice on how many entries to fetch when the iterator
+     *                  has to go to the server for more entries.
+     * @throws RemoteException          if communications with the
+     *                                  server is necessary and it can not be completed.
+     * @throws TransactionException     if there is a problem with
+     *                                  <code>txn</code>.
+     * @throws SecurityException        If the space is performing
+     *                                  access control and it can not be confirmed
+     *                                  that the subject making this call has permission
+     *                                  to create an <code>AdminIterator</code> with
+     *                                  the specified template and transaction.
+     * @throws IllegalArgumentException if fetchSize is
+     *                                  not positive, or <code>USE_DEFAULT</code>.
+     */
+    AdminIterator contents( Entry tmpl, Transaction txn, int fetchSize )
+        throws TransactionException, RemoteException;
+}

Added: incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/JoinStateManager.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/JoinStateManager.java?rev=724979&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/JoinStateManager.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/JoinStateManager.java Tue Dec  9 21:13:53 2008
@@ -0,0 +1,720 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.sun.jini.outrigger;
+
+import com.sun.jini.config.Config;
+import com.sun.jini.logging.Levels;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.rmi.MarshalledObject;
+import java.rmi.RemoteException;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import net.jini.config.Configuration;
+import net.jini.config.ConfigurationException;
+import net.jini.core.entry.Entry;
+import net.jini.core.lookup.LookupLocator;
+import net.jini.core.lookup.ServiceID;
+import net.jini.discovery.DiscoveryGroupManagement;
+import net.jini.discovery.DiscoveryLocatorManagement;
+import net.jini.discovery.DiscoveryManagement;
+import net.jini.discovery.LookupDiscoveryManager;
+import net.jini.lookup.JoinManager;
+import net.jini.security.ProxyPreparer;
+
+/**
+ * <code>JoinStateManager</code> provides a utility that manages
+ * a service's join state (optionally persisting that state) and
+ * manages the join protocol protocol on behalf of the service.
+ *
+ * @author Sun Microsystems, Inc.
+ * @see com.sun.jini.lookup.JoinManager
+ * @see JoinAdminState
+ */
+class JoinStateManager implements StorableObject
+{
+    /**
+     * <code>ProxyPreparer</code> for <code>LookupLocators</code>
+     */
+    private ProxyPreparer lookupLocatorPreparer;
+
+    /**
+     * Object used to find lookups. Has to implement DiscoveryManagement
+     * and DiscoveryLocatorManagement as well as DiscoveryGroupManagement.
+     */
+    private DiscoveryGroupManagement dgm;
+
+    /**
+     * <code>JoinManager</code> that is handling the details of binding
+     * into Jini lookup services.
+     */
+    private JoinManager mgr;
+
+    /**
+     * The object that is coordinating our persistent state.
+     */
+    private LogOps log;
+
+    /**
+     * The list of attributes to start with. This field is only used
+     * to carry data from the <code>restore</code> method to the
+     * <code>startManager</code> method. The current set of attributes
+     * is kept by <code>mgr</code>. This field is nulled out by
+     * <code>startManager</code>.
+     */
+    private Entry[] attributes;
+
+    /**
+     * The list of <code>LookupLocator</code>s to start with. This
+     * field is only used to carry data from the <code>restore</code>
+     * method to the <code>startManager</code> method. The current set
+     * of attributes is kept by <code>mgr</code>. This field is nulled
+     * out by <code>startManager</code>.
+     */
+    private LookupLocator locators[];
+
+
+    /**
+     * The list of group names to start with. This field is only used
+     * to carry data from the <code>restore</code> method to the
+     * <code>startManager</code> method. The current set of attributes
+     * is kept by <code>mgr</code>. This field is nulled out by
+     * <code>startManager</code>.
+     */
+    private String groups[];
+
+    /**
+     * Conceptually, true if this is the first time this
+     * service has come up, implemented as if there was
+     * no previous state then this is the first time.
+     */
+    private boolean initial = true;
+
+    /**
+     * Logger for logging join related information
+     */
+    private static final Logger logger =
+        Logger.getLogger( OutriggerServerImpl.joinLoggerName );
+
+    /**
+     * Simple constructor.
+     */
+    JoinStateManager()
+    {
+    }
+
+    /**
+     * Start the manager. Start looking for lookup and registering
+     * with them.
+     *
+     * @param config         object to use to obtain
+     *                       <code>DiscoveryManagement</code> object, and if
+     *                       this is the initial incarnation of this service,
+     *                       the object used to get the initial set of groups,
+     *                       locators, and deployer defined attributes.
+     * @param log            object used to persist the manager's state, may be
+     *                       <code>null</code>.
+     * @param serviceID      The <code>ServiceID</code> to register
+     *                       under.
+     * @param service        The proxy object to register with lookups.
+     * @param baseAttributes Any attributes the implementation wants
+     *                       attached, only used if this is the
+     *                       initial incarnation.
+     * @throws IOException            if the is problem persisting the
+     *                                initial state or in starting discovery.
+     * @throws ConfigurationException if the configuration
+     *                                is invalid.
+     * @throws NullPointerException   if <code>config</code>,
+     *                                or <code>serviceID</code> is  <code>null</code>.
+     */
+    void startManager( Configuration config, LogOps log, Object service,
+                       ServiceID serviceID, Entry[] baseAttributes )
+        throws IOException, ConfigurationException
+    {
+        // Default do nothing preparer
+        final ProxyPreparer defaultPreparer =
+            new net.jini.security.BasicProxyPreparer();
+
+        if( serviceID == null )
+        {
+            throw new NullPointerException( "serviceID can't be null" );
+        }
+
+        this.log = log;
+
+        lookupLocatorPreparer =
+            (ProxyPreparer) Config.getNonNullEntry( config,
+                                                    OutriggerServerImpl.COMPONENT_NAME, "lookupLocatorPreparer",
+                                                    ProxyPreparer.class, defaultPreparer );
+
+        dgm = (DiscoveryGroupManagement)
+            Config.getNonNullEntry( config,
+                                    OutriggerServerImpl.COMPONENT_NAME, "discoveryManager",
+                                    DiscoveryGroupManagement.class,
+                                    new LookupDiscoveryManager(
+                                        DiscoveryGroupManagement.NO_GROUPS, null, null,
+                                        config ) );
+
+        if( !( dgm instanceof DiscoveryManagement ) )
+        {
+            throw throwNewConfigurationException( "Entry for component " +
+                                                  OutriggerServerImpl.COMPONENT_NAME + ", name " +
+                                                  "discoveryManager must implement " +
+                                                  "net.jini.discovery.DiscoveryGroupManagement" );
+        }
+
+        if( !( dgm instanceof DiscoveryLocatorManagement ) )
+        {
+            throw throwNewConfigurationException( "Entry for component " +
+                                                  OutriggerServerImpl.COMPONENT_NAME + ", name " +
+                                                  "discoveryManager must implement " +
+                                                  "net.jini.discovery.DiscoveryLocatorManagement" );
+        }
+
+        final String[] toCheck = dgm.getGroups();
+        if( toCheck == null || toCheck.length != 0 )
+        {
+            throw throwNewConfigurationException( "Entry for component " +
+                                                  OutriggerServerImpl.COMPONENT_NAME + ", name " +
+                                                  "discoveryManager must be initially configured with no " +
+                                                  "groups" );
+        }
+
+        if( ( (DiscoveryLocatorManagement) dgm ).getLocators().length != 0 )
+        {
+            throw throwNewConfigurationException( "Entry for component " +
+                                                  OutriggerServerImpl.COMPONENT_NAME + ", name " +
+                                                  "discoveryManager must be initially configured with no " +
+                                                  "locators" );
+        }
+
+        // if this is the first incarnation, consult config for groups,
+        // locators and attributes.
+        if( initial )
+        {
+            groups = (String[])
+                config.getEntry( OutriggerServerImpl.COMPONENT_NAME,
+                                 "initialLookupGroups", String[].class,
+                                 new String[]{ "" } );
+
+            locators = (LookupLocator[])
+                Config.getNonNullEntry( config,
+                                        OutriggerServerImpl.COMPONENT_NAME,
+                                        "initialLookupLocators", LookupLocator[].class,
+                                        new LookupLocator[0] );
+
+            final Entry[] cAttrs = (Entry[])
+                Config.getNonNullEntry( config,
+                                        OutriggerServerImpl.COMPONENT_NAME,
+                                        "initialLookupAttributes", Entry[].class, new Entry[0] );
+
+            if( cAttrs.length == 0 )
+            {
+                attributes = baseAttributes;
+            }
+            else
+            {
+                attributes =
+                    new Entry[cAttrs.length + baseAttributes.length];
+                System.arraycopy( baseAttributes, 0, attributes,
+                                  0, baseAttributes.length );
+                System.arraycopy( cAttrs, 0, attributes,
+                                  baseAttributes.length, cAttrs.length );
+            }
+        }
+        else
+        {
+            /* recovery : if there are any locators get and
+            * use recoveredLookupLocatorPreparer
+            */
+            if( locators.length > 0 )
+            {
+                final ProxyPreparer recoveredLookupLocatorPreparer =
+                    (ProxyPreparer) Config.getNonNullEntry( config,
+                                                            OutriggerServerImpl.COMPONENT_NAME,
+                                                            "recoveredLookupLocatorPreparer", ProxyPreparer.class,
+                                                            defaultPreparer );
+
+                final List prepared = new java.util.LinkedList();
+                for( int i = 0; i < locators.length; i++ )
+                {
+                    final LookupLocator locator = locators[ i ];
+                    try
+                    {
+                        prepared.add( recoveredLookupLocatorPreparer.
+                            prepareProxy( locator ) );
+                    }
+                    catch( Throwable t )
+                    {
+                        logger.log( Level.INFO,
+                                    "Encountered exception preparing lookup locator " +
+                                    "for " + locator + ", dropping locator", t );
+                    }
+                }
+
+                locators =
+                    (LookupLocator[]) prepared.toArray( new LookupLocator[0] );
+            }
+        }
+
+        // Now that we have groups & locators (either from
+        // a previous incarnation or from the config) start discovery.
+        if( logger.isLoggable( Level.CONFIG ) )
+        {
+            if( groups == null )
+            {
+                logger.log( Level.CONFIG, "joining all groups" );
+            }
+            else if( groups.length == 0 )
+            {
+                logger.log( Level.CONFIG, "joining no groups" );
+            }
+            else
+            {
+                final StringBuffer buf = new StringBuffer();
+                buf.append( "joining groups:" );
+                for( int i = 0; i < groups.length; i++ )
+                {
+                    if( i != 0 )
+                    {
+                        buf.append( "," );
+                    }
+
+                    buf.append( "\"" );
+                    buf.append( groups[ i ] );
+                    buf.append( "\"" );
+                }
+                logger.log( Level.CONFIG, buf.toString() );
+            }
+
+            if( locators.length == 0 )
+            {
+                logger.log( Level.CONFIG, "joining no specific registrars" );
+            }
+            else
+            {
+                final StringBuffer buf = new StringBuffer();
+                buf.append( "joining the specific registrars:" );
+                for( int i = 0; i < locators.length; i++ )
+                {
+                    if( i != 0 )
+                    {
+                        buf.append( ", " );
+                    }
+
+                    buf.append( locators[ i ] );
+                }
+                logger.log( Level.CONFIG, buf.toString() );
+            }
+
+            if( attributes.length == 0 )
+            {
+                logger.log( Level.CONFIG, "registering no attributes" );
+            }
+            else
+            {
+                final StringBuffer buf = new StringBuffer();
+                buf.append( "registering the attributes:" );
+                for( int i = 0; i < attributes.length; i++ )
+                {
+                    if( i != 0 )
+                    {
+                        buf.append( ", " );
+                    }
+
+                    buf.append( attributes[ i ] );
+                }
+                logger.log( Level.CONFIG, buf.toString() );
+            }
+        }
+
+        dgm.setGroups( groups );
+        ( (DiscoveryLocatorManagement) dgm ).setLocators( locators );
+
+        mgr = new JoinManager( service, attributes, serviceID,
+                               (DiscoveryManagement) dgm, null, config );
+
+        // Once we are running we don't need the attributes,
+        // locators, and groups fields, null them out (the
+        // state is in the mgr and dgm.
+        attributes = null;
+        groups = null;
+        locators = null;
+
+        // Now that we have state, make sure it is written to disk.
+        update();
+    }
+
+    /**
+     * Make a good faith attempt to terminate
+     * discovery, and cancel any lookup registrations.
+     */
+    public void destroy()
+    {
+        // Unregister with lookup
+
+        // Terminate the JoinManager first so it will not call
+        // into the dgm after it has been terminated.
+        if( mgr != null )
+        {
+            mgr.terminate();
+        }
+
+        if( dgm != null )
+        {
+            ( (DiscoveryManagement) dgm ).terminate();
+        }
+    }
+
+    /* Basically we are implementing JoinAdmin, for get methods we just
+     * delegate to JoinManager, for the set methods we call
+     * JoinManager to and then persist the change by calling the
+     * appropriate method on our JoinAdminState.  If the call on our
+     * JoinAdminState throws an IOException we throw a runtime
+     * exception since JoinAdmin methods don't let us throw a
+     * IOException 
+     */
+
+    /**
+     * Get the current attribute sets for the service.
+     *
+     * @return the current attribute sets for the service
+     */
+    public Entry[] getLookupAttributes()
+    {
+        return mgr.getAttributes();
+    }
+
+    /**
+     * Add attribute sets for the service.  The resulting set will be used
+     * for all future joins.  The attribute sets are also added to all
+     * currently-joined lookup services.
+     *
+     * @param attrSets the attribute sets to add
+     * @throws java.rmi.RuntimeException if the change can not be persisted.
+     */
+    public void addLookupAttributes( Entry[] attrSets )
+    {
+        mgr.addAttributes( attrSets, true );
+        update();
+    }
+
+    /**
+     * Modify the current attribute sets, using the same semantics as
+     * ServiceRegistration.modifyAttributes.  The resulting set will be used
+     * for all future joins.  The same modifications are also made to all
+     * currently-joined lookup services.
+     *
+     * @param attrSetTemplates the templates for matching attribute sets
+     * @param attrSets         the modifications to make to matching sets
+     * @throws java.rmi.RuntimeException if the change can not be persisted.
+     * @see net.jini.core.lookup.ServiceRegistration#modifyAttributes
+     */
+    public void modifyLookupAttributes( Entry[] attrSetTemplates,
+                                        Entry[] attrSets )
+    {
+        mgr.modifyAttributes( attrSetTemplates, attrSets, true );
+        update();
+    }
+
+    /**
+     * Get the list of groups to join.  An empty array means the service
+     * joins no groups (as opposed to "all" groups).
+     *
+     * @return an array of groups to join. An empty array means the service
+     *         joins no groups (as opposed to "all" groups).
+     * @see #setLookupGroups
+     */
+    public String[] getLookupGroups()
+    {
+        return dgm.getGroups();
+    }
+
+    /**
+     * Add new groups to the set to join.  Lookup services in the new
+     * groups will be discovered and joined.
+     *
+     * @param groups groups to join
+     * @throws java.rmi.RuntimeException if the change can not be persisted.
+     * @see #removeLookupGroups
+     */
+    public void addLookupGroups( String[] groups )
+    {
+        try
+        {
+            dgm.addGroups( groups );
+        }
+        catch( IOException e )
+        {
+            throw propagateIOException( "Could not change groups", e );
+        }
+        update();
+    }
+
+    /**
+     * Remove groups from the set to join.  Leases are cancelled at lookup
+     * services that are not members of any of the remaining groups.
+     *
+     * @param groups groups to leave
+     * @throws java.rmi.RuntimeException if the change can not be persisted.
+     * @see #addLookupGroups
+     */
+    public void removeLookupGroups( String[] groups )
+    {
+        dgm.removeGroups( groups );
+        update();
+    }
+
+    /**
+     * Replace the list of groups to join with a new list.  Leases are
+     * cancelled at lookup services that are not members of any of the
+     * new groups.  Lookup services in the new groups will be discovered
+     * and joined.
+     *
+     * @param groups groups to join
+     * @throws java.rmi.RuntimeException if the change can not be persisted.
+     * @see #getLookupGroups
+     */
+    public void setLookupGroups( String[] groups )
+    {
+        try
+        {
+            dgm.setGroups( groups );
+        }
+        catch( IOException e )
+        {
+            throw propagateIOException( "Could not change groups", e );
+        }
+        update();
+    }
+
+    /**
+     * Get the list of locators of specific lookup services to join.
+     *
+     * @return the list of locators of specific lookup services to join
+     * @see #setLookupLocators
+     */
+    public LookupLocator[] getLookupLocators()
+    {
+        return ( (DiscoveryLocatorManagement) dgm ).getLocators();
+    }
+
+    /**
+     * Add locators for specific new lookup services to join.  The new
+     * lookup services will be discovered and joined.
+     *
+     * @param locators locators of specific lookup services to join
+     * @throws java.rmi.RuntimeException if the change can not be persisted.
+     * @see #removeLookupLocators
+     */
+    public void addLookupLocators( LookupLocator[] locators )
+        throws RemoteException
+    {
+        prepareLocators( locators );
+        ( (DiscoveryLocatorManagement) dgm ).addLocators( locators );
+        update();
+    }
+
+    /**
+     * Remove locators for specific lookup services from the set to join.
+     * Any leases held at the lookup services are cancelled.
+     *
+     * @param locators locators of specific lookup services to leave
+     * @throws java.rmi.RuntimeException if the change can not be persisted.
+     * @see #addLookupLocators
+     */
+    public void removeLookupLocators( LookupLocator[] locators )
+        throws RemoteException
+    {
+        prepareLocators( locators );
+        ( (DiscoveryLocatorManagement) dgm ).removeLocators( locators );
+        update();
+    }
+
+    /**
+     * Replace the list of locators of specific lookup services to join
+     * with a new list.  Leases are cancelled at lookup services that were
+     * in the old list but are not in the new list.  Any new lookup services
+     * will be discovered and joined.
+     *
+     * @param locators locators of specific lookup services to join
+     * @throws java.rmi.RuntimeException if the change can not be persisted.
+     * @see #getLookupLocators
+     */
+    public void setLookupLocators( LookupLocator[] locators )
+        throws RemoteException
+    {
+        prepareLocators( locators );
+        ( (DiscoveryLocatorManagement) dgm ).setLocators( locators );
+        update();
+    }
+
+    private void update()
+    {
+        if( log != null )
+        {
+            log.joinStateOp( this );
+        }
+    }
+
+    /**
+     * Apply <code>lookupLocatorPreparer</code> to each locator in the
+     * array, replacing the original locator with the result of the
+     * <code>prepareProxy</code> call. If call fails with an exception
+     * throw that exception.
+     *
+     * @param locators the <code>LookupLocator</code>s to be prepared.
+     * @throws RemoteException   if preparation of any of the locators
+     *                           does.
+     * @throws SecurityException if preparation of any of the locators
+     *                           does.
+     */
+    private void prepareLocators( LookupLocator[] locators )
+        throws RemoteException
+    {
+        for( int i = 0; i < locators.length; i++ )
+        {
+            locators[ i ] = (LookupLocator) lookupLocatorPreparer.prepareProxy(
+                locators[ i ] );
+        }
+    }
+
+    /**
+     * Utility method to write out an array of entities to an
+     * <code>ObjectOutputStream</code>.  Can be recovered by a call
+     * to <code>readAttributes()</code>
+     * <p>
+     * Packages each attribute in its own <code>MarshalledObject</code> so
+     * a bad codebase on an attribute class will not corrupt the whole array.
+     *
+     * @see JoinAdminActivationState#readAttributes
+     */
+    static private void writeAttributes( Entry[] attributes,
+                                         ObjectOutputStream out )
+        throws IOException
+    {
+        // Need to package each attribute in its own marshaled object,
+        // this makes sure that the attribute's code base is preserved
+        // and when we unpack to discard attributes who's codebase
+        // has been lost without throwing away those we can still deal with.
+
+        out.writeInt( attributes.length );
+        for( int i = 0; i < attributes.length; i++ )
+        {
+            out.writeObject( new MarshalledObject( attributes[ i ] ) );
+        }
+    }
+
+    /**
+     * Utility method to read in an array of entities from a
+     * <code>ObjectInputStream</code>.  Array should have been written
+     * by a call to <code>writeAttributes()</code>
+     * <p>
+     *
+     * Will try and recover as many attributes as possible.
+     * Attributes which can't be recovered won't be returned but they
+     * will remain in the log.
+     *
+     * @see JoinAdminActivationState#writeAttributes
+     */
+    static private Entry[] readAttributes( ObjectInputStream in )
+        throws IOException, ClassNotFoundException
+    {
+        final List entries = new java.util.LinkedList();
+        final int objectCount = in.readInt();
+        for( int i = 0; i < objectCount; i++ )
+        {
+            try
+            {
+                MarshalledObject mo = (MarshalledObject) in.readObject();
+                entries.add( mo.get() );
+            }
+            catch( IOException e )
+            {
+                logger.log( Level.INFO, "Encountered IOException recovering " +
+                                        "attribute, dropping attribute", e );
+            }
+            catch( ClassNotFoundException e )
+            {
+                logger.log( Level.INFO, "Encountered ClassNotFoundException " +
+                                        "recovering attribute, dropping attribute", e );
+            }
+        }
+
+        return (Entry[]) entries.toArray( new Entry[0] );
+    }
+
+    // -----------------------------------
+    //  Methods required by StorableObject
+    // -----------------------------------
+
+    // inherit doc comment
+
+    public void store( ObjectOutputStream out ) throws IOException
+    {
+        writeAttributes( mgr.getAttributes(), out );
+        out.writeObject( ( (DiscoveryLocatorManagement) dgm ).getLocators() );
+        out.writeObject( dgm.getGroups() );
+    }
+
+    // inherit doc comment
+    public void restore( ObjectInputStream in )
+        throws IOException, ClassNotFoundException
+    {
+        initial = false;
+        attributes = readAttributes( in );
+        locators = (LookupLocator[]) in.readObject();
+        groups = (String[]) in.readObject();
+    }
+
+    /**
+     * Construct, log, and throw a new ConfigurationException with
+     * the given message.
+     */
+    private static ConfigurationException throwNewConfigurationException(
+        String msg )
+        throws ConfigurationException
+    {
+        final ConfigurationException e = new ConfigurationException( msg );
+
+        if( logger.isLoggable( Levels.FAILED ) )
+        {
+            logger.log( Levels.FAILED, msg, e );
+        }
+
+        throw e;
+    }
+
+    /**
+     * Propagate an IOException by wrapping it in a RuntimeException.
+     * Performs appropriate logging.
+     */
+    private static RuntimeException propagateIOException( String msg,
+                                                          IOException nested )
+    {
+        final RuntimeException e = new RuntimeException( msg, nested );
+        if( logger.isLoggable( Levels.FAILED ) )
+        {
+            logger.log( Levels.FAILED, msg, e );
+        }
+
+        throw e;
+    }
+}

Added: incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/LeaseDesc.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/LeaseDesc.java?rev=724979&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/LeaseDesc.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/outrigger/src/main/java/com/sun/jini/outrigger/LeaseDesc.java Tue Dec  9 21:13:53 2008
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.sun.jini.outrigger;
+
+import com.sun.jini.landlord.LeasedResource;
+
+/**
+ * Interface that describes objects in the Outrigger implementation
+ * that are leased
+ *
+ * @author Sun Microsystems, Inc.
+ */
+interface LeaseDesc
+{
+    /**
+     * Return the name for the class for the entry this object represents.
+     * Used to find <code>EntryHolder</code> for this resource.
+     */
+    String classFor();
+
+    /**
+     * Return the <code>LeasedResource</code> object for this resource.
+     */
+    LeasedResource getLeasedResource();
+}