/*
 * 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 threaddemo.locking;

/**
 * Ability to acquire or release a lock by itself.
 * This class can be used when one wants to avoid creating a
 * bunch of Runnables. Instead use:
 * <pre>
 * p.enter*();
 * try {
 *     // your code here
 * } finally {
 *     p.exit*();
 * }
 * </pre>
 * You must be careful to match enter and exit calls reliably and exactly.
 * Please read the Javadoc for each method carefully.
 *
 * <p>You must control the related Lock, i.e. you must be the creator of
 * the Lock. Thus you may create a PrivilegedLock for efficient access within
 * a package and only expose the lock to outside code, to ensure that it is
 * not abused by being entered and not exited.
 * @see Lock
 */
public final class PrivilegedLock {
    
    private DuplexLock parent;
    
    /** Create a new privileged key to a lock.
     * (It may only be used in one lock.)
     */
    public PrivilegedLock() {}
    
    final synchronized void setParent(DuplexLock parent) {
        if (this.parent != null) throw new IllegalStateException();
        this.parent = parent;
    }
    
    /**
     * Get the associated lock.
     * You must have already created a lock with this privileged handle.
     * @return the lock associated with this object
     */
    public RWLock getLock() {
        if (parent == null) throw new IllegalStateException("Unbound PrivilegedLock"); // NOI18N
        return parent;
    }
    
    /** Enter read access for this lock.
     * <strong>You must ensure that {@link #exitRead} is reliably called
     * when you are done.</strong> The normal way to do this is as follows:
     * <pre>
     * p.enterRead();
     * // must be no additional code here!
     * try {
     *     // whatever code...
     * } finally {
     *     // must be no additional code here!
     *     p.exitRead();
     * }
     * </pre>
     *
     * <p>Detailed behavior:
     * <ol>
     * <li>You may already be holding the read or write lock. But you must
     *     still nest entries and exits, 1-to-1.
     * <li>If this lock has a level, you may not enter it if you are already
     *     holding another lock with a smaller or equal level in this thread.
     * <li>If another thread is holding the write lock, <strong>this method
     *     will block</strong> until it leaves.
     * </ol>
     *
     */
    public void enterRead() {
        parent.enterRead();
    }
    
    /** Enter write access for this lock.
     * <strong>You must ensure that {@link #exitWrite} is reliably called
     * when you are done.</strong> The normal way to do this is as follows:
     * <pre>
     * p.enterWrite();
     * // must be no additional code here!
     * try {
     *     // whatever code...
     * } finally {
     *     // must be no additional code here!
     *     p.exitWrite();
     * }
     * </pre>
     *
     * <p>Detailed behavior:
     * <ol>
     * <li>You may already be holding the write lock. But you must
     *     still nest entries and exits, 1-to-1.
     * <li><strong>You may not be holding the read lock</strong> - even if inside
     *     the write lock.
     * <li>If this lock has a level, you may not enter it if you are already
     *     holding another lock with a smaller or equal level in this thread.
     * <li>If other threads are holding the read or write lock, <strong>this method
     *     will block</strong> until they all leave.
     * </ol>
     *
     */
    public void enterWrite() {
        parent.enterWrite();
    }
    
    /** Exit the read lock.
     * For important usage instructions, see {@link #enterRead}.
     *
     * <p>Detailed behavior:
     * <ol>
     * <li>You must have already entered this lock in read mode (once for
     *     every time you exit it).
     * <li>You must exit a lock in the same thread you entered it.
     * <li>If this lock has a level, it must be the last lock with a level
     *     which you entered in this thread. You cannot interleave exits of
     *     locks with levels; they must nest.
     * <li>If this read access is inside another read access, this method
     *     will return immediately.
     * <li>If this read access is the outermost read access, and not inside any
     *     write access, it will return immediately.
     * <li>If this read access is the outermost read access within a write access,
     *     it will return immediately.
     * </ol>
     *
     */
    public void exitRead() {
        parent.exitRead();
    }
    
    /** Exit the write lock.
     * For important usage instructions, see {@link #enterWrite}.
     *
     * <p>Detailed behavior:
     * <ol>
     * <li>You must have already entered this lock in write mode (once for
     *     every time you exit it).
     * <li>You must exit a lock in the same thread you entered it.
     * <li>If this lock has a level, it must be the last lock with a level
     *     which you entered in this thread. You cannot interleave exits of
     *     locks with levels; they must nest.
     * <li>If this write access is inside another write access, this method
     *     will return immediately.
     * <li>If this write access is the outermost write access, it will return
     *     immediately.
     * </ol>
     *
     */
    public void exitWrite() {
        parent.exitWrite();
    }
    
}

