// **********************************************************************
//
// Copyright (c) 2003-2007 ZeroC, Inc. All rights reserved.
//
// This copy of Ice is licensed to you under the terms described in the
// ICE_LICENSE file included in this distribution.
//
// **********************************************************************

#ifdef _WIN32
#   include <IceUtil/Config.h>
#endif
#include <Operation.h>
#include <Current.h>
#include <Proxy.h>
#include <Types.h>
#include <Util.h>
#include <Ice/Communicator.h>
#include <Ice/IdentityUtil.h>
#include <Ice/IncomingAsync.h>
#include <Ice/Initialize.h>
#include <Ice/LocalException.h>
#include <Ice/ObjectAdapter.h>
#include <Ice/OutgoingAsync.h>
#include <Ice/Proxy.h>
#include <Slice/PythonUtil.h>

using namespace std;
using namespace IcePy;
using namespace Slice::Python;

namespace IcePy
{

class ParamInfo : public UnmarshalCallback
{
public:

    virtual void unmarshaled(PyObject*, PyObject*, void*);

    Ice::StringSeq metaData;
    TypeInfoPtr type;
};
typedef IceUtil::Handle<ParamInfo> ParamInfoPtr;
typedef vector<ParamInfoPtr> ParamInfoList;

class OperationI : public Operation
{
public:

    OperationI(const char*, PyObject*, PyObject*, int, PyObject*, PyObject*, PyObject*, PyObject*, PyObject*);

    virtual PyObject* invoke(const Ice::ObjectPrx&, PyObject*, PyObject*);
    virtual PyObject* invokeAsync(const Ice::ObjectPrx&, PyObject*, PyObject*, PyObject*);
    virtual void deprecate(const string&);
    virtual Ice::OperationMode mode() const;

    virtual void dispatch(PyObject*, const Ice::AMD_Object_ice_invokePtr&, const vector<Ice::Byte>&,
                          const Ice::Current&);

    void responseAsync(PyObject*, bool, const vector<Ice::Byte>&, const Ice::CommunicatorPtr&);
    void responseAsyncException(PyObject*, PyObject*);

    void sendResponse(const Ice::AMD_Object_ice_invokePtr&, PyObject*, const Ice::CommunicatorPtr&);
    void sendException(const Ice::AMD_Object_ice_invokePtr&, PyException&, const Ice::CommunicatorPtr&);

private:

    string _name;
    Ice::OperationMode _mode;
    Ice::OperationMode _sendMode;
    bool _amd;
    Ice::StringSeq _metaData;
    ParamInfoList _inParams;
    ParamInfoList _outParams;
    ParamInfoPtr _returnType;
    ExceptionInfoList _exceptions;
    string _dispatchName;
    bool _sendsClasses;
    bool _returnsClasses;
    string _deprecateMessage;

    bool prepareRequest(const Ice::CommunicatorPtr&, PyObject*, bool, vector<Ice::Byte>&);
    PyObject* unmarshalResults(const vector<Ice::Byte>&, const Ice::CommunicatorPtr&);
    PyObject* unmarshalException(const vector<Ice::Byte>&, const Ice::CommunicatorPtr&);
    bool validateException(PyObject*) const;
    void checkTwowayOnly(const Ice::ObjectPrx&) const;
    static void convertParams(PyObject*, ParamInfoList&, bool&);
};
typedef IceUtil::Handle<OperationI> OperationIPtr;

class AMICallback : public Ice::AMI_Object_ice_invoke
{
public:

    AMICallback(const OperationIPtr&, const Ice::CommunicatorPtr&, PyObject*);
    ~AMICallback();

    virtual void ice_response(bool, const vector<Ice::Byte>&);
    virtual void ice_exception(const Ice::Exception&);

private:

    OperationIPtr _op;
    Ice::CommunicatorPtr _communicator;
    PyObject* _callback;
};

struct OperationObject
{
    PyObject_HEAD
    OperationPtr* op;
};

struct AMDCallbackObject
{
    PyObject_HEAD
    OperationIPtr* op;
    Ice::CommunicatorPtr* communicator;
    Ice::AMD_Object_ice_invokePtr* cb;
};

extern PyTypeObject OperationType;
extern PyTypeObject AMDCallbackType;

}

#ifdef WIN32
extern "C"
#endif
static OperationObject*
operationNew(PyObject* /*arg*/)
{
    OperationObject* self = PyObject_New(OperationObject, &OperationType);
    if(!self)
    {
        return 0;
    }
    self->op = 0;
    return self;
}

#ifdef WIN32
extern "C"
#endif
static int
operationInit(OperationObject* self, PyObject* args, PyObject* /*kwds*/)
{
    char* name;
    PyObject* modeType = lookupType("Ice.OperationMode");
    assert(modeType);
    PyObject* mode;
    PyObject* sendMode;
    int amd;
    PyObject* meta;
    PyObject* inParams;
    PyObject* outParams;
    PyObject* returnType;
    PyObject* exceptions;
    if(!PyArg_ParseTuple(args, STRCAST("sO!O!iO!O!O!OO!"), &name, modeType, &mode, modeType, &sendMode, &amd,
                         &PyTuple_Type, &meta, &PyTuple_Type, &inParams, &PyTuple_Type, &outParams, &returnType,
                         &PyTuple_Type, &exceptions))
    {
        return -1;
    }

    OperationIPtr op = new OperationI(name, mode, sendMode, amd, meta, inParams, outParams, returnType, exceptions);
    self->op = new OperationPtr(op);

    return 0;
}

