/*
 * Written by Dawid Kurzyniec and released to the public domain, as explained
 * at http://creativecommons.org/licenses/publicdomain
 */

package edu.emory.mathcs.util.concurrent;

import java.util.*;

import edu.emory.mathcs.backport.java.util.concurrent.*;
import edu.emory.mathcs.backport.java.util.concurrent.locks.*;
import edu.emory.mathcs.backport.java.util.concurrent.locks.Lock;
import edu.emory.mathcs.util.collections.*;

/**
 * Implements a reentrant named lock that can be shared between different parts
 * of the application without sharing the lock object, by using a common name.
 *
 * @author Dawid Kurzyniec
 */
public class ReentrantNamedLock implements Lock {

    /** Name object that identifies the lock. */
    private final Object name;

    /** Resolved lock, used for the actual synchronization */
    private final ReentrantLock resolvedLock;

    /**
     * Create a new ReentrantRemoteLock instance.
     */
    public ReentrantNamedLock(Object name) {
        this.name = name;
        this.resolvedLock = getLockFor(name);
    }

    /**
    * {@inheritDoc}
    */
    public void lock() {
        resolvedLock.lock();
        if (resolvedLock.getHoldCount() == 1) pin(resolvedLock);
    }

    /**
    * {@inheritDoc}
    */
    public boolean tryLock() {
        if (!resolvedLock.tryLock()) return false;
        if (resolvedLock.getHoldCount() == 1) pin(resolvedLock);
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public void lockInterruptibly() throws InterruptedException {
        resolvedLock.lockInterruptibly();
        if (resolvedLock.getHoldCount() == 1) pin(resolvedLock);
    }

    /**
    * {@inheritDoc}
    */
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        if (!resolvedLock.tryLock(timeout, unit)) return false;
        if (resolvedLock.getHoldCount() == 1) pin(resolvedLock);
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public void unlock() {
        // must unpin w/lock held
        if (resolvedLock.getHoldCount() == 1) unpin(resolvedLock);
        resolvedLock.unlock();
    }

    /**
     * {@inheritDoc}
     */
    public Condition newCondition() {
        // safe to just return the underlying condition, because, as long as
        // the condition exists, it keeps strong reference to the resolved lock,
        // so it can't disappear from the resolver map, so anybody else who
        // will come asking for a condition will obtain the same one.
        return resolvedLock.newCondition();
    }

    private static WeakValueHashMap lockResolver = new WeakValueHashMap();

    // ensures that locked ReentrantLocks do not dissappear (from the weak-value
    // resolver map), which would be terrible and lead to deadlocks and other
    // very bad things. (It is OK for unlocked ones to disappear though).
    private static Set lockedLocks = new HashSet();

    /**
     * Returns an internal ReentrantLock object, shared between all instances
     * of ReentrantNamedLock with the same name. (Name identity is defined
     * in terms of the equals() method).
     * A static instance of WeakValueHashMap is used to keep track of the
     * (name,resolved lock) pairs.
     *
     * @param name name of the named lock
     * @return an interned reentrant lock
     */
    synchronized static ReentrantLock getLockFor(Object name) {
        ReentrantLock lock = (ReentrantLock)lockResolver.get(name);
        if (lock == null) {
            lock = new ReentrantLock();
            lockResolver.put(name, lock);
        }
        return lock;
    }

    /**
     * Adds a reentrant lock to the set of locked reentrant locks, removing
     * the possibility that it becomes subject to garbage collection.
     *
     * @param lock reentrant lock to add
     */
    synchronized static void pin(ReentrantLock lock) {
        lockedLocks.add(lock);
    }

    /**
     * Removes a reentrant lock from the set of locked reentrant locks, opening
     * up the possibility that it becomes subject to garbage collection.
     *
     * @param lock reentrant lock to remove
     */
    synchronized static void unpin(ReentrantLock lock) {
        lockedLocks.remove(lock);
    }

    public int hashCode() {
        return name.hashCode();
    }

    /**
     * Two named locks are equal if their names are equal.
     */
    public boolean equals(Object other) {
        if (other == this) return true;
        if (! (other instanceof ReentrantNamedLock)) {
            return false;
        }
        ReentrantNamedLock that = (ReentrantNamedLock) other;
        return this.name.equals(that.name);
    }
}
