/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (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.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 */


/*

  A stack-based lock object that makes using PRLock a bit more
  convenient. It acquires the monitor when constructed, and releases
  it when it goes out of scope.

  For example,

    class Foo {
    private:
        PRLock* mLock;

    public:
        Foo(void) {
            mLock = PR_NewLock();
        }

        virtual ~Foo(void) {
            PR_DestroyLock(mLock);
        }

        void ThreadSafeMethod(void) {
            // we're don't hold the lock yet...

            nsAutoLock lock(mLock);
            // ...but now we do.

            // we even can do wacky stuff like return from arbitrary places w/o
            // worrying about forgetting to release the lock
            if (some_weird_condition)
                return;

            // otherwise do some other stuff
        }

        void ThreadSafeBlockScope(void) {
            // we're not in the lock here...

            {
                nsAutoLock lock(mLock);
                // but we are now, at least until the block scope closes
            }

            // ...now we're not in the lock anymore
        }
    };

 */

#ifndef nsAutoLock_h__
#define nsAutoLock_h__

#include "nscore.h"
#include "prlock.h"
#include "prlog.h"

class NS_COM nsAutoLockBase {
protected:
    nsAutoLockBase() {}
    enum nsAutoLockType {eAutoLock, eAutoMonitor, eAutoCMonitor};

#ifdef DEBUG
    nsAutoLockBase(void* addr, nsAutoLockType type);
    ~nsAutoLockBase();

    void*           mAddr;
    nsAutoLockBase* mDown;
    nsAutoLockType  mType;
#else
    nsAutoLockBase(void* addr, nsAutoLockType type) {}
    ~nsAutoLockBase() {}
#endif
};

// If you ever decide that you need to add a non-inline method to this
// class, be sure to change the class declaration to "class NS_COM
// nsAutoLock".

class NS_COM nsAutoLock : public nsAutoLockBase {
private:
    PRLock* mLock;
    PRBool mLocked;

    // Not meant to be implemented. This makes it a compiler error to
    // construct or assign an nsAutoLock object incorrectly.
    nsAutoLock(void) {}
    nsAutoLock(nsAutoLock& /*aLock*/) {}
    nsAutoLock& operator =(nsAutoLock& /*aLock*/) {
        return *this;
    }

    // Not meant to be implemented. This makes it a compiler error to
    // attempt to create an nsAutoLock object on the heap.
#if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95))
    static void* operator new(size_t /*size*/) throw () {
#else
    static void* operator new(size_t /*size*/) {
#endif
        return nsnull;
    }
    static void operator delete(void* /*memory*/) {}

public:
    nsAutoLock(PRLock* aLock)
        : nsAutoLockBase(aLock, eAutoLock),
          mLock(aLock),
          mLocked(PR_TRUE) {
        PR_ASSERT(mLock);

        // This will assert deep in the bowels of NSPR if you attempt
        // to re-enter the lock.
        PR_Lock(mLock);
    }

    ~nsAutoLock(void) {
        if (mLocked)
            PR_Unlock(mLock);
    }

    void lock() {
        PR_ASSERT(!mLocked);
        PR_Lock(mLock);
        mLocked = PR_TRUE;
    }

    void unlock() {
        PR_ASSERT(mLocked);
        PR_Unlock(mLock);
        mLocked = PR_FALSE;
    }
};

////////////////////////////////////////////////////////////////////////////////
// Same sort of shit here. Imagine if you will:
//
//    nsresult MyClass::MyMethod(...) {
//       nsAutoMonitor mon(this);   // or some random object as a monitor
//       ...
//       // go ahead and do deeply nested returns...
//                    return NS_ERROR_FAILURE;
//       ...
//       // or call Wait or Notify...
//       mon.Wait();
//       ...
//       // cleanup is automatic
//    }

#include "prcmon.h"
#include "nsError.h"

class NS_COM nsAutoMonitor : public nsAutoLockBase {
public:
    static PRMonitor* NewMonitor(const char* name);
    static void       DestroyMonitor(PRMonitor* mon);

    nsAutoMonitor(PRMonitor* mon)
        : nsAutoLockBase((void*)mon, eAutoMonitor),
          mMonitor(mon)
    {
        NS_ASSERTION(mMonitor, "null monitor");
        PR_EnterMonitor(mMonitor);
    }

    ~nsAutoMonitor() {
#ifdef DEBUG
        PRStatus status = 
#endif
         PR_ExitMonitor(mMonitor);
        NS_ASSERTION(status == PR_SUCCESS, "PR_ExitMonitor failed");
    }

    void Enter();
    void Exit();

    nsresult Wait(PRIntervalTime interval = PR_INTERVAL_NO_TIMEOUT) {
        return PR_Wait(mMonitor, interval) == PR_SUCCESS
            ? NS_OK : NS_ERROR_FAILURE;
    }

    nsresult Notify() {
        return PR_Notify(mMonitor) == PR_SUCCESS
            ? NS_OK : NS_ERROR_FAILURE;
    }

    nsresult NotifyAll() {
        return PR_NotifyAll(mMonitor) == PR_SUCCESS
            ? NS_OK : NS_ERROR_FAILURE;
    }

private:
    PRMonitor*  mMonitor;

    // Not meant to be implemented. This makes it a compiler error to
    // construct or assign an nsAutoLock object incorrectly.
    nsAutoMonitor(void) {}
    nsAutoMonitor(nsAutoMonitor& /*aMon*/) {}
    nsAutoMonitor& operator =(nsAutoMonitor& /*aMon*/) {
        return *this;
    }

    // Not meant to be implemented. This makes it a compiler error to
    // attempt to create an nsAutoLock object on the heap.
#if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95))
    static void* operator new(size_t /*size*/) throw () {
#else
    static void* operator new(size_t /*size*/) {
#endif
        return nsnull;
    }
    static void operator delete(void* /*memory*/) {}
};

////////////////////////////////////////////////////////////////////////////////
// Once again, this time with a cache...
// (Using this avoids the need to allocate a PRMonitor, which may be useful when
// a large number of objects of the same class need associated monitors.)

#include "prcmon.h"
#include "nsError.h"

class NS_COM nsAutoCMonitor : public nsAutoLockBase {
public:
    nsAutoCMonitor(void* lockObject)
        : nsAutoLockBase(lockObject, eAutoCMonitor),
          mLockObject(lockObject)
    {
        NS_ASSERTION(lockObject, "null lock object");
        PR_CEnterMonitor(mLockObject);
    }

    ~nsAutoCMonitor() {
#ifdef DEBUG
        PRStatus status =
#endif
         PR_CExitMonitor(mLockObject);
        NS_ASSERTION(status == PR_SUCCESS, "PR_CExitMonitor failed");
    }

    void Enter();
    void Exit();

    nsresult Wait(PRIntervalTime interval = PR_INTERVAL_NO_TIMEOUT) {
        return PR_CWait(mLockObject, interval) == PR_SUCCESS
            ? NS_OK : NS_ERROR_FAILURE;
    }

    nsresult Notify() {
        return PR_CNotify(mLockObject) == PR_SUCCESS
            ? NS_OK : NS_ERROR_FAILURE;
    }

    nsresult NotifyAll() {
        return PR_CNotifyAll(mLockObject) == PR_SUCCESS
            ? NS_OK : NS_ERROR_FAILURE;
    }

private:
    void* mLockObject;

    // Not meant to be implemented. This makes it a compiler error to
    // construct or assign an nsAutoLock object incorrectly.
    nsAutoCMonitor(void) {}
    nsAutoCMonitor(nsAutoCMonitor& /*aMon*/) {}
    nsAutoCMonitor& operator =(nsAutoCMonitor& /*aMon*/) {
        return *this;
    }

    // Not meant to be implemented. This makes it a compiler error to
    // attempt to create an nsAutoLock object on the heap.
#if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95))
    static void* operator new(size_t /*size*/) throw() {
#else
    static void* operator new(size_t /*size*/) {
#endif
        return nsnull;
    }
    static void operator delete(void* /*memory*/) {}
};

#endif // nsAutoLock_h__