#ifdef WIN32
extern "C"
#endif
static void
operationDealloc(OperationObject* self)
{
    delete self->op;
    PyObject_Del(self);
}

#ifdef WIN32
extern "C"
#endif
static PyObject*
operationInvoke(OperationObject* self, PyObject* args)
{
    PyObject* pyProxy;
    PyObject* opArgs;
    PyObject* ctx;
    if(!PyArg_ParseTuple(args, STRCAST("O!O!O"), &ProxyType, &pyProxy, &PyTuple_Type, &opArgs, &ctx))
    {
        return 0;
    }

    if(ctx != Py_None && !PyDict_Check(ctx))
    {
        PyErr_Format(PyExc_ValueError, STRCAST("context argument must be None or a dictionary"));
        return 0;
    }

    Ice::ObjectPrx prx = getProxy(pyProxy);

    assert(self->op);
    return (*self->op)->invoke(prx, opArgs, ctx);
}

#ifdef WIN32
extern "C"
#endif
static PyObject*
operationInvokeAsync(OperationObject* self, PyObject* args)
{
    PyObject* pyProxy;
    PyObject* cb;
    PyObject* opArgs;
    PyObject* ctx;
    if(!PyArg_ParseTuple(args, STRCAST("O!OO!O"), &ProxyType, &pyProxy, &cb, &PyTuple_Type, &opArgs, &ctx))
    {
        return 0;
    }

    if(ctx != Py_None && !PyDict_Check(ctx))
    {
        PyErr_Format(PyExc_ValueError, STRCAST("context argument must be None or a dictionary"));
        return 0;
    }

    Ice::ObjectPrx prx = getProxy(pyProxy);

    assert(self->op);
    return (*self->op)->invokeAsync(prx, cb, opArgs, ctx);
}

#ifdef WIN32
extern "C"
#endif
static PyObject*
operationDeprecate(OperationObject* self, PyObject* args)
{
    char* msg;
    if(!PyArg_ParseTuple(args, STRCAST("s"), &msg))
    {
        return 0;
    }

    assert(self->op);
    (*self->op)->deprecate(msg);

    Py_INCREF(Py_None);
    return Py_None;
}

#ifdef WIN32
extern "C"
#endif
static AMDCallbackObject*
amdCallbackNew(PyObject* /*arg*/)
{
    AMDCallbackObject* self = PyObject_New(AMDCallbackObject, &AMDCallbackType);
    if(!self)
    {
        return 0;
    }
    self->op = 0;
    self->communicator = 0;
    self->cb = 0;
    return self;
}

#ifdef WIN32
extern "C"
#endif
static void
amdCallbackDealloc(AMDCallbackObject* self)
{
    delete self->op;
    delete self->communicator;
    delete self->cb;
    PyObject_Del(self);
}

#ifdef WIN32
extern "C"
#endif
static PyObject*
amdCallbackIceResponse(AMDCallbackObject* self, PyObject* args)
{
    try
    {
        assert(self->op);
        (*self->op)->sendResponse(*self->cb, args, *self->communicator);
    }
    catch(const Ice::Exception& ex)
    {
        (*self->cb)->ice_exception(ex);
    }
    catch(...)
    {
        //
        // No exceptions should propagate to Python.
        //
        assert(false);
    }

    Py_INCREF(Py_None);
    return Py_None;
}

#ifdef WIN32
extern "C"
#endif
static PyObject*
amdCallbackIceException(AMDCallbackObject* self, PyObject* args)
{
    PyObject* ex;
    if(!PyArg_ParseTuple(args, STRCAST("O"), &ex))
    {
        return 0;
    }

    try
    {
        assert(self->op);
        PyException pye(ex); // No traceback information available.
        (*self->op)->sendException(*self->cb, pye, *self->communicator);
    }
    catch(const Ice::Exception& ex)
    {
        (*self->cb)->ice_exception(ex);
    }
    catch(...)
    {
        //
        // No exceptions should propagate to Python.
        //
        assert(false);
    }

    Py_INCREF(Py_None);
    return Py_None;
}

//
// Operation implementation.
//
IcePy::Operation::~Operation()
{
}

//
// ParamInfo implementation.
//
void
IcePy::ParamInfo::unmarshaled(PyObject* val, PyObject* target, void* closure)
{
    assert(PyTuple_Check(target));
    long i = reinterpret_cast<long>(closure);
    PyTuple_SET_ITEM(target, i, val);
    Py_INCREF(val); // PyTuple_SET_ITEM steals a reference.
}

