/** @file
 * MS COM / XPCOM Abstraction Layer - ErrorInfo class declaration.
 */

/*
 * Copyright (C) 2006-2015 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */

#ifndef ___VBox_com_ErrorInfo_h
#define ___VBox_com_ErrorInfo_h

#include "VBox/com/ptr.h"
#include "VBox/com/string.h"
#include "VBox/com/Guid.h"
#include "VBox/com/assert.h"


/** @defgroup grp_com_errinfo   ErrorInfo Classes
 * @ingroup grp_com
 * @{
 */

COM_STRUCT_OR_CLASS(IProgress);
COM_STRUCT_OR_CLASS(IVirtualBoxErrorInfo);

namespace com
{

/**
 * General discussion:
 *
 * In COM all errors are stored on a per thread basis. In general this means
 * only _one_ active error is possible per thread. A new error will overwrite
 * the previous one. To prevent this use MultiResult or ErrorInfoKeeper (see
 * below). The implementations in MSCOM/XPCOM differ slightly, but the details
 * are handled by this glue code.
 *
 * We have different classes which are involved in the error management. I try
 * to describe them separately to make clear what they are there for.
 *
 * ErrorInfo:
 *
 *  This class is able to retrieve the per thread error and store it into its
 *  member variables. This class can also handle non-VirtualBox errors (like
 *  standard COM errors).
 *
 * ProgressErrorInfo:
 *
 *  This is just a simple wrapper class to get the ErrorInfo stored within an
 *  IProgress object. That is the error which was stored when the progress
 *  object was in use and not an error produced by IProgress itself.
 *
 * IVirtualBoxErrorInfo:
 *
 *  The VirtualBox interface class for accessing error information from Main
 *  clients. This class is also used for storing the error information in the
 *  thread context.
 *
 * ErrorInfoKeeper:
 *
 *  A helper class which stores the current per thread info internally. After
 *  calling methods which may produce other errors it is possible to restore
 *  the previous error and therefore restore the situation before calling the
 *  other methods.
 *
 * MultiResult:
 *
 *  Creating an instance of MultiResult turns error chain saving on. All errors
 *  which follow will be saved in a chain for later access.
 *
 * COMErrorInfo (Qt/Gui only):
 *
 *  The Qt GUI does some additional work for saving errors. Because we create
 *  wrappers for _every_ COM call, it is possible to automatically save the
 *  error info after the execution. This allow some additional info like saving
 *  the callee. Please note that this error info is saved on the client side
 *  and therefore locally to the object instance. See COMBaseWithEI,
 *  COMErrorInfo and the generated COMWrappers.cpp in the GUI.
 *
 * Errors itself are set in VirtualBoxBase::setErrorInternal. First a
 * IVirtualBoxErrorInfo object is created and the given error is saved within.
 * If MultiResult is active the current per thread error is fetched and
 * attached to the new created IVirtualBoxErrorInfo object. Next this object is
 * set as the new per thread error.
 *
 * Some general hints:
 *
 * - Always use setError, especially when you are working in an asynchronous thread
 *   to indicate an error. Otherwise the error information itself will not make
 *   it into the client.
 *
 */

/**
 *  The ErrorInfo class provides a convenient way to retrieve error
 *  information set by the most recent interface method, that was invoked on
 *  the current thread and returned an unsuccessful result code.
 *
 *  Once the instance of this class is created, the error information for
 *  the current thread is cleared.
 *
 *  There is no sense to use instances of this class after the last
 *  invoked interface method returns a success.
 *
 *  The class usage pattern is as follows:
 *  <code>
 *      IFoo *foo;
 *      ...
 *      HRESULT rc = foo->SomeMethod();
 *      if (FAILED(rc)) {
 *          ErrorInfo info(foo);
 *          if (info.isFullAvailable()) {
 *              printf("error message = %ls\n", info.getText().raw());
 *          }
 *      }
 *  </code>
 *
 *  This class fetches error information using the IErrorInfo interface on
 *  Win32 (MS COM) or the nsIException interface on other platforms (XPCOM),
 *  or the extended IVirtualBoxErrorInfo interface when when it is available
 *  (i.e. a given IErrorInfo or nsIException instance implements it).
 *  Currently, IVirtualBoxErrorInfo is only available for VirtualBox components.
 *
 *  ErrorInfo::isFullAvailable() and ErrorInfo::isBasicAvailable() determine
 *  what level of error information is available. If #isBasicAvailable()
 *  returns true, it means that only IErrorInfo or nsIException is available as
 *  the source of information (depending on the platform), but not
 *  IVirtualBoxErrorInfo. If #isFullAvailable() returns true, it means that all
 *  three interfaces are available. If both methods return false, no error info
 *  is available at all.
 *
 *  Here is a table of correspondence between this class methods and
 *  and IErrorInfo/nsIException/IVirtualBoxErrorInfo attributes/methods:
 *
 *  ErrorInfo       IErrorInfo      nsIException    IVirtualBoxErrorInfo
 *  --------------------------------------------------------------------
 *  getResultCode   --              result          resultCode
 *  getIID          GetGUID         --              interfaceID
 *  getComponent    GetSource       --              component
 *  getText         GetDescription  message         text
 *
 *  '--' means that this interface does not provide the corresponding portion
 *  of information, therefore it is useless to query it if only
 *  #isBasicAvailable() returns true. As it can be seen, the amount of
 *  information provided at the basic level, depends on the platform
 *  (MS COM or XPCOM).
 */
class ErrorInfo
{
public:

    /**
     *  Constructs a new, "interfaceless" ErrorInfo instance that takes
     *  the error information possibly set on the current thread by an
     *  interface method of some COM component or by the COM subsystem.
     *
     *  This constructor is useful, for example, after an unsuccessful attempt
     *  to instantiate (create) a component, so there is no any valid interface
     *  pointer available.
     */
    explicit ErrorInfo()
        : mIsBasicAvailable(false),
          mIsFullAvailable(false),
          mResultCode(S_OK),
          mResultDetail(0),
          m_pNext(NULL)
    {
        init();
    }

    ErrorInfo(IUnknown *pObj, const GUID &aIID)
        : mIsBasicAvailable(false),
          mIsFullAvailable(false),
          mResultCode(S_OK),
          mResultDetail(0),
          m_pNext(NULL)
    {
        init(pObj, aIID);
    }

    /** Specialization for the IVirtualBoxErrorInfo smart pointer */
    ErrorInfo(const ComPtr<IVirtualBoxErrorInfo> &aPtr)
        : mIsBasicAvailable(false), mIsFullAvailable(false)
        , mResultCode(S_OK), mResultDetail(0)
        { init(aPtr); }

    /**
     *  Constructs a new ErrorInfo instance from the IVirtualBoxErrorInfo
     *  interface pointer. If this pointer is not NULL, both #isFullAvailable()
     *  and #isBasicAvailable() will return |true|.
     *
     *  @param aInfo    pointer to the IVirtualBoxErrorInfo interface that
     *                  holds error info to be fetched by this instance
     */
    ErrorInfo(IVirtualBoxErrorInfo *aInfo)
        : mIsBasicAvailable(false), mIsFullAvailable(false)
        , mResultCode(S_OK), mResultDetail(0)
        { init(aInfo); }

    ErrorInfo(const ErrorInfo &x)
    {
        copyFrom(x);
    }

    virtual ~ErrorInfo()
    {
        cleanup();
    }

    ErrorInfo& operator=(const ErrorInfo& x)
    {
        cleanup();
        copyFrom(x);
        return *this;
    }

    /**
     *  Returns whether basic error info is actually available for the current
     *  thread. If the instance was created from an interface pointer that
     *  supports basic error info and successfully provided it, or if it is an
     *  "interfaceless" instance and there is some error info for the current
     *  thread, the returned value will be true.
     *
     *  See the class description for details about the basic error info level.
     *
     *  The appropriate methods of this class provide meaningful info only when
     *  this method returns true (otherwise they simply return NULL-like values).
     */
    bool isBasicAvailable() const
    {
        return mIsBasicAvailable;
    }