//
// AMICallback implementation.
//
IcePy::AMICallback::AMICallback(const OperationIPtr& op, const Ice::CommunicatorPtr& communicator,
                                PyObject* callback) :
    _op(op), _communicator(communicator), _callback(callback)
{
    Py_INCREF(_callback);
}

IcePy::AMICallback::~AMICallback()
{
    AdoptThread adoptThread; // Ensure the current thread is able to call into Python.

    Py_DECREF(_callback);
}

void
IcePy::AMICallback::ice_response(bool ok, const vector<Ice::Byte>& result)
{
    AdoptThread adoptThread; // Ensure the current thread is able to call into Python.

    _op->responseAsync(_callback, ok, result, _communicator);
}

void
IcePy::AMICallback::ice_exception(const Ice::Exception& ex)
{
    AdoptThread adoptThread; // Ensure the current thread is able to call into Python.

    PyObjectHandle exh = convertException(ex);
    assert(exh.get());
    _op->responseAsyncException(_callback, exh.get());
}

//
// OperationI implementation.
//
IcePy::OperationI::OperationI(const char* name, PyObject* mode, PyObject* sendMode, int amd, PyObject* meta,
                              PyObject* inParams, PyObject* outParams, PyObject* returnType, PyObject* exceptions)
{
    _name = name;

    //
    // mode
    //
    PyObjectHandle modeValue = PyObject_GetAttrString(mode, STRCAST("value"));
    assert(PyInt_Check(modeValue.get()));
    _mode = (Ice::OperationMode)static_cast<int>(PyInt_AS_LONG(modeValue.get()));

    //
    // sendMode
    //
    PyObjectHandle sendModeValue = PyObject_GetAttrString(sendMode, STRCAST("value"));
    assert(PyInt_Check(sendModeValue.get()));
    _sendMode = (Ice::OperationMode)static_cast<int>(PyInt_AS_LONG(sendModeValue.get()));

    //
    // amd
    //
    _amd = amd ? true : false;
    if(_amd)
    {
        _dispatchName = fixIdent(_name) + "_async";
    }
    else
    {
        _dispatchName = fixIdent(_name);
    }

    //
    // metaData
    //
#ifndef NDEBUG
    bool b =
#endif
    tupleToStringSeq(meta, _metaData);
    assert(b);

    Py_ssize_t i, sz;

    //
    // inParams
    //
    convertParams(inParams, _inParams, _sendsClasses);

    //
    // outParams
    //
    convertParams(outParams, _outParams, _returnsClasses);

    //
    // returnType
    //
    if(returnType != Py_None)
    {
        _returnType = new ParamInfo;
        _returnType->type = getType(returnType);
        if(!_returnsClasses)
        {
            _returnsClasses = _returnType->type->usesClasses();
        }
    }

    //
    // exceptions
    //
    sz = PyTuple_GET_SIZE(exceptions);
    for(i = 0; i < sz; ++i)
    {
        _exceptions.push_back(getException(PyTuple_GET_ITEM(exceptions, i)));
    }
}

PyObject*
IcePy::OperationI::invoke(const Ice::ObjectPrx& proxy, PyObject* args, PyObject* pyctx)
{
    Ice::CommunicatorPtr communicator = proxy->ice_getCommunicator();

    //
    // Marshal the input parameters to a byte sequence.
    //
    Ice::ByteSeq params;
    if(!prepareRequest(communicator, args, false, params))
    {
        return 0;
    }

    if(!_deprecateMessage.empty())
    {
        PyErr_Warn(PyExc_DeprecationWarning, const_cast<char*>(_deprecateMessage.c_str()));
        _deprecateMessage.clear(); // Only show the warning once.
    }

    try
    {
        checkTwowayOnly(proxy);

        //
        // Invoke the operation.
        //
        Ice::ByteSeq result;
        bool status;
        {
            if(pyctx != Py_None)
            {
                Ice::Context ctx;

                if(!PyDict_Check(pyctx))
                {
                    PyErr_Format(PyExc_ValueError, STRCAST("context argument must be None or a dictionary"));
                    return 0;
                }

                if(!dictionaryToContext(pyctx, ctx))
                {
                    return 0;
                }

                AllowThreads allowThreads; // Release Python's global interpreter lock during remote invocations.
                status = proxy->ice_invoke(_name, _sendMode, params, result, ctx);
            }
            else
            {
                AllowThreads allowThreads; // Release Python's global interpreter lock during remote invocations.
                status = proxy->ice_invoke(_name, _sendMode, params, result);
            }
        }

        //
        // Process the reply.
        //
        if(proxy->ice_isTwoway())
        {
            if(!status)
            {
                //
                // Unmarshal a user exception.
                //
                PyObjectHandle ex = unmarshalException(result, communicator);

                //
                // Set the Python exception.
                //
                setPythonException(ex.get());
                return 0;
            }
            else if(_outParams.size() > 0 || _returnType)
            {
                //
                // Unmarshal the results. If there is more than one value to be returned, then return them
                // in a tuple of the form (result, outParam1, ...). Otherwise just return the value.
                //
                PyObjectHandle results = unmarshalResults(result, communicator);
                if(!results.get())
                {
                    return 0;
                }

                if(PyTuple_GET_SIZE(results.get()) > 1)
                {
                    return results.release();
                }
                else
                {
                    PyObject* ret = PyTuple_GET_ITEM(results.get(), 0);
                    Py_INCREF(ret);
                    return ret;
                }
            }
        }
    }
    catch(const AbortMarshaling&)
    {
        return 0;
    }
    catch(const Ice::Exception& ex)
    {
        setPythonException(ex);
        return 0;
    }

    Py_INCREF(Py_None);
    return Py_None;
}

PyObject*
IcePy::OperationI::invokeAsync(const Ice::ObjectPrx& proxy, PyObject* callback, PyObject* args, PyObject* pyctx)
{
    Ice::CommunicatorPtr communicator = proxy->ice_getCommunicator();

    //
    // Marshal the input parameters to a byte sequence.
    //
    Ice::ByteSeq params;
    if(!prepareRequest(communicator, args, true, params))
    {
        return 0;
    }

    if(!_deprecateMessage.empty())
    {
        PyErr_Warn(PyExc_DeprecationWarning, const_cast<char*>(_deprecateMessage.c_str()));
        _deprecateMessage.clear(); // Only show the warning once.
    }

    Ice::AMI_Object_ice_invokePtr cb = new AMICallback(this, communicator, callback);
    try
    {
        checkTwowayOnly(proxy);

        //
        // Invoke the operation asynchronously.
        //
        if(pyctx != Py_None)
        {
            Ice::Context ctx;

            if(!PyDict_Check(pyctx))
            {
                PyErr_Format(PyExc_ValueError, STRCAST("context argument must be None or a dictionary"));
                return 0;
            }

            if(!dictionaryToContext(pyctx, ctx))
            {
                return 0;
            }

            AllowThreads allowThreads; // Release Python's global interpreter lock during remote invocations.
            proxy->ice_invoke_async(cb, _name, _sendMode, params, ctx);
        }
        else
        {
            AllowThreads allowThreads; // Release Python's global interpreter lock during remote invocations.
            proxy->ice_invoke_async(cb, _name, _sendMode, params);
        }
    }
    catch(const Ice::Exception& ex)
    {
        cb->ice_exception(ex);
    }

    Py_INCREF(Py_None);
    return Py_None;
}

void
IcePy::OperationI::deprecate(const string& msg)
{
    if(!msg.empty())
    {
        _deprecateMessage = msg;
    }
    else
    {
        _deprecateMessage = "operation " + _name + " is deprecated";
    }
}

Ice::OperationMode
IcePy::OperationI::mode() const
{
    return _mode;
}

void
IcePy::OperationI::dispatch(PyObject* servant, const Ice::AMD_Object_ice_invokePtr& cb,
                            const vector<Ice::Byte>& inBytes, const Ice::Current& current)
{
    Ice::CommunicatorPtr communicator = current.adapter->getCommunicator();

    //
    // Unmarshal the in parameters.
    //
    Py_ssize_t count = static_cast<Py_ssize_t>(_inParams.size()) + 1; // Leave room for a trailing Ice::Current object.

    Py_ssize_t start = 0;
    if(_amd)
    {
        ++count; // Leave room for a leading AMD callback argument.
        start = 1;
    }

    PyObjectHandle args = PyTuple_New(count);
    if(!args.get())
    {
        throwPythonException();
    }

    if(!_inParams.empty())
    {
        Ice::InputStreamPtr is = Ice::createInputStream(communicator, inBytes);
        try
        {
            Py_ssize_t i = start;
            for(ParamInfoList::iterator p = _inParams.begin(); p != _inParams.end(); ++p, ++i)
            {
                void* closure = reinterpret_cast<void*>(i);
                (*p)->type->unmarshal(is, *p, args.get(), closure, &(*p)->metaData);
            }
            if(_sendsClasses)
            {
                is->readPendingObjects();
            }
        }
        catch(const AbortMarshaling&)
        {
            throwPythonException();
        }
    }

    //
    // Create an object to represent Ice::Current. We need to append this to the argument tuple.
    //
    PyObjectHandle curr = createCurrent(current);
    if(PyTuple_SET_ITEM(args.get(), PyTuple_GET_SIZE(args.get()) - 1, curr.get()) < 0)
    {
        throwPythonException();
    }
    curr.release(); // PyTuple_SET_ITEM steals a reference.

    if(_amd)
    {
        //
        // Create the callback object and pass it as the first argument.
        //
        AMDCallbackObject* obj = amdCallbackNew(0);
        if(!obj)
        {
            throwPythonException();
        }
        obj->op = new OperationIPtr(this);
        obj->communicator = new Ice::CommunicatorPtr(communicator);
        obj->cb = new Ice::AMD_Object_ice_invokePtr(cb);
        if(PyTuple_SET_ITEM(args.get(), 0, (PyObject*)obj) < 0) // PyTuple_SET_ITEM steals a reference.
        {
            Py_DECREF(obj);
            throwPythonException();
        }
    }

    //
    // Dispatch the operation. Use _dispatchName here, not current.operation.
    //
    PyObjectHandle method = PyObject_GetAttrString(servant, const_cast<char*>(_dispatchName.c_str()));
    if(!method.get())
    {
        ostringstream ostr;
        ostr << "servant for identity " << communicator->identityToString(current.id) << " does not define operation `"
             << _dispatchName << "'";
        string str = ostr.str();
        PyErr_Warn(PyExc_RuntimeWarning, const_cast<char*>(str.c_str()));
        Ice::UnknownException ex(__FILE__, __LINE__);
        ex.unknown = str;
        throw ex;
    }

    PyObjectHandle result = PyObject_Call(method.get(), args.get(), 0);

    //
    // Check for exceptions.
    //
    if(PyErr_Occurred())
    {
        PyException ex; // Retrieve it before another Python API call clears it.
        sendException(cb, ex, communicator);
        return;
    }

    if(!_amd)
    {
        sendResponse(cb, result.get(), communicator);
    }
}