    /**
     *  Returns whether full error info is actually available for the current
     *  thread. If the instance was created from an interface pointer that
     *  supports full error info and successfully provided it, or if it is an
     *  "interfaceless" instance and there is some error info for the current
     *  thread, the returned value will be true.
     *
     *  See the class description for details about the full error info level.
     *
     *  The appropriate methods of this class provide meaningful info only when
     *  this method returns true (otherwise they simply return NULL-like values).
     */
    bool isFullAvailable() const
    {
        return mIsFullAvailable;
    }

    /**
     *  Returns the COM result code of the failed operation.
     */
    HRESULT getResultCode() const
    {
        return mResultCode;
    }

    /**
     *  Returns the (optional) result detail code of the failed operation.
     */
    LONG getResultDetail() const
    {
        return mResultDetail;
    }

    /**
     *  Returns the IID of the interface that defined the error.
     */
    const Guid& getInterfaceID() const
    {
        return mInterfaceID;
    }

    /**
     *  Returns the name of the component that generated the error.
     */
    const Bstr& getComponent() const
    {
        return mComponent;
    }

    /**
     *  Returns the textual description of the error.
     */
    const Bstr& getText() const
    {
        return mText;
    }

    /**
     *  Returns the next error information object or @c NULL if there is none.
     */
    const ErrorInfo* getNext() const
    {
        return m_pNext;
    }

    /**
     *  Returns the name of the interface that defined the error
     */
    const Bstr& getInterfaceName() const
    {
        return mInterfaceName;
    }

    /**
     *  Returns the IID of the interface that returned the error.
     *
     *  This method returns a non-null IID only if the instance was created
     *  using template \<class I\> ErrorInfo(I *i) or
     *  template \<class I\> ErrorInfo(const ComPtr<I> &i) constructor.
     *
     *  @todo broken ErrorInfo documentation links, possibly misleading.
     */
    const Guid& getCalleeIID() const
    {
        return mCalleeIID;
    }

    /**
     *  Returns the name of the interface that returned the error
     *
     *  This method returns a non-null name only if the instance was created
     *  using template \<class I\> ErrorInfo(I *i) or
     *  template \<class I\> ErrorInfo(const ComPtr<I> &i) constructor.
     *
     *  @todo broken ErrorInfo documentation links, possibly misleading.
     */
    const Bstr& getCalleeName() const
    {
        return mCalleeName;
    }

    HRESULT getVirtualBoxErrorInfo(ComPtr<IVirtualBoxErrorInfo> &pVirtualBoxErrorInfo);

    /**
     *  Resets all collected error information. #isBasicAvailable() and
     *  #isFullAvailable will return @c true after this method is called.
     */
    void setNull()
    {
        cleanup();
    }

protected:

    ErrorInfo(bool /* aDummy */)
        : mIsBasicAvailable(false),
          mIsFullAvailable(false),
          mResultCode(S_OK),
          m_pNext(NULL)
    { }

    void copyFrom(const ErrorInfo &x);
    void cleanup();

    void init(bool aKeepObj = false);
    void init(IUnknown *aUnk, const GUID &aIID, bool aKeepObj = false);
    void init(IVirtualBoxErrorInfo *aInfo);

    bool mIsBasicAvailable : 1;
    bool mIsFullAvailable : 1;

    HRESULT mResultCode;
    LONG    mResultDetail;
    Guid    mInterfaceID;
    Bstr    mComponent;
    Bstr    mText;

    ErrorInfo *m_pNext;

    Bstr mInterfaceName;
    Guid mCalleeIID;
    Bstr mCalleeName;

    ComPtr<IUnknown> mErrorInfo;
};

/**
 *  A convenience subclass of ErrorInfo that, given an IProgress interface
 *  pointer, reads its errorInfo attribute and uses the returned
 *  IVirtualBoxErrorInfo instance to construct itself.
 */
class ProgressErrorInfo : public ErrorInfo
{
public:

    /**
     *  Constructs a new instance by fetching error information from the
     *  IProgress interface pointer. If the progress object is not NULL,
     *  its completed attribute is true, resultCode represents a failure,
     *  and the errorInfo attribute returns a valid IVirtualBoxErrorInfo pointer,
     *  both #isFullAvailable() and #isBasicAvailable() will return true.
     *
     *  @param  progress    the progress object representing a failed operation
     */
    ProgressErrorInfo(IProgress *progress);
};

/**
 *  A convenience subclass of ErrorInfo that allows to preserve the current
 *  error info. Instances of this class fetch an error info object set on the
 *  current thread and keep a reference to it, which allows to restore it
 *  later using the #restore() method. This is useful to preserve error
 *  information returned by some method for the duration of making another COM
 *  call that may set its own error info and overwrite the existing
 *  one. Preserving and restoring error information makes sense when some
 *  method wants to return error information set by other call as its own
 *  error information while it still needs to make another call before return.
 *
 *  Instead of calling #restore() explicitly you may let the object destructor
 *  do it for you, if you correctly limit the object's lifetime.
 *
 *  The usage pattern is:
 *  <code>
 *      rc = foo->method();
 *      if (FAILED(rc))
 *      {
 *           ErrorInfoKeeper eik;
 *           ...
 *           // bar may return error info as well
 *           bar->method();
 *           ...
 *           // no need to call #restore() explicitly here because the eik's
 *           // destructor will restore error info fetched after the failed
 *           // call to foo before returning to the caller
 *           return rc;
 *      }
 *  </code>
 */
class ErrorInfoKeeper : public ErrorInfo
{
public:

    /**
     *  Constructs a new instance that will fetch the current error info if
     *  @a aIsNull is @c false (by default) or remain uninitialized (null)
     *  otherwise.
     *
     *  @param aIsNull  @c true to prevent fetching error info and leave
     *                  the instance uninitialized.
     */
    ErrorInfoKeeper(bool aIsNull = false)
        : ErrorInfo(false), mForgot(aIsNull)
    {
        if (!aIsNull)
            init(true /* aKeepObj */);
    }

    /**
     *  Constructs a new instance from an ErrorInfo object, to inject a full
     *  error info created elsewhere.
     *
     *  @param aInfo    @c true to prevent fetching error info and leave
     *                  the instance uninitialized.
     */
    ErrorInfoKeeper(const ErrorInfo &aInfo)
        : ErrorInfo(false), mForgot(false)
    {
        copyFrom(aInfo);
    }

    /**
     *  Destroys this instance and automatically calls #restore() which will
     *  either restore error info fetched by the constructor or do nothing
     *  if #forget() was called before destruction.
     */
    ~ErrorInfoKeeper() { if (!mForgot) restore(); }

    /**
     *  Tries to (re-)fetch error info set on the current thread.  On success,
     *  the previous error information, if any, will be overwritten with the
     *  new error information. On failure, or if there is no error information
     *  available, this instance will be reset to null.
     */
    void fetch()
    {
        setNull();
        mForgot = false;
        init(true /* aKeepObj */);
    }

    /**
     *  Restores error info fetched by the constructor and forgets it
     *  afterwards. Does nothing if the error info was forgotten by #forget().
     *
     *  @return COM result of the restore operation.
     */
    HRESULT restore();

    /**
     *  Forgets error info fetched by the constructor to prevent it from
     *  being restored by #restore() or by the destructor.
     */
    void forget() { mForgot = true; }

    /**
     *  Forgets error info fetched by the constructor to prevent it from
     *  being restored by #restore() or by the destructor, and returns the
     *  stored error info object to the caller.
     */
    ComPtr<IUnknown> takeError() { mForgot = true; return mErrorInfo; }

private:

    bool mForgot : 1;
};

} /* namespace com */

/** @} */

#endif