void
IcePy::OperationI::responseAsync(PyObject* callback, bool ok, const vector<Ice::Byte>& results,
                                 const Ice::CommunicatorPtr& communicator)
{
    try
    {
        if(ok)
        {
            //
            // Unmarshal the results.
            //
            PyObjectHandle args;
            try
            {
                args = unmarshalResults(results, communicator);
                if(!args.get())
                {
                    assert(PyErr_Occurred());
                    PyErr_Print();
                    return;
                }
            }
            catch(const Ice::Exception& ex)
            {
                PyObjectHandle h = convertException(ex);
                responseAsyncException(callback, h.get());
                return;
            }

            PyObjectHandle method = PyObject_GetAttrString(callback, STRCAST("ice_response"));
            if(!method.get())
            {
                ostringstream ostr;
                ostr << "AMI callback object for operation `" << _name << "' does not define ice_response()";
                string str = ostr.str();
                PyErr_Warn(PyExc_RuntimeWarning, const_cast<char*>(str.c_str()));
            }
            else
            {
                PyObjectHandle tmp = PyObject_Call(method.get(), args.get(), 0);
                if(PyErr_Occurred())
                {
                    PyErr_Print();
                }
            }
        }
        else
        {
            PyObjectHandle ex = unmarshalException(results, communicator);
            responseAsyncException(callback, ex.get());
        }
    }
    catch(const AbortMarshaling&)
    {
        assert(PyErr_Occurred());
        PyErr_Print();
    }
    catch(const Ice::Exception& ex)
    {
        ostringstream ostr;
        ostr << "Exception raised by AMI callback for operation `" << _name << "':" << ex;
        string str = ostr.str();
        PyErr_Warn(PyExc_RuntimeWarning, const_cast<char*>(str.c_str()));
    }
}

void
IcePy::OperationI::responseAsyncException(PyObject* callback, PyObject* ex)
{
    PyObjectHandle method = PyObject_GetAttrString(callback, STRCAST("ice_exception"));
    if(!method.get())
    {
        ostringstream ostr;
        ostr << "AMI callback object for operation `" << _name << "' does not define ice_exception()";
        string str = ostr.str();
        PyErr_Warn(PyExc_RuntimeWarning, const_cast<char*>(str.c_str()));
    }
    else
    {
        PyObjectHandle args = Py_BuildValue(STRCAST("(O)"), ex);
        PyObjectHandle tmp = PyObject_Call(method.get(), args.get(), 0);
        if(PyErr_Occurred())
        {
            PyErr_Print();
        }
    }
}

void
IcePy::OperationI::sendResponse(const Ice::AMD_Object_ice_invokePtr& cb, PyObject* args,
                                const Ice::CommunicatorPtr& communicator)
{
    //
    // Marshal the results. If there is more than one value to be returned, then they must be
    // returned in a tuple of the form (result, outParam1, ...).
    //
    Ice::OutputStreamPtr os = Ice::createOutputStream(communicator);
    try
    {
        Py_ssize_t i = _returnType ? 1 : 0;
        Py_ssize_t numResults = static_cast<Py_ssize_t>(_outParams.size()) + i;
        if(numResults > 1)
        {
            if(!PyTuple_Check(args) || PyTuple_GET_SIZE(args) != numResults)
            {
                ostringstream ostr;
                ostr << "operation `" << fixIdent(_name) << "' should return a tuple of length " << numResults;
                string str = ostr.str();
                PyErr_Warn(PyExc_RuntimeWarning, const_cast<char*>(str.c_str()));
                throw Ice::MarshalException(__FILE__, __LINE__);
            }
        }

        ObjectMap objectMap;

        for(ParamInfoList::iterator p = _outParams.begin(); p != _outParams.end(); ++p, ++i)
        {
            PyObject* arg;
            if(_amd || numResults > 1)
            {
                arg = PyTuple_GET_ITEM(args, i);
            }
            else
            {
                arg = args;
                assert(_outParams.size() == 1);
            }

            if(!(*p)->type->validate(arg))
            {
                // TODO: Provide the parameter name instead?
                ostringstream ostr;
                ostr << "invalid value for out argument " << (i + 1) << " in operation `" << fixIdent(_name)
                     << (_amd ? "_async" : "") << "'";
                string str = ostr.str();
                PyErr_Warn(PyExc_RuntimeWarning, const_cast<char*>(str.c_str()));
                throw Ice::MarshalException(__FILE__, __LINE__);
            }
            (*p)->type->marshal(arg, os, &objectMap, &(*p)->metaData);
        }

        if(_returnType)
        {
            PyObject* res;
            if(_amd || numResults > 1)
            {
                res = PyTuple_GET_ITEM(args, 0);
            }
            else
            {
                assert(_outParams.size() == 0);
                res = args;
            }
            if(!_returnType->type->validate(res))
            {
                ostringstream ostr;
                ostr << "invalid return value for operation `" << fixIdent(_name) << "'";
                string str = ostr.str();
                PyErr_Warn(PyExc_RuntimeWarning, const_cast<char*>(str.c_str()));
                throw Ice::MarshalException(__FILE__, __LINE__);
            }
            _returnType->type->marshal(res, os, &objectMap, &_metaData);
        }

        if(_returnsClasses)
        {
            os->writePendingObjects();
        }

        Ice::ByteSeq outBytes;
        os->finished(outBytes);
        cb->ice_response(true, outBytes);
    }
    catch(const AbortMarshaling&)
    {
        throwPythonException();
    }
}

void
IcePy::OperationI::sendException(const Ice::AMD_Object_ice_invokePtr& cb, PyException& ex,
                                 const Ice::CommunicatorPtr& communicator)
{
    try
    {
        //
        // A servant that calls sys.exit() will raise the SystemExit exception.
        // This is normally caught by the interpreter, causing it to exit.
        // However, we have no way to pass this exception to the interpreter,
        // so we act on it directly.
        //
        if(PyObject_IsInstance(ex.ex.get(), PyExc_SystemExit))
        {
            handleSystemExit(ex.ex.get()); // Does not return.
        }

        PyObject* userExceptionType = lookupType("Ice.UserException");

        if(PyObject_IsInstance(ex.ex.get(), userExceptionType))
        {
            //
            // Get the exception's type and verify that it is legal to be thrown from this operation.
            //
            PyObjectHandle iceType = PyObject_GetAttrString(ex.ex.get(), STRCAST("ice_type"));
            assert(iceType.get());
            ExceptionInfoPtr info = ExceptionInfoPtr::dynamicCast(getException(iceType.get()));
            assert(info);
            if(!validateException(ex.ex.get()))
            {
                ex.raise(); // Raises UnknownUserException.
            }
            else
            {
                Ice::OutputStreamPtr os = Ice::createOutputStream(communicator);
                ObjectMap objectMap;
                info->marshal(ex.ex.get(), os, &objectMap);

                if(info->usesClasses)
                {
                    os->writePendingObjects();
                }

                Ice::ByteSeq bytes;
                os->finished(bytes);
                cb->ice_response(false, bytes);
            }
        }
        else
        {
            ex.raise();
        }
    }
    catch(const AbortMarshaling&)
    {
        throwPythonException();
    }
}

bool
IcePy::OperationI::prepareRequest(const Ice::CommunicatorPtr& communicator, PyObject* args, bool async,
                                  vector<Ice::Byte>& bytes)
{
    assert(PyTuple_Check(args));

    //
    // Validate the number of arguments.
    //
    Py_ssize_t argc = PyTuple_GET_SIZE(args);
    Py_ssize_t paramCount = static_cast<Py_ssize_t>(_inParams.size());
    if(argc != paramCount)
    {
        string fixedName = fixIdent(_name);
        PyErr_Format(PyExc_RuntimeError, STRCAST("%s expects %d in parameters"), fixedName.c_str(),
                     static_cast<int>(paramCount));
        return false;
    }

    if(!_inParams.empty())
    {
        try
        {
            //
            // Marshal the in parameters.
            //
            Ice::OutputStreamPtr os = Ice::createOutputStream(communicator);

            ObjectMap objectMap;
            int i = 0;
            for(ParamInfoList::iterator p = _inParams.begin(); p != _inParams.end(); ++p, ++i)
            {
                PyObject* arg = PyTuple_GET_ITEM(args, i);
                if(!(*p)->type->validate(arg))
                {
                    string opName;
                    if(async)
                    {
                        opName = fixIdent(_name) + "_async";
                    }
                    else
                    {
                        opName = fixIdent(_name);
                    }
                    PyErr_Format(PyExc_ValueError, STRCAST("invalid value for argument %d in operation `%s'"),
                                 async ? i + 2 : i + 1, const_cast<char*>(opName.c_str()));
                    return false;
                }
                (*p)->type->marshal(arg, os, &objectMap, &(*p)->metaData);
            }

            if(_sendsClasses)
            {
                os->writePendingObjects();
            }

            os->finished(bytes);
        }
        catch(const AbortMarshaling&)
        {
            return false;
        }
        catch(const Ice::Exception& ex)
        {
            setPythonException(ex);
            return false;
        }
    }

    return true;
}

PyObject*
IcePy::OperationI::unmarshalResults(const vector<Ice::Byte>& bytes, const Ice::CommunicatorPtr& communicator)
{
    Py_ssize_t i = _returnType ? 1 : 0;
    Py_ssize_t numResults = static_cast<Py_ssize_t>(_outParams.size()) + i;

    PyObjectHandle results = PyTuple_New(numResults);
    if(results.get() && numResults > 0)
    {
        //
        // Unmarshal the results. If there is more than one value to be returned, then return them
        // in a tuple of the form (result, outParam1, ...). Otherwise just return the value.
        //
        Ice::InputStreamPtr is = Ice::createInputStream(communicator, bytes);
        for(ParamInfoList::iterator p = _outParams.begin(); p != _outParams.end(); ++p, ++i)
        {
            void* closure = reinterpret_cast<void*>(i);
            (*p)->type->unmarshal(is, *p, results.get(), closure, &(*p)->metaData);
        }

        if(_returnType)
        {
            _returnType->type->unmarshal(is, _returnType, results.get(), 0, &_metaData);
        }

        if(_returnsClasses)
        {
            is->readPendingObjects();
        }
    }

    return results.release();
}

PyObject*
IcePy::OperationI::unmarshalException(const vector<Ice::Byte>& bytes, const Ice::CommunicatorPtr& communicator)
{
    Ice::InputStreamPtr is = Ice::createInputStream(communicator, bytes);

    is->readBool(); // usesClasses

    string id = is->readString();
    while(!id.empty())
    {
        ExceptionInfoPtr info = lookupExceptionInfo(id);
        if(info)
        {
            PyObjectHandle ex = info->unmarshal(is);
            if(info->usesClasses)
            {
                is->readPendingObjects();
            }

            if(validateException(ex.get()))
            {
                return ex.release();
            }
            else
            {
                PyException pye(ex.get()); // No traceback information available.
                pye.raise();
            }
        }
        else
        {
            is->skipSlice();
            id = is->readString();
        }
    }

    //
    // Getting here should be impossible: we can get here only if the
    // sender has marshaled a sequence of type IDs, none of which we
    // have factory for. This means that sender and receiver disagree
    // about the Slice definitions they use.
    //
    throw Ice::UnknownUserException(__FILE__, __LINE__);
}

bool
IcePy::OperationI::validateException(PyObject* ex) const
{
    for(ExceptionInfoList::const_iterator p = _exceptions.begin(); p != _exceptions.end(); ++p)
    {
        if(PyObject_IsInstance(ex, (*p)->pythonType.get()))
        {
            return true;
        }
    }

    return false;
}

void
IcePy::OperationI::checkTwowayOnly(const Ice::ObjectPrx& proxy) const
{
    if((_returnType != 0 || !_outParams.empty()) && !proxy->ice_isTwoway())
    {
        Ice::TwowayOnlyException ex(__FILE__, __LINE__);
        ex.operation = _name;
        throw ex;
    }
}

void
IcePy::OperationI::convertParams(PyObject* p, ParamInfoList& params, bool& usesClasses)
{
    usesClasses = false;
    int sz = static_cast<int>(PyTuple_GET_SIZE(p));
    for(int i = 0; i < sz; ++i)
    {
        PyObject* item = PyTuple_GET_ITEM(p, i);
        assert(PyTuple_Check(item));
        assert(PyTuple_GET_SIZE(item) == 2);

        ParamInfoPtr param = new ParamInfo;

        //
        // metaData
        //
        PyObject* meta = PyTuple_GET_ITEM(item, 0);
        assert(PyTuple_Check(meta));
#ifndef NDEBUG
        bool b =
#endif
        tupleToStringSeq(meta, param->metaData);
        assert(b);

        //
        // type
        //
        param->type = getType(PyTuple_GET_ITEM(item, 1));
        params.push_back(param);
        if(!usesClasses)
        {
            usesClasses = param->type->usesClasses();
        }
    }
}

static PyMethodDef OperationMethods[] =
{
    { STRCAST("invoke"), reinterpret_cast<PyCFunction>(operationInvoke), METH_VARARGS,
      PyDoc_STR(STRCAST("internal function")) },
    { STRCAST("invokeAsync"), reinterpret_cast<PyCFunction>(operationInvokeAsync), METH_VARARGS,
      PyDoc_STR(STRCAST("internal function")) },
    { STRCAST("deprecate"), reinterpret_cast<PyCFunction>(operationDeprecate), METH_VARARGS,
      PyDoc_STR(STRCAST("internal function")) },
    { 0, 0 } /* sentinel */
};

static PyMethodDef AMDCallbackMethods[] =
{
    { STRCAST("ice_response"), reinterpret_cast<PyCFunction>(amdCallbackIceResponse), METH_VARARGS,
      PyDoc_STR(STRCAST("internal function")) },
    { STRCAST("ice_exception"), reinterpret_cast<PyCFunction>(amdCallbackIceException), METH_VARARGS,
      PyDoc_STR(STRCAST("internal function")) },
    { 0, 0 } /* sentinel */
};

namespace IcePy
{

PyTypeObject OperationType =
{
    /* The ob_type field must be initialized in the module init function
     * to be portable to Windows without using C++. */
    PyObject_HEAD_INIT(0)
    0,                               /* ob_size */
    STRCAST("IcePy.Operation"),      /* tp_name */
    sizeof(OperationObject),         /* tp_basicsize */
    0,                               /* tp_itemsize */
    /* methods */
    reinterpret_cast<destructor>(operationDealloc), /* tp_dealloc */
    0,                               /* tp_print */
    0,                               /* tp_getattr */
    0,                               /* tp_setattr */
    0,                               /* tp_compare */
    0,                               /* tp_repr */
    0,                               /* tp_as_number */
    0,                               /* tp_as_sequence */
    0,                               /* tp_as_mapping */
    0,                               /* tp_hash */
    0,                               /* tp_call */
    0,                               /* tp_str */
    0,                               /* tp_getattro */
    0,                               /* tp_setattro */
    0,                               /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,              /* tp_flags */
    0,                               /* tp_doc */
    0,                               /* tp_traverse */
    0,                               /* tp_clear */
    0,                               /* tp_richcompare */
    0,                               /* tp_weaklistoffset */
    0,                               /* tp_iter */
    0,                               /* tp_iternext */
    OperationMethods,                /* tp_methods */
    0,                               /* tp_members */
    0,                               /* tp_getset */
    0,                               /* tp_base */
    0,                               /* tp_dict */
    0,                               /* tp_descr_get */
    0,                               /* tp_descr_set */
    0,                               /* tp_dictoffset */
    reinterpret_cast<initproc>(operationInit), /* tp_init */
    0,                               /* tp_alloc */
    reinterpret_cast<newfunc>(operationNew), /* tp_new */
    0,                               /* tp_free */
    0,                               /* tp_is_gc */
};

PyTypeObject AMDCallbackType =
{
    /* The ob_type field must be initialized in the module init function
     * to be portable to Windows without using C++. */
    PyObject_HEAD_INIT(0)
    0,                               /* ob_size */
    STRCAST("IcePy.AMDCallback"),    /* tp_name */
    sizeof(AMDCallbackObject),       /* tp_basicsize */
    0,                               /* tp_itemsize */
    /* methods */
    reinterpret_cast<destructor>(amdCallbackDealloc), /* tp_dealloc */
    0,                               /* tp_print */
    0,                               /* tp_getattr */
    0,                               /* tp_setattr */
    0,                               /* tp_compare */
    0,                               /* tp_repr */
    0,                               /* tp_as_number */
    0,                               /* tp_as_sequence */
    0,                               /* tp_as_mapping */
    0,                               /* tp_hash */
    0,                               /* tp_call */
    0,                               /* tp_str */
    0,                               /* tp_getattro */
    0,                               /* tp_setattro */
    0,                               /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,              /* tp_flags */
    0,                               /* tp_doc */
    0,                               /* tp_traverse */
    0,                               /* tp_clear */
    0,                               /* tp_richcompare */
    0,                               /* tp_weaklistoffset */
    0,                               /* tp_iter */
    0,                               /* tp_iternext */
    AMDCallbackMethods,              /* tp_methods */
    0,                               /* tp_members */
    0,                               /* tp_getset */
    0,                               /* tp_base */
    0,                               /* tp_dict */
    0,                               /* tp_descr_get */
    0,                               /* tp_descr_set */
    0,                               /* tp_dictoffset */
    0,                               /* tp_init */
    0,                               /* tp_alloc */
    reinterpret_cast<newfunc>(amdCallbackNew), /* tp_new */
    0,                               /* tp_free */
    0,                               /* tp_is_gc */
};

}

bool
IcePy::initOperation(PyObject* module)
{
    if(PyType_Ready(&OperationType) < 0)
    {
        return false;
    }
    PyTypeObject* opType = &OperationType; // Necessary to prevent GCC's strict-alias warnings.
    if(PyModule_AddObject(module, STRCAST("Operation"), reinterpret_cast<PyObject*>(opType)) < 0)
    {
        return false;
    }

    if(PyType_Ready(&AMDCallbackType) < 0)
    {
        return false;
    }
    PyTypeObject* cbType = &AMDCallbackType; // Necessary to prevent GCC's strict-alias warnings.
    if(PyModule_AddObject(module, STRCAST("AMDCallback"), reinterpret_cast<PyObject*>(cbType)) < 0)
    {
        return false;
    }

    return true;
}

IcePy::OperationPtr
IcePy::getOperation(PyObject* p)
{
    assert(PyObject_IsInstance(p, reinterpret_cast<PyObject*>(&OperationType)) == 1);
    OperationObject* obj = reinterpret_cast<OperationObject*>(p);
    return *obj->op;
}


syntax highlighted by Code2HTML, v. 0.9.1